Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes.
2 changes: 1 addition & 1 deletion mcp-client/README.md → mcp-client-python/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# An LLM-Powered Chatbot MCP Client
# An LLM-Powered Chatbot MCP Client written in Python

See the [Building MCP clients](https://modelcontextprotocol.io/tutorials/building-a-client) tutorial for more information.
File renamed without changes.
File renamed without changes.
File renamed without changes.
1 change: 1 addition & 0 deletions mcp-client-typescript/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ANTHROPIC_API_KEY=
3 changes: 3 additions & 0 deletions mcp-client-typescript/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/build
/node_modules
.env
3 changes: 3 additions & 0 deletions mcp-client-typescript/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# An LLM-Powered Chatbot MCP Client written in TypeScript

See the [Building MCP clients](https://modelcontextprotocol.io/tutorials/building-a-client) tutorial for more information.
193 changes: 193 additions & 0 deletions mcp-client-typescript/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import { Anthropic } from "@anthropic-ai/sdk";
import {
MessageParam,
Tool,
} from "@anthropic-ai/sdk/resources/messages/messages.mjs";

import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import readline from "readline/promises";

import dotenv from "dotenv";

dotenv.config(); // load environment variables from .env

const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
if (!ANTHROPIC_API_KEY) {
throw new Error("ANTHROPIC_API_KEY is not set");
}

class MCPClient {
private mcp: Client;
private anthropic: Anthropic;
private transport: StdioClientTransport | null = null;
private tools: Tool[] = [];

constructor() {
// Initialize Anthropic client and MCP client
this.anthropic = new Anthropic({
apiKey: ANTHROPIC_API_KEY,
});
this.mcp = new Client({ name: "mcp-client-cli", version: "1.0.0" });
}

async connectToServer(serverScriptPath: string) {
/**
* Connect to an MCP server
*
* @param serverScriptPath - Path to the server script (.py or .js)
*/
try {
// Determine script type and appropriate command
const isJs = serverScriptPath.endsWith(".js");
const isPy = serverScriptPath.endsWith(".py");
if (!isJs && !isPy) {
throw new Error("Server script must be a .js or .py file");
}
const command = isPy
? process.platform === "win32"
? "python"
: "python3"
: process.execPath;

// Initialize transport and connect to server
this.transport = new StdioClientTransport({
command,
args: [serverScriptPath],
});
this.mcp.connect(this.transport);

// List available tools
const toolsResult = await this.mcp.listTools();
this.tools = toolsResult.tools.map((tool) => {
return {
name: tool.name,
description: tool.description,
input_schema: tool.inputSchema,
};
});
console.log(
"Connected to server with tools:",
this.tools.map(({ name }) => name),
);
} catch (e) {
console.log("Failed to connect to MCP server: ", e);
throw e;
}
}

async processQuery(query: string) {
/**
* Process a query using Claude and available tools
*
* @param query - The user's input query
* @returns Processed response as a string
*/
const messages: MessageParam[] = [
{
role: "user",
content: query,
},
];

// Initial Claude API call
const response = await this.anthropic.messages.create({
model: "claude-3-5-sonnet-20241022",
max_tokens: 1000,
messages,
tools: this.tools,
});

// Process response and handle tool calls
const finalText = [];
const toolResults = [];

for (const content of response.content) {
if (content.type === "text") {
finalText.push(content.text);
} else if (content.type === "tool_use") {
// Execute tool call
const toolName = content.name;
const toolArgs = content.input as { [x: string]: unknown } | undefined;

const result = await this.mcp.callTool({
name: toolName,
arguments: toolArgs,
});
toolResults.push(result);
finalText.push(
`[Calling tool ${toolName} with args ${JSON.stringify(toolArgs)}]`,
);

// Continue conversation with tool results
messages.push({
role: "user",
content: result.content as string,
});

// Get next response from Claude
const response = await this.anthropic.messages.create({
model: "claude-3-5-sonnet-20241022",
max_tokens: 1000,
messages,
});

finalText.push(
response.content[0].type === "text" ? response.content[0].text : "",
);
}
}

return finalText.join("\n");
}

async chatLoop() {
/**
* Run an interactive chat loop
*/
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});

try {
console.log("\nMCP Client Started!");
console.log("Type your queries or 'quit' to exit.");

while (true) {
const message = await rl.question("\nQuery: ");
if (message.toLowerCase() === "quit") {
break;
}
const response = await this.processQuery(message);
console.log("\n" + response);
}
} finally {
rl.close();
}
}

async cleanup() {
/**
* Clean up resources
*/
await this.mcp.close();
}
}

async function main() {
if (process.argv.length < 3) {
console.log("Usage: node build/index.js <path_to_server_script>");
return;
}
const mcpClient = new MCPClient();
try {
await mcpClient.connectToServer(process.argv[2]);
await mcpClient.chatLoop();
} finally {
await mcpClient.cleanup();
process.exit(0);
}
}

main();
Loading