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