Model Context Protocol (MCP): 本当に動くAIエージェントを構築する完全ガイド
2025年にAIアプリケーションを作っていると、みんな同じ壁にぶつかりますよね。LLMはテキスト生成は神がかってるのに、実際のDBやAPIに繋ごうとすると途端にAPIを無理やり繋ぎ合わせてプロンプトで祈る羽目になる。
そこで登場したのが**Model Context Protocol(MCP)**です。AI開発においてREST APIレベルの基盤になりつつあるオープン標準です。Anthropicが作って業界全体で採用が進んでおり、AIエンジニアリング最大の課題を解決します:AIエージェントに外部世界への安全なアクセスをどう与えるか?
このガイドでは、MCPとは何か、なぜ重要か、どう動くか、そして最も大事な実装方法まで全て解説します。
MCPが解決する問題
MCPを理解する前に、どんな苦痛を解決するのか見ていきましょう。
インテグレーション地獄
従来のAIアプリケーション開発はこんな感じでした:
┌─────────────────────────────────────────────────────────────┐
│ あなたのAIアプリケーション │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ OpenAI │ │ Database │ │ Slack │ │
│ │ API │ │ Queries │ │ API │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
│ カスタムパーサー カスタムパーサー カスタムパーサー │
│ │ │ │ │
│ プロンプトハック プロンプトハック プロンプトハック │
│ │ │ │ │
│ エラーハンドラー エラーハンドラー エラーハンドラー │
│ │ │ │ │
│ └───────────────┴───────────────┘ │
│ │ │
│ ┌─────────▼─────────┐ │
│ │ スパゲッティコード │ │
│ └───────────────────┘ │
└─────────────────────────────────────────────────────────────┘
各インテグレーションで必要なもの:
- カスタム認証処理
- 専用レスポンスパース
- LLMにツールを説明するプロンプトエンジニアリング
- インテグレーションごとに違うエラーハンドリング
- 手動スキーマ管理
典型的なAIエージェントが10個以上のインテグレーションを必要とすると、メンテナンス地獄になります。
Function Callingの限界
OpenAIのfunction callingは助けになりますが、根本的にLLMベンダー固有です。GPT-4向けに丁寧に作った関数定義がClaude、Gemini、最新のオープンソースモデルでは動きません。
// OpenAIではこう... const tools = [{ type: "function", function: { name: "get_weather", description: "現在の天気を取得", parameters: { type: "object", properties: { location: { type: "string" } } } } }]; // でもClaudeは別フォーマット... // Geminiも違う... // Llamaも違う...
本当に必要なもの
理想的なソリューションは:
- ユニバーサル: LLMプロバイダー問わず動作
- 標準化: 全データソースに一つのインテグレーションパターン
- 双方向: AIがデータ取得も更新受信もできる
- セキュア: 認証と権限管理が組み込み
- 発見可能: AIがランタイムで利用可能なツールを学習
MCPはまさにこれを提供します。
Model Context Protocolとは?
MCPはAIアプリケーションが外部データソースやツールに接続する方法を標準化するオープンプロトコルです。**「AI用USB」**と考えてください。どのAIモデルでもどのデータソースにも差し込めるユニバーサルコネクタです。
アーキテクチャ
MCPはクライアント・サーバーアーキテクチャです:
┌────────────────────────────────────────────────────────────────┐
│ MCPアーキテクチャ │
├────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────────────────┐ │
│ │ MCP Client │ │ MCP Servers │ │
│ │ │ │ │ │
│ │ ┌───────────┐ │ │ ┌─────────┐ ┌─────────┐ │ │
│ │ │ AI Model │ │ JSON-RPC│ │ GitHub │ │ Slack │ │ │
│ │ │(GPT/Claude│◄─┼─────────┼─►│ Server │ │ Server │ │ │
│ │ │ /Gemini) │ │ over │ └─────────┘ └─────────┘ │ │
│ │ └───────────┘ │ stdio/ │ │ │
│ │ │ SSE/WS │ ┌─────────┐ ┌─────────┐ │ │
│ │ ┌───────────┐ │ │ │Database │ │ Custom │ │ │
│ │ │Host App │ │ │ │ Server │ │ Server │ │ │
│ │ │(Your App) │ │ │ └─────────┘ └─────────┘ │ │
│ │ └───────────┘ │ │ │ │
│ └─────────────────┘ └─────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────┘
コアコンポーネント:
- MCP Client: AIアプリ内にある。MCPサーバーを発見して接続。
- MCP Server: データソースやツールを標準フォーマットで公開。
- Transport Layer: stdio、SSE、WebSocket上のJSON-RPC 2.0。
3つのプリミティブ
MCPはAI-外部世界のほぼ全てのやり取りをカバーする3つのコア要素を定義:
1. Resources
AIが読める静的・動的データ。AIがアクセスできる「ファイル」と思ってください。
{ "uri": "file:///project/README.md", "name": "Project README", "mimeType": "text/markdown" }
2. Tools
AIがアクションを実行するために呼び出せる関数。
{ "name": "create_github_issue", "description": "GitHubリポジトリに新しいissueを作成", "inputSchema": { "type": "object", "properties": { "repo": { "type": "string" }, "title": { "type": "string" }, "body": { "type": "string" } }, "required": ["repo", "title"] } }
3. Prompts
パラメータ付きで呼び出せる再利用可能なプロンプトテンプレート。
{ "name": "code_review", "description": "ベストプラクティスのコードレビュー", "arguments": [ { "name": "code", "description": "レビューするコード", "required": true } ] }
なぜ今MCPが重要なのか
AIエージェント爆発
2025年はAIエージェントの年です。OpenAIのOperatorからClaudeのコンピュータ利用機能まで、AIはチャットを超えて自律行動に移行しています。でも裏の真実があります:自律AIは実世界へのアクセスの質でしか賢くならない。
以下を確実にできないAIエージェントは:
- コードベースを読む
- データベースにクエリする
- カレンダーを確認する
- チームにメッセージを送る
...ただの高いチャットボットです。
MCPはこれらの統合を信頼性高く、一貫して、メンテナンス可能にします。
標準化の瞬間
2000年代初頭のWebサービス時代と同じ転換点にいます。当時はCORBA、DCOM、プロプライエタリプロトコルが覇権を争っていました。RESTが勝ち、突然みんなが相互運用可能なWebサービスを作れるようになりました。
MCPはAI統合のRESTになろうとしています。主要プレイヤーはすでに参加:
- Anthropic: プロトコルを作成・維持
- Microsoft: Copilotに統合中
- Cursor: AI IDEでネイティブMCPサポート
- Sourcegraph: コードインテリジェンス用MCPサーバー
MCPサーバーを動かすのは新しいWebサーバーを動かすこと
大胆な予測:2026年には「MCPサーバー動かせる?」が今日の「REST API作れる?」と同じくらい一般的な面接質問になります。
なぜ?価値あるデータを持つ全ての企業がAIエージェントに制御された標準化された方法で公開したいからです。つまり:
- 社内ドキュメント用MCPサーバー
- 顧客データ用(適切な認可付き)
- ビジネスプロセス用
- ドメイン固有ツール用
最初のMCPサーバーを構築
実際にやってみましょう。シンプルなTodoリストAPIを公開するMCPサーバーを作ります。
プロジェクトセットアップ
# 新規プロジェクト作成 mkdir mcp-todo-server cd mcp-todo-server npm init -y # 依存関係インストール npm install @modelcontextprotocol/sdk zod npm install -D typescript @types/node tsx
基本サーバー構造
src/index.tsを作成:
import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; // インメモリTodoストレージ interface Todo { id: string; title: string; completed: boolean; createdAt: Date; } const todos: Map<string, Todo> = new Map(); // MCPサーバー作成 const server = new Server( { name: "todo-server", version: "1.0.0", }, { capabilities: { tools: {}, resources: {}, }, } ); // 利用可能なツール一覧 server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "create_todo", description: "新しいTodoアイテムを作成", inputSchema: { type: "object", properties: { title: { type: "string", description: "Todoアイテムのタイトル", }, }, required: ["title"], }, }, { name: "complete_todo", description: "Todoアイテムを完了にする", inputSchema: { type: "object", properties: { id: { type: "string", description: "完了にするTodoアイテムのID", }, }, required: ["id"], }, }, { name: "delete_todo", description: "Todoアイテムを削除", inputSchema: { type: "object", properties: { id: { type: "string", description: "削除するTodoアイテムのID", }, }, required: ["id"], }, }, ], }; }); // ツール呼び出し処理 server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; switch (name) { case "create_todo": { const id = crypto.randomUUID(); const todo: Todo = { id, title: args.title as string, completed: false, createdAt: new Date(), }; todos.set(id, todo); return { content: [ { type: "text", text: JSON.stringify({ success: true, todo }, null, 2), }, ], }; } case "complete_todo": { const todo = todos.get(args.id as string); if (!todo) { return { content: [ { type: "text", text: JSON.stringify({ error: "Todoが見つかりません" }) }, ], isError: true, }; } todo.completed = true; return { content: [ { type: "text", text: JSON.stringify({ success: true, todo }, null, 2), }, ], }; } case "delete_todo": { const deleted = todos.delete(args.id as string); return { content: [ { type: "text", text: JSON.stringify({ success: deleted }, null, 2), }, ], }; } default: return { content: [ { type: "text", text: JSON.stringify({ error: "不明なツール" }) }, ], isError: true, }; } }); // 利用可能なリソース一覧 server.setRequestHandler(ListResourcesRequestSchema, async () => { return { resources: [ { uri: "todo://list", name: "Todoリスト", description: "現在の全Todoアイテム一覧", mimeType: "application/json", }, ], }; }); // リソース読み取り server.setRequestHandler(ReadResourceRequestSchema, async (request) => { if (request.params.uri === "todo://list") { const todoList = Array.from(todos.values()); return { contents: [ { uri: "todo://list", mimeType: "application/json", text: JSON.stringify(todoList, null, 2), }, ], }; } throw new Error(`不明なリソース: ${request.params.uri}`); }); // サーバー起動 async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Todo MCPサーバーがstdioで起動しました"); } main().catch(console.error);
Claude Desktop設定
Claude Desktopで使うにはclaude_desktop_config.jsonに追加:
{ "mcpServers": { "todo": { "command": "npx", "args": ["tsx", "/path/to/mcp-todo-server/src/index.ts"] } } }
これでClaudeが:
- Todo作成:「買い物リストにTodo追加して」
- Todo完了:「買い物Todo完了にして」
- Todo一覧:「今のTodoリスト見せて」
高度なMCPパターン
パターン1:データベース統合
最も強力なMCP活用の一つがAIにDB読み取り(時には書き込み)アクセスを与えること:
import { Pool } from 'pg'; const pool = new Pool({ connectionString: process.env.DATABASE_URL, }); server.setRequestHandler(CallToolRequestSchema, async (request) => { if (request.params.name === "query_database") { const { query } = request.params.arguments as { query: string }; // 重要:クエリ検証とサニタイズ if (!isReadOnlyQuery(query)) { return { content: [{ type: "text", text: "SELECTクエリのみ許可されています" }], isError: true, }; } try { const result = await pool.query(query); return { content: [{ type: "text", text: JSON.stringify(result.rows, null, 2), }], }; } catch (error) { return { content: [{ type: "text", text: `クエリエラー: ${error.message}` }], isError: true, }; } } }); function isReadOnlyQuery(query: string): boolean { const normalized = query.trim().toLowerCase(); return normalized.startsWith('select') && !normalized.includes('into') && !normalized.includes('update') && !normalized.includes('delete') && !normalized.includes('insert') && !normalized.includes('drop') && !normalized.includes('alter'); }
パターン2:OAuth統合
ユーザー認証が必要なAPI向け:
import { OAuth2Client } from 'google-auth-library'; const oauth2Client = new OAuth2Client( process.env.GOOGLE_CLIENT_ID, process.env.GOOGLE_CLIENT_SECRET, 'http://localhost:3000/callback' ); server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "list_calendar_events", description: "今後のカレンダーイベント一覧", inputSchema: { type: "object", properties: { maxResults: { type: "number", description: "返す最大イベント数", default: 10, }, }, }, }, ], }; });
パターン3:長時間処理と進捗
時間のかかる処理はMCPが進捗通知をサポート:
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => { if (request.params.name === "analyze_codebase") { const files = await getAllFiles(request.params.arguments.path); const total = files.length; for (let i = 0; i < files.length; i++) { // 進捗更新を送信 await extra.sendNotification({ method: "notifications/progress", params: { progressToken: request.params._meta?.progressToken, progress: i, total, }, }); await analyzeFile(files[i]); } return { content: [{ type: "text", text: `${total}ファイルの分析完了`, }], }; } });
MCPセキュリティベストプラクティス
1. 全入力を検証
AIからのデータを絶対に信頼しない。常に検証:
import { z } from 'zod'; const CreateTodoSchema = z.object({ title: z.string().min(1).max(200), priority: z.enum(['low', 'medium', 'high']).optional(), }); server.setRequestHandler(CallToolRequestSchema, async (request) => { if (request.params.name === "create_todo") { const parsed = CreateTodoSchema.safeParse(request.params.arguments); if (!parsed.success) { return { content: [{ type: "text", text: `検証エラー: ${parsed.error.message}`, }], isError: true, }; } // parsed.dataは型安全で検証済み } });
2. Rate Limiting実装
暴走するAIエージェントから保護:
import { RateLimiter } from 'limiter'; const limiter = new RateLimiter({ tokensPerInterval: 100, interval: 'minute', }); server.setRequestHandler(CallToolRequestSchema, async (request) => { if (!await limiter.tryRemoveTokens(1)) { return { content: [{ type: "text", text: "レート制限超過。後でやり直してください。", }], isError: true, }; } // リクエスト処理... });
3. 監査ログ
セキュリティとデバッグのため全ツール呼び出しをログ:
function logToolInvocation(name: string, args: unknown, result: unknown) { console.log(JSON.stringify({ timestamp: new Date().toISOString(), tool: name, arguments: args, result: result, })); }
4. 最小権限の原則
必要なものだけ公開:
// ダメ:生DBアクセスを公開 tools: [{ name: "execute_sql", description: "任意のSQLクエリを実行", // セキュリティ悪夢! }] // 良い:特定範囲の操作を公開 tools: [ { name: "get_user_orders", description: "特定ユーザーの注文を取得", inputSchema: { type: "object", properties: { userId: { type: "string" }, limit: { type: "number", maximum: 100 }, }, required: ["userId"], }, }, ]
本番MCP:学んだ教訓
教訓1:失敗を想定した設計
AIエージェントは予想外の方法でツールを呼びます。防御的に作る:
server.setRequestHandler(CallToolRequestSchema, async (request) => { try { const result = await handleTool(request); return result; } catch (error) { // AIが復旧できる構造化エラーを返す return { content: [{ type: "text", text: JSON.stringify({ error: error.message, suggestion: "別のパラメータで試してください", validExamples: [ { title: "買い物" }, { title: "母に電話" }, ], }), }], isError: true, }; } });
教訓2:豊富な説明を提供
ツール説明の質がAIの使い方に直結:
// ダメ { name: "search", description: "アイテム検索", } // 良い { name: "search_products", description: `商品カタログを検索。クエリに一致する最大20商品を返却。 カテゴリ、価格帯、在庫状況フィルター対応。結果には商品名、価格、 在庫状態、サムネイルURLを含む。最良の結果のため、 一般的な単語より具体的な商品名やカテゴリを推奨。`, inputSchema: { type: "object", properties: { query: { type: "string", description: "検索クエリ。例:'ワイヤレスヘッドホン'、'10万円以下のノートPC'", }, }, }, }
教訓3:サーバーバージョン管理
MCPサーバーの進化時に後方互換性を維持:
const server = new Server( { name: "my-server", version: "2.1.0", // セマンティックバージョニング }, { capabilities: { tools: {}, resources: {}, }, } ); // 移行中は新旧ツール名両方サポート server.setRequestHandler(CallToolRequestSchema, async (request) => { const name = request.params.name; // レガシーツール名を処理 if (name === "old_tool_name") { console.warn("非推奨: 'new_tool_name'を使ってください"); return handleNewTool(request); } if (name === "new_tool_name") { return handleNewTool(request); } });
MCPの未来
今後の展開
- ストリーミングレスポンス: ツール出力ストリーミングのファーストクラスサポート
- マルチモーダルツール: 画像、音声、動画を返すツール
- ツール合成: 複数ツールをワークフローに結合
- 強化セキュリティ: 組み込みOAuthフローと権限スコープ
MCP vs. 代替手段
| 機能 | MCP | OpenAI Functions | LangChain Tools |
|---|---|---|---|
| ベンダー非依存 | ✅ | ❌ | ✅ |
| 標準化プロトコル | ✅ | ❌ | ❌ |
| リソースアクセス内蔵 | ✅ | ❌ | ❌ |
| 進捗通知 | ✅ | ❌ | 部分的 |
| コミュニティサーバー | 成長中 | N/A | 限定的 |
まとめ:MCP学習のベストタイミング
MCPはまだ初期段階ですが、方向性は明確です。2010年代にRESTを知らなければWeb開発者じゃなかったように、2020年代のAI開発者はMCPを知る必要があります。
ポイントまとめ:
- MCP = AI-外部世界連携の標準
- まず簡単なサーバーから — SDKが簡単にしてくれる
- セキュリティは最優先 — AIは予測不能
- 説明を丁寧に — AIが読むAPIドキュメント
- エラー対応をしっかり — AIが復旧できるように
MCPを早く習得したチームがAI時代で先行します。まだAPIを無理やり繋いでるチームより遥かに速く動けますから。
質問は「MCP学ぶべき?」じゃなくて、先行するか後追いするかです。
クイックリファレンス:MCP概念
| 概念 | 説明 | 例 |
|---|---|---|
| Server | ツールとリソースを公開 | DBアクセスサーバー |
| Client | サーバーに接続、AIが使用 | Claude Desktop |
| Tool | 呼び出し可能な関数 | create_github_issue |
| Resource | 読み取り可能なデータ | file:///project/README.md |
| Prompt | 再利用可能なテンプレート | コードレビューテンプレート |
| Transport | 通信レイヤー | stdio, SSE, WebSocket |
リソース:
関連ツールを見る
Pockitの無料開発者ツールを試してみましょう