TL; DR
- MCPサーバー になって、手動でエージェントへレスポンスを返してみた
はじめに
流行真っただ中のMCP。凄そうなのは伝わった。何やら「USBポート的なもの」らしい。
でも、結局のところ何なのかよく分からん。
――じゃあ、MCPサーバーになってみますか。
構成
こちらが今回構築する「人間MCPサーバー」です。
公式チュートリアルにならって、天気を取得するツールです。
VSCodeのGitHub Copilot Agent(MCPホスト)で天気の質問をすると、MCPクライアントからサーバーへリクエストが送られます。
私(MCPサーバー)の仕事は、リクエストを読み、適切なレスポンスをクライアントへ返却することです。
いっしょに試してみたい方は以下のリポジトリをご利用ください
(補足)
人間の手前に別途MCPサーバーがあり気になった方がいるかもしれません。
MCPサーバーは名前こそ「サーバー」ですが、実体は子プロセスを起動し、標準入出力を使ってやりとりしています1。
そのままでは人間が子プロセスの標準入出力を扱うことができないため、ファイルを介してやりとりできるように細工をしています(MCPのプロトコルの本質的な要素ではありません)。
- MCPサーバーは標準入力をそのまま
in.txt
に横流しする - 人間は
in.txt
を読みレスポンスをout.txt
に書き込む - MCPサーバーは
out.txt
に書き込まれた内容を標準出力に横流しする
MCPサーバーのおしごと
MCPのクライアント、サーバー間のやり取りは以下の流れで行われます(公式サイトより引用)。
さあ、エージェントからサーバーが起動されました。仕事のはじまりです!
初期化依頼に応えよう
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
レスポンスを返すと、またクライアントから何か送られてきました。
{
"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サーバーへのリクエスト許可を求めます。
ユーザーから許可が出ました。すかさず 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
}
返答にも反映されたようです。
北海道の天気を取得しようとしましたが、情報を取得できませんでした。東京の天気以外は対応していないようです。他に知りたいことがあれば教えてください。
疲れたのでMCPサーバーは閉店!
おわりに
以上、実際にMCPサーバーを体験してみた紹介記事でした。こうやって見ると、秘術のように感じていたMCPも意外とシンプルな仕組みで動いていることが分かりました。みなさんも、MCPの中身を覗いてみてはいかがでしょうか?
自分のプロンプトに自分で答えていると、子供の頃両手に指人形をはめて遊んでいたことを思い出して空しくなりました
余談
本記事作成に当たり、実際のMCPサーバーの中身をこっそり確認していました。
プロトコルの形式を知りたいだけであればこちらの方が何倍も手軽です