MCPサーバに手動でアクセスしてフローを学ぶ
はじめに
MCPは、LLMアプリケーションが外部のデータソースやツールと安全に連携するためのオープンなプロトコルです。
通常、MCPサーバーはClaude for Desktopのようなクライアントアプリケーション経由で使用しますが、今回は、MCPサーバーに直接JSON-RPCリクエストを送信して、プロトコルの仕組みを理解してみたいと思います。
MCPとは何か?
MCPについての解説記事は多くあるので、簡単に概要を説明します。
MCP(Model Context Protocol)は、LLMが外部システムと連携するための標準プロトコルです。公式サイトによると、MCPは以下の3つの主要機能を提供します
1. Tools(ツール)
LLMが実行できる関数です。例えば、ファイルの読み書き、API呼び出し、計算処理などを行えます。
2. Resources(リソース)
ファイル内容やAPIレスポンスなど、LLMが読み取り可能なデータです。
3. Prompts(プロンプト)
特定のタスクを実行するための事前定義されたテンプレートです。
今回は、Toolsを中心に手動リクエストの方法を学んでいきます。
MCPプロトコルの3つのフェーズ
MCPプロトコルは以下の3つのフェーズで構成されています
1. 初期化フェーズ(Initialization Phase)
- クライアントがサーバーに接続を開始
- プロトコルバージョンと対応機能の交換
- 双方の準備が完了したことを確認
2. 操作フェーズ(Operation Phase)
- 実際のMCP機能(Tools、Resources、Prompts)の利用
- リクエスト・レスポンスによる双方向通信
- エラーハンドリングとリトライ
3. 終了フェーズ(Shutdown)
- セッションの正常な終了
- リソースのクリーンアップ
- 接続の切断
MCPにリクエストを送る
事前準備:環境セットアップ
今回は公式のMCPサーバーの一つである、時間を取得するだけの簡単なMCPサーバーを使用します。
uvがインストールされてない場合はインストールしてください。
curl -LsSf https://astral.sh/uv/install.sh | sh
フェーズ1: 初期化フェーズ(Initialization Phase)
まずは初期化フェーズです。
サーバーの起動
最初にMCPサーバーを起動していきます。
特に何も出力はありませんが、問題ありません。
uvx mcp-server-time --local-timezone=Asia/Japan
Initialize Request(初期化リクエスト)
MCPセッションの最初の手順として、サーバーに初期化リクエストを送信します。
今回使用している方法は、MCPプロトコルのstdio(Standard Input/Output)トランスポートです。これは、MCP公式仕様で定義されている2つの標準通信方式の一つです。
標準入出力を使うことで、コマンドラインからでも簡単にMCPサーバーにリクエストを送ることができます。以下のJSON-RPCメッセージを1行で入力してEnterを押してください
入力:
{"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2024-11-05", "capabilities": {"tools": {}}, "clientInfo": {"name": "manual-client", "version": "1.0.0"}}}
ここでは、プロコトルのバージョンや、クライアント側の情報をサーバーに送信します。
Initialize Response(初期化レスポンス)
すると、サーバーから以下のようなレスポンスが返ってくるかと思います。
レスポンス:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2024-11-05",
"capabilities": {
"experimental": {},
"tools": {
"listChanged": false
}
},
"serverInfo": {
"name": "mcp-time",
"version": "1.9.4"
}
}
}
レスポンスでは、サーバーが提供する機能の詳細やサーバーの情報が返ってきます。
Initialized Notification(初期化完了通知)
続いて、初期化が完了したことをサーバーに通知します。
入力:
{"jsonrpc": "2.0", "method": "notifications/initialized"}
ここでは何もレスポンスは帰ってきませんが、これで初期化フェーズは完了です。
フェーズ2: 操作フェーズ(Operation Phase)
初期化が完了したら、実際のMCP機能を使用できます。
ツール一覧の取得
まずは、このMCPサーバーではどのようなToolが使用できるのかを確認してみます。
使用できるToolの一覧を取得するには、メソッドに"tools/list"を指定してリクエストを送ります。
入力:
{"jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {}}
以下のようなレスポンスが返ってくるかと思います。
レスポンス:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{
"name": "get_current_time",
"description": "Get current time in a specific timezones",
"inputSchema": {
"type": "object",
"properties": {
"timezone": {
"type": "string",
"description": "IANA timezone name (e.g., 'America/New_York', 'Europe/London'). Use 'Asia/Tokyo' as local timezone if no timezone provided by the user."
}
},
"required": [
"timezone"
]
}
},
{
"name": "convert_time",
"description": "Convert time between timezones",
"inputSchema": {
"type": "object",
"properties": {
"source_timezone": {
"type": "string",
"description": "Source IANA timezone name (e.g., 'America/New_York', 'Europe/London'). Use 'Asia/Tokyo' as local timezone if no source timezone provided by the user."
},
"time": {
"type": "string",
"description": "Time to convert in 24-hour format (HH:MM)"
},
"target_timezone": {
"type": "string",
"description": "Target IANA timezone name (e.g., 'Asia/Tokyo', 'America/San_Francisco'). Use 'Asia/Tokyo' as local timezone if no target timezone provided by the user."
}
},
"required": [
"source_timezone",
"time",
"target_timezone"
]
}
}
]
}
}
ここから、get_current_time, convert_timeという2つのツールが利用可能であることがわかります。
ツールの実行
今回はシンプルにget_current_timeで現在の時刻を取得してみます。tools/listから必須パラメータにtimezoneを指定する必要があるので、こちらも日本時間で取るように指定します。
入力:
{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "get_current_time", "arguments": {"timezone": "Asia/Tokyo"}}}
レスポンス:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "{\n\t\"timezone\": \"Asia/Tokyo\",\n\t\"datetime\": \"2025-06-12T20:10:30+09:00\",\n\t\"is_dst\": false\n}"
}
],
"isError": false
}
}
MCPサーバー経由で現在時刻を取得することができました。
実際のMCPクライアントは、この情報を使用してLLMで回答を生成することになります。
シャットダウン
stdioでの通信では、プロセスを切ることでシャットダウンします。Ctrl+Dなどを入力して、終了しましょう。
エラーとなるリクエストを試す。
これまでに説明してきた流れで、MCPサーバーとの通信の実際のフローを体験することができました。
この順序性を無視して、あえてエラーになるリクエストを起こってみましょう。
操作フェーズの前には初期化フェーズが必ず必要ですが、初期化前にMCPサーバーを操作してみます。
エラーが返ってくることが確認できます。
このように、MCPが想定する処理順序を無視してリクエストを送信すると、適切なエラーが返されることがわかりました。
まとめ
今回は、MCPサーバーに手動でリクエストを送ることで、実際のフローを体験しました。
MCPは簡単に実行できるがゆえに、どのような流れでアクセスしてるのかがわかりにくかったので、手動で実行することでそのフローをよく理解することができました。
次は実際にMCPクライアントを自作して、LLMで回答するところまでの流れを確認してみたいです。
Discussion