🤖

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サーバーを使用します。

https://github.com/modelcontextprotocol/servers/tree/main/src/time

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