最初のステップ
トップページ は駆け足です。サーバーを書き、実行し、ツールを呼び出します。
このページではじっくり進めます。サーバーが公開できる 3 種類のものをすべて取り上げ、途中で出てくるものすべてに名前を付けていきます。
ホスト、クライアント、サーバー
ここから先、どのページにも登場する言葉が 3 つあります。
- ホストは LLM アプリケーションです。Claude、IDE、エージェントランタイムなどがこれにあたります。ユーザーが対話している相手です。
- クライアントはホストの中にあり、MCP を話します。ホストは、接続するサーバーごとにクライアントを 1 つずつ動かします。
- サーバーは、この SDK で作るものです。クライアントに対して何かを公開します。モデルと直接やり取りすることは決してありません。
自分で書くのはサーバーです。ホストは別の誰かが作る製品です。SDK には Client も用意されています。サーバーのテストに使うもので、このページの後半にも登場します。
3 つのプリミティブ
サーバーが公開するものは、ちょうど 3 種類です。それらを分けるのは、誰が使うと決めるのかという点です。
| プリミティブ | 制御する主体 | どんなものか | 例 |
|---|---|---|---|
| ツール | モデル | アクションを起こすためにモデルが呼び出す関数 | API 呼び出し、データベースへの書き込み |
| リソース | アプリケーション | ホストがモデルのコンテキストに読み込むデータ | ファイルの内容、API のレスポンス |
| プロンプト | ユーザー | ユーザーが名前で呼び出す、再利用可能なメッセージテンプレート | スラッシュコマンド、メニュー項目 |
「制御する主体」こそが、この区分の核心です。ツールが実行されるのは、モデルが呼び出すと決めたからです。リソースが添付されるのは、アプリケーションがモデルに必要だと判断したからです。プロンプトが実行されるのは、ユーザーが選んだからです。
Info
Web API を作ったことがあれば、勘どころはもうほとんどつかめています。リソースは GET(データを読み込み、何も変更しない)で、ツールは POST(処理を行い、副作用を持つことがある)です。プロンプトに HTTP の対応物はありません。ユーザーが名前を指定して実行する、保存済みのクエリに近いものです。
1 つのサーバーで 3 つすべて
from mcp.server import MCPServer
mcp = MCPServer("Demo")
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers."""
return a + b
@mcp.resource("greeting://{name}")
def greeting(name: str) -> str:
"""Greet someone by name."""
return f"Hello, {name}!"
@mcp.prompt()
def summarize(text: str) -> str:
"""Summarize a piece of text in one sentence."""
return f"Summarize the following text in one sentence:\n\n{text}"
ごく普通の関数が 3 つ、デコレーターが 3 つです。どのデコレーターも、それだけで登録が完結します。
@mcp.tool()はaddをツールにします。@mcp.resource("greeting://{name}")はgreetingをリソーステンプレートにします。URI の中の{name}が関数のパラメーターです。@mcp.prompt()はsummarizeをプロンプトにします。返した文字列がユーザーメッセージになります。
それ以外のもの(名前、説明、引数のスキーマ)は、SDK が関数そのものから読み取ります。関数名、docstring、型ヒントからです。どれも別途宣言してはいません。
Tip
SDK の 2 つの半分には、インポートパスも 2 つあります。from mcp import Client と from mcp.server import MCPServer です。from mcp import MCPServer はありません。
試してみる
MCP Inspector で実行してください。
uv run mcp dev server.py
出力された URL を開いてください。Inspector にはプリミティブごとにタブが 1 つずつあります。順に見ていきましょう。
Tools タブには項目が 1 つあります。add で、説明は Add two numbers. です。フォームには必須の整数フィールドが 2 つあり、1 つは a 用、もう 1 つは b 用です。値を入力して呼び出すと、結果は 3 です。Inspector はこのフォームを a: int, b: int から組み立てました。ほかのどのクライアントも同じことをします。
Resources タブでは、Resources の一覧は空です。greeting は Resource Templates の下にあります。greeting://{name} にはパラメーターがあり、誰かが name を指定するまでは一覧に載せられる単体のリソースが存在しないからです。World を指定して読み取ると、こう返ってきます。
Hello, World!
Prompts タブにも項目が 1 つあります。summarize で、必須の引数 text を 1 つだけ取ります。適当なテキストを渡して取得すると、role: user を持ち、レンダリングされた文字列を内容とするメッセージが 1 つ返ってきます。プロンプトとはそれだけのものです。メッセージを組み立てる関数にすぎません。
Inspector はサーバーを stdio で実行しました。MCP サーバーが話せるトランスポートの 1 つです。トランスポートを選ぶのはまだ先で、そのためのページが サーバーの実行 です。
ケイパビリティ
Inspector にはタブが 3 つありました。3 つあると、どうやってわかったのでしょうか。
クライアントが接続すると、サーバーは自身のケイパビリティを宣言します。どの系統のリクエストに応答するか、ということです。クライアントはこの宣言をもとに、そもそも何を要求するかを決めます。この宣言を自分で書いてはいません。MCPServer が代わりに宣言します。
自分の目で確かめてみましょう。SDK の Client はサーバーオブジェクトをそのまま受け取り、インメモリで接続します(サブプロセスもポートも使いません)。
import asyncio
from mcp import Client
from server import mcp
async def main() -> None:
async with Client(mcp) as client:
print(client.server_capabilities.model_dump(exclude_none=True))
asyncio.run(main())
{'prompts': {'list_changed': True}, 'resources': {'subscribe': True, 'list_changed': True}, 'tools': {'list_changed': True}}
この辞書が、サーバーが宣言したケイパビリティです。接続してくるどのクライアントも、最初にこれを知ります。
| ケイパビリティ | クライアントが呼び出せるようになるもの |
|---|---|
tools |
tools/list, tools/call |
resources |
resources/list, resources/templates/list, resources/read |
prompts |
prompts/list, prompts/get |
MCPServer は 3 つのプリミティブすべてを提供するので、3 つとも常に宣言されます。
ここにないものにも注目してください。completions(リソーステンプレートとプロンプトの引数の自動補完)には自分で書くハンドラーが必要ですが、このサーバーにはありません。そのためこのケイパビリティは宣言されず、行儀のよいクライアントなら要求もしません。オプションのものはすべてこのルールに従います。登録すればケイパビリティが現れます。補完 のページがそれを実証しています。
Info
Client(mcp) は、このドキュメントのすべてのサンプルをテストしているのと同じインメモリクライアントで、自分のサーバーをテストするときにもこれを使います。まるごと 1 ページを割いています。テスト です。
書かなかったもの
このページを振り返ってみてください。書いたのは小さな Python 関数 3 つです。次のものは書いていません。
- JSON Schema。
a: int, b: intがそのままaddのスキーマです。 - リクエストハンドラー。
tools/list、resources/read、prompts/getは、すべて代わりに処理されます。 - ケイパビリティの宣言。
MCPServerが代わりに作りました。 - プロトコルのコードを 1 行も。バージョンのネゴシエーション、JSON-RPC のフレーミング、ケイパビリティの交換は、すべて
mcp devとClient(mcp)の内部で行われ、目にすることはありませんでした。
この比率こそが、この SDK の存在意義です。
まとめ
- ホストは LLM アプリ、クライアントはそのうち MCP を話す部分、サーバーは自分で作るものです。
- ツールはモデルが、リソースはアプリケーションが、プロンプトはユーザーが制御します。
- デコレーターはプリミティブごとに 1 つです。
@mcp.tool()、@mcp.resource(uri)、@mcp.prompt()。名前、説明、スキーマは関数から取られます。 {param}を含む URI はリソーステンプレートを作り、具体的なリソースとは別に一覧表示されます。- サーバーのケイパビリティは代わりに宣言され、クライアントはサーバーが宣言したものだけを要求します。
Client(mcp)はサーバーオブジェクトにインメモリで接続します。初日から使えるテストハーネスです。
次は 実際のホストに接続する です。このサーバーを Claude Desktop や IDE の中で、本当に動かします。その次は テスト です。1 ページ、インメモリクライアント 1 つで、動くかどうかを当て推量することはもうありません。そのあとは各プリミティブに専用のページがあり、まずはモデルが動かすもの、ツール から始まります。