Claude 学習
コースガイド

Model Context Protocol: Advanced Topics — 日本語学習ガイド

Anthropic Academy「Model Context Protocol: Advanced Topics」コース(講師: Stephen Grider, Member of Technical Staff at Anthropic)の日本語学習ガイド。動画の公式英語字幕(verbatim transcript) を一次情報源とし、講師が実際に語った具体例・デモの手順・比喩・注意点をできる限り漏らさず反映して構成した。NotebookLM 等で音声概要/動画概要を作るためのソース資料としても使える。

前提コースの MCP 基礎(tools / resources / prompts / MCPクライアント・サーバの基本構造)は別コース(building-with-the-claude-api の 07章など)で扱われている想定で、本コースはその先の応用トピックを扱う。扱われるのは Python 実装が中心で、Python が「書けなくても読める」程度の前提知識と、MCP のクライアント・サーバ・ツールについての基本理解が要求される。


0. イントロダクション(Let's get started!)

講師の Stephen Grider が自己紹介した後、コース全体の見取り図を提示する。取り扱うトピックは次の順序で並んでいる。

  1. Sampling — MCPサーバがクライアントに依頼して、Claude のような言語モデルにテキスト生成をさせる仕組み。
  2. Logging and progress notifications — MCPサーバへアクセスするクライアントに、より良いフィードバックを返す仕組み。
  3. Roots — MCPサーバがアクセスすべき特定のファイル・フォルダを指し示す仕組み。
  4. MCP のメッセージ形式(JSON message types) — サーバ・クライアント間通信の技術的な実体を理解するために、MCP 仕様そのものを詳しく見る。
  5. Standard IO transport — 上記の知識を実際に適用して、stdio transport の動作を確認する。
  6. Streamable HTTP transport — リモートホストされたサーバをテーマにした深堀り。

このコースを受講する上で押さえておくべき前提が2つある。第一に、Python のコードを多く見ていくが、Python のエキスパートである必要はなく、最低限読める程度でよい。第二に、クライアント・サーバ・ツールを含む MCP の基本的な理解はすでに持っている前提で話が進む。


1. Sampling

Sampling とは、接続された MCP クライアントを経由して、MCPサーバが Claude のような言語モデルにアクセスできるようにする仕組みである。

デモアプリで見る sampling の必要性

講師はまず、Claude を使ったチャットアプリケーションのデモを見せる。「1+1は?」のような簡単な質問には即座に応答が返る。このアプリは「Research Tool(リサーチツール)」という1つのツールを実装した単一の MCP サーバに接続されている。このツールを使って「考古学についてレポートを書いて」と頼むと、Claude はリサーチツールを使うことを決定し、「リサーチを行い、結果を統合し、レポートとして提示する」という動きを見せる。

この裏側で何が起きているかを、講師は次のように図解する。

2つの選択肢

この「内容の統合」をどう実現するかについて、講師は2つの選択肢を提示する。

選択肢1: MCPサーバが直接 LLM にアクセスする

MCPサーバが直接 Claude(または類似の LLM)にリクエストを送り、Wikipedia 検索結果の要約を依頼する方法。これは確実に動作するが、MCPサーバに大きな複雑さを追加することになる。具体的には、Claude へリクエストを送り、応答を受け取り、生成テキストを抽出する、といったコードをサーバ側に書き込む必要がある。さらに、MCPサーバが Claude(または他の LLM)にアクセスするための API キーを持つ必要も出てくる。つまり選択肢1は動作はするが、それなりの複雑さを伴う。

選択肢2: Sampling を使う

MCPサーバは「sampling」という技法を使い、クライアントに対して「代わりにこのプロンプトを実行してほしい」と依頼する。具体的には、MCPサーバがプロンプトを作成し、それを MCP クライアントに送信して「これを Claude に食わせてくれないか」と頼む。作成した MCP クライアントはそのプロンプトを受け取り、Claude に送信し、応答を受け取り、生成されたテキストの結果を MCPサーバへ送り返す。サーバはその生成テキストを使って好きなように処理できる。

この方式は複雑さをクライアント側へ移す。そしてこの移し方は、実は都合が良い。なぜなら、デモで見た Next.js アプリケーション(MCP クライアント)はすでに Claude への接続を持っており、すでに Claude へリクエストを送っているからだ。MCPサーバの代わりに追加でもう1回リクエストを実行するだけなら、大した負担増にはならない。もう1つの明白な利点は、MCPサーバが Claude へアクセスするための API キーを一切必要としない点である。これが公開アクセス可能な MCPサーバであれば、他人がサーバを使うことで生成されるトークンの費用を心配する必要もなくなる。

Sampling とは何か(まとめ)

この技法は sampling と呼ばれ、MCPサーバがクライアントに依頼して Claude や他の言語モデルに何らかのテキストを生成させることを可能にする。Sampling とは、Claude を呼び出す責任をサーバからクライアントへ移すことだと考えるとよい。

Sampling が最も役に立つのは、公開アクセス可能な MCPサーバを構築するときである。誰でも自由に使える公開 MCPサーバが、誰かに好き放題テキストを生成させてしまうような状態は望ましくない。公開サーバを作るのであれば、sampling を活用して、テキスト生成の責任をサーバから、接続してくるクライアント側へ移すべきである。

実装に必要なセットアップ

Sampling を利用するには、サーバ側とクライアント側の両方に多少のセットアップが必要になる。

以上が sampling である。見てのとおり大して複雑ではなく、テキスト生成の負担をサーバからクライアントへシフトするという考え方そのものだ。公開アクセス可能な MCPサーバを構築する際には、必ず検討すべき技法である。


2. Log and Progress Notifications(ログ・進捗通知)

次のテーマはログと進捗通知である。これらの通知は設定が非常に簡単でありながら、MCPサーバ周りのユーザー体験を大きく改善する。

デモで見る問題と解決

講師は先ほどのリサーチアプリケーションを再び使う。Claude に考古学のレポート生成を依頼すると、先述の MCPサーバを経由してリサーチ関数のツールコールが実行される。この関数は完了までに時間がかかる。実行中、ユーザーには何のフィードバックも与えられない。そのため、ユーザーからすると、このツールコールが実際に失敗しているのか、単に停止した状態にあるだけなのか区別がつかない。裏側で何が起きているかについて、もう少しユーザーに洞察を与えられれば望ましい。

そこでログと通知を使う。講師はページをリロードしてログと通知をオンにし、同じクエリをもう一度送る。すると同じリサーチツールコールが実行されるが、今度はプログレスメーター(進捗バー)といくつかのログステートメントが一緒に表示される。この進捗・ログメーターは偽のデータではなく、実際にリサーチツールの内部で発行されているログと進捗のステートメントである。

サーバ側の実装

サーバ上のツール関数の中でログと進捗通知を使い始めるには、ツール関数の最後の引数として自動的に含まれる context 引数を受け取る。この context オブジェクトには、情報をログ出力したり、このツール実行の進捗をクライアントへ報告したりするための様々なメソッドが用意されている。具体的には inforeport_progress のようなメソッドがある。これらの関数を呼び出すたびに、自動的にメッセージがクライアントへ送信される。

クライアント側の実装

クライアント側でログステートメントと進捗更新を利用するには、先ほど sampling で見たのと似たコードを少し書く。具体的には、サーバからログステートメントを受け取るたびに呼ばれるコールバック関数を1つ用意する。それとは別に、サーバから進捗の更新を受け取るコールバックも用意する。ログ用のコールバックはクライアントセッションに渡し、進捗用のコールバックは call_tool 関数に渡す。

これらのコールバックの中で、ログステートメントと進捗をユーザーへどう提示するかは開発者次第である。CLI アプリケーションであれば、単にターミナルへ出力すればよい。Web アプリケーションであれば、その情報をブラウザまで届けるためにもう少し工夫した仕組みが必要になる。もちろん、これらのログステートメントや進捗ステートメントをユーザーに一切見せない、という選択も可能である。これらはあくまでユーザー体験の向上を目的としたものであり、必須ではない。ユーザーに「裏側で何が起きているか」をより良く伝えるための仕組みという位置づけである。


3. Roots

次のトピックは root(ルート)の仕組みである。Roots は、ユーザーが特定のファイル・フォルダの集合へのアクセスを、サーバに対して許可できるようにするものである。「MCPサーバさん、これらのファイルにはアクセスしていいですよ」ということを体系立った方法で伝える手段だと考えるとよい。ただし roots は単なる権限付与にとどまらない、それ以上の役割も持つ。

Roots がない場合に起きる問題

講師はまず、roots が存在しない場合に起きる典型的な問題を示す。ここでは非常にシンプルな MCPサーバを想定する。ツールは1つだけ、「Convert Video」というツールで、ユーザーのローカルマシン上にある動画ファイルへのパスを受け取り、その動画ファイルを別のファイル形式(例: MP4 から MOV)へ変換する。

ユーザーは CLI アプリケーションなどで、Claude に「biking.mp4 という動画ファイルを MOV 形式に変換して」と頼むとする。Claude はこの MCPサーバが提供するツール一覧を確認し、Convert Video ツールが利用可能であることを見つける。そこで Claude は「よし、このツールを呼び出そう。パスとして biking.mp4 を渡そう」と判断するだろう。ユーザーはまさに biking.mp4 という名前の動画ファイルの変換を求めたのだから、これは自然な判断に思える。

しかし、実際にはここでエラーが返ってくると予想される。「Viking.mp4 というファイルは存在しません」といったエラーである。なぜそうなるかというと、ユーザーのファイルシステムは非常に複雑であり得るからだ。様々な種類のファイルやフォルダが、あちこちに大量に存在しているかもしれない。ユーザー自身は Viking.mp4 が Movies ディレクトリの中にあることを知っているかもしれないが、Claude にはそれを知る術がない。ユーザーが単に「Viking.mp4」とだけ言った場合、Claude にはユーザーのファイルシステム全体を検索してそのファイルの場所を突き止める能力はない。

この問題を解決する1つの方法は、ユーザーに常にフルパスを指定させることである。「このツールを使いたければ、必ず movies/Viking.mp4 のような完全修飾パスを渡してください」というルールにすれば、おそらく動作はする。しかしこれはあまり便利ではない。ユーザーは、アクセスしたいファイルのたびにフルパスをいちいちタイプしたいとは思わないだろう。むしろ「この動画を別の形式に変換して」とだけ言って、あとはファイル名を渡すだけで済ませたいはずだ。

Roots による解決

そこで roots の出番になる。この問題を解決するために、MCPサーバへ追加のツールを組み込む。同じ Convert Video ツールに加えて、次の2つのツールを追加する。

さらに、他のツールにも要件を追加する。Read Directory ツールと Convert Video ツールの両方に、あるディレクトリや動画ファイルへアクセスしようとするたびに、それがいずれかの root に含まれるファイル・フォルダの範囲内であることを確認するコードを組み込む。

デモ: 3つのツールの連携

講師はこれら3つのツールがどう連携するかを示す小さなサンプルアプリケーションを使ってデモを行う。uv run main.py を実行してプログラムを起動し、追加のコマンドライン引数として desktop を渡す。これにより Desktop フォルダが MCPサーバの root として設定される。つまり、サーバに対して「Desktop フォルダとその中のすべてのファイルへのアクセスを許可する」と伝えたことになる。

デスクトップディレクトリの中には Viking.mp4 というファイルがある。ここで Claude に「Viking.mp4 を MOV ファイルに変換して」と依頼すると、いくつかのツールコールが表示される。まず Claude は List Roots ツールを呼び出して、アクセス可能なファイル・フォルダの一覧を確認する。アクセス可能なディレクトリが Desktop フォルダの1つだけであることを見て取る(これがこのプログラムでアクセスを許可した唯一のものだからだ)。次に、そのディレクトリを読み取ろうとし、その中に Viking.mp4 ファイルがあることを発見する。この時点で、Convert Video ツールを実際に成功裏に実行するために必要な情報がすべて揃う。Desktop フォルダへの完全修飾パス付きの Viking.mp4 を指定して、このツールを実行できる。

Roots はあくまで MCPサーバがアクセスできるファイル・フォルダを制限することを意図している。デスクトップフォルダに加えて、Documents フォルダも存在し、その中に swimming.mp4 というファイルがあるとする。ここで Claude に「swimming.mp4 も MOV に変換して」と頼み、今度は MP4 ファイルへのフルパスを最初から与えるとする。理論上、Claude は root の一覧を確認する必要はなく、フルパスを既に持っているので直接 Convert Video ツールを呼び出せるはずである。

実際に実行してみると、まず Convert Video を呼び出そうとするが、途中で処理が打ち切られる。Claude は「そのツールの実行でエラーが出た。特定のファイルを見つけられなかった」と言う。そして Claude は利用可能な root(この場合は Desktop のみ)を確認しに行き、「そのファイルには実際にはアクセスできないようだ。アクセスできるディレクトリは Desktop だけであり、swimming ファイルを変換したいなら、私にはアクセスできない。アクセス権を与えるか、ファイルを移動するなどしてほしい」と気づく。

Roots が持つ2つの目的

このデモから分かるように、roots には2つの異なる目的がある。1つは、ユーザーが特定のファイル・フォルダへのアクセスを許可できるようにすること。もう1つの利点は、Claude がファイルシステムの特定の領域だけに集中できるようにすることである。見てきたとおり、Claude にアクセスさせたい特定のディレクトリやファイルへの完全修飾パスをいちいち渡す必要はない。代わりに、Claude は自律的に利用可能な root を確認し、その root の中を検索して特定のファイルを見つけ出すことができる。

重要な注意点: 実装は「緩い」

ここで講師が特に強調しているのは、roots という考え方はやや緩く、実装面での裏付けがそれほど多くないという点である。言い換えると、Anthropic の SDK の中には、特定のファイルやフォルダへのアクセスを自動的に制限するような仕組みは一切存在しない。代わりに、ツールがファイルやフォルダにアクセスしようとするたびに、それがいずれかの root の中に含まれているかどうかを確認するのは、開発者自身の MCPサーバの責任である。そのために、例えば is_path_allowed のような関数を実装することになる。この関数はリクエストされたパスを受け取り、クライアントから root の一覧を取得し、ツールが使おうとしているパスがそれらの root のいずれかに含まれているかを確認する。含まれていればアクセスを許可し、含まれていなければアクセスを許可しない。

最後にもう1点。講師は「List Roots のようなツールを実装して、いつでも Claude がそれを呼び出せるようにする」というやり方を見せたが、これは厳密には必須ではない。root の一覧をツールにせず、単純にプロンプトの中へ直接すべて詰め込むという方法もある。ツールとして用意する必要はない。これは、Claude がファイル・フォルダのアクセス可否を判断する必要があると自ら判断したタイミングで、root の一覧を自発的に確認できるようにするために講師が有用だと感じたパターンにすぎない。


4. JSON Message Types(JSONメッセージ型)

これまでいくつかのトピックを見てきたが、次に進む前に一言前置きしておきたい。この後の2〜3本の動画は、MCP メッセージと標準入出力トランスポートに焦点を当てる。これらの内容は少し無味乾燥で退屈に感じられるかもしれないが、扱う理由は重要である。

このコースの大きな目標の1つは、リモートホストされた MCPサーバへクライアントが接続できるようにする streamable HTTP transport を理解できるようにすることである。問題は、MCPサーバが HTTP transport を使っている場合にできることには、いくつか大きな制限があるという点だ。その制限を理解するのは、MCP メッセージと標準入出力トランスポートについて確かな土台があると、はるかに理解しやすくなる。これが、最初にこの2つのトピックへ時間を割く理由である。

メッセージという概念

そのことを踏まえて、MCP が通信に使うフォーマット、特にメッセージのフォーマットを見ていく。クライアントとサーバは JSON を使って通信する。このJSONの断片のことを「メッセージ」と呼ぶ。MCP 仕様の中には多くの異なるメッセージ型が定義されており、それぞれが明確な目的のために設計されている。

例を挙げると、MCP クライアントに接続された言語モデルが、MCPサーバの提供するツールを呼び出すことを決定した場合、クライアントはサーバへメッセージを送る。このメッセージ型は「call tool request」と呼ばれる。MCPサーバはそのツールを実行し、実行結果を「call tool result」という別のメッセージ型に格納する。

schema.ts というスキーマファイル

MCP 仕様は、すべてのメッセージ型の完全なリストを定義している。この一覧は GitHub 上でホストされている MCP 仕様のリポジトリの中にある。ここで注意すべきは、この仕様リポジトリは、Python や TypeScript の MCP SDK など、各種 SDK のリポジトリとは別物だという点である。この仕様リポジトリには、MCP 仕様の動作を説明するいくつかのドキュメントが置かれている。

このリポジトリの中で、すべてのメッセージ型は schema.ts という TypeScript ファイルに記述されている。ここで念のため明確にしておくと、この TypeScript ファイルは何かによって実行されるものではない。どの SDK にも組み込まれていない。型が TypeScript で書かれているのは、単に TypeScript が型情報を記述するのに非常に便利な言語だからにすぎない。

このスキーマファイルには興味深い発見がいくつも含まれているので、講師は実際にリポジトリを開いて見せる。schema.ts の中で「call tool request」を検索すると、call tool request は method フィールドに tools/call(複数形)を持ち、呼び出したいツールの名前と渡す引数を含む params オブジェクトを持たなければならない、という定義が見つかる。先ほど図で示した中にあった JSON-RPC の部分や ID についても、別のオブジェクト「JSON RPC request」として定義されている。これを調べると、JSON RPC request にも ID を持たなければならないことが分かる。

ファイル末尾にある重要な区分

このファイル全体の中で講師が特に注目してほしいのは、ファイルの一番下に定義されているいくつかの型である。一番下までスクロールすると、「server messages」というコメントと「client messages」というコメントが見つかる。この2つのセクションの中には、似た名前の型がいくつも存在する。

メッセージの2〜3のカテゴリ

このファイルに定義されたすべての型は、大きく2〜3のカテゴリに分けられる。

Request-Result のペア: 図の左側に当たるもので、常にペアで登場するメッセージ型である。必ず「なんとか request」という名前と、それに対応する「なんとか result」という名前の組み合わせになる。例えば CallToolRequestCallToolResult とペアになり、InitializeRequestInitializeResult とペアになる。これらのメッセージ型はすべて、クライアントまたはサーバへ送るメッセージと、それに応じて期待される応答の型を表している。call tool request を送れば、call tool result が返ってくることを期待する、という具合である。

Notification(通知)メッセージ: もう1種類のメッセージは通知であり、これはイベントに近い。クライアントまたはサーバに何かが起きたことを伝えるが、応答を必要としない。ProgressNotificationLoggingMessageNotificationToolListChangedNotification などがこれに当たる。

そして「server messages」「client messages」という見出しとその中の型は、それらのメッセージがクライアントから送られるべきものか、サーバから送られるべきものかを示している。ClientRequest はクライアントから送られることが期待されるすべてのリクエスト型を表し、同様に ServerRequest はサーバから送られることが期待されるリクエストを表す。ServerNotification はサーバが発行するすべての通知であり、ClientNotification はクライアントが発行するすべての通知である。

これを図にまとめると、MCP クライアントから送られることが意図されているリクエストがあり、サーバから送られる結果があり、クライアントから送られる結果があり、サーバから送られるリクエストがある、という整理になる(すべての型を網羅した図ではなく、あくまで方向性を示す例である)。

決定的に重要なポイント

ここが、この動画で理解してほしい最も重要な点であり、後で streamable HTTP transport を扱う際に非常に重要になってくる。サーバからクライアントへ送られることを意図したメッセージが数多く存在する、ということである。具体的には server request 型と server notification 型がそれに当たる。これらはすべて、サーバからクライアントへ送られるメッセージである。

今の時点では、これがなぜ重要なのか全く分からないかもしれない。しかし streamable HTTP transport を見始めると、これが極めて重要な意味を持ってくる。しばらくこのことを頭の片隅に置いておいてほしい。


5. The STDIO Transport(標準入出力トランスポート)

先ほど述べたとおり、クライアントとサーバは「メッセージ」と呼ぶ JSON オブジェクトをやり取りすることで通信する。JSON は一般に、クライアントとサーバの間で実に様々な方法で送受信できる。HTTP リクエストを使うこともできるし、WebSocket を使うこともできる。極端な話、JSON を書いたはがきを誰かに送って手動でサーバへ打ち込んでもらう、ということすら理論上は可能である。つまり JSON を送受信する方法は非常にたくさんある。MCP 仕様の中で、クライアントとサーバの間で実際に JSON を動かす手段のことを「transport(トランスポート)」と呼ぶ。

MCPサーバやクライアントを開発し始めるとき、非常によく使われるトランスポートが standard IO transport(標準入出力トランスポート)である。この考え方は、クライアントがサーバを別プロセスとして起動する、というものだ。そうすることでクライアントはそのプロセスへのハンドルを持ち、サーバの標準入力(stdin)チャンネルへメッセージを書き込むことでサーバへメッセージを送信でき、サーバの標準出力(stdout)チャンネルを監視することでメッセージを受信できる。

標準入出力トランスポートの良い点は、クライアントとサーバのどちら側からでも通信を非常に簡単に開始できることである。つまり、いつでもクライアントは stdin へ書き込むことでサーバへメッセージを送信でき、サーバは stdout へ書き込むことでクライアントへ通信を返せる。ただし、標準入出力トランスポートには1つ大きな欠点がある。それは、クライアントとサーバが同じ物理マシン上で動作している場合にしか、このトランスポートを使えないということである。

デモ: ターミナルから直接サーバと会話する

標準入出力トランスポートをよりよく理解してもらうため、講師は小さな MCPサーバを使ったデモを行う。冒頭にいくつかの print 文があるだけで、それ以外にサーバに大したものは入っていない。サーバを作成し、1つのツールを定義し、標準入出力トランスポートでサーバを起動する、というシンプルな構成である。

クライアントを別途作成して標準入出力トランスポート経由でサーバへ接続することもできるが、講師はここで、独立したクライアントを作らずに、ターミナルから直接サーバに接続する様子を見せる。uv run server.py を実行するとサーバが起動し、標準入力を待ち受けながら、送信するメッセージを標準出力へ書き込むようになる。ターミナルでは、プログラムを実行しているときに何かをタイプすると、それはその実行中のプログラムの標準入力へ書き込んでいることになる。つまり、JSON メッセージをターミナルの下部に貼り付けて実行すれば、それがサーバへの入力として受け取られる。

講師はサーバを再起動し、あらかじめ用意していた1つ目のサンプルメッセージをコピーして貼り付け、Enter を押す。ほぼ即座に応答が返ってくる。標準入力にメッセージを書き込み、標準出力に出力メッセージが表示された、というわけだ。次に別のメッセージを送信すると、今度は何の応答も見られない。最後にもう1つメッセージを実行すると、今度はすぐに1つの応答が返り、続けてもう1つ、さらにもう1つと、合計3つのオブジェクトが返ってくる。

図解: 3つのメッセージのやり取り

左側に「MCP クライアント」とラベルを付けたものがあるが、実際にはターミナルでメッセージを貼り付けている講師自身のことである。メッセージを貼り付けることで、MCPサーバの標準入力へ書き込んでいる。サーバはそのメッセージを処理し、必要に応じて標準出力へ結果を出力することで応答を返す。それがターミナルにそのまま表示される。

最初に送ったメッセージは「initialize request」というものだった。ここで少し背景を説明すると、クライアントが最初にサーバへ接続するとき、MCP 仕様は3つの異なるメッセージを往復させなければならないと定めている。最初のメッセージは必ず initialize request でなければならない。これが最初に貼り付けたメッセージである。リクエスト型のメッセージを送ったので、結果(result)が返ってくることを期待する。実際、「initialize result」と呼ばれるものが返ってきた。

続いて MCP 仕様は、この最初のメッセージ交換の後、クライアントからサーバへ「initialized notification」(method: notifications/initialized)を送らなければならないとも定めている。念のため確認しておくと、通知(notification)は応答を必要としない。実際に initialized notification を送信したところ、即座の応答は得られなかった。これは、通知が応答を必要としないという性質どおりの挙動である。

この3つのメッセージ交換を終えると、MCPサーバへの接続は「初期化済み(initialized)」とみなされる。その時点から、call tool request やプロンプト一覧のリクエストなど、やりたいことを何でも実行できるようになる。講師のケースでは、その後 call tool request を送信し、具体的には引数 5 と 3 を指定して add ツールを呼び出した。

この add ツールは、いくつかのログを送信してから最終的に call tool result を返すよう作られている。ログを見ていくと、まず message notification(ログステートメント)が届き、次に progress のステートメント(進捗更新)が届いた。これらはいずれもサーバからクライアントへ送られる通知である。そして最後に call tool response が届き、その中に 3 + 5 を計算した結果である 8 が含まれていた。

標準入出力トランスポートの重要な特徴

このデモを通じて理解してほしい、非常に重要な考え方がいくつかある。これらは後で streamable HTTP transport を見ていく際に、大いに関連してくる。

第一に、先ほど示した図には、クライアントからサーバへ送られることを意図したメッセージ型と、サーバからクライアントへ送られることを意図したメッセージ型があった。この図から理解してほしいもう1つのことは、いくつかのケース——特に図の左上にあるメッセージ——では、クライアントがサーバとの通信を開始していると考えられる、という点である。つまり、クライアントが「リクエストを送るので、応答を期待する」と言っている状態である。同様に、MCPサーバが最初のリクエストを送信し、応答を期待するケースもある。これは例えば sampling を開始するために使われる create message request がそれに当たる。サーバはこのリクエストをクライアントへ送信しなければならず、何らかの結果が返ってくることを期待する。

もう少し明確に言い換えると、標準入出力トランスポートのようなトランスポートで対応しなければならないシナリオは実質的に4つある。

  1. クライアントからサーバへの初期リクエストの発行
  2. サーバからクライアントへの応答の送信
  3. サーバからクライアントへの初期リクエストの発行
  4. クライアントからサーバへの応答の送信

これら4つのシナリオを、標準入出力トランスポートでどう実装するかを順に見ていく。

シナリオ1: クライアントからサーバへの初期リクエスト。 クライアントが call tool request のような何かをサーバへ送りたいときのシナリオである。標準入出力トランスポートでこれを実装するには、単に stdin へ書き込むだけでよい。MCPサーバはそのメッセージを受け取り、処理し、うまくいけば応答を組み立てる。応答ができたら、stdout へメッセージを書き込むことで応答する。

シナリオ3: サーバからクライアントへの初期リクエスト。 サーバが sampling を行いたい場合など、サーバがクライアントへ何らかの初期メッセージを送る必要があるシナリオである。サーバがクライアントへ初期リクエストを送る必要があるときは、単に stdout へ書き込むだけでよい。そして、クライアントが応答するには、同様に stdin へ書き込むだけでよい。

ここまでの数分間とここで見た一連の図は、少し分かりにくく感じられたかもしれない。「初期リクエストと応答とは何なのか」と疑問に思うかもしれない。ここでのポイントは次のとおりである。標準入出力トランスポートが優れているのは、いつでもクライアントまたはサーバのどちらからでも通信を開始できる点にある。どちらの側も、いつでもリクエストを送信し、応答を期待できる。

ここが重要なところである。streamable HTTP transport では、これが必ずしも成り立たない。streamable HTTP transport には、この状況が許されないシナリオが存在する。サーバがクライアントへ何らかの初期リクエストを送信できないケースがある。これが streamable HTTP transport について理解しづらい、厄介なところである。ここでいったん立ち止まり、次に HTTP transport を見ていく中で、このシナリオを実際に確認し、開発時に必ず理解しておくべき「少し厄介な点」を理解していく。


6. The StreamableHTTP Transport(StreamableHTTP トランスポート)

いよいよ Streamable HTTP transport について説明する。このトランスポートは、クライアントとサーバの間で HTTP 接続を介してメッセージを送受信できるようにする。このトランスポートの良い点は、リモートホストされた MCPサーバを実現できることである。標準入出力トランスポートでは常にクライアントとサーバを同じマシン上で動かす必要があったのに対し、Streamable HTTP transport を使えば、サーバをリモートでホストできる。例えば mcpserver.com のようなところにリモートサーバを置ける。これにより、誰でも接続できる公開サーバを作れるようになり、MCPサーバの可能性が大きく広がる。

しかし、ここ数本の動画でほのめかしてきたとおり、このトランスポートには適用が必要になる設定がいくつかあり、それによって MCPサーバの機能——具体的には、サーバからクライアントへ送信できるメッセージの種類——が制限されることになる。これが、理解しておくべき大きな問題である。ある設定を true にすると、このトランスポートを使っているときのサーバの機能が制限される。もしローカルマシン上で標準入出力トランスポートを使って開発しているときは何の問題もなく動いていたのに、デプロイして HTTP transport を使い始めた途端にうまく動かなくなった、という場合には、この動画に立ち返って何が起きているかを理解してほしい。

デモ: フラグを切り替えると何が壊れるか

講師は、コースの中で以前に見せた Wikipedia リサーチアシスタントのアプリを再び使う。これは Next.js アプリケーションが、リサーチツールを実装した MCPサーバへ接続している構成である。このサーバは Streamable HTTP transport を使っており、このアプリを使って Streamable HTTP transport が期待どおりに動かないいくつかのシナリオを見せていく。

まず、考古学についてのレポートを依頼するリクエストを送る。通常であれば、call tool request が表示され、プログレスバーが出て、ステータス更新があり、しばらく待てば完全な応答が返ってくる。

MCPサーバのソースコードの中には、コメントアウトされている2つの設定がある。stateless HTTPJSON response である。デフォルトではこれらの設定は false になっているが、シナリオによっては、これらを有効化(true)しなければならない場合がある。詳しい理由は後述する。これらの設定を true に変更すると、MCPサーバの機能に影響が及び、場合によってはクライアント側の動作を壊してしまうこともある。

講師はまず stateless_httptrue に変更してファイルを保存し、ページをリロードして全く同じクエリをもう一度実行する。今回は大きな違いはすぐには見えないが、1つすぐに気づく点がある。プログレスバーが出なくなる。ログステートメントの上にプログレスバーが全く表示されない。加えて、しばらく待ってみると(動画では早送りされているが)、最終的にはテキストを一切生成することなく、リクエストは完全に失敗してしまう。

続いてページをリロードし、今度は json_responsetrue にして保存し、同じクエリをもう一度実行する。両方の設定を true にすると、さらに驚くべき結果になる。今度はプログレスバーもログステートメントも一切表示されず、そしてやはりこのまま放置すると、リクエストは失敗する。

この2つの設定は一見無害に見えるが、MCPサーバに大きな影響を与えることが、これで確認できる。この動画の残りは、これらの設定が何を意味するのか、なぜ true に設定することがあるのか、実際に何が起きているのかを理解するためのものである。

HTTP 通信のおさらい

まず、前の動画で見せた図をもう一度思い出してほしい。標準入出力トランスポートには、クライアントからサーバへリクエストを開始して応答を得る能力があり、同様にサーバもいつでもクライアントへリクエストを開始して応答を得られる、という話を強調した。それを踏まえて、HTTP通信の基本を少しおさらいする(これは MCP 固有の話ではなく、一般的な HTTP 通信の話である)。

クライアントとサーバがある場合、いつでもクライアントはサーバへ簡単にリクエストを送れる。例えば、クライアントはサーバへ POST リクエストを送り、何らかの応答を期待できる。この構成に何の問題もない。

これを MCP の世界に置き換えると、クライアントからサーバへ初期リクエストを送りたい場合は問題なく動く。そしてサーバからクライアントへ応答を返したい場合も問題なく動く。

しかし、逆のシナリオを考えるとどうなるか。サーバがクライアントへリクエストを開始したい場合、HTTP リクエストではそう簡単にはいかない。基本的なレベルの話として、サーバはクライアントのアドレスを知らないし、クライアントはそもそも公開アクセス可能でさえないかもしれない。従来の HTTP において、サーバがクライアントへリクエストを開始するのは非常に難しい。ということは、MCP の世界において、サーバがその初期リクエストを送るのは難しく、さらにそのリクエストへの応答をクライアントからサーバへ返してもらうことも想像しにくい、ということになる。

先ほど見た図——サーバからクライアントへ発行されるいくつかのリクエストがある、というあの図——を思い出してほしい。つまり、この HTTP の世界では、通常の HTTP リクエストでは実装が難しいメッセージ型がいくつか存在する、ということになる。具体的には、sampling request、root の一覧取得、progress notification、logging notification、そして図には示していないが他にもいくつかのメッセージ型が、HTTP の世界では実装が難しい。

そして、まさにこれが先ほどのデモで見えた現象だった。設定を false から true に切り替えた途端、アプリケーションの一部が壊れ、期待どおりに動かなくなった。何が壊れたのか。そのとおり、progress notification が壊れ、logging が壊れ、sampling——つまり create message request——も壊れた。sampling はリサーチレポートを執筆するために使われていたものだった。

良いニュース

朗報もある。サーバからクライアントへリクエストを送ることは純粋な HTTP の世界では難しいものの、Streamable HTTP transport にはこれを解決する巧妙な仕組みが用意されている。ただし、いくつか留意すべき点(caveat)がある。次のレッスンで、このトランスポートが実際にどう動作するのか、そしてその留意点が何なのかを見ていく。


7. StreamableHTTP in Depth(StreamableHTTP の詳細)

前の数本の動画のおさらいをして、認識を揃えておきたい。まず理解したのは、sampling・notification・logging のような一部の MCP 機能は、サーバがクライアントへリクエストを送ることに依存しているという点だった。そのすぐ後で、HTTP を一般的に使う場合、サーバがクライアントへリクエストを送ることは難しいということも学んだ。この2つの事実は、互いに衝突している。フル機能を実現するにはサーバがクライアントへリクエストを構築・送信できる必要があるが、同時に、それは HTTP を使っている場合には難しい。

Streamable HTTP には、これを解決する回避策(workaround)がある。この動画では、その回避策が具体的に何であり、このトランスポートが全体としてどう動作するのかを説明する。ここでようやく、何が起きているのかをはっきりと理解できるようになる。最後に留意してほしいのは、いくつかのシナリオでは、あの2つのフラグを true に設定することになり、その理由も議論するが、true に設定するとこの回避策そのものが壊れてしまうという点である。これが、Streamable HTTP をトランスポートとして理解しにくくしている理由である。HTTP に起因する制限があり、それに対する回避策があるが、場合によってはその回避策をあえて使わないようにしたい、というのが、このテーマを厄介にしている所以である。

初期化フローのおさらいと HTTP 特有の変化

まず、クライアントが最初にサーバへ接続するときに何が起こるかをおさらいする。MCP 仕様によれば、クライアントは initialize request をサーバへ送らなければならず、サーバは result を返し、その後クライアントは initialized notification というフォローアップの通知を送らなければならない。この時点で、サーバはクライアントが接続済みとみなし、準備が整う。

HTTP transport を使い始めると、この流れが少しだけ変化する。HTTP transport を使い始めると、サーバから返ってくる initialize result には、HTTP レスポンスのヘッダーの中に「MCP session ID」 が含まれるようになる。これは、サーバへの接続に対して本質的に識別子として割り当てられる、ランダムな文字と数字の文字列である。このヘッダーを受け取ったら、以降サーバへ送るすべてのフォローアップリクエストにこの ID を含めることが求められる。これにより、サーバは自分のクライアントを識別できるようになる。

マジックの核心: GET リクエストと SSE

初期化を終えてセッション ID を取得した後、ここからが「マジック」の核心部分であり、サーバがクライアントへリクエストを送れるようにする大きな回避策である。初期化が終わると、クライアントは(任意で、必須ではない)セッション ID を含めた GET リクエストを MCPサーバへ送ることができる。これに対して返ってくる応答が特別なもので、SSE(Server-Sent Events)応答と呼ばれる。これは、任意の時間の長さ、開いたまま保持できる種類の応答である。

このクライアントとの応答が確立されると、サーバはその後、小さな情報の断片——実質的には個々のメッセージ——をクライアントへストリーミングして送り返せるようになる。この接続が確立された時点、ここが本当に重要な部分だが、サーバはいつでもクライアントへメッセージを送信できるようになる。この接続は、実質的にサーバがクライアントへリクエストを送るために使える。これがトリックであり、HTTP transport が使っている回避策である。この長寿命の SSE 応答を利用し、クライアントへ送りたいメッセージをストリーミングで流していく。

ツール呼び出し時に何が起きるか

ここからもう少し進んで、クライアントがツールを呼び出したいときに何が起きるかを見ていく。複雑さはまだ終わっていない。理解すべき重要なポイントがもう1つある。

図の一番上には、先ほど確立した SSE 応答がある。この応答(開いたままの接続)にはセッション ID が紐づいている。そのため MCPサーバは、その接続がどのクライアントに属するものかを正確に把握できる。その後、ある時点で、その応答がまだ実行中の間に、クライアントが call tool request をサーバへ送ることを決めたとする。このリクエストを送るとき、クライアントはヘッダーとしてセッション ID を含める。すると MCPサーバは2つ目の SSE 応答を新たに開く。

つまりこの時点で、2つの別々の SSE 応答が存在することになる。1つ目(上側)は、サーバからクライアントへ向かうリクエストのために使われることを意図している。2つ目の新しい SSE 応答は、この call tool request に関連するメッセージのために使われることを意図している。そして重要なのは、この2つ目の SSE 応答は、call tool result メッセージが送信されるとすぐに自動的にクローズされるという点である。下側の応答はすぐに自動的にクローズされるのに対し、上側の応答は任意の時間開いたままに保たれることを意図している。

最終的なゴールは実際にツールを呼び出すことだったので、例えば何度も登場している add ツールを呼び出すとする。これまで示した実装には、ログメッセージと進捗通知が含まれていて、最終的には結果——つまり call tool result——を返す。

ここでもう1つ厄介な点がある。技術的には、ログメッセージと進捗通知の両方とも call tool request に紐づいている。だから、両方とも下側の応答で送り返されるはずだと考えるかもしれない。両方ともこの call tool request に紐づいているのだから。しかし、現時点での多くの SDK の実装はそのようにはなっていない。進捗通知(progress notification)は、受信した call tool request とは切り離されたものとして扱われる。そのため、進捗通知は実際には1つ目の SSE 応答——つまり、サーバがクライアントへリクエストを送るために長期間開いたままに保たれることを意図した応答——の側で送られる。そして、ログメッセージと実際の call tool result は、POST リクエスト(または GET リクエスト)に対する応答として送り返される。これが一連の流れの全体像である。

デモ: すべてのリクエストを可視化する

これをはっきりと理解してもらうため、講師はこれらすべての異なるリクエストが実際に発行される様子を非常にビジュアルに確認できる、小さなデモアプリケーションを見せる。

まず、シンプルな add ツールを1つ持つシンプルな MCPサーバが用意されている。add ツールは情報をログに出力し、2秒待ってから進捗を報告し、その後結果を返す。このサーバには、ブラウザ内で動作するクライアントが接続されており、このクライアントを使っていくつかの異なるリクエストをサーバへ送り、その正確な応答を確認できる。

このクライアントは合計で、画面に表示されている一連のフローを辿る。まず画面上部にある一連の処理を通じて初期化プロセス全体を行う。その後、GET SSE 接続を確立し、これによりサーバがクライアントへリクエストを送れるようになる。そして call tool request を行い、これによってこの特定のツールコールに関連するメッセージを処理するための2つ目の SSE 接続が確立される。

実際に動かしてみる。まず initialize request を送信する。これによってセッション ID が取得され、以降のあらゆるリクエストでクライアントを一意に識別するために使われる(このセッション ID は Streamable HTTP transport を使っているときにのみ提供されるものであり、標準入出力トランスポートでは全く使われない)。このセッション ID は EAA から始まる。このヘッダーは自動的に取得され、以降のすべてのリクエストに適用される。次のリクエストを見ると、上のリクエストで取得した正しいセッション ID が自動的に MCP session ID として適用されていることが分かる。

initialize request を送った次は、2つ目のリクエストとして initialized notification を送信する。これで最初の3つのボックスに対応する処理が済んだので、次にサーバへ GET リクエストを送る。この GET リクエストは長時間開いたまま保持される。これにより、サーバはいつでもクライアントへリクエストを送れるようになる。sampling や、その他あらゆるサーバ起点のリクエストのために使われるものである。「Start GET SSE」というボタンをクリックしてこのリクエストを送ると、このリクエストにもセッション ID が含まれ、これで接続が確立される。理論上、この時点からいつでも、サーバは sampling やログ、進捗通知などのメッセージをクライアントへ送信できるようになり、それらを受信すればこのボックスの中に表示されるはずである。

最後のステップとして、Add 関数を呼び出す。ここでも正しいセッション ID が含まれている。ここで振り返ると、この呼び出しをクリックしたとき、理論上はログステートメント、進捗ステートメント、そして最終的な call tool result のすべてが、このレスポンスに対応するボックスの中に現れることを期待したくなる。しかし実際には、Python 版の MCP SDK の作りにより、進捗通知は上側のレスポンス(長寿命の GET SSE 接続)の一部として送信される。したがって、こちら側にはログステートメントと call tool result が表示され、右下のボックスには進捗ステートメントが表示されるはずである。

実行してみると、内部に2秒の待機があるための少しの遅延の後、結果が返ってくる。予想どおり、右下側には notification/progress が表示され、これが進捗ステートメントである(「80/100 の完了」)。これはあちらのレスポンスチャンネルで送信されたものだ。そして call tool request への実際の応答には notification/message という形でログステートメントが含まれ、その後に call tool request の実際の結果が続く。これが call tool result であり、2つの数字を足した答えである 8 が含まれている。そして call tool result を受け取るとすぐに、この接続は自動的にクローズされる。実際、「接続がクローズされました」という表示が現れ、この SSE レスポンスを通じてこれ以上メッセージを受信することはできなくなる。

以上が、Streamable HTTP transport の裏側で起きている全体の流れである。しかし、まだ完全に終わったわけではない。先ほど何度も述べたとおり、あの2つのフラグを true に設定したくなるシナリオがあり、それがこのフローの一部を壊してしまう。これが最後に理解すべきことである。なぜそれらを true に設定したくなるのか、そして具体的に何が壊れるのか。それが最後に解き明かすべき謎である。


8. State and the StreamableHTTP Transport(Stateless HTTP)

最後に理解すべきなのは、stateless と JSON response という2つのフラグが何を意味するのか、サーバにどう影響するのか、いつ true に設定することになるのか、という点である。まず、これらのフラグを true に設定したくなるシナリオを説明し、なぜこれらのフラグが存在し、サーバにどう影響するのかという背景を示す。

水平スケーリングというシナリオ

あなたと私で MCPサーバを構築し、誰でも接続できるようにどこかへ公開してデプロイしたと想像してほしい。自分のクライアントで接続するかもしれないし、他の人々も自分のクライアントで接続してくるかもしれない。時間が経つにつれ、このサーバが非常に人気になり、最初に接続していた3つのクライアントに加えて、他にも大勢の人が接続してくるようになったとする。

ある時点で、単一のマシン上でサーバを1インスタンスだけ動かすのでは、入ってくるトラフィック量にはおそらく対応しきれなくなる。この解決策の1つが水平スケーリングである。水平スケーリングでは、サーバの複数のコピーを動かし、ロードバランサーでそれらへのアクセスをゲートする。入ってくるリクエストはそれぞれ、これらの異なるサーバのいずれかへランダムにルーティングされる。これにより、より高いトラフィック需要に対応できるようになる。

ここでロードバランサーを配置した場合に何が起きるかを考えてみる。特に重要なのは、任意のクライアントから MCPサーバへの接続は、実質的に2つの別個の接続を必要とするという点である。1つは、サーバからクライアントへのリクエストを受け取るために常に稼働させておきたい GET リクエスト。もう1つは、クライアントが POST リクエストを送り、いくつかのメッセージを含む SSE 応答を受け取るという接続である。

1つのクライアントが2つの異なるサーバへ接続する場合を考えてみる。このクライアントはまず初期リクエストを行い、GET SSE のレスポンスパイプラインを確立する。この応答は保留状態、つまり継続的にクライアントへ向けて動き続けているものと想像してほしい(これは、サーバがクライアントへリクエストを送り返せるようにするためのものである、という点を思い出してほしい)。

その後、ある将来の時点で、クライアントがツールを実行することを決めたとする。POST リクエストを使って call tool request を送るかもしれない。そして、このリクエストが2つ目の MCPサーバへルーティングされたとする。2つ目のサーバは自身の独立した SSE レスポンスを確立することで応答する(ここでは分かりやすさのためロードバランサーは図から省く)。

このツールがサーバの中で実行される際、例えば Claude を利用する必要があり、create_message リクエストを作成することで Claude を利用しようとするとする。これは sampling を使おうとしていることを表す。sampling リクエストは常に GET SSE 応答を経由する必要があるが、その接続は完全に別のサーバによって確立されたものである。したがって、このリクエストを別のサーバへ渡し、そのサーバに GET SSE 応答を通じてリクエストを送らせ、MCP クライアントに Claude を実行させてテキストを生成させ、それを元のサーバへ送り返し、さらにその生成されたテキストを別のサーバへ届ける、という方法を何とかして見つけ出す必要がある。

見てのとおり、これらすべてを調整するのは非常に難しい。絶対に不可能というわけではないが、大変な困難と、多くの追加設定・インフラが必要になる。もし水平スケーリングを想定した MCPサーバを構築していて、こうした余分な調整やインフラのセットアップを避けたいのであれば、stateless HTTP フラグを true に設定するという選択肢がある。

Stateless HTTP フラグの効果

このフラグを true に設定すると、即座に、しかし非常に重要な効果が1つ生じる。クライアントがセッション ID を受け取らなくなるということだ。それはつまり、サーバがクライアントを追跡できなくなることを意味する。これにはすぐにいくつかの大きな波及効果がある。セッション ID がなければ、MCPサーバはもはや GET SSE の応答経路を使ってクライアントへリクエストを送ることができなくなる。

なぜこの応答経路がもう使えなくなるのか理解するために、講師は「口座 ID の存在しない銀行」を例に挙げる。そうした銀行はお金を受け取ることはできるかもしれないが、誰がそのお金を渡したのか分からず、誰にいくら渡すべきかも分からない。何の識別トークンも持たない状態というのは、それと同じ状況である。

そして、この GET SSE の応答経路が使えないということは、サーバがクライアントへ何のリクエストも送れないということを意味し、その結果として sampling、progress logging、リソース変更に関する subscription といった機能が使えなくなる。

一方で、良いこともある。stateless モードでは、確実に言えるのはセッション ID が発行されず、サーバがクライアントを状態として追跡しなくなるという点である。講師はこれを「クライアントの初期化を行う必要がなくなる」——つまり通常サーバに接続する際に必要な initialize request とそれに続く initialized notification を送らずに済む——とも説明しているが、この初期化ハンドシェイク自体の省略が現行の仕様や各 SDK の安定版で明確に保証されているとまでは言い切れず、実装によって挙動が異なりうる点には注意しておきたい。いずれにせよ、少なくともセッション追跡に伴うオーバーヘッドを削減できる点は、なかなか悪くないトレードオフである。

JSON Response フラグ

もう1つ議論してきたフラグが JSON response である。json_responsetrue に設定するということは、クライアントへ送信する POST リクエストがストリーミングを行わなくなる、ということを意味する。講師はこれについて非常にシンプルで分かりやすいデモを示す。

まず通常時の動作をおさらいする。initialize request、initialized notification を送信し、add ツール関数を呼び出すと、SSE 接続が開かれ、最初にメッセージが1つ返り、その後 call tool result が返ってくる。

ここで json_response フラグを true に切り替える。これは、返ってくるすべての応答が、最終的な結果だけをプレーンな JSONとして返すようになる、ストリーミングは一切行われない、ということを意味する。同じプロセスをもう一度実行してみると、initialize と notification を実行した後、今度は add 関数を呼び出しても、ログに関する中間メッセージなどは一切返ってこない。代わりに、最終的なツール呼び出しの結果だけが返ってくる。実行してみると、ストリーミングされた応答が返ってこないことが分かる。代わりに、ツール呼び出しが完了するまで待たされ、結果だけを受け取る。ログステートメントは一切ない。

このように、この2つのフラグがサーバの挙動にどれほど大きな影響を与えるかがすぐに分かる。しかし、アプリケーションをどう開発・デプロイしようとしているかによっては、これらのフラグを true に設定することが完全に適切な場合もある。

まとめ

多くの忍耐と数多くの図を経て、ようやく Streamable HTTP transport がどう動作するかについて、まずまず妥当な理解が得られたはずである。この2つのフラグと、それらがサーバにどう影響するかを、しっかり頭に入れておいてほしい。

もしローカルマシン上で標準入出力トランスポートを使って開発していて、すべてが問題なく動いているのに、本番環境にデプロイして Streamable HTTP transport を使い始めた途端に、うまく動かなくなったとしたら——サーバの挙動が少し変わりうる、ということを念頭に置いておいてほしい。だからこそ、サーバを開発する際には、本番で使う予定のトランスポートを開発時から使うことを強く勧める。そうすることで、後々のトラブルを大きく減らせるはずだ。


9. Wrapping up(まとめと次のステップ)

さて、いよいよ締めくくりの時間だが、その前に、ここから先の次のステップについていくつか案を伝えたい。

第一に、MCP コミュニティへの参加を強くお勧めする。GitHub 上にホストされているディスカッションボードがあり、MCP の今後の変更や、様々なアイデアについて活発な議論が行われている。他の人々が MCP のユースケースについてどんなアイデアを出しているか、ぜひ見てみてほしい。

第二に、MCP のホームページを継続的にウォッチすることも強く勧める。仕様に対する今後のニュースや変更が発表される場所である。

最後に、自分自身で MCPサーバを構築すること以上の学習方法はない。このコースを通じて提供されたコードのいくつかを実際に使い、学んだ様々なトピックを練習したり、実装してみたりすることを強くお勧めする。

以上でこのコースは終わりである。このコースをまとめるのは間違いなく楽しい作業だったので、受講者にも楽しんでもらえていたら幸いである。


このコースの要点(暗記用)

Claude 学習サイト — 完全ローカル静的サイト(build.py で生成)。進捗はこのブラウザの localStorage に保存されます。