MCP(Model Context Protocol)Server を いちから自分で書けるようになるための学習資料を作る。読んで終わりではなく、手元で動く MCP Server を持ち帰り、Claude Code・Codex・Antigravity CLI から実際に呼び出せる状態にする。
| 項目 | 内容 |
|---|---|
| MCP の知識 | まったく無いことを前提にする。「MCP という言葉は聞いたことがある」程度から始める |
| JavaScript | 文法は十分に知っている。async/await・モジュール・クラスの説明は不要 |
| AI コーディング CLI | Claude Code・Codex を「とりあえず使える」水準。設定ファイルの場所や MCP の登録方法は知らない前提 |
| サーバー開発 | HTTP・プロセス・標準入出力の概念は分かるが、JSON-RPC は知らないかもしれない前提 |
120 分。読み物として通読するのではなく、手を動かしながら進む前提で時間を配分する。配分案は第 4 章に置く。
当初は 90 分・本編 8 章で見積もっていたが、Tasks 拡張(論点 6-B)と stdio ブリッジ(論点 7-B)まで扱うと決めたため、本編 11 章・120 分に改めた。章を 2 部に割らず、通しの 1 本として構成する。
この資料が扱わないもの:MCP Client(ホスト側)の実装、LLM そのものの仕組み、TypeScript の型システム(別資料に譲る)。
| 対象 | バージョン | 確認方法 |
|---|---|---|
| Node.js | v26.8.1 | node --version |
| npm | 11.19.0 | npm --version |
| @modelcontextprotocol/sdk | 1.30.0 | npm view @modelcontextprotocol/sdk version |
| MCP 仕様(最新) | 2026-07-28 | 仕様サイトの最新リビジョン |
| MCP 仕様(資料の基準) | 2025-11-25 | Claude Code が実際に喋る版。次節で実測した |
| Claude Code | 2.1.263 | claude --version |
| Codex | 0.153.4 | codex --version |
| Antigravity | 1.107.0 | antigravity --version |
3 つの CLI に接続させて、届いた initialize を記録した。最新の 2026-07-28 を喋る CLI は無かった。
| ホスト | 版 | 名乗った能力 | tasks |
|---|---|---|---|
| Claude Code 2.1.263 | 2025-11-25 | roots ・ elicitation | 非対応 |
| Codex 0.153.4 | 2025-06-18 | elicitation(form ・ url) | 未確認 |
| Antigravity 1.107.0 | — | — | 未確認 |
この結果を受けて、資料は 2025-11-25 を基準にした(論点 2 の A)。2025-11-25 は 2026-07-28 とは作りが違い、接続ごとの状態を持ち、サーバーからクライアントへリクエストを送れる(sampling・roots・elicitation)。タスクもコア仕様に入っている。以下に残した 2026-07-28 の記述は将来の変更点として読むこと。
Tasks は Claude Code では使われなかった。サーバー側で capabilities.tasks を名乗り、ツールに taskSupport: 'optional' を付けても、task を付けずに普通に呼んでくる。仕様どおりの動きであり、09 章にはこの実測をそのまま書いた。
| トランスポート | 内容 | 位置づけ |
|---|---|---|
| stdio | クライアントが起動した子プロセスの標準入出力に、改行区切りの JSON-RPC を流す | 標準 |
| Streamable HTTP | 1 つのエンドポイントへの HTTP POST。応答は JSON か、そのリクエスト専用の SSE ストリーム | 標準 |
| HTTP+SSE(旧) | 旧リビジョンの方式。後方互換として扱われ、標準の一覧からは外れている | 互換のみ |
| カスタム | TCP・Unix ドメインソケット・名前付きパイプなど。stdio と同じ改行区切りのフレーミングを再利用することが推奨 | 任意 |
2026-07-28 では、運べるメッセージの向きが次の 4 つに限定されている。
| 向き | リクエスト | 通知 | レスポンス |
|---|---|---|---|
| クライアント → サーバー | 可 | 可 | 不可 |
| サーバー → クライアント | 不可 | 可 | 可 |
「push 通知を送りたい」はここに直撃する。旧リビジョンではサーバーがクライアントへリクエストを投げられたが、現行仕様では Servers MUST NOT initiate JSON-RPC requests と明記され、通知とレスポンスだけになった。何をどこまで実現できるかは第 5 章の論点 6 に表でまとめている。
サーバーが勝手に送りつけるのではなく、通知には必ず流し込む先が要る。先は 2 通りある。
| 流し先 | 作られ方 | 送れる通知 |
|---|---|---|
| 処理中のリクエスト | クライアントのリクエストを処理している間だけ開いている | notifications/progress・notifications/message |
| 購読ストリーム | クライアントが subscriptions/listen を送ると、その応答が長寿命の通知ストリームになる | 一覧の変化・資源の更新・notifications/tasks など |
どちらも起点はクライアントである。接続が無い相手へサーバーから届ける経路は無い。
サーバーが利用者に尋ねたいとき(確認・選択・sampling)は、リクエストを送るのではなく InputRequiredResult を結果として返す。クライアントは答えを inputResponses に添えて同じリクエストを送り直す。これを Multi Round-Trip Requests(MRTR)と呼ぶ。Tasks 拡張では、これがタスクの input_required 状態と tasks/update に対応する。
旧リビジョンは initialize ハンドシェイクで接続ごとのセッションを張っていたが、現行仕様ではリクエスト 1 本が自己完結し、プロトコルバージョンとクライアント能力は毎回 _meta.io.modelcontextprotocol/* に載る。これはプロセス共有と相性がよい(論点 7 で扱う)。
各 CLI が実際に喋るリビジョンは未確認。Claude Code・Codex・Antigravity CLI が 2026-07-28 に追随しているとは限らない。これは資料の内容を左右するため、執筆前に実測する(第 6 章 ステップ 0)。
既存の TypeScript 学習資料・PowerShell 学習資料 と同じ作りにする。読み手が同じシリーズとして読めることを優先する。
| 項目 | 内容 |
|---|---|
| ファイル構成 | docs/ に本編 01-〜.html、付録 A1-〜.html、構成案 ZZ-構成.html。root に README.html |
| 番号体系 | 「資料番号.章番号」(資料 03 の章は 03.1・03.2…) |
| ナビゲーション | 本文先頭とフッターの両方に 前へ・次へ・目次 のバッジ。付録は目次のみ |
| 色 | 資料ごとに通し色相(280°→0°)。章見出しは資料内で虹色 |
| 公開 | GitHub Pages(public)。index.html から README.html へリダイレクト |
| Markdown | HTML を正とし、html2md で生成。SVG は images/ へ切り出す |
TypeScript 資料は「同じ処理の JavaScript を並べる」「理由を先に説明する」「短い実行例で終わる」を原則にしていた。MCP は言語ではなくプロトコルなので、次のように置き換える。
本編 11 章+付録 3 本(TypeScript 版の A4 は本編を書き終えてから)。120 分で本編を通せる分量にする。ハンズオンを 2 つに割り、前半で作ったサーバーを後半で育てる形にする。
| 番号 | タイトル | 扱う内容 | 目安 |
|---|---|---|---|
| 01 | 背景 | MCP が無かった頃は何をしていたか。なぜ規約が要ったか。ホスト・クライアント・サーバーの関係。LSP との類似 | 8 分 |
| 02 | 最小のサーバー | JSON-RPC を手で書いて標準入出力に流す。SDK を使わず、初期化とツール 1 個の応答だけを返す。ここで中身を見せておく | 15 分 |
| 03 | SDK で書き直す | 02 と同じものを @modelcontextprotocol/sdk で書く。何が省けたかを対比する | 10 分 |
| 04 | ホストに登録する | Claude Code・Codex・Antigravity CLI それぞれの登録方法。設定ファイルの場所と書式。動かないときの切り分け | 12 分 |
| 05 | Tools・Resources・Prompts | 3 種類の提供物の違いと使い分け。入力スキーマの書き方。エラーの返し方 | 13 分 |
| 06 | ハンズオン① メモサーバー | お題 A。メモを検索・追記するサーバーを 01〜05 の内容で作りきる。以降の章はこれを育てる | 12 分 |
| 07 | Streamable HTTP | 06 のサーバーを HTTP に載せ替える。stdio との違い、いつどちらを選ぶか | 12 分 |
| 08 | 通知で結果を伝える | 進捗通知とログ通知。subscriptions/listen の仕組み。サーバー起点でできること・できないことの線。OS のトースト通知という枠外の手 | 10 分 |
| 09 | Tasks 拡張 | 長い処理を投げて後から結果を取る。tasks/get によるポーリングと notifications/tasks による push。拡張の合意が要ること | 10 分 |
| 10 | プロセス共有 | 同一 PC で 1 本のサーバーを共有する 3 方式の比較。localhost HTTP と stdio ブリッジを実装する | 8 分 |
| 11 | ハンズオン② ビルド実行サーバー | お題 B。開発コマンドを実行し、終わったら知らせ、複数のホストで共有する。07〜10 の総仕上げ | 10 分 |
| 番号 | タイトル | 扱う内容 |
|---|---|---|
| A1 | 逆引き | 「〜したい」から引く。ツールを増やす/ログを見る/登録を消す/バージョンを確かめる |
| A2 | 詰まったとき | 症状から原因へ。サーバーが起動しない・ツールが見えない・応答が返らない・stdout 汚染 |
| A3 | C# で書く | csc.exe で単一 exe にする構成。Node.js を入れられない相手へ配るときの選択肢 |
| A4 | TypeScript で書く | 本編のサーバーを TypeScript に置き換える。本編を書き終えた後に着手する(論点 1 の C) |
02 章を SDK より先に置くのがこの構成の肝。SDK から入ると server.tool(...) を書けば動いてしまい、登録が効かないときに「どこまで届いているか」を確かめる手段が無くなる。手で JSON-RPC を 1 往復させておくと、以降すべての章で切り分けができる。
資料を書き始める前に決めた項目。採った案を見出しのバッジで示し、案とその理由は残す。後から「なぜこうしたか」を辿れるようにするため。
| 論点 | 採用 | 内容 |
|---|---|---|
| 1. 実装言語 | A + C | 本編は JavaScript(ESM・.mjs)。TypeScript 版は本編を書き終えてから付録 A4 として足す |
| 2. 仕様リビジョン | A | 3 つの CLI が実際に喋るリビジョンに合わせる。各章を書きながら CLI で検証する |
| 3. C# | A | 付録 A3 に 1 本。本編は Node.js のみ |
| 4. ハンズオン | A + B | 2 題とも扱う。06 章でメモサーバー(A)、11 章でビルド実行サーバー(B) |
| 5. 実測範囲 | A | Claude Code で全章を実測。Codex・Antigravity は登録方法と疎通のみ |
| 6. push 通知 | A + B | できること・できないことを示し、進捗通知と OS トースト通知を実装(08 章)。Tasks 拡張も実装まで踏み込む(09 章) |
| 7. プロセス共有 | A + B | 3 方式を図で比較し、localhost HTTP と stdio ブリッジの両方を実装(10 章) |
| 8. リポジトリ | A | 20260907-ai-mcp-servers-learn・public・Pages は develop の / |
| 9. サンプル置き場 | A | docs/samples/ |
| 構成 | B | 本編 11 章・120 分。2 部に割らず通しの 1 本にする |
6 と 7 で B を足したことで、当初の 90 分・8 章には収まらなくなった。Tasks 拡張と stdio ブリッジがそれぞれ 1 章分の分量になるため、本編を 11 章・120 分に改めている。
.mjs)で本編を書き、TypeScript 版は付録にしないtsconfig と実行方法の説明で 10 分以上取られるcsc.exe)の扱い A実現したいことは「処理が終わった結果を伝える」と決まった。現行仕様ではサーバーからリクエストを送れないため(第 2 章参照)、目的の側から手段を並べ直す。
| やりたいこと | 現行仕様での実現手段 | 可否 |
|---|---|---|
| 進捗を伝えたい | notifications/progress を送る。そのリクエストを処理している間だけ | 可 |
| ログを出したい | notifications/message を送る。同じく処理中だけ | 可 |
| 一覧や資源の変化を伝えたい | クライアントが subscriptions/listen で張ったストリームへ通知を流す | 可 |
| 処理の途中で利用者に尋ねたい | InputRequiredResult を返し、クライアントが答えを添えて再送する(MRTR) | 可 |
| 長い処理を投げて後で結果を取りたい | Tasks 拡張。tasks/get でポーリングする | 拡張 |
| 長い処理の完了を push したい | Tasks 拡張 + subscriptions/listen で notifications/tasks を受ける | 拡張 |
| 人に確実に気づかせたい | サーバープロセスが OS のトースト通知・音・メールを直接出す。MCP の枠外 | 可 |
| 何も呼ばれていないのに会話へ割り込みたい | —。通知が届いてもホストが会話に出すかは実装次第で、仕様は保証しない | 不可 |
08 章と 09 章に分けて扱う。08 章で表の前半(進捗通知・subscriptions/listen・OS トースト)と「できないことの線」を示し、09 章で Tasks 拡張を実装する。subscriptions/listen はコア仕様のパターンで、クライアントのリクエストへの応答が長寿命の通知ストリームになるのが要点。これが MCP における push の実体になる。
Tasks への対応状況は実測が要る。公式のクライアント対応表には Tasks の列がまだ無く(MCP Apps・OAuth 系のみ)、Claude Code も表に載っていない。09 章の実装が 3 つの CLI で動くかはステップ 0 で確かめる。動かない場合も、動かないことを実測として書く。
stdio はホストが子プロセスを起動するため、ホストの数だけサーバーが立つ。同一 PC で 1 本を共有したい場合の構成。
| 方式 | 内容 | 難所 |
|---|---|---|
| localhost の HTTP | 常駐サーバーを 1 本立て、各ホストは Streamable HTTP で繋ぐ | 常駐の起動・終了と、待ち受けポートの扱い |
| stdio ブリッジ | 各ホストには軽い stdio のプロセスを起こさせ、それが常駐本体へ中継する | ブリッジ自身の実装。ただしホスト側の設定は stdio のまま済む |
| 名前付きパイプ | stdio と同じフレーミングをパイプで運ぶ(仕様が推奨する再利用の形) | Windows 固有。SDK の既製品が無い |
20260907-ai-mcp-servers-learn、public、GitHub Pages を develop ブランチの / で公開lightspeedc.com/20260907-ai-mcp-servers-learn/ に並ぶ20260907-mcp-server-learn 等)docs/samples/ に置く(資料と同じ側)samples/ に置く| ステップ | 内容 | 成果物 |
|---|---|---|
| 0 | 3 つの CLI の実測 済。結果は第 2 章に記載。資料の基準は 2025-11-25 に決まった | 第 2 章 |
| 1 | 構成案を読者向けに書く | docs/ZZ-構成.html |
| 2 | サンプルコードを先に書いて動かす。資料の本文は動いたものから起こす | docs/samples/ |
| 3 | 本編 11 章を書く | docs/01-〜11- |
| 4 | 付録 3 本を書く | docs/A1-〜A3- |
| 5 | README とナビゲーションを整える | README.html |
| 6 | コントラスト比の実測、Markdown 生成と検査 | 検査結果 |
| 7 | GitHub リポジトリ作成・push・Pages 公開 | 公開 URL |
| 8 | TypeScript 版の付録を足す(論点 1 の C)。公開の後でよい | docs/A4- |
--version で実測する。記憶で書かない