49
34

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

MCPを肌で実感。俺がMCPサーバーだ

Posted at

TL; DR

  • MCPサーバー になって、手動でエージェントへレスポンスを返してみた

はじめに

流行真っただ中のMCP。凄そうなのは伝わった。何やら「USBポート的なもの」らしい。
でも、結局のところ何なのかよく分からん。

――じゃあ、MCPサーバーになってみますか

構成

こちらが今回構築する「人間MCPサーバー」です。
公式チュートリアルにならって、天気を取得するツールです。

構成図

VSCodeのGitHub Copilot Agent(MCPホスト)で天気の質問をすると、MCPクライアントからサーバーへリクエストが送られます。
私(MCPサーバー)の仕事は、リクエストを読み、適切なレスポンスをクライアントへ返却することです。

いっしょに試してみたい方は以下のリポジトリをご利用ください :muscle:

(補足)

人間の手前に別途MCPサーバーがあり気になった方がいるかもしれません。

MCPサーバーは名前こそ「サーバー」ですが、実体は子プロセスを起動し、標準入出力を使ってやりとりしています1

そのままでは人間が子プロセスの標準入出力を扱うことができないため、ファイルを介してやりとりできるように細工をしています(MCPのプロトコルの本質的な要素ではありません)。

  • MCPサーバーは標準入力をそのままin.txt に横流しする
  • 人間は in.txt を読みレスポンスを out.txt に書き込む
  • MCPサーバーは out.txt に書き込まれた内容を標準出力に横流しする

MCPサーバーのおしごと

MCPのクライアント、サーバー間のやり取りは以下の流れで行われます(公式サイトより引用)。

image.png

さあ、エージェントからサーバーが起動されました。仕事のはじまりです!

初期化依頼に応えよう

initialize request

クライアントからリクエストが送られてきました。

リクエスト(読みやすいように改行)
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2024-11-05",
    "capabilities": {
      "roots": {
        "listChanged": true
      }
    },
    "clientInfo": {
      "name": "Visual Studio Code",
      "version": "1.99.2"
    }
  }
}

MCPの通信では、JSON-RPC 2.0という形式が使用されています

initialize メソッドなので、初期化の依頼のようです。

initialize response

初期化依頼にレスポンスを返しましょう。レスポンスもJSON-RPCにする必要があります。

MCPのレスポンスは以下の形式で作成します。

レスポンスの仕様
{
  jsonrpc: "2.0";
  id: string | number;
  result?: {
    [key: string]: unknown;
  }
  error?: {
    code: number;
    message: string;
    data?: unknown;
  }
}

id には、リクエストと同じ 1 を指定しましょう。
intilalize メソッドの結果として返したい情報は result の中に詰めます。こちらも仕様で定義されています。

レスポンス
{
  "result": {
    "protocolVersion": "2024-11-05",
    "capabilities": {
      "tools": {}
    },
    "serverInfo": {
      "name": "humanmcp",
      "version": "1.0.0",
      "capabilities": {
        "resources": {},
        "tools": {}
      }
    }
  },
  "jsonrpc": "2.0",
  "id": 1
}

initialized notification

レスポンスを返すと、またクライアントから何か送られてきました。

notification
{
  "method": "notifications/initialized",
  "jsonrpc": "2.0"
}

notifications/initialized はクライアントが initilize メソッドのレスポンスを受け取り、初期化が完了したことを伝えるものです。

notificationという名前の通り、単なる通知なのでレスポンスは不要です。

使えるツールは?

request

ほっとしたのもつかの間、次のリクエストが飛んできました。

リクエスト
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/list",
  "params": {}
}

tools/list という名前の通り、MCPサーバーのツール一覧をリクエストしています。
サーバーがどんなツールを扱っているか知りたいようです。

response

というわけでツールの一覧を返しましょう。
今回私は天気の質問に答えようと思っているので、以下の形式のツールとして動作することに決めました。
人間MCPサーバーは開発工数0

  • 名前: get-japan-forecast
リクエスト形式
{
  // 地名
  "location": "string"
}
レスポンス形式
{
  // 天気
  "text": "string"
}

この情報をクライアントへ伝えましょう2

レスポンス
{
  "result": {
    "tools": [
      {
        "name": "get-japan-forecast",
        "description": "Get weather forecast",
        "inputSchema": {
          "type": "object",
          "properties": {
            "location": {
              "type": "string",
              "description": "location name"
            }
          },
          "required": [
            "location"
          ],
          "additionalProperties": false,
          "$schema": "http://json-schema.org/draft-07/schema#"
        }
      }
    ]
  },
  "jsonrpc": "2.0",
  "id": 2
}

メインの質問、今日の天気は?

エージェントがプロンプトに応じてMCPサーバーへリクエスト

しばらくして、エージェント側では何やら天気についてのプロンプトを受け取ったようです。

東京の天気を教えてください。

天気の質問ならMCPツール get-japan-forecast が使えそうだと判断したようです。
ユーザーに、MCPサーバーへのリクエスト許可を求めます。

エージェントがMCPサーバーへのリクエスト許可を求めるダイアログ

ユーザーから許可が出ました。すかさず get-japan-forecast のリクエストが送られてきます。
形式は tools/list のレスポンスで伝えたとおりになっています。

東京の天気は?
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "get-japan-forecast",
    "arguments": {
      "location": "東京"
    }
  }
}

レスポンスを返す

というわけで、東京の天気を答えましょう。

雲一つない青空

情報は result.content の中に記載します。

晴れ!
{
  "result": {
    "content": [
      {
        "type": "text",
        "text": "sunny"
      }
    ]
  },
  "jsonrpc": "2.0",
  "id": 3
}

エージェントがレスポンスをもとにプロンプトに回答

エージェントは受け取った天気の情報をもとに、ユーザーに回答します。

東京の天気は晴れです。

エージェントがプロンプトに返答

これにて一件落着です。

エラーを返したい

またしばらくすると、別のリクエストが送られてきました。

大阪の天気は?
{
  "jsonrpc": "2.0",
  "id": 4,
  "method": "tools/call",
  "params": {
    "name": "get-japan-forecast",
    "arguments": {
      "location": "大阪"
    }
  }
}

のぞみに飛び乗っても2時間かかります。困った。
こんなときはエラーレスポンスを返しましょう。

MCPサーバーでは、不正なリクエストや想定外のエラーが発生した際にエラーを返します。
エラーコードでエラーの理由、メッセージでその詳細を伝えます。

エラーコード
// https://modelcontextprotocol.io/docs/concepts/architecture#error-handling より引用
enum ErrorCode {
  // Standard JSON-RPC error codes
  ParseError = -32700,
  InvalidRequest = -32600,
  MethodNotFound = -32601,
  InvalidParams = -32602,
  InternalError = -32603
}

今回は不正なパラメータの指定 -32602 を使いましょう。

エラー
{
  "error": {
    "code": -32602,
    "message": "遠い。自分で調べて"
  },
  "jsonrpc": "2.0",
  "id": 4
}

エージェントの返答でもエラーが出ました。

大阪の天気情報を取得できませんでした。別の方法で調べていただけますか?

エラー

エラーメッセージで原因を伝える

往生際の悪いことにユーザーはまた別の場所の天気を訊いたようです。

北海道の天気は?
{
  "jsonrpc": "2.0",
  "id": 5,
  "method": "tools/call",
  "params": {
    "name": "get-japan-forecast",
    "arguments": {
      "location": "北海道"
    }
  }
}

エラーメッセージでもっとはっきり書かないとダメみたいですね。

{
  "error": {
    "code": -32602,
    "message": "東京の天気以外知りません。自分でググって"
  },
  "jsonrpc": "2.0",
  "id": 5
}

返答にも反映されたようです。

北海道の天気を取得しようとしましたが、情報を取得できませんでした。東京の天気以外は対応していないようです。他に知りたいことがあれば教えてください。

mcphuman3.png

疲れたのでMCPサーバーは閉店!

おわりに

以上、実際にMCPサーバーを体験してみた紹介記事でした。こうやって見ると、秘術のように感じていたMCPも意外とシンプルな仕組みで動いていることが分かりました。みなさんも、MCPの中身を覗いてみてはいかがでしょうか?

自分のプロンプトに自分で答えていると、子供の頃両手に指人形をはめて遊んでいたことを思い出して空しくなりました

余談

本記事作成に当たり、実際のMCPサーバーの中身をこっそり確認していました。
プロトコルの形式を知りたいだけであればこちらの方が何倍も手軽です

  1. リンク先にあるように、HTTP通信を利用することもできます。

  2. レスポンス形式は含めません。おそらく、サーバー側で決められるので伝える必要が無いからだと思います。

49
34
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
49
34

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?