以下の内容はhttps://dshimizu.hatenablog.com/entry/2025/07/28/155929より取得しました。


MCP 公式チュートリアルの MCP Client の QuickStart を TypeScript でやってみた

前回作った 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:

参考




以上の内容はhttps://dshimizu.hatenablog.com/entry/2025/07/28/155929より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14