今日はAnthropicが2024年にリリースしたMCPというものを触ってみます。
Message Context Protocol(メッセージコンテキストプロトコル)
https://docs.anthropic.com/en/docs/agents-and-tools/mcp
MCPは、アプリケーションがLLMにコンテキストを提供する方法を標準化するオープンプロトコルです。MCPは、AIアプリケーション用のUSB-Cポートのようなものです。USB-Cがデバイスをさまざまな周辺機器やアクセサリに接続するための標準的な方法を提供するのと同様に、MCPはAIモデルを多様なデータソースやツールに接続するための標準化された仕組みを備えています。
LLMを活用したアプリケーション開発時の外部連携を定義するオープン規格のようです。
MCP Serverが外部呼び出しをつかさどり、必要に応じて呼ばれるようです。
OpenAIはもともと異なるアプローチとして、Function Calling
というインターフェースを提供していました。
生成AIは学習時点のデータに基づいて回答するため、例えば天気やイベントなどの最新情報には、そのままでは答えられません。
これを補うためにFunction Calling
では、最新情報を外部API呼び出しで取得するかどうか、取得する場合はどのAPIをどのようなケースで呼ぶべきか、といった機能が提供されていました。
このAnthropic版、というよりAnthropicがリリースしたオープン規格がMCPです。
2025年3月には、OpenAIもMCPをサポートしました。
https://openai.github.io/openai-agents-python/mcp/
どちらが今後主流になるかはわかりませんが、LLMベンダーをまたいだ外部API呼び出しの実装では、MCPのほうが有利かもしれません。
さっそくやってみる
https://modelcontextprotocol.io/quickstart/server
こちらの手順をやってみます。
環境構築
node
とnpm
を入れておきます。
MCPを動作させるには、MCPサーバと連携するMCPホスト/クライアントが必要です。
ホストは、クライアントからのリクエストに応じてMCP ServerとMCPプロトコルで通信を行う役割を担います。クライアントはUXを担当します。
Anthropicはもともと「Claude Desktop」というアプリを提供しており、これはMCPホストとクライアントの両方の機能を備えているため、まずはこちらをインストールしておきます。
プロジェクト初期化
mkdir firstmcp cd firstmcp npm init -y
初期化が完了したら、必要なモジュールをインストールします。
npm install @modelcontextprotocol/sdk zod npm install -D @types/node typescript
次にpackage.json
を以下に置換します。オリジナルのサイトではWindows用を配布していませんでしたので、以下がWindows用です。
{ "type": "module", "bin": { "weather": "./build/index.js" }, "scripts": { "build": "tsc && node -e \"try{require('fs').chmodSync('build/index.js', 0o755)}catch(e){}\"" }, "files": [ "build" ] }
(Macは以下のようです)
{ "type": "module", "bin": { "weather": "./build/index.js" }, "scripts": { "build": "tsc && chmod 755 build/index.js" }, "files": [ "build" ] }
tsconfig.json
を作成します。
{ "compilerOptions": { "target": "ES2022", "module": "Node16", "moduleResolution": "Node16", "outDir": "./build", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true }, "include": ["src/**/*"], "exclude": ["node_modules"] }

MCPサーバの構築
mkdir src cd src
src
ディレクトリの下にindex.ts
を作成し以下をコピーします。
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; const NWS_API_BASE = "https://api.weather.gov"; const USER_AGENT = "weather-app/1.0"; // Create server instance const server = new McpServer({ name: "weather", version: "1.0.0", capabilities: { resources: {}, tools: {}, }, }); // Helper function for making NWS API requests async function makeNWSRequest<T>(url: string): Promise<T | null> { const headers = { "User-Agent": USER_AGENT, Accept: "application/geo+json", }; try { const response = await fetch(url, { headers }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return (await response.json()) as T; } catch (error) { console.error("Error making NWS request:", error); return null; } } interface AlertFeature { properties: { event?: string; areaDesc?: string; severity?: string; status?: string; headline?: string; }; } // Format alert data function formatAlert(feature: AlertFeature): string { const props = feature.properties; return [ `Event: ${props.event || "Unknown"}`, `Area: ${props.areaDesc || "Unknown"}`, `Severity: ${props.severity || "Unknown"}`, `Status: ${props.status || "Unknown"}`, `Headline: ${props.headline || "No headline"}`, "---", ].join("\n"); } interface ForecastPeriod { name?: string; temperature?: number; temperatureUnit?: string; windSpeed?: string; windDirection?: string; shortForecast?: string; } interface AlertsResponse { features: AlertFeature[]; } interface PointsResponse { properties: { forecast?: string; }; } interface ForecastResponse { properties: { periods: ForecastPeriod[]; }; } // Register weather tools server.tool( "get-alerts", "Get weather alerts for a state", { state: z.string().length(2).describe("Two-letter state code (e.g. CA, NY)"), }, async ({ state }) => { const stateCode = state.toUpperCase(); const alertsUrl = `${NWS_API_BASE}/alerts?area=${stateCode}`; const alertsData = await makeNWSRequest<AlertsResponse>(alertsUrl); if (!alertsData) { return { content: [ { type: "text", text: "Failed to retrieve alerts data", }, ], }; } const features = alertsData.features || []; if (features.length === 0) { return { content: [ { type: "text", text: `No active alerts for ${stateCode}`, }, ], }; } const formattedAlerts = features.map(formatAlert); const alertsText = `Active alerts for ${stateCode}:\n\n${formattedAlerts.join("\n")}`; return { content: [ { type: "text", text: alertsText, }, ], }; }, ); server.tool( "get-forecast", "Get weather forecast for a location", { latitude: z.number().min(-90).max(90).describe("Latitude of the location"), longitude: z.number().min(-180).max(180).describe("Longitude of the location"), }, async ({ latitude, longitude }) => { // Get grid point data const pointsUrl = `${NWS_API_BASE}/points/${latitude.toFixed(4)},${longitude.toFixed(4)}`; const pointsData = await makeNWSRequest<PointsResponse>(pointsUrl); if (!pointsData) { return { content: [ { type: "text", text: `Failed to retrieve grid point data for coordinates: ${latitude}, ${longitude}. This location may not be supported by the NWS API (only US locations are supported).`, }, ], }; } const forecastUrl = pointsData.properties?.forecast; if (!forecastUrl) { return { content: [ { type: "text", text: "Failed to get forecast URL from grid point data", }, ], }; } // Get forecast data const forecastData = await makeNWSRequest<ForecastResponse>(forecastUrl); if (!forecastData) { return { content: [ { type: "text", text: "Failed to retrieve forecast data", }, ], }; } const periods = forecastData.properties?.periods || []; if (periods.length === 0) { return { content: [ { type: "text", text: "No forecast periods available", }, ], }; } // Format forecast periods const formattedForecast = periods.map((period: ForecastPeriod) => [ `${period.name || "Unknown"}:`, `Temperature: ${period.temperature || "Unknown"}°${period.temperatureUnit || "F"}`, `Wind: ${period.windSpeed || "Unknown"} ${period.windDirection || ""}`, `${period.shortForecast || "No forecast available"}`, "---", ].join("\n"), ); const forecastText = `Forecast for ${latitude}, ${longitude}:\n\n${formattedForecast.join("\n")}`; return { content: [ { type: "text", text: forecastText, }, ], }; }, ); async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Weather MCP Server running on stdio"); } main().catch((error) => { console.error("Fatal error in main():", error); process.exit(1); });
保存したらnpm run build
でMCPサーバを起動します。
MCPホストとクライアントの設定
冒頭で説明した通りClaudeデスクトップアプリがMCPホストとクライアントの機能を提供してくれますので、こちらを使いテストしていきます。
まずは設定
をクリックします。
開発者
→構成を編集
をクリックします。
claude_desktop_config.json
というファイルが自動で生成されます。
先ほどトランスパイルされたindex.js
をMCPServerとして設定します。
{ "mcpServers": { "weather": { "command": "node", "args": [ "C:\\Users\\h.kameda\\firstmcp\\build\\index.js" ] } } }
パスは\\
としてエスケープ処理が必須ですので注意してください。
保存が完了したらCluade Desktopを再起動します。Windowsの場合単純にアプリを起動しなおすだけでは、常駐しているアプリが停止されず設定を読み込まないので注意してください。
設定がうまくいけば、プロンプト入力画面にトンカチが出てきます。
クリックすると連携先として設定を読み込み済のMCPサーバ一覧が出てきます。
では2025年3月30日のNYの天気は?
と質問してみます。
MCP Serverを呼び出しを許可するか聞いてきますのでこちらを承認します。


MCPServerのweather
から`get-forecast`が無事動作しています。
なにがおきているのか
トランスパイルされたindex.js
を見てみます。
const NWS_API_BASE = "https://api.weather.gov"; const USER_AGENT = "weather-app/1.0"; // Create server instance const server = new McpServer({ name: "weather", version: "1.0.0", capabilities: { resources: {}, tools: {}, }, });
MCP Serverの呼び出し先が定義されています。
必要に応じて以下でAPI呼び出しが設定されています。
// Helper function for making NWS API requests async function makeNWSRequest(url) { const headers = { "User-Agent": USER_AGENT, Accept: "application/geo+json", }; try { const response = await fetch(url, { headers }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return (await response.json());
ではClaudeはどのようなケースにMCPServerを呼び出すか、自己の知識を回答するかを判断しているかといえばMCPServer起動時のプロファイルに依存しています。
// Register weather tools server.tool( "get-alerts", "Get weather alerts for a state",
この記述によりLLMモデルは天気予報についてはMCPServerのweather
を呼ぶ、と判断しています。