MCP.so
ログイン
ブログに戻る

Python MCP Server:公式 SDK v2 で MCP サーバーを作る(2026)

2026年10月15日MCP.so TeamMCP.so Team

公式 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 MCPServerfrom fastmcp import FastMCP
ツールデコレーター@mcp.tool()@mcp.tool
HTTP トランスポート名"streamable-http""http"(または "streamable-http")
現在のバージョン2.3.04.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 で構築しましょう(理由)。

次のステップ

よくある質問

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 コマンドラインツールが入ります。