🤖

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ポート」に例えた図が有名です。

MCPアーキテクチャ

引用元:What is Model Context Protocol (MCP)? How it simplifies AI integrations compared to APIs

MCPのライフサイクル

MCPのライフサイクルは、3つのフェーズが定義されています。全体像は以下の通りです。

MCPライフサイクル

引用元: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/listtools/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/jsontext/event-streamの2つに対応する必要があります。この2つに対応することにより、単一のJSONを返すか、SSE(Server-Sent Events)ストリームを開始するか、必要に応じて選ぶことができます。

Mcp-Session-Idヘッダーは、MCPサーバーがステートフルな機能を提供する場合にセッション管理に使用し、途中で切断されたセッションを再開する際にLast-Event-IDヘッダーで最後のイベントIDを渡します。

3. Session

MCPサーバーはステートレス・ステートフルどちらも対応しています。ステートフルな機能を提供する場合、Initializationフェーズでセッションを確立し、以降はMcp-Session-Idヘッダーを使用してセッションを管理する必要があります。

MCPセッションフロー

引用元:Model Context Protocol Transports

MCPの認可

MCPの認可は、OAuth 2.0 PKCEをベースに定義されています。対応は任意ですが、対応するとユーザー毎のデータ操作が実現できるようになります。

MCPの認可に対応する場合、以下の4つの仕様を実装する必要があります。

また、以下の仕様も実装が推奨されています。

  • OAuth 2.0 動的クライアント登録プロトコル (RFC7591)

MCPにおける認可フローは以下の通りです。

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サーバーを実装してみてください!

参考

SRE Holdings 株式会社

Discussion