Skip to content

Commit 1a6be37

Browse files
authored
Merge pull request #10 from allenzhou101/feature/mcp-client-typescript
Add MCP Client Typescript Example
2 parents 7ff2889 + 7b68e28 commit 1a6be37

File tree

13 files changed

+952
-43
lines changed

13 files changed

+952
-43
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
# An LLM-Powered Chatbot MCP Client
1+
# An LLM-Powered Chatbot MCP Client written in Python
22

33
See the [Building MCP clients](https://modelcontextprotocol.io/tutorials/building-a-client) tutorial for more information.

mcp-client-typescript/.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ANTHROPIC_API_KEY=

mcp-client-typescript/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/build
2+
/node_modules
3+
.env

mcp-client-typescript/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# An LLM-Powered Chatbot MCP Client written in TypeScript
2+
3+
See the [Building MCP clients](https://modelcontextprotocol.io/tutorials/building-a-client) tutorial for more information.

mcp-client-typescript/index.ts

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
import { Anthropic } from "@anthropic-ai/sdk";
2+
import {
3+
MessageParam,
4+
Tool,
5+
} from "@anthropic-ai/sdk/resources/messages/messages.mjs";
6+
7+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
8+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
9+
import readline from "readline/promises";
10+
11+
import dotenv from "dotenv";
12+
13+
dotenv.config(); // load environment variables from .env
14+
15+
const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
16+
if (!ANTHROPIC_API_KEY) {
17+
throw new Error("ANTHROPIC_API_KEY is not set");
18+
}
19+
20+
class MCPClient {
21+
private mcp: Client;
22+
private anthropic: Anthropic;
23+
private transport: StdioClientTransport | null = null;
24+
private tools: Tool[] = [];
25+
26+
constructor() {
27+
// Initialize Anthropic client and MCP client
28+
this.anthropic = new Anthropic({
29+
apiKey: ANTHROPIC_API_KEY,
30+
});
31+
this.mcp = new Client({ name: "mcp-client-cli", version: "1.0.0" });
32+
}
33+
34+
async connectToServer(serverScriptPath: string) {
35+
/**
36+
* Connect to an MCP server
37+
*
38+
* @param serverScriptPath - Path to the server script (.py or .js)
39+
*/
40+
try {
41+
// Determine script type and appropriate command
42+
const isJs = serverScriptPath.endsWith(".js");
43+
const isPy = serverScriptPath.endsWith(".py");
44+
if (!isJs && !isPy) {
45+
throw new Error("Server script must be a .js or .py file");
46+
}
47+
const command = isPy
48+
? process.platform === "win32"
49+
? "python"
50+
: "python3"
51+
: process.execPath;
52+
53+
// Initialize transport and connect to server
54+
this.transport = new StdioClientTransport({
55+
command,
56+
args: [serverScriptPath],
57+
});
58+
this.mcp.connect(this.transport);
59+
60+
// List available tools
61+
const toolsResult = await this.mcp.listTools();
62+
this.tools = toolsResult.tools.map((tool) => {
63+
return {
64+
name: tool.name,
65+
description: tool.description,
66+
input_schema: tool.inputSchema,
67+
};
68+
});
69+
console.log(
70+
"Connected to server with tools:",
71+
this.tools.map(({ name }) => name),
72+
);
73+
} catch (e) {
74+
console.log("Failed to connect to MCP server: ", e);
75+
throw e;
76+
}
77+
}
78+
79+
async processQuery(query: string) {
80+
/**
81+
* Process a query using Claude and available tools
82+
*
83+
* @param query - The user's input query
84+
* @returns Processed response as a string
85+
*/
86+
const messages: MessageParam[] = [
87+
{
88+
role: "user",
89+
content: query,
90+
},
91+
];
92+
93+
// Initial Claude API call
94+
const response = await this.anthropic.messages.create({
95+
model: "claude-3-5-sonnet-20241022",
96+
max_tokens: 1000,
97+
messages,
98+
tools: this.tools,
99+
});
100+
101+
// Process response and handle tool calls
102+
const finalText = [];
103+
const toolResults = [];
104+
105+
for (const content of response.content) {
106+
if (content.type === "text") {
107+
finalText.push(content.text);
108+
} else if (content.type === "tool_use") {
109+
// Execute tool call
110+
const toolName = content.name;
111+
const toolArgs = content.input as { [x: string]: unknown } | undefined;
112+
113+
const result = await this.mcp.callTool({
114+
name: toolName,
115+
arguments: toolArgs,
116+
});
117+
toolResults.push(result);
118+
finalText.push(
119+
`[Calling tool ${toolName} with args ${JSON.stringify(toolArgs)}]`,
120+
);
121+
122+
// Continue conversation with tool results
123+
messages.push({
124+
role: "user",
125+
content: result.content as string,
126+
});
127+
128+
// Get next response from Claude
129+
const response = await this.anthropic.messages.create({
130+
model: "claude-3-5-sonnet-20241022",
131+
max_tokens: 1000,
132+
messages,
133+
});
134+
135+
finalText.push(
136+
response.content[0].type === "text" ? response.content[0].text : "",
137+
);
138+
}
139+
}
140+
141+
return finalText.join("\n");
142+
}
143+
144+
async chatLoop() {
145+
/**
146+
* Run an interactive chat loop
147+
*/
148+
const rl = readline.createInterface({
149+
input: process.stdin,
150+
output: process.stdout,
151+
});
152+
153+
try {
154+
console.log("\nMCP Client Started!");
155+
console.log("Type your queries or 'quit' to exit.");
156+
157+
while (true) {
158+
const message = await rl.question("\nQuery: ");
159+
if (message.toLowerCase() === "quit") {
160+
break;
161+
}
162+
const response = await this.processQuery(message);
163+
console.log("\n" + response);
164+
}
165+
} finally {
166+
rl.close();
167+
}
168+
}
169+
170+
async cleanup() {
171+
/**
172+
* Clean up resources
173+
*/
174+
await this.mcp.close();
175+
}
176+
}
177+
178+
async function main() {
179+
if (process.argv.length < 3) {
180+
console.log("Usage: node build/index.js <path_to_server_script>");
181+
return;
182+
}
183+
const mcpClient = new MCPClient();
184+
try {
185+
await mcpClient.connectToServer(process.argv[2]);
186+
await mcpClient.chatLoop();
187+
} finally {
188+
await mcpClient.cleanup();
189+
process.exit(0);
190+
}
191+
}
192+
193+
main();

0 commit comments

Comments
 (0)