構建 MCP 客戶端-Node.js
系統要求
在開始之前,請確保你的系統滿足以下要求:
- Mac 或 Windows 電腦
- 安裝 Node.js 16 或更高版本
- npm(隨 Node.js 一起安裝)
配置環境
首先,創建一個新的 Node.js 項目:
# 創建項目目錄
mkdir mcp-client
cd mcp-client
# 初始化 npm 項目
npm init -y
# 安裝依賴
npm install @modelcontextprotocol/sdk @anthropic-ai/sdk dotenv
npm install -D typescript @types/node
# 創建 TypeScript 配置
npx tsc --init
# 創建必要的文件
mkdir src
touch src/client.ts
touch .env更新 package.json 添加必要的配置:
{
"type": "module",
"scripts": {
"build": "tsc",
"start": "node build/client.js"
}
}使用適當的設置更新 tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./build",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"]
}設置 API 密鑰
你需要從 Anthropic Console 獲取 Anthropic API 密鑰。
創建 .env 文件:
ANTHROPIC_API_KEY=你的密鑰將 .env 添加到 .gitignore:
echo ".env" >> .gitignore創建客戶端
首先,在 src/client.ts 中設置導入並創建基本的客戶端類:
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import Anthropic from "@anthropic-ai/sdk";
import dotenv from "dotenv";
import { Tool } from "@anthropic-ai/sdk/resources/messages.js";
import {
CallToolResultSchema,
ListToolsResultSchema,
} from "@modelcontextprotocol/sdk/types.js";
import * as readline from "node:readline";
dotenv.config();
interface MCPClientConfig {
name?: string;
version?: string;
}
class MCPClient {
private client: Client | null = null;
private anthropic: Anthropic;
private transport: StdioClientTransport | null = null;
constructor(config: MCPClientConfig = {}) {
this.anthropic = new Anthropic();
}
// 方法將在這裡實現
}服務器連接管理
接下來,實現連接到 MCP 服務器的方法:
async connectToServer(serverScriptPath: string): Promise<void> {
const isPython = serverScriptPath.endsWith(".py");
const isJs = serverScriptPath.endsWith(".js");
if (!isPython && !isJs) {
throw new Error("服務器腳本必須是 .py 或 .js 文件");
}
const command = isPython ? "python" : "node";
this.transport = new StdioClientTransport({
command,
args: [serverScriptPath],
});
this.client = new Client(
{
name: "mcp-client",
version: "1.0.0",
},
{
capabilities: {},
}
);
await this.client.connect(this.transport);
// 列出可用工具
const response = await this.client.request(
{ method: "tools/list" },
ListToolsResultSchema
);
console.log(
"\n已連接到服務器,可用工具:",
response.tools.map((tool: any) => tool.name)
);
}查詢處理邏輯
現在添加處理查詢和工具調用的核心功能:
async processQuery(query: string): Promise<string> {
if (!this.client) {
throw new Error("客戶端未連接");
}
// 使用用戶查詢初始化消息數組
let messages: Anthropic.MessageParam[] = [
{
role: "user",
content: query,
},
];
// 獲取可用工具
const toolsResponse = await this.client.request(
{ method: "tools/list" },
ListToolsResultSchema
);
const availableTools: Tool[] = toolsResponse.tools.map((tool: any) => ({
name: tool.name,
description: tool.description,
input_schema: tool.inputSchema,
}));
const finalText: string[] = [];
let currentResponse = await this.anthropic.messages.create({
model: "claude-3-5-sonnet-20241022",
max_tokens: 1000,
messages,
tools: availableTools,
});
// 處理響應和工具調用
while (true) {
// 將 Claude 的響應添加到最終文本和消息中
for (const content of currentResponse.content) {
if (content.type === "text") {
finalText.push(content.text);
} else if (content.type === "tool_use") {
const toolName = content.name;
const toolArgs = content.input;
// 執行工具調用
const result = await this.client.request(
{
method: "tools/call",
params: {
name: toolName,
args: toolArgs,
},
},
CallToolResultSchema
);
finalText.push(
`[調用工具 ${toolName},參數:${JSON.stringify(toolArgs)}]`
);
// 將 Claude 的響應(包括工具使用)添加到消息中
messages.push({
role: "assistant",
content: currentResponse.content,
});
// 將工具結果添加到消息中
messages.push({
role: "user",
content: [
{
type: "tool_result",
tool_use_id: content.id,
content: [
{ type: "text", text: JSON.stringify(result.content) },
],
},
],
});
// 使用工具結果獲取 Claude 的下一個響應
currentResponse = await this.anthropic.messages.create({
model: "claude-3-5-sonnet-20241022",
max_tokens: 1000,
messages,
tools: availableTools,
});
// 將 Claude 對工具結果的解釋添加到最終文本中
if (currentResponse.content[0]?.type === "text") {
finalText.push(currentResponse.content[0].text);
}
// 繼續循環以處理任何額外的工具調用
continue;
}
}
// 如果到達這裡,說明響應中沒有工具調用
break;
}
return finalText.join("\n");
}交互式聊天界面
添加聊天循環和清理功能:
async chatLoop(): Promise<void> {
console.log("\nMCP 客戶端已啟動!");
console.log("輸入你的查詢或輸入 'quit' 退出。");
// 使用 Node 的 readline 進行控制檯輸入
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const askQuestion = () => {
rl.question("\n查詢:", async (query: string) => {
try {
if (query.toLowerCase() === "quit") {
await this.cleanup();
rl.close();
return;
}
const response = await this.processQuery(query);
console.log("\n" + response);
askQuestion();
} catch (error) {
console.error("\n錯誤:", error);
askQuestion();
}
});
};
askQuestion();
}
async cleanup(): Promise<void> {
if (this.transport) {
await this.transport.close();
}
}主入口點
最後,在類外添加主執行邏輯:
// 主執行
async function main() {
if (process.argv.length < 3) {
console.log("用法:ts-node client.ts <服務器腳本路徑>");
process.exit(1);
}
const client = new MCPClient();
try {
await client.connectToServer(process.argv[2]);
await client.chatLoop();
} catch (error) {
console.error("錯誤:", error);
await client.cleanup();
process.exit(1);
}
}
// 如果這是主模塊則運行 main
if (import.meta.url === new URL(process.argv[1], "file:").href) {
main();
}
export default MCPClient;運行客戶端
要使用任何 MCP 服務器運行你的客戶端:
# 構建 TypeScript 代碼。每次更新 `client.ts` 後都要重新運行!
npm run build
# 運行客戶端
node build/client.js path/to/server.py # 對於 Python 服務器
node build/client.js path/to/server.js # 對於 Node.js 服務器客戶端將:
- 連接到指定的服務器
- 列出可用工具
- 啟動交互式聊天會話,你可以:
- 輸入查詢
- 查看工具執行情況
- 獲取來自 Claude 的響應
關鍵組件說明
1. 客戶端初始化
MCPClient類初始化會話管理和 API 客戶端- 設置具有基本功能的 MCP 客戶端
- 配置用於 Claude 交互的 Anthropic 客戶端
2. 服務器連接
- 支持 Python 和 Node.js 服務器
- 驗證服務器腳本類型
- 設置適當的通信通道
- 連接時列出可用工具
3. 查詢處理
- 維護對話上下文
- 處理 Claude 的響應和工具調用
- 管理 Claude 和工具之間的消息流
- 將結果組合成連貫的響應
4. 交互界面
- 提供簡單的命令行界面
- 處理用戶輸入並顯示響應
- 包含基本錯誤處理
- 允許優雅退出
5. 資源管理
- 正確清理資源
- 連接問題的錯誤處理
- 優雅的關閉程序
常見自定義點
工具處理
- 修改
processQuery()以處理特定工具類型 - 為工具調用添加自定義錯誤處理
- 實現工具特定的響應格式化
- 修改
響應處理
- 自定義工具結果的格式化方式
- 添加響應過濾或轉換
- 實現自定義日誌記錄
用戶界面
- 添加 GUI 或 Web 界面
- 實現豐富的控制檯輸出
- 添加命令歷史或自動完成
最佳實踐
錯誤處理
- 始終將工具調用包裝在 try-catch 塊中
- 提供有意義的錯誤消息
- 優雅地處理連接問題
資源管理
- 使用適當的清理方法
- 完成後關閉連接
- 處理服務器斷開連接
安全性
- 在
.env中安全存儲 API 密鑰 - 驗證服務器響應
- 謹慎處理工具權限
- 在
故障排除
服務器路徑問題
- 仔細檢查服務器腳本的路徑
- 如果相對路徑不起作用,請使用絕對路徑
- 對於 Windows 用戶,使用正斜槓(/)或轉義的反斜槓(\)
- 驗證服務器文件具有正確的擴展名(.py 或 .js)
正確路徑使用示例:
# 相對路徑
node build/client.js ./server/weather.js
# 絕對路徑
node build/client.js /Users/username/projects/mcp-server/weather.js
# Windows 路徑(兩種格式都可以)
node build/client.js C:/projects/mcp-server/weather.js
node build/client.js C:\\projects\\mcp-server\\weather.js連接問題
- 驗證服務器腳本存在並具有正確的權限
- 檢查服務器腳本是否可執行
- 確保已安裝服務器腳本的依賴項
- 嘗試直接運行服務器腳本以檢查錯誤
工具執行問題
- 檢查服務器日誌中的錯誤消息
- 驗證工具輸入參數是否匹配模式
- 確保工具依賴項可用
- 添加調試日誌以跟蹤執行流程