Skip to content

Commit 0411bf6

Browse files
committed
Add mcp-client-typescript
1 parent 6541c17 commit 0411bf6

File tree

8 files changed

+847
-1
lines changed

8 files changed

+847
-1
lines changed

mcp-client-python/README.md

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: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
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();
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+
25+
constructor() {
26+
this.anthropic = new Anthropic({
27+
apiKey: ANTHROPIC_API_KEY,
28+
});
29+
this.mcp = new Client({ name: "mcp-client-cli", version: "1.0.0" });
30+
}
31+
32+
async connectToServer(serverScriptPath: string) {
33+
try {
34+
const isJs = serverScriptPath.endsWith(".js");
35+
const isPy = serverScriptPath.endsWith(".py");
36+
if (!isJs && !isPy) {
37+
throw new Error("Server script must be a .js or .py file");
38+
}
39+
const command = isPy
40+
? process.platform === "win32"
41+
? "python"
42+
: "python3"
43+
: process.execPath;
44+
this.transport = new StdioClientTransport({
45+
command,
46+
args: [serverScriptPath],
47+
});
48+
this.mcp.connect(this.transport);
49+
const toolsResult = await this.mcp.listTools();
50+
const tools = toolsResult.tools.map((tool) => {
51+
return {
52+
name: tool.name,
53+
description: tool.description,
54+
input_schema: tool.inputSchema,
55+
};
56+
});
57+
console.log(
58+
"Connected to server with tools:",
59+
tools.map(({ name }) => name)
60+
);
61+
} catch (e) {
62+
console.log("Failed to connect to MCP server: ", e);
63+
throw e;
64+
}
65+
}
66+
67+
async processQuery(query: string) {
68+
const messages: MessageParam[] = [
69+
{
70+
role: "user",
71+
content: query,
72+
},
73+
];
74+
75+
const toolsResult = await this.mcp.listTools();
76+
const tools: Tool[] = toolsResult.tools.map((tool) => {
77+
return {
78+
name: tool.name,
79+
description: tool.description,
80+
input_schema: tool.inputSchema,
81+
};
82+
});
83+
const response = await this.anthropic.messages.create({
84+
model: "claude-3-5-sonnet-20241022",
85+
max_tokens: 1000,
86+
messages,
87+
tools,
88+
});
89+
90+
const finalText = [];
91+
const toolResults = [];
92+
93+
for (const content of response.content) {
94+
if (content.type === "text") {
95+
finalText.push(content.text);
96+
} else if (content.type === "tool_use") {
97+
const toolName = content.name;
98+
const toolArgs = content.input as { [x: string]: unknown } | undefined;
99+
100+
const result = await this.mcp.callTool({
101+
name: toolName,
102+
arguments: toolArgs,
103+
});
104+
toolResults.push(result);
105+
finalText.push(
106+
`[Calling tool ${toolName} with args ${JSON.stringify(toolArgs)}]`
107+
);
108+
109+
messages.push({
110+
role: "user",
111+
content: result.content as string,
112+
});
113+
114+
const response = await this.anthropic.messages.create({
115+
model: "claude-3-5-sonnet-20241022",
116+
max_tokens: 1000,
117+
messages,
118+
});
119+
120+
finalText.push(
121+
response.content[0].type === "text" ? response.content[0].text : ""
122+
);
123+
}
124+
}
125+
126+
return finalText.join("\n");
127+
}
128+
129+
async chatLoop() {
130+
const rl = readline.createInterface({
131+
input: process.stdin,
132+
output: process.stdout,
133+
});
134+
135+
console.log("\nMCP Client Started!");
136+
console.log("Type your queries or 'quit' to exit.");
137+
138+
while (true) {
139+
const message = await rl.question("\nQuery: ");
140+
if (message === "quit") {
141+
break;
142+
}
143+
const response = await this.processQuery(message);
144+
console.log("\n" + response);
145+
}
146+
}
147+
148+
async cleanup() {
149+
await this.mcp.close();
150+
}
151+
}
152+
async function main() {
153+
if (process.argv.length < 3) {
154+
console.log("Usage: node index.ts <path_to_server_script>");
155+
return;
156+
}
157+
const mcpClient = new MCPClient();
158+
try {
159+
await mcpClient.connectToServer(process.argv[2]);
160+
await mcpClient.chatLoop();
161+
} finally {
162+
await mcpClient.cleanup();
163+
process.exit(0);
164+
}
165+
}
166+
167+
main();

0 commit comments

Comments
 (0)