MCPのプロトコルを読み解く
SREホールディングス株式会社の松本です。
AIコードエディタやAIエージェントの普及が進み、日々の業務に活用している方も多いと思います。中には、GitHubやPlaywrightなどのMCPを利用し、PRの作成やE2Eテストケースの作成の自動化も行っている方もいるかもしれません。
MCPはAIを活用した開発をパワーアップしてくれる頼もしい存在ですが、MCPサーバーを自作できると、公開データや社内データを取得させたり、APIが公開されているサービスを操作させたり、自社のプロダクトをMCP対応させることもできるかもしれません。
MCPサーバーはSDKが公開されていて簡単に実装することができますが、本記事ではMCPの理解が深まるように、プロトコル(2025-06-18)の仕様をざっくり読み解いていこうと思います。
対象読者
- MCPの理解を深めたい方
- MCPサーバーを作ってみたい方
MCPとは
MCP(Model Context Protocol)とは、AIアプリケーションが外部のサービスやリソースにアクセスできるようにするための標準プロトコルです。JSON-RPCをベースに、ライフサイクル・トランスポート・認可が定義されています。
MCPはClaudeを開発しているAnthropic社によって2024年11月に発表された技術ですが、OpenAIやGoogleなどの競合も続々と対応を発表し、今やAIアプリケーションにはなくてはならない存在になりつつあります。
MCPが生まれる前は、AIアプリケーションと外部サービスを接続する場合、サービス毎に個別の開発を行って接続する必要がありましたが、MCPによってAIアプリケーションと外部サービスが簡単に接続できるようになりました。この様子を「AIアプリケーションのUSB-Cポート」に例えた図が有名です。
引用元:What is Model Context Protocol (MCP)? How it simplifies AI integrations compared to APIs
MCPのライフサイクル
MCPのライフサイクルは、3つのフェーズが定義されています。全体像は以下の通りです。
引用元:Model Context Protocol Lifecycle
1. Initialization
MCPクライアントがMCPサーバーに接続する時、最初に初期化を行うフェーズです。このフェーズでは、プロトコルのバージョンや機能をお互いに確認します。
初期化フェーズのMCPサーバーの関数は以下の2つです。
関数 | 説明 |
---|---|
initialize | 初期化 |
notifications/initialized | 初期化完了の通知 |
initializeリクエスト
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {
"roots": {},
"sampling": {},
"elicitation": {
"listChanged": true
}
},
"clientInfo": {
"name": "MCPClientForBlog",
"title": "ブログ用MCPクライアント",
"version": "1.0.0"
}
}
}
initializeレスポンス
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-06-18",
"capabilities": {
"logging": {},
"prompts": {},
"resources": {},
"tools": {
"listChanged": true
}
},
"serverInfo": {
"name": "MCPServerForBlog",
"title": "ブログ用MCPサーバー",
"version": "1.0.0"
}
}
}
MCPサーバーは、MCPクライアントが要求したプロトコルバージョンに対応している場合は同じバージョンを、対応していない場合は対応しているバージョンを返します。MCPクライアントは、レスポンスのプロトコルバージョンによって対応可能なMCPサーバーかを判断できます。
対応している機能をcapabilities
でお互い渡していますので、これによってセッション中に使用できる機能を決定します。もし、MCPサーバー側もMCPクライアント毎に処理を変える場合、この時点からセッションを保持しておく必要があります。
notifications/initializedリクエスト
{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}
MCPクライアントは初期化が完了したことをMCPサーバーに通知します。通知に対して、MCPサーバーは204で空のレスポンスを返します。
2. Operation
MCPクライアントとMCPサーバーがメッセージをやり取りするフェーズです。一般的にイメージするMCPの使用は、このフェーズです。
MCPクライアントの機能
機能 | 説明 |
---|---|
Roots | MCPサーバーがディレクトリ・ファイルにアクセス |
Sampling | MCPサーバーがMCPクライアント経由でLLMを実行 |
Elicitation | MCPサーバーが対話的にユーザーから追加情報を取得 |
いずれもMCPの幅を広げられる機能ですが、MCPサーバー側の対応は任意です。
MCPサーバーの機能
機能 | 説明 |
---|---|
Prompts | ユーザーが利用可能な定型プロンプト |
Resources | MCPクライアントが管理可能なコンテキストデータ |
Tools | LLMが実行可能な関数 |
操作フェーズのMCPサーバーの関数は以下の通りです。
関数 | 説明 |
---|---|
prompts/list | 定型プロンプトのリストを取得 |
prompts/get | 指定した定型プロンプトを取得 |
resources/list | リソースのリストを取得 |
resources/read | 指定したリソースを取得 |
resources/subscribe | 指定したリソースをサブスクライブ |
tools/list | ツールのリストを取得 |
tools/call | 指定したツールを実行 |
それぞれの機能はリストを提供し、その中から使用してもらう構成になっています。JSON-RPCは関数のハンドリングを行いますが、MCPではその中にもう1つ機能毎の関数のハンドリングがあり、二段階のハンドリングが行われる形です。
提供しない機能の関数は実装不要なので、Toolsのみ提供する場合はtools/list
とtools/call
の2つが必要です。本記事では、この2つを例に説明します。
tools/listリクエスト
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list",
"params": {
"cursor": "optional-cursor-value"
}
}
tools/listレスポンス
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{
"name": "get_item",
"title": "Item Information Provider",
"description": "Get item information",
"inputSchema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "item name"
}
},
"required": ["name"]
},
"outputSchema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "item name"
},
"description": {
"type": "string",
"description": "item description"
},
"price": {
"type": "number",
"description": "item price"
}
},
"required": ["name","price"]
}
}
],
"nextCursor": "next-page-cursor"
}
}
tools
が提供しているツールのリストです。name
がツール名、inputSchema
が引数のスキーマ、outputSchema
が戻り値のスキーマです。outputSchema
は任意ですが、実行結果をJSONで返すツールの場合は、指定しておくと構造化された状態でデータを返せるので、AIアプリケーションが効率的に利用できます。
tools/callリクエスト
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "get_item",
"arguments": {
"name": "game console"
}
}
}
tools/callレスポンス
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [
{
"type": "text",
"text": "{\"name\": \"game console\", \"price\": 49980}"
}
],
"structuredContent": {
"name": "game console",
"price": 49980
},
"isError": false
}
}
result
がツールの実行結果です。content
は種類毎の値が入りますが、構造化されたデータには対応するものがないため、テキストコンテンツで文字列化したJSONを入れる必要があります。前述のoutputSchema
を定義していると、structuredContent
で構造化データを渡すことができます。
テキストのコンテンツ以外は、画像・音声・リソースリンク・埋め込みリソースに対応しています。テキストとリソースリンクの2つを使って、回答と関連情報のリンクといった構成のメッセージを返すこともできます。
プロトコルエラーレスポンス
{
"jsonrpc": "2.0",
"id": 2,
"error": {
"code": -32602,
"message": "Unknown tool: invalid_tool_name"
}
}
対応していないツール・引数が渡された場合、JSON-RPC準拠のエラーを返す必要があります。
ツール実行エラーレスポンス
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [
{
"type": "text",
"text": "Failed to fetch item data: This item is sold out"
}
],
"isError": true
}
}
ツールの実行中にエラーが発生した場合、isError
をtrueにして、content
でエラー内容を返す必要があります。これによって、MCPクライアントが適切にエラー処理を行えるようになります。
3. Shutdown
MCPクライアントがMCPサーバーとの接続を切断するフェーズです。このフェーズでは、メッセージは定義されておらず、使用しているトランスポート毎の終了処理を行います。HTTPを使用するMCPの場合、HTTP接続を閉じれば完了です。
MCPのトランスポート
MCPのトランスポートは、stdioとStreamable HTTPの2つが定義されています。本記事では、Streamable HTTPについて説明します。
1. Endpoint
Streamable HTTPのMCPサーバーは、単一のエンドポイントでJSON-RPCリクエストを受信する必要があります。
エンドポイント | 説明 |
---|---|
POST /mcp | MCPサーバー |
パスは決められていませんが、わかりやすく/mcp
とするケースをよく見ます。現在はStreamable HTTPが推奨されていますが、後方互換(HTTP+SSE)のために、GETやDELETEのサポートも求められています。ただし、405を返すだけでも良いようです。
2. Header
Streamable HTTPのMCPサーバーは、次のヘッダーに対応する必要があります。
ヘッダー | 説明 |
---|---|
MCP-Protocol-Version | MCPのプロトコルバージョン |
Origin | リクエスト元 |
Accept | データ形式 |
Mcp-Session-Id | セッションID |
Last-Event-ID | 最後に受信したイベントID |
DNSリバインディング攻撃防止のため、Origin
ヘッダーの検証が必須になっています。また、Accept
ヘッダーではapplication/json
とtext/event-stream
の2つに対応する必要があります。この2つに対応することにより、単一のJSONを返すか、SSE(Server-Sent Events)ストリームを開始するか、必要に応じて選ぶことができます。
Mcp-Session-Id
ヘッダーは、MCPサーバーがステートフルな機能を提供する場合にセッション管理に使用し、途中で切断されたセッションを再開する際にLast-Event-ID
ヘッダーで最後のイベントIDを渡します。
3. Session
MCPサーバーはステートレス・ステートフルどちらも対応しています。ステートフルな機能を提供する場合、Initializationフェーズでセッションを確立し、以降はMcp-Session-Id
ヘッダーを使用してセッションを管理する必要があります。
引用元:Model Context Protocol Transports
MCPの認可
MCPの認可は、OAuth 2.0 PKCEをベースに定義されています。対応は任意ですが、対応するとユーザー毎のデータ操作が実現できるようになります。
MCPの認可に対応する場合、以下の4つの仕様を実装する必要があります。
- OAuth 2.1 IETF DRAFT (draft-ietf-oauth-v2-1-13)
- OAuth 2.0 認可サーバーメタデータ (RFC8414)
- OAuth 2.0 保護リソースメタデータ (RFC9728)
- OAuth 2.0 リソースインジケーター (RFC8707)
また、以下の仕様も実装が推奨されています。
- OAuth 2.0 動的クライアント登録プロトコル (RFC7591)
MCPにおける認可フローは以下の通りです。
引用元:Model Context Protocol Authorization
MCPクライアントは、MCPサーバーから認可が要求されたら、ヘッダーから保護リソースメタデータの在処を取得してアクセスします。保護リソースメタデータには認可サーバーの在処が記載されているので、認可サーバーメタデータにアクセスします。認可サーバーメタデータには認可エンドポイントの在処が記載されているので、クライアントを登録して認可を開始します。このフローを実装するには、以下のエンドポイントが必要です。
エンドポイント | 説明 |
---|---|
GET /.well-known/oauth-protected-resource | 保護リソースメタデータ |
GET /.well-known/oauth-authorization-server | 認可サーバーメタデータ |
POST /register | クライアント登録API |
POST /authorize | 認可API |
POST /token | トークン取得API |
OAuth 2.0の具体的な仕様は本記事では割愛しますが、プロダクトのMCP対応をする場合、プロダクト本体もOAuth 2.0ベースで作られていれば、同様に/mcp
エンドポイントを保護すれば良いので、対応しやすいのではないかと思います。
まとめ
MCPのプロトコルを、必須のポイントに絞って解説してみました。プロトコルをざっくり理解してからSDKを使って開発すると、何がどう動いているのか・何故これが必要なのか・自身で担保すべきものは何か、などがわかりやすいと思います。
MCPは公式ドキュメントが充実していて、本記事で触れていないものも沢山あります。もっと詳しく知りたいと思った方は、ぜひ公式ドキュメントやSDKのソースコードを読みながら、MCPサーバーを実装してみてください!
Discussion