Building with the Claude API 第6章 — Model Context Protocol(MCP)
情報源は動画の公式英語字幕(トランスクリプト)。具体例・数値・デモ手順・コード例・比喩はできる限り漏らさず反映している。技術用語(MCP, tool, client, server, server inspector 等)は英語表記のまま用いる。
このファイルは MCP モジュールの前半(MCP の概念・クライアント/サーバーの通信フロー・自作サーバーの構築・inspector によるデバッグ・自作クライアントの実装)をカバーする。resources・prompts・review 等の後半部分は別ファイルで扱う。
Lesson: Introducing MCP(MCPの導入)
このモジュールでは model context protocol(MCP)を扱う。MCP は、開発者側に大量の面倒なコードを書かせることなく、Claude にコンテキストと tools を提供するための通信レイヤーである。
MCP を学び始めると、頻繁に目にすることになる図がある。それは MCP の2つの主要な要素、すなわち client と server を示す図である。server はしばしば内部にいくつかのコンポーネントを持ち、それぞれ tools、resources、prompts と呼ばれる。用語が多いので、これを理解する助けとして、小さなアプリを作っていると想像しながら、MCP がその中にどう収まるかを見ていく(注: MCP の公式アーキテクチャでは、これに加えて MCP Host——クライアントを生成・管理する側のAIアプリケーション本体——を含めた Host/Client/Server の3層構成が定義されている。本ガイドで以下「自分のサーバー」「自分たちのサーバー」と呼んでいる存在は、この公式分類では MCP Host に相当する)。
題材となるサンプルアプリ: もう一つのチャットインターフェースを作るとする。ユーザーが自分の GitHub データについて Claude とチャットできるようにするアプリである。例えばユーザーが「自分の全リポジトリを通じてオープンな pull request は何がある?」のような質問をしたとする。その場合、Claude はおそらく tool を使って GitHub にアクセスし、ユーザーのアカウントを参照して、オープンな pull request や、場合によってはオープンなリポジトリなどを確認することになる。ここでのポイントは、これを実装するにはおそらく一連の tools を使うことになる、という点である。
GitHub 全機能を tools 化する負担: ここで手早く触れておきたいのは、GitHub には膨大な機能があるということである。リポジトリ、pull request、issue、project、その他多数の機能がある。したがって完全な GitHub チャットボットを作るには、非常に多数の tools を用意しなければならない。もしこのサンプルアプリを作ろうとすれば、私たちはこれらすべてのスキーマとすべての関数を書く責任を負うことになる。これはすべて、開発者である私たちが書き、テストし、保守しなければならないコードである。それは大変な労力であり、大きな負担が私たちに課せられる。この「開発者に大きなインテグレーション群を保守させる」という課題こそ、model context protocol が解決しようとしている主要な難題の一つである。
MCP が肩代わりするもの: MCP は、tools を定義し実行する負担を、自分たちのサーバーから「MCP server」と呼ばれる別のものへとシフトさせる。もはや私たちがこの tool を自分で書く必要はなく、代わりにそれはどこか別の場所、この MCP server の内部で書かれ、実行されることになる。これらの MCP servers は、何らかの外部サービスへのインターフェースだと考えることができる。例えば GitHub MCP server というものがあれば、それは特に GitHub が提供するデータや機能へのアクセスを提供する。つまり GitHub にまつわる膨大な機能を一式まとめて、この MCP server の中に tools の集合という形で配置しているということである。
この時点で、MCP server が何であるかについての非常に基本的な理解が得られる。それは、何らかの外部サービスに関連する機能を公開する tools の集合へのアクセスを与えてくれるものである。そしてここでの利点は、私たちがこれらすべての異なる tool スキーマや関数などを自分で書かなくてよいということである。
よくある3つの質問:
-
これらの MCP servers は誰が作るのか? 答えは「誰でも」である。誰でも MCP server の実装を作ることができる。ただし非常によくあるのは、サービスプロバイダー自身が公式実装を作るケースである。例えば AWS が独自の公式 MCP server 実装をリリースするかもしれず、その中には利用可能な多種多様な tools が含まれているかもしれない。
-
MCP server を使うことは、サービスの API を直接呼び出すこととどう違うのか? 先ほど見たように、もし API を直接呼び出したければ(例えば GitHub を)、その tool を自分で書かなければならない。そして今度は GitHub を直接呼び出せるようになる。では何が変わったのか。実際に変わったのは、スキーマと関数の実装を自分で書かなければならなくなった、という点だけである。したがって単に MCP server を追加するだけで、私たちは少し時間を節約できることになる。
-
(最後の質問というより、よくある批判)MCP と tool use は同じものなのか? これは MCP についてよく見られる批判で、多くの場合、MCP が何なのかをきちんと理解していない人たちから出てくるものである。ここまで説明してきたように、MCP servers と tool use は補完関係にある。両者は異なるものだが、補完し合う。MCP の考え方の核心は、tool function と tool schema を自分で書かなくてよいということであり、それは誰か他の人によって行われ、この MCP server の中にまとめられている、ということである。ある意味では、両方とも tool use について話しているという点で似てはいるが、MCP servers が本当に語っているのは「実際の作業を誰がやっているか」という点である。もしこの種の批判を見かけたら、それは大抵、MCP が何なのかをきちんと理解していないことが原因である。
Lesson: MCP clients(MCP client)
MCP のもう一つの構成要素として、client を見ていく。client の目的は、自分のサーバーと MCP server との間の通信手段を提供することである。この client は、その server が実装するすべての tools へのアクセスポイントになる。
トランスポート非依存: MCP は transport agnostic(トランスポート非依存)である。これは、client と server がさまざまな異なるプロトコル上で通信できることを意味する専門用語である。現在よく見られる MCP server の実行方法は、MCP client と同じ物理マシン上で動かすというものである。この2つが同じマシン上で動いている場合、standard input/output(標準入出力)を介して通信できる。これが、このセクションでこの後実際に構築していく方式である。ただし、client と server を接続する方法は他にもある。MCP の公式仕様が標準として定義しているトランスポートは stdio と Streamable HTTP の2つで、WebSockets のような他のプロトコルも実装は可能だが、それらは仕様上「custom transport」という位置づけになる。
メッセージのやり取り: client と server の間に接続が形成されると、両者はメッセージを交換することで通信する。許可される正確なメッセージの種類はすべて MCP の仕様の中で定義されている。ここで注目するメッセージタイプには以下がある。
- list tools request: client から server へ送られ、server が提供するすべての異なる tools を列挙するよう求める。
- list tools result: server が返す応答で、提供可能なすべての tools のリストを含む。
- call tool request: server に対して、特定の引数である tool を実行するよう求める。
- call tool result: そのtool 実行の結果を含む。
一連の呼び出しのウォークスルー: ここまでで server と client という考え方が出てきたが、これらが実際にどう連携して動くのかはまだはっきりしないかもしれない。そこで、ユーザー、自分たちが組み立てているサーバー、MCP client、MCP server、データ取得先のプロバイダーとしての GitHub、そして Claude ――これらの間で行われるやり取りの例を、一通りウォークスルーする。
- ユーザーが自分たちのサーバーに対して「自分にはどんなリポジトリがある?」のような質問・クエリを送信する。
- 自分たちのサーバーは Claude にリクエストを送る必要があるが、そのリクエストには Claude がアクセスできる tools のリストを含めたい。したがって Claude へのリクエストを送る前に、MCP client と server を経由する少し寄り道をする。
- サーバーは、Claude に送るために tools のリストを取得する必要があると判断し、MCP client に対して tools のリストを取得するよう依頼する。
- MCP client は list tools request を server に送り、server は list tools result で応答する。
- MCP client は tools のリストを手に入れたので、それをサーバーに返す。
- サーバーは、ユーザーからの元のメッセージと tools のリストという、Claude への初回リクエストに必要なすべてを揃えたことになる。そこで、そのクエリと tools のセットを添えて Claude にリクエストを送る。
- Claude は tools を確認し、ユーザーの元の質問に答えるために tool を呼び出したいと判断する。そして tool use メッセージパートを含む応答を返す。
- サーバーは、Claude が tool の実行を求めていると認識する。しかしサーバー自身はもう tools を実行する責任を持たない。代わりに tools は MCP server によって実行される。したがって、Claude が求めている tool を実行するために、サーバーは MCP client に対して、Claude が提供した特定の引数で tool を実行するよう依頼する。
- MCP client は実際には tool を実行しない。代わりに call tool request を MCP server に送る。
- MCP server はそのリクエストを受け取り、GitHub へフォローアップのリクエストを行う。ここで実際に、この特定ユーザーが持つリポジトリのリストを取得することになる。
- GitHub はそのリポジトリのリストで応答する。
- MCP server はそのデータを call tool result の中に包んで MCP client に送り返す。
- MCP client はその結果を自分たちのサーバーに渡す。
- サーバーはリポジトリのリストを手に入れたので、tool result パートをユーザーメッセージの中に含めて、Claude へフォローアップリクエストを行う。この tool result には、Claude が求めていたリポジトリのリストが含まれる。
- これで Claude は最終応答を組み立てるために必要な情報をすべて手にしたことになる。そこで「あなたのリポジトリは……」といったテキストを書き出し、それをサーバーに送り返し、サーバーはそれをユーザーに送る。
このフローは確かにかなり込み入っている。これをあえて見せたのは、この後、自作の MCP client と MCP server を実装し始める際に、これらすべての要素が実際に登場することになるからである。
Lesson: Project setup(プロジェクトのセットアップ)
MCP のいくつかの側面をより良く理解するために、自作の CLI ベースのチャットボットを実装していく。これにより、Claude と servers が実際にどう連携して動くのか、より良い理解が得られる。このレッスンでは少しプロジェクトのセットアップを行い、これから何を作るのかを正確に理解してもらう。プロジェクトの説明は多くあり、それは時間をかけて見ていくが、ここではまず全体像を高いレベルで把握しておく。
作るもの: CLI ベースのチャットボットである。ユーザーがドキュメントのコレクションを扱えるようにする。これらは架空のドキュメントであり、メモリ上にのみ保存される。自作の小さな MCP client を作り、それが自作のカスタム MCP server に接続する構成にする。現時点では、server には2つの tools が実装される。1つはドキュメントの内容を読み取る tool、もう1つはドキュメントの内容を更新する tool である。これらのドキュメントは(レクチャー画面の)右側に表示されているもので、すべて架空であり、メモリ上にのみ永続化される。
重要な注意点: 通常のプロジェクトでは、典型的には client か MCP server のいずれか一方だけを実装することになる。実際のプロジェクトでは、世界に配布して開発者が自分たちの構築したサービスにアクセスできるようにするための MCP server だけを作る、というケースがあり得る。あるいは、client だけを作るプロジェクトを構築することもあり得る。その場合の意図は、他のエンジニアによってすでに実装されている外部の MCP servers に接続することである。このプロジェクトでは、client と server の両方を作る。それを1つのプロジェクトの中で行うのは、これらがどう連携して動くのかをより良く理解してもらうためである。
セットアップ手順:
- レクチャーに添付されている
CLIproject.zipというファイルをダウンロードし、展開し、コードエディタでそのプロジェクトディレクトリを開く。 - プロジェクト内の
README.mdを確認する。ここには、プロジェクトの.envファイルに API key を設定する手順や、uvを使う場合・使わない場合それぞれの依存関係インストール手順が書かれている。 - セットアップが済んだら、スターターのプロジェクトをすぐに実行できる。プロジェクトディレクトリの中にいることを確認する(このプロジェクトは "MCP" という名前で作成されている)。
uvを使っている場合はuv run main.pyを実行する。uvを使っていない場合はpython main.pyを実行する。- 実行するとチャットプロンプトが表示される。「what's one plus one」のように尋ねると、比較的すぐに応答が返ってくることを確認できる。
セットアップはこれで完了であり、この後アプリケーションに新しい機能を追加する作業に取りかかる。
Lesson: Defining tools with MCP(MCPでtoolsを定義する)
CLI チャットボット用の MCP server を作り始める。すでに見たように CLI 自体はすでに動作しており、Claude とチャットもできるが、それに紐づく追加の MCP server 機能はまだない。そこでこの MCP server を追加していく。現時点では2つの tools を持たせる。1つはドキュメントを読み取る tool、もう1つはドキュメントの内容を更新する tool である。
server の実装は、ルートプロジェクトディレクトリ内の mcp_server.py ファイルに配置する。このファイルには、すでにいくらか作業を進めた基本的な MCP server のセットアップが入っている。そして、メモリ上にのみ存在するドキュメントのコレクションが定義されている。さらに、これから取り組むべき異なるタスクを示す to-do 項目がいくつか置かれている。現時点では、最初の2つの項目、すなわち2つの tools を書くことに取り組む。
MCP Python SDK の利点: これまで tools を作ってきた際には、大きな JSON schema を含む煩雑な構文が必要だった。しかしこのプロジェクトでは、公式の MCP Python SDK を利用する。それがこの mcp パッケージである。この MCP パッケージは、以下のようなたった1行のコードで MCP server を作ってくれる。
この SDK は、tool を定義するのも非常に簡単にしてくれる。tool を定義するには、以下のように書くだけでよい。これは add_integers という名前の tool を、指定した description と、渡す必要のある2つの引数とともに作る。このように tool の定義を書くと、裏側で MCP が tool の JSON schema を生成してくれ、それを Claude に渡すことができる。このように、tools の定義のような基本的なことがずっと簡単になる。
1つ目の tool: read_document: 目標は、あるドキュメントの名前を受け取り、その内容を返すことだけである。すべてのドキュメントはすでに docs という辞書に格納されている。キーはドキュメントの ID(実質的には名前)であり、値はそのドキュメントの内容である。したがって tool は非常にシンプルで、こうした文字列の1つを受け取り、docs 辞書の中で対応する値を探し、それを返すだけである。
実装手順:
- 最初の to-do を見つけ、その直下に
@mcp.tool()(括弧を忘れずに。括弧なしの@mcp.toolはTypeErrorになる)と書いて新しい tool を定義する。 - tool 名を
read_contents、description を「read the contents of a document and return it as a string(ドキュメントの内容を読み取り、文字列として返す)」とする。理想的な世界であれば、Claude にとってこの tool をいつ使うべきかが明確にわかるよう、しっかり作り込んだ description を書くべきだが、ここでは時間短縮のため、あえて非常にシンプルな description にとどめる。 - 実際の tool 関数を定義する。この tool が実行されるときに走る関数であり、
read_documentと名付ける。引数としてdoc_id(文字列型)を受け取る。これをFieldに設定し、description を「ID of the document to read(読み取るドキュメントの ID)」とする。 - ファイル冒頭に
from pydantic import Fieldのインポートを追加する。 - 関数本体の実装: まず、Claude が存在しないドキュメントを要求してきたケースを処理する。
if doc_id not in docs:(渡されたドキュメント ID が辞書のキーとして見つからない場合)、ValueError(f"doc with id {doc_id} not found")を発生させる。 - このチェックを通過したら、実際のドキュメントを返す。
return docs[doc_id]。
これで tool の定義は完了である。tool の名前、description、期待される引数とその型、そしてその引数の description を指定した。これらすべてのデコレータや Field 型などが MCP Python SDK によってまとめて処理され、JSON schema が生成される。
2つ目の tool: edit_document: 同じプロセスを繰り返す。
@mcp.tool()と書き、名前をedit_document、description を「edit a document by replacing a string in the document's content with a new string(ドキュメントの内容の中の文字列を新しい文字列で置き換えることでドキュメントを編集する)」とする。- 実装関数は
edit_documentと名付ける(一貫性のため)。 - 引数は3つ:
doc_id(文字列、description は「ID of the document that will be edited(編集対象のドキュメントの ID)」)、old_string(文字列、description は「the text to replace, must match exactly including white space(置き換え対象のテキスト。空白を含め完全に一致する必要がある)」)、new_string(文字列、description は「the new text to insert in place of the old text(古いテキストの代わりに挿入する新しいテキスト)」)。 - ドキュメント編集は単純な find and replace である。
- ここでも、Claude が実在するドキュメントを要求しているか確認する。
if doc_id not in docs:の場合、ValueError(f"doc with id {doc_id} not found")を発生させる。 - 正しいドキュメントが見つかった場合の編集処理:
docs[doc_id] = docs[doc_id].replace(old_string, new_string)。
このように、2つの tool 実装を非常に手早く組み立てることができた。この MCP Python SDK を使って tools を定義するのは、スキーマ定義を手で書くよりもはるかに簡単である、ということを重ねて強調しておきたい。両方の tools が揃ったところで、対応する to-do を削除する。ここまでで MCP server を組み立て、その中に2つの tools を実装した、という良いスタート地点に到達した。
Lesson: The server inspector(server inspector)
MCP server の中にいくつか機能を組み立てたが、それが実際に動くかどうかはまだわからない。何らかの形でテストできれば非常に良い。実は、この Python SDK を使うことで、自動的にブラウザ内デバッガーへのアクセスが得られ、この server が期待通りに動作していることを確認できる。
起動手順:
- ターミナルに戻り、Python 環境が有効化されていることを確認する(README にこの環境を有効化する正確なコマンドの詳細が書かれている)。
- 有効化を確認したら、
mcp devに続けて server が入っているファイルの名前を指定して実行する。今回はmcp_server.pyである。 - 実行すると、コンソールにポート 6277 でリッスンしているプロキシサーバーがあると伝えられる。ブラウザで実際に開くべき MCP Inspector 本体のアドレスは別途表示されるポート 6274 の方なので、そちらをブラウザで開く。
MCP Inspector の画面: そこにアクセスすると、MCP Inspector と呼ばれる画面が表示される。この inspector は現在活発に開発が進められているものなので、視聴時点で画面の見た目はここで示しているものとかなり異なっている可能性がある。とはいえ、それでもおそらく非常によく似た機能を持っているはずである。
画面左側には Connect ボタンがある。これをクリックすると、たった今編集したファイルである MCP server が起動する。Connect をクリックすると、画面上にいくつかのものが現れる。まず上部のメニューバーに注目する。ここには resources、prompts、tools、その他いくつかの項目が並んでいる。UI は視聴時点で変わっている可能性があるので、このメニューバーが見当たらない場合は、何らかの "Tools" セクションを探せばよい。
tool の手動実行:
- Tools をクリックし、List Tools をクリックすると、先ほど組み立てた tools の名前が表示される。
- いずれか1つをクリックすると、右側のパネルが変化する。このパネルを使って、tool の1つを手動で呼び出し、期待通りに動作しているかを確認できる。これにより、実際のアプリケーションに配線することなく、MCP server 上でライブ開発を行うことができる。
read_contents tool のテスト:
- ドキュメント ID を入力するだけでよい。エディタに戻り
docs辞書を見て、ドキュメント ID の1つ(deposition.md)をコピーする。 - それを doc ID として入力し、Run Tool をクリックする。
- "Run tool" success と表示され、ドキュメントの内容が表示されるはずである。エディタ内の同じ文字列であることを確認して検証できる。
edit_document tool のテスト: 同じ手法で、もう一方の tool もテストできる。
- edit document tool に切り替える。
- ドキュメント ID を入力する。
- 置き換えたい old string を入力する(デモでは「deposition」という単語ではなく、もっと入力しやすい別の単語を使っている)。この old string の照合は大文字小文字を区別する(case sensitive)。
- new string として「a report」を指定する。
- Run Tool を実行すると success が返る。この tool 自体はドキュメントの内容を返さない。ドキュメントを編集するだけである。
- 編集が正しく行われたことを確認するために、
read_contentstool に戻り、同じドキュメント ID で再度実行する。すると "a report deposition" のような内容が表示され、編集が反映されたことがわかる。
このように、MCP inspector を使うことで、実際のアプリケーションに配線することなく、開発中の MCP server を非常に簡単にデバッグできる。自分で MCP servers を構築していく中で、この inspector ツールをかなり頻繁に使うことになるだろう。このモジュールの中でも、server の開発が順調に進んでいることを確認するために、もう少し使っていくことになる。
Lesson: Implementing a client(clientの実装)
server が良い状態になったところで、少し方向を変えて MCP client の実装に取りかかる。client はルートプロジェクトディレクトリ内の mcp_client.py ファイルにある。
前提の再確認: 先ほど述べたことをもう一度手短に確認しておく。典型的なプロジェクトでは、通常 client を作るか、server を実装するかのいずれか一方だけである。取り組んでいるこの特定のプロジェクトだけが、両方を作っている。それはパズルの両側を見てもらうためである。
MCP client クラスの構造: このファイル内の MCP client 自体は、単一のクラスから構成されている。中には多くのコードがあり、server 側で書いたコードほどきれいには見えない。その理由を説明する。
このファイルの中では MCPClient クラスを作る。このクラスは client session(ClientSession)と呼ばれるものをラップしている。client session とは、MCP server への実際の接続そのものである。この client session は Anthropic の Python SDK(anthropic パッケージ)ではなく、MCP Python SDK(mcp パッケージ、mcp.client.session.ClientSession)の一部である。つまりこのセッションこそが、外部の server とのこの接続を与えてくれるものである。
session 自体は、ある程度のリソースクリーンアップを必要とする。つまり、プログラムを終了させるとき、あるいは server がもう必要ないと判断したときには、多少のクリーンアップ処理を経なければならない。この MCPClient クラスの中には、そうしたクリーンアップコードの多くがすでに書かれている。それがこのクラスが存在する本当の理由であり、そのクリーンアップを少し楽にするためである。そのクリーンアップコードの一部は connect 関数の中や、少し下にある cleanup、__aenter__、__aexit__ 関数の中にも見られる。したがって、client session を直接使うのではなく、リソース周りの管理をしてくれるこうしたより大きなクラスでラップするのが、非常に一般的なプラクティスである。
この client が存在する理由: 少し前に見たフロー図を思い出す。あの図の中では、ある時点で Claude に送るための tools のリストが必要だった。そしてその後、Claude から要求された tool を実行する必要があった。MCP server にアクセスしてこの tools のリストを取得したり、tool を実行したりするために利用するのが、この MCP client である。つまりこの client は、server に属する機能をコードベースの他の部分に公開しているとイメージできる。
このプロジェクトの core ディレクトリの中には、すでにこのクラスを利用する多くのコードが用意されている。そこには list_tools、call_tool、list_prompts、get_prompt などの関数を呼び出す他のコードが存在する。このレッスンでは、そのうち list_tools と call_tool の2つの関数の実装に絞って取り組む。先ほどの図で見たように、この2つの関数はコードベースの異なる箇所で使われ、Claude に提供する tools のリストを取得したり、Claude が要求した tool を最終的に呼び出したりする。
list_tools の実装: to-do を削除し、以下のように置き換える。result = await self.session.list_tools() を呼び出し、return result.tools とする。これは session(MCP server への実際の接続)にアクセスし、その server が実装しているすべての tools の定義・リストを取得する組み込み関数を呼び出している。result を受け取り、その中の tools だけを返す。
call_tool の実装: 同様の要領で実装する。return await self.session.call_tool(tool_name, tool_input) とする。ここでも session(server への接続)にアクセスし、渡された名前の特定の tool を、Claude から提供された入力引数とともに呼び出そうとする。
テストハーネスでの検証: この2つの関数を手早くテストする。ファイル末尾には、この mcp_client.py ファイルを直接実行できる小さなテストハーネスが用意されている。これを実行すると、MCP server への接続が形成され、それに対していくつかのコマンドを実行して結果を確認できる。uv を使っていない場合はコマンドと引数を変更する必要がある旨のコメントがコード中にあるので、その点は確認する。
with ブロックの中に、以下のテストコードを追加する。result = await _client.list_tools() として、結果をプリントする。これにより、自作の MCP server の1コピーが起動し、それが定義しているすべての tools のリスト取得を試み、その結果をプリントする。
これをテストするため、ターミナルに戻り uv run mcp_client.py(uv を使わない場合は python mcp_client.py)を実行する。実行すると、tool 定義のリストが表示される。この中に、先ほど組み立てた read_contents tool と edit_document tool が見えるはずである。それぞれに description と input schema がある。これが、最終的に Claude に渡されることになる tool の定義である。
CLI 経由での動作確認: list_tools と call_tool を呼び出すコードは、このプロジェクトの別の場所にすでに実装済みである。したがってこの機能を追加した今、CLI を再度実行し、Claude にこれらの tools を使わせてみることができる。具体的には、Claude に特定のドキュメントの内容を調べさせたり、ドキュメントを編集させたりできる。
自作の MCP server には、ID が report.pdf のドキュメントがあり、その内容には「20 meter condenser tower(20メートルのコンデンサータワー)」といった記述が含まれている。ターミナルに戻り、uv run main.py でプロジェクトを実行する。そして Claude に「what is the contents of the report.pdf document(report.pdf ドキュメントの内容は何ですか)」と尋ねる(report.pdf は正確に入力する)。
これを実行すると、リクエストと一緒に tools のリストが送信される。Claude は read document tool を使うことを決定し、ドキュメントの内容を取得する。そして Claude がそのドキュメントの内容を取得できたことがわかる。レポートが「20メートルのコンデンサータワー」に関する何かであると教えてくれる。
この時点で、client 周りの機能を追加できた。この client は、MCP server の内部に実装された機能へのアクセスを与えてくれるものであることを思い出してほしい。ここまでで、server によって作られた tools のリストを取得し、server によって実装された tool を実行する、ということができるようになった。
章末まとめ
- MCP の core motivation: 外部サービス(GitHub 等)向けの tool schema・tool function を開発者自身が大量に書いて保守する負担を、MCP server という別のレイヤーに肩代わりさせる仕組みである。MCP server は誰でも作れるが、サービスプロバイダー自身が公式実装を出すことが多い。
- MCP と tool use の関係: 同じものではなく補完関係。tool use の仕組み自体は変わらず、「誰が tool の schema と実装を書き、実行するか」という責任の所在が変わる。
- client と server の役割分担: client はサーバー(アプリ本体)と MCP server の間の通信窓口。server は実際に tools を実装・実行する側。両者は standard input/output(同一マシン上)や Streamable HTTP など transport agnostic な方式で通信する(公式仕様が定義する標準トランスポートは stdio と Streamable HTTP の2つで、WebSockets 等は custom transport という位置づけ)。
- 主要メッセージ4種: list tools request / list tools result / call tool request / call tool result。この4つのやり取りが、Claude へのリクエストに tools リストを含める場面と、Claude が要求した tool を実際に実行する場面の両方を支える。
- 通しフロー: ユーザー → サーバー → (MCP client → MCP server で tools 一覧取得) → Claude(tools 付きリクエスト)→ tool_use 応答 → (MCP client → MCP server → 外部サービス(GitHub等) で実際の tool 実行) → tool result を添えて Claude に再リクエスト → 最終応答。
- 自作プロジェクトの構成: CLI ベースのチャットボットで、ドキュメント(メモリ上の架空データ)を読み書きする
read_contents/edit_documentの2 tools を持つ MCP server と、それに接続する MCP client の両方を1プロジェクト内に作る(実務では通常どちらか一方のみを作る点に注意)。 - MCP Python SDK の効果:
@mcp.tool()デコレータ(括弧を忘れずに)と Pydantic のFieldを使うだけで、tool 名・description・引数の型と description から JSON schema が自動生成される。手書きのスキーマ定義に比べて大幅に簡単になる。 - server inspector:
mcp dev <server file>を実行すると、ポート 6277 にプロキシサーバー、ポート 6274 にブラウザデバッガー(MCP Inspector)本体が立ち上がる。ブラウザで実際に開くのは Inspector 本体が動くポート 6274 の方で、Connect → Tools → List Tools → 個別 tool の手動実行、という流れで実アプリに配線せずに tool の動作検証ができる。 - client 実装の勘所:
MCPClientクラスは client session(server への実接続、MCP Python SDK のmcpパッケージ由来。Claude API呼び出し用のanthropicパッケージとは別物)をラップし、リソースクリーンアップ(connect/cleanup/__aenter__/__aexit__)を担う。中核となるlist_tools(session.list_tools()を呼びresult.toolsを返す)とcall_tool(session.call_tool(tool_name, tool_input)を呼ぶ)の2関数だけで、Claude への tools 提供と tool 実行要求への応答が実現できる。
情報源は動画の公式英語字幕(トランスクリプト)。具体例・数値・デモ手順・コード例・比喩はできる限り漏らさず反映している。技術用語(MCP, resource, prompt, client, server, tool 等)は英語表記のまま用いる。
Lesson: Defining resources(resourcesを定義する)
MCP server の中の次の主要機能である Resources に進む。resources を理解するために、これまで構築してきたプロジェクトに新しい機能を追加する。
追加する機能: ユーザーがメッセージの中で @ 記号とドキュメント名を入力することで、ドキュメントを mention(メンション)できるようにする。ユーザーがそうした場合、自動的にそのドキュメントの内容を取得し、Claude に送るプロンプトの中に挿入する。この機能には大きく2つの側面がある。
- ユーザーがメッセージの中で
@記号を入力すると、自動的に mention 可能なドキュメントの一覧を小さな autocomplete(オートコンプリート)ウィンドウに表示する。 - ユーザーが mention を含むメッセージを送信すると、自動的にそのドキュメントの内容を取得し、Claude に送るプロンプトに挿入する。
例えば、ユーザーが「@report.pdf ファイルの中身は?」のように言った場合、次のようなプロンプトを組み立てて Claude に送りたい。ユーザーからのクエリを含めつつ、ユーザーが何らかのドキュメントを参照した可能性があることを Claude に伝え、そのドキュメントの内容を渡す。この方式の狙いは、report.pdf の中身を調べるために Claude が何らかの tool を使う必要がなくなる点にある。その代わりに、ユーザーが事前にファイルを mention しておくことで、こちら側があらかじめコンテキストを挿入しておける。
ここで整理しておきたいのは、これが実質的に2つの別々の機能だという点である。1つ目の機能は、ユーザーが @ 記号を入力したときに、mention 可能なすべてのドキュメントの一覧を MCP server から取得する必要があるというもの。2つ目の機能は、ユーザーが mention を含むメッセージを送信したときに、単一ドキュメントの内容を MCP server から取得する必要があるというものである。この情報を MCP server から取り出すために resources を使う。
resources は、MCP server がクライアントに対してある種のデータを公開する仕組みである。通常、個別の読み取り操作ごとに1つの resource を定義する。この例では、ドキュメントの一覧を取得する操作と、単一ドキュメントの内容を読み取る操作の2つが必要なので、おそらく2つの別々の resource を作ることになる。1つ目の resource は autocomplete に入れるためのドキュメント名一覧だけを返す役割を持ち、もう1つの resource はドキュメント ID に基づいて単一ドキュメントの内容を公開する役割を持つ。
これらの resource を定義すると、MCP client を通してアクセスされることになる。全体のフローは次のようになる。ユーザーが「@ の中に何か…」のように入力し始めると、@ 文字を入力した瞬間に autocomplete に表示するドキュメント名の一覧を表示する必要がある。そのために、こちらのコードは MCP client にアクセスし、client は read resource request を MCP server に送信する。この read resource request の中には URI と呼ばれるものを含める。これは読み取りたい resource の「アドレス」に相当するもので、resource を最初に組み立てる際に定義される。
この read resource request を送信すると、MCP server は渡された URI を見て、対応する関数を実行し、その結果を read resource result メッセージに載せて返す。こちら側はそのデータを取り出して autocomplete に表示したり、その他必要な処理に使ったりできる。
resource には2種類ある: direct(direct resource は static resource とも呼ばれる)と templated(テンプレート化された resource)。
- direct resource: 静的な URI を持つ。例えば
docs://documentsのように常に同じ URI になる。 - templated resource: URI の中に1つ以上のパラメータを持つ。例えば
documents/の後にワイルドカードを置き、任意のドキュメント ID を指定できるようにする。この resource を要求すると、URI の中のそのドキュメント ID 部分が Python MCP SDK によって自動的にパースされ、関数へのキーワード引数として渡される。キーワード引数の名前は、URI テンプレートの中に書いた文字列と完全に同じ名前になる。templated resource は、MCP server から何かを取得する際に選択肢・バリエーション・カスタマイズ性が欲しい場合に使う。
実装は非常にシンプル。 エディタで mcp_server.py ファイルを開き、「ドキュメント ID を全部返す resource を書く」というコメントと、「特定のドキュメントの内容を返す resource を書く」というコメントを見つける。
1つ目は「document IDs」というコメント。このプロジェクトでは、ドキュメント ID は実質的にドキュメントの名前そのものなので、単純に IDs を返せば、それを名前としても使える(つまり autocomplete 要素にそのまま入れられる)。TODO コメントを削除し、@mcp.resource を追加する。最初の引数は、この resource へアクセスするための URI。これは route handler に相当するものだと考えるとよい。ここでは docs://documents を使い、さらに MIME type として application/json を指定する。resource はどんな種類のデータでも返せる(プレーンテキスト、JSON、バイナリデータなど何でもよい)。それがどんな種類のデータかを client にヒントとして伝えるのがこの MIME type の役割である。application/json という MIME type は、後でこの resource を要求する client に対して「構造化された JSON データを含む文字列を返す」というヒントを与える。それにより、client 側でそのデータをデシリアライズして使いやすいデータ構造に変換する責任を負うことになる。
デコレータの下に list_docs 関数を書き、文字列のリストを返すようにする。中身は docs.keys()(辞書からキーだけを取り出してリスト化したもの)を返すだけである。ここで返しているものは明示的な JSON 文字列ではない点に注意。つまり文字列を返しているわけではない。MCP Python SDK が、返した値を自動的に文字列へと変換してくれる。
2つ目の resource も実装する。コメントを削除し、@mcp.resource("docs://documents/{doc_id}") を書く。今回はワイルドカードを入れているので templated resource になる。MIME type は今回は少し変化をつけて text/plain にする。これは単一ドキュメントの中身そのもの(特に構造を持たないプレーンテキスト)を返すためである。実際のアプリケーションでは、「ドキュメントを読む」ような操作であれば、ID・content・author name・author ID などを含む完全なドキュメントレコード(辞書のようなもの)を返すことが多いだろうが、この例ではあくまでプレーンテキストの返し方を示すために、ドキュメントの本文テキストだけを返すことにする。
関数名は fetch_doc。doc_id(文字列)を受け取り、文字列を返す。ここでも、URI テンプレートに書いた文字列がそのまま関数のキーワード引数名になる。もし doc_type のような追加パラメータを URI に加えれば、それも追加のキーワード引数として現れる。関数の中では、まずリクエストされた ID が実際に存在するかを確認する。if doc_id not in docs: の場合は ValueError を、f-string で "doc with ID {doc_id} not found" のようなメッセージとともに発生させる。そのチェックを通過したら docs[doc_id] を返す。それだけである。
MCP Inspector でのテスト: ターミナルで uv run mcp dev mcp_server.py を実行すると、デフォルトでポート6277にプロキシサーバー、ポート6274にInspector本体のweb serverが起動する。ブラウザで開くのはInspector本体が動くポート6274の方である。それをブラウザで開き、Connect をクリックする。Resources を見つけると、利用可能な resource の一覧が表示される。一覧表示されるのは具体的には static(direct)resource のみで、この例では docs://documents だけが見える。resource templates は別枠で一覧表示され、fetch_doc の resource template が1つ見えるはずである。
まず documents を実行してみると、MCP server から実際に返ってくるメッセージ構造が確認できる。text プロパティがあり、その中に返しているすべてのデータが JSON 文字列としてシリアライズされて入っている。つまり、CLI アプリケーション側でこの text を JSON 文字列から文字列のリストへとデシリアライズする責任を負うことになる。次に fetch_doc もテストできる。doc ID を入力する必要があるので、report.pdf ファイルを読みたいと指定して resource を読み取ると、そのドキュメントの内容が表示される。ここでも MIME type が text/plain になっていることが確認でき、これはこのデータを JSON としてデシリアライズしようとしてはいけないというヒントになる。
Lesson: Accessing resources(resourcesにアクセスする)
MCP server の中に2つの resource を定義したので、次は client 側にこれらの resource をリクエストする機能を追加する。そのために、MCP client の中に1つの関数を追加する。この MCP client は、アプリケーションの他の部分から使われる機能を持っている。この機能を呼び出すコードは既に用意されているという前提で進める。つまり、プロジェクトのどこか別の場所が、これから追加するこの関数を利用しようとしている。
MCP client のファイルを開き、下にスクロールして read_resource を見つける。ここでの目標は、MCP server に対してリクエストを送り、特定の resource を読み取り、返ってきた内容を MIME type に応じてパースし、得られたデータを返すことである。引数は URI であり、これは server から取得したい resource の URI になる。
リクエストを行うために、また型がきれいに扱えるように、ファイルの先頭に2つの import を追加する。1つは json モジュールの import。もう1つは pydantic から AnyUrl の import。その後 read_resource 関数に戻り、コメントと return 文をクリアする。result = await self.session.read_resource(...) を呼び出し、型を通すために引数には AnyUrl(uri) を渡す(入力の URI を AnyUrl でラップする)。
続けて、その result から result.contents[0] を取り出す。これがなぜ必要なのかを説明すると、先ほど Inspector で確認したレスポンスが、まさにこの result 変数に相当する。result は contents というリストを持ち、その中に複数の要素が入りうるが、ここでは最初の1つだけを使いたい。その最初の要素の type プロパティと mime_type を確認する。特に mime_type を見ることで、どういう種類のデータが返ってきたのかを理解できる。
もし JSON であれば、text を JSON としてパースしてその結果を返す必要がある。実装としては、if isinstance(resource.contents[0], ResourceTypeText) and resource.mime_type == "application/json":(server が「JSON を返す」と伝えてきた場合のヒント)という条件分岐を書く。この場合は json.loads(resource.text) を返す。そうでなければ(つまりこの if 文に入らず早期リターンしなかった場合)、単に resource.text を返す。この場合はプレーンテキストとして扱い、何もパースしない。これは単一ドキュメントの内容を取得するケースに相当する。
これで read_resource の実装は完了である。ここで改めて念を押しておきたいのは、MCP client の中に書いているこのコードは、コードベースの他の複数箇所から使われているという点である。コードベースのどこか別の場所が、この関数を呼び出してドキュメント名の一覧を取得し、最終的には単一ドキュメントの内容を取得してプロンプトに挿入する、という流れになる。この時点で、残りの作業はすでに終わっているため、基本的にすべてが動作するはずである。
動作確認: ターミナルに戻り、uv run main.py(uv を使っていない場合は python main.py)で CLI アプリケーションを起動する。「@ に何が入っているか」のように入力すると、resource の一覧が表示され、矢印キーでスクロールできる。使いたい resource のところで space キーを押すと、それが挿入される。「What's in the report.pdf document?」のようなメッセージを作り、送信すると即座に応答が返り、report.pdf の中身について答えてくれる。この場合、Claude はドキュメントの内容を読むために tool を使う必要が一切なかった。これで resources の説明は完了である。resources は、MCP server からある種の情報を公開するために使うものである。
Lesson: Defining prompts(promptsを定義する)
MCP server における最後の主要トピックが prompts である。resources のときと同様に、この機能を理解するためにプロジェクトへ小さな機能を追加する。
追加する機能: スラッシュコマンドのサポートを追加する。具体的には format コマンドを実装する。ユーザーが / と入力すると、アプリケーションがサポートしているコマンドの一覧が表示される(今回は format の1つのみ)。/ だけを入力すると autocomplete が現れ、選択肢は format のみになる。Format を選ぶと、続けてドキュメント ID(report.pdf などこれまで扱ってきたドキュメント名のいずれか)を入力するよう促される。このコマンドを実行すると、Claude にそのドキュメントを Markdown 構文で書き直させることが目的になる。つまり、現状 MCP server の中の各ドキュメントはただのプレーンテキストになっているが、これを Claude に渡して Markdown 構文で書き直させる。想定する出力イメージは、「ドキュメントを書き直します」というテキストがまず表示され、その後 Claude がドキュメントの内容を読むために tool を使い、最終的な応答の中でそのドキュメントの Markdown 版が返ってくる、というものである。
ここで注目したい点がある。この機能の本質的なゴール、つまり「ドキュメントを Markdown 構文に書き直す」という操作自体は、実は開発者側がコードを書かなくても実現できる操作である。どういうことかというと、ユーザーはすでに CLI を起動して「report.pdf ファイルを Markdown 構文で書き直して」のようなことを言えば、この操作を実行できる。何の問題もなくできる。Claude はドキュメントの内容を取得し、それを Markdown に書き直すという妥当な仕事をやってのける。実際、この操作は完全にうまくいく。
では、この機能で本当にやろうとしていることは何か。もしこれをユーザー任せにして、「これを Markdown に変換して」のように手入力させるだけにすると、それなりの結果は得られるかもしれないが、もし開発者側で事前にしっかり作り込まれた、この特定のシナリオに特化したプロンプトが用意されていれば、はるかに良い結果を得られる可能性がある。つまり、MCP server の開発者である私たちが、この Markdown 変換シナリオのために書き上げ、テストし、評価し、そのプロセス全体を経た、非常にしっかりしたプロンプトを用意しておければ、ユーザーはずっと満足度が高くなるだろう。繰り返しになるが、ユーザーはこのワークフロー全体を自分でも実行できる。しかし、こちら側が用意したこの手の込んだプロンプトを代わりに使えば、より良い結果が得られる。これこそが MCP server における prompts 機能の本当の目的である。
考え方としては、事前に、自分たちの server が本当に得意とする内容に特化した一連の prompts を server 内に定義しておく、というものである。今回の例では、この server はドキュメントの管理・読み取り・編集などを専門にしているので、非常に質の高い、様々なシナリオでテスト・評価済みの prompts のセットを追加することが考えられる。そうした prompts を、今作っている CLI アプリのようなあらゆる client アプリケーションから利用できるように公開できる。
ここで補足しておきたいのは、こうしたプロンプトは CLI コードベースの中に直接書き込んでも実現できるという点である。それは間違いなく可能である。しかし、ここでの考え方は、ある特定のタスクに特化した MCP server が、あらかじめ開発しておく手間をユーザー側にかけさせることなく、誰でもそのまま使える一連の prompts を公開できる、という点にある。
MCP server 内で prompt を定義する構文 は、これまで作ってきた tools や resources とよく似ている。prompt デコレータを使い、prompt に名前を付け、任意で description も付ける。client がこの prompt を要求すると、実際の user メッセージと assistant メッセージからなるメッセージのリストが返される。これらのメッセージはそのまま Claude に送ることができる。
server 側での実装: MCP server ファイルを開き、「rewriting a document in Markdown format」というコメントのところまでスクロールする。TODO を削除し、@mcp.prompt を追加し、名前を format、description を「rewrites the contents of the document in Markdown format」とする。実際の実装として format_document 関数を書き、引数として doc_id を受け取る。ここでも tool のときと同様に、任意で Field を使い description(「ID of the document to format」)を追加し、型注釈として string も明示する。
この関数からはメッセージのリストを返す。ファイルの先頭、既存の MCP server の import のすぐ下に from mcp.server.fastmcp.prompts import base という import を追加する。関数の下部で、十分にテスト・評価された prompt を定義する。あらかじめ用意しておいたこの prompt をそのまま貼り付ける。この prompt は、ドキュメント ID を受け取り、暗黙的に read_document tool を使ってそのドキュメント ID の内容を取得するよう Claude に求め、その後内容を Markdown 構文で書き直し、最後にその更新内容を server 内に保存するために編集も行うよう指示する内容になっている。
prompt を定義したら、メッセージのリストを返す。具体的には [base.UserMessage(prompt)] のように、先ほど書いた prompt を渡した base.UserMessage を1つ含むリストを返す。
MCP Inspector でのテスト: ファイルを保存し、同じコマンドで MCP development inspector を起動し、ブラウザでそのアドレスを開く。server に接続し、Prompts セクションを見つける。利用可能な prompts の一覧を表示すると、現時点では format の1つだけが存在する。format をクリックし、ドキュメント ID を入力する(例えば outlook.pdf)。Get Prompt を実行すると、事前に組み立てられたメッセージのリストが返る。ここには1つのメッセージパート(テキストパート)があり、完全な prompt のテキストの中にドキュメント ID が補間されていることが確認できる。このメッセージのリストを Claude に送れば、期待どおりの応答が得られるはずである。改めて要点をまとめると、MCP server 内で定義する prompts の狙いは、よくテストされ、よく評価され、特定のユースケースに深く特化したものにすることである。
Lesson: Prompts in the client(clientでpromptsを扱う)
最後のタスクとして、MCP client の中に機能を実装し、MCP server 内で定義されているすべての prompts を一覧表示できるようにし、さらに変数が補間された特定の prompt を取得できるようにする。
まず list_prompts を実装する。コメントを削除し、result = await self.session.list_prompts() を書き、result.prompts を返す。それだけである。
次に get_prompt を実装する。個々の prompt を取得する際には、いくつかの引数を渡すことになる。これらの引数は最終的に prompt 関数の中に現れる。例えば format_document では、ドキュメント ID を受け取ることを想定している。この args 辞書の中に document_id というキーが存在することが期待され、それが対応する関数(先ほどの format_document)に渡され、その値が prompt の中に補間される。
get_prompt 関数の中で、self.session.get_prompt(...) から結果を取得する。prompt の名前(取得したい prompt の名前)を渡し、続けて arguments を渡す。そして result.messages を返す。これが返ってくるメッセージであり、これらは何らかの会話を構成し、Claude にそのまま渡すことを意図している。client 側で必要な作業はこれだけである。
CLI での動作確認: プロジェクトに戻って再度実行する。今度は / を入力すると、format コマンドにアクセスできることが確認できる。format は、実際にはこれから呼び出す prompt の名前そのものである。それを選択して space キーを押すと、ドキュメントの中から1つを選ぶよう求められるので、plan.md を選び、Enter を押す。すると、その prompt 全体(実質的には単一の user メッセージ)がそのまま Claude に渡される。これにより Claude は、ドキュメントを Markdown 構文で書き直すという指示を受け取り、書き直す対象ドキュメントの ID も与えられる。Claude がまず行う必要があるのは、そのドキュメントの内容を取得することであり、そのために Get document tool を使う。そして最終的に、Claude は Markdown 構文が適用されたバージョンのドキュメントで応答する。ここでは、多数の Markdown 構文が含まれたドキュメントが得られる。
prompts の振り返り: この動作が問題なく機能したことを確認したうえで、prompts についての要点を整理しておく。まず、自分たちの MCP server の目的に関連性のある prompt を書き、evaluate(評価)することから始める。今回の例ではドキュメントを扱う server を作っていたので、「ドキュメントを別のスタイルで書き直す」という機能や操作は理にかなっている。prompt を組み立てたら、それを MCP server 内で定義する。すると client はいつでもその prompt を要求できるようになる。prompt を要求する際には、いくつかの引数を渡すことができ、それらは prompt 関数へキーワード引数として渡される。関数側はその引数を prompt 自体の中で利用できる。
Lesson: MCP review(MCPの復習)
プロジェクトはすべて完了したが、次に進む前に、これまで学んできた3つの server primitives(tools、resources、prompts)について簡単に復習しておく。特に強調したいのは、典型的なアプリケーションにおいて、これら3つのそれぞれを実際に「実行する」責任を負うのは誰か、つまりアプリケーションのどの部分がそれぞれを本当に動かし、そこから恩恵を受けるのは誰か、という点である。
- tools は model controlled(モデル制御)である。 つまり、ある tool をいつ実行するかを決める責任を負うのは Claude だけである。
- resources は app controlled(アプリ制御)である。 言い換えると、アプリケーション内で動くコードが、ある resource から提供されるデータが必要だと判断する。resource を実行し、返ってきたデータを何らかの形で(例えば UI 上での利用などに)活用すると決めるのは、あなたのアプリのコードである。今回の例では、resource を取得してそのデータを UI の中で使い、autocomplete の選択肢一覧を提供した。また、prompt を補強するためにも resource を取得した。これらはいずれも、開発者であるこちら側が書いたアプリケーション関連のコードである。
- prompts は user controlled(ユーザー制御)である。 つまり、prompt をいつ実行するかを決めるのはユーザーである。ユーザーは、ボタンやメニュー項目のような UI 要素をクリックすることで prompt の呼び出しを開始するかもしれないし、今回実装したようなスラッシュコマンドを使うかもしれない。
それぞれを「誰が制御しているか」を強調する理由は、それぞれの用途のイメージを持ってもらうためである。Claude に何らかの能力を追加したい場合は、おそらく MCP server の中に tools を実装するか、あるいは MCP client を通じて何らかの server の tools を利用することを検討することになる。UI 上でコンテンツを表示するなどの目的でアプリにデータを取り込みたい場合は、おそらく resource を使いたくなる。そして、何らかの定型化されたワークフローを実装したい場合は、おそらく prompts を検討することになる。
これらの考え方はすべて、公式の Claude インターフェース(Claude.ai)の中でも実例を見ることができる。メインのチャット入力欄の下に、いくつかのボタンが並んでいる。その1つをクリックし、さらにその中の例の1つをクリックすると、すぐにチャットに入る。これはユーザー制御のアクションである。ユーザー(この場合は講師自身)がこの特定のワークフローを開始することを決めており、おそらく事前に書かれ、何らかの形で最適化されている prompt を利用している。したがって、あの一連のボタンを実装するには、おそらく MCP server の中に一連の異なる prompts を用意することになるだろう。
同様に、小さなタブ(プラスボタン)をクリックすると、「Add from Google Drive」ボタンがある。これはクリックしない(内部のドキュメントが表示されてしまうため)。しかし、もしこれをクリックすると、このチャットにコンテキストとして追加できるドキュメントの一覧が表示される。ここで実際に一覧表示すべきドキュメントが何かを把握し、いずれかをクリックしたときにその内容を自動的にチャットのコンテキストへ挿入する処理は、すべてアプリケーション関連のコードである。つまり、ここに何を一覧表示するかを知っているのは完全にアプリケーション側の責任であり、まさに UI 関連の要素そのものである。したがって、Google Drive からのドキュメント一覧表示を実装するには、おそらく MCP server の中に resource を実装することを検討することになる。
最後に、このチャットに「3の2乗は?JavaScript を使って計算して送って」のようなメッセージを入力すると、明らかに Claude に何らかの JavaScript コードを実行してもらうことを期待している。これはおそらく tool の利用によって実現される。この場合、tool を使うという判断は100%モデル制御である。JavaScript の tool 実行を使うと決めたのはモデル自身である。このようなものを MCP server 内で実装するには、当然ながら tool を提供することになるだろう。
まとめると、以上が3つの異なる server primitives である。それぞれが、アプリケーション全体の異なる部分によって利用されることを意図している。tools は主にモデルに仕えるもの、resources は主にアプリに仕えるもの、prompts は主にユーザーに仕えるもの である。これらはあくまで大まかなガイドラインであり、ここで触れているのは、何かを組み立てようとする際に、どのシチュエーションでどの primitive を使うべきかの感覚をつかんでもらうためである。
章末まとめ
- resources は「MCP server からアプリへのデータ公開」を担う primitive。 個別の読み取り操作ごとに1つの resource を定義するのが基本方針。今回の例では「ドキュメント一覧(direct resource,
docs://documents,application/json)」と「単一ドキュメントの内容(templated resource,docs://documents/{doc_id},text/plain)」の2つを定義した。 - direct(static)resource は URI が固定。templated resource は URI にワイルドカード(例:
{doc_id})を含み、そのパラメータ名がそのまま関数のキーワード引数名として渡される。 - resource は関数の返り値を MCP Python SDK が自動的に文字列へシリアライズしてくれるため、開発者が明示的に JSON 文字列化する必要はない。MIME type(
application/json/text/plainなど)は client 側へのデシリアライズ方法のヒントとして重要。 - client 側の
read_resourceはsession.read_resource(AnyUrl(uri))を呼び、result.contents[0]のmime_typeを見てapplication/jsonならjson.loads()、それ以外はそのままtextを返す、という分岐で実装する。 - prompts は「MCP server からユーザーへのワークフロー公開」を担う primitive。 ユーザーが自分で同じ指示を手入力しても大枠は実現できるが、server 開発者があらかじめテスト・評価済みの高品質な prompt を用意しておくことで、より良い結果を安定して得られるようにするのが狙い。
- server 側では
@mcp.promptデコレータ+名前+description で prompt を定義し、from mcp.server.fastmcp.prompts import baseのbase.UserMessage(prompt)を含むメッセージのリストを返す。client 側はlist_prompts()で一覧取得、get_prompt(name, arguments)で名前と引数(キーワード引数として prompt 関数に渡る)を指定して具体的なメッセージを取得する。 - 3つの server primitives と、それぞれの「制御主体」:
- tools = model controlled(Claude がいつ使うか決める)
- resources = app controlled(アプリのコードがいつ・どう使うか決める。UI 表示やプロンプト補強に利用)
- prompts = user controlled(ユーザーがボタンやスラッシュコマンドなどで起動を決める)
- Claude.ai 公式アプリでの対応例: チャット下のプリセットボタン・サンプル例 → prompts、「Add from Google Drive」のドキュメント一覧・挿入 → resources、「JavaScriptで計算して」という自然文からのコード実行判断 → tools。この整理はあくまで高レベルの指針であり、何を実装する際にどの primitive を検討すべきかの目安として使うとよい。
情報源は動画の公式英語字幕(トランスクリプト)。具体例・数値・デモ手順・コード例・比喩はできる限り漏らさず反映している。技術用語(workflow, agent, parallelization, chaining, routing 等)は英語表記のまま用いる。
重要な注記: 2026-07-08〜09時点のトランスクリプト取得作業では、以下の「Anthropic apps」レッスンの冒頭で Claude Code と並んで Computer Use への言及が一度だけ出てくる以外、それ以降のレッスン(Claude Code setup 以降)で Computer Use を単独で扱う内容は確認できなかった。「Environment inspection」レッスンでは過去形で computer use の挙動を振り返る記述があるが、これは Claude Code の説明を導入するための比喩的な参照であり、Computer Use 自体の独立した解説ではない。ただし、この「レッスンが存在しない」という判定はこちらの取得作業に基づく限定的な確認であり、他の資料(LinkedIn Learning版の目次や公式サイトの紹介文)には現在も Computer Use を扱うレッスンをうかがわせる記述が見られるため、実際に削除されたと断定はできない。本ノートでは取得できたトランスクリプトの範囲でComputer Useの内容を創作・補完していないが、最新のカリキュラムは公式サイトで直接確認することを推奨する。