サーバーは initialize にどう答えるか
ハンドシェイクは、MCPのクライアントとサーバーが最初に交わすやり取りです。クライアントが、使いたいバージョンを添えて initialize を送ります。サーバーは、使うバージョンと、できることを答えます。
クライアントは、サーバーが「ある」と言ったものだけを使います。言いすぎるサーバーは、クライアントを壊します。
このレッスンのゴール
メモ
initialize の要求に答え、サーバーにできることを知らせることができる。
キーワード
| ことば | いみ | れい |
|---|---|---|
| ハンドシェイク | セッションを始める最初のやり取り | |
| プロトコルのバージョン | 規則の版を示す日付 | 2024-11-05 |
| capability | サーバーが持つと知らせる機能のまとまり | tools |
| 状態 | サーバーが覚えていること: new、waiting、ready | |
| 折り合う | 両方が話せる一つのバージョンに決める |
学生が入力するセルは、このプレビューでは空白です。
サーバーは知っている版を選ぶ
クライアントがバージョンを名指しします。サーバーは、話せるバージョンのリストを持っています。求められたものがリストにあれば、それで答えます。
なければ、このサーバーはコード -32602 で断ります。求められた版と自分のリストをエラーに入れるので、クライアントはやり直せます。
例: 三つのクライアントが求める
| クライアントが求める版 | サーバーが話せる版 | サーバーの答え |
|---|---|---|
| 2024-11-05 | 2025-03-26, 2024-11-05 | 2024-11-05 を使う result |
| 2025-03-26 | 2025-03-26, 2024-11-05 | 2025-03-26 を使う result |
| 1999-01-01 | 2025-03-26, 2024-11-05 | 二つのリストを添えたエラー -32602 |
学生が入力するセルは、このプレビューでは空白です。
サーバーはできることだけ知らせる
initialize の result には capabilities が入ります。それぞれが、サーバーの持つメソッドのまとまりです。tools、resources、prompts、logging があります。
旗が付くものもあります。listChanged は、一覧が変わったらクライアントに知らせるという意味です。このサーバーは知らせないので false です。知らせていないまとまりのメソッドには、-32601 を返します。
よくある間違い
注意
念のため、すべての capability を知らせる人は多いです。するとクライアントは、サーバーにないメソッドを呼びます。知らせるのは、書いて試したものだけにします。知らせることは、約束です。
モデル用の補助を書く
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const base = process.env.AI_URL || \"http://ai:4000\";' 'async function ask(messages, sample, max) {' ' try {' ' const res = await fetch(base + \"/v1/chat/completions\", { method: \"POST\", headers: { \"content-type\": \"application/json\", authorization: \"Bearer lab\" }, body: JSON.stringify({ model: \"lab\", messages: messages, max_tokens: max || 80 }), signal: AbortSignal.timeout(30000) });' ' if (res.ok === false) throw new Error(\"status \" + res.status);' ' const data = await res.json();' ' return { text: data.choices[0].message.content, recorded: false };' ' } catch (error) {' ' console.log(\"記録済みサンプル: モデルが答えませんでした (\" + error.message + \")。\");' ' return { text: sample, recorded: true };' ' }' '}' 'module.exports = { ask };' > model.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
サーバーを書く(1)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const SUPPORTED = [\"2025-03-26\", \"2024-11-05\"];' 'let state = \"new\";' 'function send(m) { process.stdout.write(JSON.stringify(Object.assign({ jsonrpc: \"2.0\" }, m)) + \"\n\"); }' 'function reply(id, result) { send({ id: id, result: result }); }' 'function fail(id, code, text, data) { send({ id: id, error: { code: code, message: text, data: data } }); }' > server2.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
サーバーを書く(2)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function initialise(msg) {' ' const asked = (msg.params || {}).protocolVersion;' ' if (SUPPORTED.indexOf(asked) < 0) return fail(msg.id, -32602, \"Unsupported protocol version\", { requested: asked, supported: SUPPORTED });' ' state = \"waiting\";' ' reply(msg.id, { protocolVersion: asked, capabilities: { tools: { listChanged: false } }, serverInfo: { name: \"lunch\", version: \"0.2\" } });' '}' >> server2.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
サーバーを書く(3)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function handle(msg) {' ' if (msg.id === undefined) {' ' if (msg.method === \"notifications/initialized\" && state === \"waiting\") state = \"ready\";' ' return;' ' }' ' if (msg.method === \"ping\") return reply(msg.id, {});' ' if (msg.method === \"initialize\") return state === \"new\" ? initialise(msg) : fail(msg.id, -32600, \"Already initialised\");' ' if (state === \"new\" || state === \"waiting\") return fail(msg.id, -32600, \"Not initialised\");' >> server2.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
サーバーを書く(4)
docker exec -w /home/node/project agent sh -c "printf '%s\n' ' if (msg.method === \"tools/list\") return reply(msg.id, { tools: [] });' ' fail(msg.id, -32601, \"Method not found: \" + msg.method);' '}' 'require(\"readline\").createInterface({ input: process.stdin }).on(\"line\", function (line) {' ' let msg;' ' try { msg = JSON.parse(line); } catch (error) { return fail(null, -32700, \"Parse error\"); }' ' handle(msg);' '});' >> server2.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
台本のクライアントを書く(1)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' 'const server = require(\"child_process\").spawn(\"node\", [\"server2.js\"]);' 'const init = function (id, version) { return { id: id, method: \"initialize\", params: { protocolVersion: version, capabilities: {}, clientInfo: { name: \"demo\", version: \"0.1\" } } }; };' 'const script = [' ' { id: 1, method: \"tools/list\" },' ' init(2, \"1999-01-01\"),' ' init(3, \"2024-11-05\"),' ' { id: 4, method: \"tools/list\" },' ' { method: \"notifications/initialized\" },' ' { id: 5, method: \"tools/list\" },' ' { id: 6, method: \"resources/list\" },' '];' > client2.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
台本のクライアントを書く(2)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const seen = [];' 'require(\"readline\").createInterface({ input: server.stdout }).on(\"line\", function (line) {' ' console.log(\"<- \" + line);' ' seen.push(JSON.parse(line));' ' if (seen.length === 6) server.stdin.end();' '});' 'script.forEach(function (m) {' ' const full = Object.assign({ jsonrpc: \"2.0\" }, m);' ' console.log(\"-> \" + JSON.stringify(full));' ' server.stdin.write(JSON.stringify(full) + \"\n\");' '});' >> client2.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
台本のクライアントを書く(3)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'server.on(\"close\", async function () {' ' const caps = JSON.stringify(seen[2].result.capabilities);' ' const r = await ask([{ role: \"user\", content: \"次の capabilities を持つサーバーにできることを、一文で書いてください: \" + caps }], \"ツールの一覧を返せます。その一覧が変わることは知らせません。\", 60);' ' console.log(\"モデル: \" + r.text);' '});' >> client2.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
クライアントをサーバーに向けて動かす
docker exec -w /home/node/project agent node client2.jsここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
台本が送るもの
七つのメッセージが決まった順に入ります。まず、早すぎる一覧と、悪い版。次に、良い版と、二度目の早すぎる一覧。
そのあと、準備完了の通知、通る一覧、知らせていないメソッドを送ります。最後に、モデルが capabilities の意味を一文で書きます。モデルがないと「記録済みサンプル」になります。
六つの答え
| 要求 | 答え |
|---|---|
| 1 tools/list | エラー -32600、Not initialised |
| 2 initialize、1999-01-01 | エラー -32602、話せる二つの版つき |
| 3 initialize、2024-11-05 | tools とサーバー名を含む result |
| 4 tools/list | エラー -32600、まだ準備前 |
| 5 tools/list | 空の一覧の result |
| 6 resources/list | エラー -32601、知らせていない |
学生が入力するセルは、このプレビューでは空白です。