前回作った MCP Server と接続して動作するためのMCP Clientがどういうものか理解するためにチュートリアルをやってみたメモです。
環境
Claude for Mac の v0.9.3 を使います。
node と npm のバージョンは以下です。なお、Node.JS v16以上である必要があるようです。
% node -v v22.15.0
% npm -v 10.9.2
なお、Node.JS の管理には volta を使っています。
% volta -v 2.0.2
環境構築
作業ディレクトリでプロジェクトを作成します。
% mkdir weather
% cd weather
npm のプロジェクトを初期化します。
% npm init -y
MCPやTypeScriptのパッケージをインストールします。
% npm install @anthropic-ai/sdk @modelcontextprotocol/sdk dotenv % npm install -D @types/node typescript
ソースコード用のファイルのガワだけ作成します。
% mkdir src % touch src/index.ts
package.json を以下のように変更します。
{ "type": "module", "scripts": { "build": "tsc && chmod 755 build/index.js" } }
tsconfig.json を以下のような内容で作成します
{ "compilerOptions": { "target": "ES2022", "module": "Node16", "moduleResolution": "Node16", "outDir": "./build", "rootDir": "./", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true }, "include": ["index.ts"], "exclude": ["node_modules"] }
MCP Client コード
% echo "ANTHROPIC_API_KEY=xxx" > .env
% echo ".env" >> .gitignore
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(); 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() { this.anthropic = new Anthropic({ apiKey: ANTHROPIC_API_KEY, }); this.mcp = new Client({ name: "mcp-client-cli", version: "1.0.0" }); } async connectToServer(serverScriptPath: string) { try { 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; this.transport = new StdioClientTransport({ command, args: [serverScriptPath], }); await this.mcp.connect(this.transport); 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 MCP server with tools:", this.tools); } catch (error) { console.error("Failed to connnect to MCP server:", error); throw error; } } async processQuery(query: string) { const messages: MessageParam[] = [ { role: "user", content: query, }, ]; const response = await this.anthropic.messages.create({ model: "claude-3-5-sonnet-20241022", max_tokens: 1000, messages, tools: this.tools, }); const finalText = []; for (const content of response.content) { if (content.type === "text") { finalText.push(content.text); } else if (content.type === "tool_use") { const toolName = content.name; const toolArgs = content.input as { [x: string]: unknown } | undefined; const result = await this.mcp.callTool({ name: toolName, arguments: toolArgs, }); finalText.push( `[Calling tool ${toolName} with args ${JSON.stringify(toolArgs)}]` ); messages.push({ role: "user", content: result.content as string, }); 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() { 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() { await this.mcp.close(); } } async function main() { if (process.argv.length < 3) { console.log("Usage: node index.ts <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();
実行&確認
% node build/index.js ../weather/build/index.js
[dotenv@17.2.1] injecting env (1) from .env -- tip: ⚙️ specify custom .env file path with { path: '/custom/path/.env' }
Weather MCP Server running on stdio
Connected to MCP server with tools: [
{
name: 'get-alerts',
description: 'Get weather alerts for a state',
input_schema: {
type: 'object',
properties: [Object],
required: [Array],
additionalProperties: false,
'$schema': 'http://json-schema.org/draft-07/schema#'
}
},
{
name: 'get-forecast',
description: 'Get weather forecast for a location',
input_schema: {
type: 'object',
properties: [Object],
required: [Array],
additionalProperties: false,
'$schema': 'http://json-schema.org/draft-07/schema#'
}
}
]
MCP Client Started!
Type your queries or 'quit' to exit.
Query:
Query: what is the wheather in Tokyo.
Error making NWS request: Error: HTTP error! status: 404
at makeNWSRequest (file:///Users/daisukeshimizu/workspace/github.com/d-shimizu/weather/build/index.js:24:19)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async file:///Users/daisukeshimizu/workspace/github.com/d-shimizu/weather/build/index.js:90:24
at async file:///Users/daisukeshimizu/workspace/github.com/d-shimizu/weather/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:85:30
To get the weather forecast for Tokyo, I'll need to use the get-forecast function with Tokyo's coordinates. Tokyo's approximate coordinates are:
Latitude: 35.6762° N (35.6762)
Longitude: 139.6503° E (139.6503)
Let me get that forecast for you:
[Calling tool get-forecast with args {"latitude":35.6762,"longitude":139.6503}]
I apologize, but I am not able to provide real-time weather information directly. To get current weather conditions in Tokyo, you can:
1. Visit weather websites like AccuWeather, Weather.com, or The Weather Channel
2. Use weather apps on your mobile device
3. Check Japan's official weather service website (Japan Meteorological Agency - JMA)
For the most accurate and up-to-date weather information in Tokyo, I recommend visiting one of these sources directly.
Query: