Python MCP Server:公式 SDK v2 で MCP サーバーを作る(2026)
公式 mcp SDK v2 で Python MCP Server を構築する方法。uv でのセットアップ、ツール・リソース・プロンプト、型付き引数、非同期、Streamable HTTP、pytest、uvx での配布まで解説します。
Python MCP Server を作るには、uv add "mcp[cli]" で公式 SDK をインストールし、MCPServer を作成して、普通の Python 関数に @mcp.tool()、@mcp.resource()、@mcp.prompt() を付けるだけです。型ヒントがそのまま入力スキーマになります。mcp.run() はデフォルトで stdio を、mcp.run(transport="streamable-http") は HTTP を提供します。テストは Client(mcp) でインメモリに行い、ユーザーが uvx で実行できるパッケージとして配布します。
以下の内容はすべて、2026 年 10 月時点の最新安定版である mcp 2.3.0 で実際に動かして確認しています。2026-07-28 のプロトコル改訂版に対応しつつ、古いクライアントにも引き続き応答します。以前のチュートリアルから来た方にとって最も目立つ変更は、v1 の FastMCP クラスが MCPServer になったことです。
公式 SDK と FastMCP、どちらを使うべき?
似たデコレーターを使う Python ライブラリが 2 つあり、ネット上の記事の多くはこれを混同しています。
公式 SDK(mcp) | スタンドアロン版 FastMCP(fastmcp) | |
|---|---|---|
| メンテナー | Model Context Protocol プロジェクト | Prefect |
| サーバークラス | from mcp.server import MCPServer | from fastmcp import FastMCP |
| ツールデコレーター | @mcp.tool() | @mcp.tool |
| HTTP トランスポート名 | "streamable-http" | "http"(または "streamable-http") |
| 現在のバージョン | 2.3.0 | 4.1.0(mcp 2.x ベース) |
| 追加機能 | コアプロトコル、mcp CLI | 認証プロバイダー、サーバー合成、Apps、独自 CLI |
混乱の原因は経緯にあります。FastMCP 1.0 は 2024 年に mcp.server.fastmcp.FastMCP として公式 SDK に取り込まれました。その後スタンドアロン版は独自に開発が続き、SDK v2 では組み込みクラスが MCPServer に改名されたため、from mcp.server.fastmcp import FastMCP は現在 ModuleNotFoundError になります。本ガイドでは公式 SDK を使います。スタンドアロン版 FastMCP を選ぶ場合も考え方は同じですが、そちらのドキュメントに従ってください。
ステップ 1:uv でプロジェクトを作る
Python 3.11 以上(SDK ドキュメントによる)と uv が必要です。
uv init --package pypi-info
cd pypi-info
uv add "mcp[cli]" httpx
--package を付けると src/pypi_info/ 構成と [project.scripts] エントリが作られます。これは後で uvx を使うときに必要です。cli エクストラで mcp コマンド(mcp dev、mcp run、mcp install)が追加されます。サンプルサーバーは PyPI JSON API を呼ぶだけなので、API キーは不要です。
ステップ 2:サーバーを書く
src/pypi_info/server.py を作成します。
from typing import Annotated
import httpx
from pydantic import BaseModel, Field
from mcp.server import MCPServer
from mcp.server.mcpserver.exceptions import ToolError
mcp = MCPServer("pypi-info")
class PackageInfo(BaseModel):
name: str
version: str
summary: str | None
requires_python: str | None
recent_versions: list[str] = []
@mcp.tool()
async def get_package(
name: Annotated[str, Field(description="Exact PyPI project name, e.g. 'httpx'")],
include_versions: Annotated[int, Field(ge=0, le=20, description="How many recent releases to list")] = 0,
) -> PackageInfo:
"""Get the latest version, summary and Python requirement of a PyPI package. Use before pinning a dependency."""
async with httpx.AsyncClient(timeout=10) as client:
r = await client.get(f"https://pypi.org/pypi/{name}/json")
if r.status_code == 404:
raise ToolError(f"No PyPI project named {name!r}. Check the spelling.")
r.raise_for_status()
data = r.json()
info = data["info"]
return PackageInfo(
name=info["name"],
version=info["version"],
summary=info["summary"],
requires_python=info["requires_python"],
recent_versions=list(data["releases"])[-include_versions:] if include_versions else [],
)
@mcp.resource("pypi://about")
def about() -> str:
"""What this server does and where its data comes from."""
return "Package metadata comes from the public PyPI JSON API."
@mcp.prompt()
def compare(first: str, second: str) -> str:
"""Compare two PyPI packages for a new project."""
return (
f"Use the get_package tool on {first} and {second}, then compare them on "
"maintenance, Python support and scope. Recommend one."
)
def main() -> None:
mcp.run()
if __name__ == "__main__":
main()
SDK はこのファイルから次の情報を読み取ります。
- 名前と説明は関数名と docstring から取られます。docstring はモデル向けに書きましょう。ツールが何をするのか、いつ使うべきかを書きます。
- 型付き引数:
Annotated[..., Field(...)]で引数ごとの説明と制約を追加できます。ge=0, le=20はスキーマのminimum/maximumになり、include_versions=999を渡す呼び出しは関数が実行される前に拒否されます。モデルはそのエラーを見て再試行します。Literal[...]で列挙型を、Pydantic モデルの引数でネストしたオブジェクトを表現できます。 - 構造化出力:Pydantic モデルを返すと、ツールに出力スキーマが付きます。クライアントは読みやすいテキスト(
content)と型付きのstructured_contentの両方を受け取ります。 - 非同期:I/O を行うなら
async defを使います。普通のdefのツールも動作し、SDK がワーカースレッドで実行するのでサーバーをブロックしません。 - エラー:モデルが対処できるメッセージ付きで
ToolErrorを送出します。クラッシュではなくis_errorの結果として返ります。
ツールはモデルが制御し、リソースはアプリケーションが制御するデータ、プロンプトはユーザーが選ぶテンプレートです。使い分けは MCP のリソースとツールの違いを参照してください。
ツールで進捗を報告したり、サーバー自身のリソースを読んだりしたい場合は、ctx: Context 引数(from mcp.server.mcpserver import Context)を宣言し、await ctx.report_progress(...) や await ctx.read_resource(...) を呼びます。この引数は SDK が自動で渡すもので、モデルからは見えません。
ステップ 3:起動して確認する
uv run mcp dev src/pypi_info/server.py
これで MCP Inspector 上でサーバーが起動します(PATH に npx が必要です)。表示された URL を開き、Tools タブで get_package を呼び、Resources で pypi://about を読み、Prompts で compare を表示してみましょう。
stdio では stdout がプロトコルの通信路です。stderr に書き出す logging モジュールを使い、print() は使わないでください。
ステップ 4:Streamable HTTP で公開する
ローカルのクライアントは stdio でファイルを起動します。コードを持っていない人にもサーバーを使ってもらうには、URL で公開します。
uv run mcp run src/pypi_info/server.py --transport streamable-http
コードで指定することもできます:mcp.run(transport="streamable-http", port=8000)。host、port、streamable_http_path(デフォルトは /mcp)、json_response などのオプションは MCPServer のコンストラクタではなく run() に渡します。クライアントは http://127.0.0.1:8000/mcp に接続します。
実際のホスト名の背後にデプロイする前に、知っておくべきことが 1 つあります。アプリは DNS リバインディング対策を有効にしており、許可リストを渡すまでは localhost の Host ヘッダーしか受け付けません。許可リストがないと、すべてのリクエストが 421 Misdirected Request になります。
from mcp.server.transport_security import TransportSecuritySettings
security = TransportSecuritySettings(
allowed_hosts=["mcp.example.com", "mcp.example.com:*"],
allowed_origins=["https://app.example.com"],
)
app = mcp.streamable_http_app(transport_security=security)
# run with: uvicorn pypi_info.server:app --workers 4
最新の(2026-07-28)クライアントはセッションを持たないため、スティッキーセッションなしでどのワーカーでもどのリクエストも処理できます。ホスティング、OAuth、スケーリングについては MCP サーバーのデプロイ方法で解説しています。
ステップ 5:pytest でテストする
Client はサーバーオブジェクトにインメモリで接続できます。サブプロセスもポートも不要です。
uv add --dev pytest anyio
# tests/test_server.py
import pytest
from mcp import Client
from pypi_info.server import mcp
@pytest.fixture
def anyio_backend():
return "asyncio"
@pytest.mark.anyio
async def test_get_package():
async with Client(mcp, raise_exceptions=True) as client:
result = await client.call_tool("get_package", {"name": "httpx"})
assert not result.is_error
assert result.structured_content["name"] == "httpx"
@pytest.mark.anyio
async def test_unknown_package_is_tool_error():
async with Client(mcp) as client:
result = await client.call_tool("get_package", {"name": "no-such-package-xyz-123"})
assert result.is_error
uv run pytest を実行します。raise_exceptions=True を付けると、リモートの呼び出し元が受け取る汎用メッセージではなく、サーバー側の実際のエラーがテストで見えます。同じ Client を "http://localhost:8000/mcp" に向ければ、HTTP サーバーのスモークテストにもなります。
ステップ 6:クライアントに接続する
ローカルのクライアントはどれも同じ起動コマンドを使うので、絶対パスを指定してください。
claude mcp add pypi-info -- uv run --directory /absolute/path/to/pypi-info pypi-info
Claude Desktop なら、uv run mcp install src/pypi_info/server.py が設定エントリを書き込んでくれます。環境変数を渡すには -v API_KEY=... または -f .env を追加します。Cursor と VS Code はそれぞれの JSON ファイルで同じ command と args を使います。詳しくは Claude Desktop、Cursor、VS Code の各ガイドを参照してください。
ステップ 7:uvx 向けにパッケージ化する
uv init --package がコンソールスクリプトを作成しているので、それを main に向けます。
[project.scripts]
pypi-info = "pypi_info.server:main"
ユーザーと同じ方法で wheel をビルドして試します。
uv build
npx @modelcontextprotocol/inspector --cli uvx --from ./dist/pypi_info-0.1.0-py3-none-any.whl pypi-info --method tools/list
uv publish で公開します。これで誰でも uvx pypi-info で実行でき、クライアント設定は "command": "uvx", "args": ["pypi-info"] になります。公式 MCP Registry に掲載するには、README に mcp-name: io.github.your-username/pypi-info のような行を追加します(HTML コメント内でも構いません)。Registry はこれで PyPI パッケージの所有者を確認します。その後、MCP サーバーの作り方で説明しているとおり mcp-publisher init、login、publish を実行します。
よくある間違い
mcp.server.fastmcpをインポートする:このパスは v2 で削除されました。from mcp.server import MCPServerを使うか、まだ移行できない場合はmcp<2に固定してください。MCPServer(...)にport=を渡す:TypeErrorになります。トランスポートのオプションはrun()に渡します。- クライアント設定で相対パスを使う:クライアントは自分の作業ディレクトリからサーバーを起動します。
- 新規に SSE サーバーを作る:
transport="sse"は古いクライアント向けにまだ動きますが、2025-03-26 以降は置き換えられています。Streamable HTTP で構築しましょう(理由)。
次のステップ
- 人気の Python サーバーがどう作られているかを MCP サーバーディレクトリで見てみましょう。例えば Serena や Postgres MCP Pro です。
- データを書き込むツールを公開する前に、MCP セキュリティのベストプラクティスを読んでおきましょう。
- 公開できたら、MCP.so に登録してください。
よくある質問
FastMCP と公式 MCP Python SDK は同じものですか?
- いいえ。FastMCP 1.0 は 2024 年に公式 SDK に取り込まれましたが、Prefect が開発するスタンドアロン版の fastmcp パッケージは現在別プロジェクトです。公式 SDK v2 では組み込みクラスは MCPServer という名前で、from mcp.server import MCPServer でインポートします。
Python の MCP サーバーはどうやって起動しますか?
- Claude Code や Cursor などのローカルクライアントが使う stdio なら mcp.run() を、HTTP なら mcp.run(transport='streamable-http') を呼ぶと 8000 番ポートの /mcp で公開されます。開発中は uv run mcp dev server.py で MCP Inspector 上に起動できます。
Python の MCP サーバーを uvx で実行できるように配布するには?
- mcp.run() を呼ぶ関数を指す [project.scripts] エントリを追加し、uv build でビルドして uv publish で公開します。ユーザーは uvx とパッケージ名で起動でき、クライアント設定では command に uvx、引数にパッケージ名を指定します。
MCP Python SDK にはどの Python バージョンが必要ですか?
- 公式 SDK v2 のドキュメントは Python 3.11 以上を対象としています。uv add 'mcp[cli]' または pip install 'mcp[cli]' でインストールすると、SDK と mcp コマンドラインツールが入ります。