AI

MCP Python SDK v2 移行:FastMCPからMCPServerへの書き換え手順と据え置き判断

MCP Python SDK v2 移行:FastMCPからMCPServerへの書き換え手順と据え置き判断

MCP Python SDK v2は、PyPIのmcpパッケージ2.x系です。2026年7月28日に2.0.0が公開され、10月11日時点の最新は2.3.0です。v2ではFastMCPがMCPServerへ改名され、依存ライブラリ、トランスポート設定、型の属性名、テストの既定動作まで変わりました。この記事では、v1からの移行で手を入れる箇所を作業順に並べ、黙って壊れる変更と、mcp<2で据え置くほうがよい条件を整理します。MCPそのものの仕組みはMCP(Model Context Protocol)の標準規格としての役割で確認できます。

まとめ|v2移行の本体はimportより依存とバックチャネルの見直し

importの書き換えは数分で終わります。時間を取られるのは、httpxからhttpx2への置き換え、コンストラクタの位置引数、Streamable HTTPの4MiB上限、そしてサーバーからクライアントへの問いかけが2026-07-28仕様の接続で使えなくなる点です。最後の点はテストの既定動作も変えるため、CIが落ちて初めて気づく形になりがちです。

v1は保守モードに入りましたが、1.30.0(2026年9月7日)まで修正版が出ています。新仕様のクライアントへの対応や水平スケールの要件がなければ、mcp<2で固定して計画的に移る判断で問題ありません。逆に、pip install mcpを上限なしで書いている環境は、次のビルドでv2が入って壊れます。まず上限の有無を確認してください。

v2.0.0の公開時点とv1保守モードの範囲を確かめる版番号と依存条件

移行計画は、どの版がいつ出たかと、v1がいつまで直るかの把握から始めます。

PyPIの履歴で見るv2.0.0の公開日とv1.30.0までの保守系列

PyPIのmcpプロジェクトのリリース履歴を2026年10月11日に確認した結果です。v2系とv1系が並行して出ています。

系列 版 公開日
v2 試験版 2.0.0a1〜2.0.0rc1 6月11日〜7月27日
v2 正式版 2.0.0 7月28日
v2 更新 2.1.0/2.2.0 8月24日/9月7日
v2 最新 2.3.0 10月2日
v1 保守 1.29.0/1.30.0 7月28日/9月7日

v2.0.0のリリースノートは、v1.xを保守モードとし、今後はセキュリティ修正のみと明記しています。新機能はv2にしか入りません。2.3.0の要求Pythonは3.10以上です。

pydantic 2.12・httpx2・mcp-typesに変わった依存関係の差分

公式の移行ガイドが挙げる依存の変化のうち、解決エラーや挙動差につながるものを抜き出しました。

依存 v1(1.28.1) v2
pydantic 2.11以上3未満 2.12以上
HTTPクライアント httpx・httpx-sse httpx2
sse-starlette 1.6.1以上 3.0.0以上
型定義 mcp本体に同梱 mcp-types(同版固定)
OpenTelemetry なし opentelemetry-api必須
wsエクストラ websockets 削除

一番効くのはhttpx2です。APIは互換ですが別パッケージなので、except httpx.ConnectError:のような例外処理は一致しなくなり、エラーで落ちずに素通りします。httpx.AsyncClient(auth=provider)のように旧httpxのクライアントへSDKの認証プロバイダを渡す書き方は、TypeErrorで止まります。grepでhttpx.を洗い出し、SDKに渡す箇所から先に置き換えてください。

v1に留まる場合の上限指定とpip install mcpの既定挙動

上限なしのmcp指定は、いまインストールすると2.x系を引きます。移行を後回しにするなら、上限を入れることが最初の作業です。

# pyproject.toml(v1に留まる場合)
[project]
dependencies = [
    "mcp>=1.28,<2",
]

# pyproject.toml(v2へ移る場合・移行ガイドの推奨)
[project]
dependencies = [
    "mcp>=2,<3",
    "sse-starlette>=3",
]

v1のドキュメントは公式サイトの/v1/配下に残っています。

FastMCPからMCPServerへ書き換えるサーバー側コードの移行手順

v1の高水準API(FastMCP)で書いたサーバーは、デコレータの書き方がそのまま使えます。直すのは入口と設定の渡し方です。

importパスとコンストラクタ引数順を直すBefore/Afterのコード例

旧パスmcp.server.fastmcpは非推奨ではなく削除済みで、ModuleNotFoundErrorになります。気づきにくいのはコンストラクタです。v2の位置引数はname、title、description、instructions、website_url、icons、versionの順に変わりました。v1でFastMCP("Demo", "説明文")と書いていた2番目の値は、エラーにならずにtitleへ入ります。name以外はキーワード引数で渡します。

# v1(mcp 1.x)
from mcp.server.fastmcp import FastMCP, Context

mcp = FastMCP("inventory", "在庫数を返すサーバー", port=9000)

@mcp.tool()
async def stock(sku: str, ctx: Context) -> str:
    await ctx.info(f"lookup {sku}")
    return "12"

mcp.run(transport="streamable-http")

# v2(mcp 2.x)
from mcp.server.mcpserver import MCPServer, Context

mcp = MCPServer("inventory", instructions="在庫数を返すサーバー", version="1.4.0")

@mcp.tool()
async def stock(sku: str, ctx: Context) -> str:
    await ctx.info(f"lookup {sku}")
    return "12"

# ポートなどのトランスポート設定は run() へ渡す
mcp.run(transport="streamable-http", host="127.0.0.1", port=9000)

versionを省くと、v2では空文字になります。v1はSDKの版が自動で入っていたため、server_infoを監視やログで使っているならversionを明示します。既定のサーバー名も”FastMCP”から”mcp-server”へ変わりました。ctx.fastmcpはctx.mcp_server、FastMCPErrorはMCPServerErrorです。v1時代にFastMCPで作る手順はFastMCPでPythonのMCPサーバーを作る手順にまとめています。

host・portなどをrun()へ移すトランスポート設定の書き換え手順

v1でコンストラクタに渡していたhost、port、sse_path、json_response、stateless_httpは、run()、sse_app()、streamable_http_app()の引数へ移りました。streamable_http_app(port=...)はTypeError、mcp.settings.port = 9000のような代入はValueErrorです。mount_pathは削除されています。

ASGIアプリへマウントしている構成では、ホスト側のlifespanでmcp.session_manager.run()を開始する必要があります。lifespanが走るのはセッションごとではなく、マネージャーの起動時に限った1回だけです。v1でセッション単位の資源をlifespanで開いていたなら、ハンドラの中かexit_stackへ移します。

環境変数も読まれなくなりました。v1はMCP_で始まる環境変数と.envファイルから設定を拾っていましたが、v2ではどちらも無視されます。コンテナの環境変数でポートを渡していた場合、エラーは出ず既定値で起動します。

get_contextとclient_idの削除などContextで黙って壊れる変更

Contextの扱いは、型チェックで拾えるものと拾えないものが混ざります。移行ガイドから影響の大きい順に並べました。

  • get_context()は削除。ツール関数の引数にctx: Contextを宣言して受け取る
  • Context.client_idは削除。_metaかget_access_token()から取る
  • 同期関数のツールはワーカースレッドで動き、asyncio.get_running_loop()がRuntimeErrorになる
  • ツール内でMCPErrorを投げるとJSON-RPCエラーになる。ツールの失敗はToolErrorかis_error=Trueの結果で返す
  • ProgressContextは削除。ctx.report_progress()に絶対値を渡す

3つ目と4つ目は型では検出できません。v1では例外の種類を問わずツールエラーとして扱われていたため、エラー時の応答をテストで固定しておくと差分に気づけます。

Streamable HTTPの4MiB上限で413を返す仕様と上限の引き上げ方

v2のStreamable HTTPサーバーは、リクエスト本文が4MiBを超えるとHTTP 413で拒否します。移行ガイドはこの上限をv2の変更点に挙げており、画像やPDFをbase64でツール引数に載せる設計は、移行後に大きなファイルだけ失敗します。上限を変更するための設定項目は、run()のmax_request_body_sizeです。

# 上限を8MiBへ引き上げる例
mcp.run(transport="streamable-http", max_request_body_size=8 * 1024 * 1024)

ただし、引き上げは対症療法です。数MBのファイルを毎回JSONで運ぶなら、オブジェクトストレージのURLを引数に渡す設計へ寄せたほうが、メモリとタイムアウトの両面で扱いやすくなります。同じくローカルの127.0.0.1などで待ち受ける場合はDNSリバインディング保護が自動で有効になり、ポートなしのHostヘッダは421で拒否されます。リバースプロキシがHostを書き換える構成では、この挙動の確認が必要です。

低水準Serverとクライアント側で手作業が残る変更点の洗い出し

FastMCPを使わずmcp.server.Serverで直接書いたサーバーと、SDKをクライアントとして使うコードは、書き換え量が一段多くなります。

デコレータからon_*引数へ移る低水準Serverのハンドラ登録

v1の@server.list_tools()のようなデコレータは廃止され、コンストラクタのon_*キーワード引数で渡します。ハンドラは(ctx, params)を受け、結果型をそのまま返します。

from mcp.server import Server, ServerRequestContext
from mcp.types import CallToolRequestParams, CallToolResult, TextContent

async def handle_call_tool(ctx: ServerRequestContext, params: CallToolRequestParams) -> CallToolResult:
    # v1 の戻り値自動ラップは無い。例外も自分で is_error=True に変換する
    try:
        text = f"Called {params.name}"
        return CallToolResult(content=[TextContent(type="text", text=text)], is_error=False)
    except Exception as e:
        return CallToolResult(content=[TextContent(type="text", text=str(e))], is_error=True)

server = Server("my-server", on_call_tool=handle_call_tool)

v1ではリストや文字列を返すとSDKが結果型に包み、例外もisErrorの結果へ変えていました。v2はどちらも行いません。params.argumentsはNoneになりうるので、v1と同じ挙動にするにはparams.arguments or {}と書きます。

streamablehttp_client削除とhttpx2への接続の書き換え

クライアント側ではstreamablehttp_clientが削除され、streamable_http_clientへ変わりました。ヘッダ、タイムアウト、認証は引数ではなく、渡すhttpx2.AsyncClient側に設定します。戻り値は2要素のタプルになり、get_session_idはなくなりました。

新しく入ったClientクラスを使うと、接続、版の交渉、サーバー情報の取得を1つのオブジェクトで扱えます。既定はmode="auto"で、まず2026-07-28仕様のserver/discoverを試し、応じないサーバーには従来の初期化ハンドシェイクで接続します。タイムアウトの指定はtimedeltaではなく秒数のfloatになり、タイムアウト時のエラーはJSON-RPCの-32001(REQUEST_TIMEOUT)です。v1のHTTP 408を条件にしたリトライ処理は書き直します。

camelCaseからsnake_caseへ変わる型属性とby_alias出力の落とし穴

プロトコル型はmcp-typesパッケージへ分かれ、属性名がsnake_caseになりました。inputSchemaはinput_schema、isErrorはis_error、nextCursorはnext_cursorです。import mcp.typesは別名として残るので、import文は直さずに済みます。

見落としやすいのは自前のシリアライズです。tool.model_dump()はsnake_caseで出力され、ワイヤ形式のcamelCaseを得るにはmodel_dump(by_alias=True, mode="json")が必要です。ツール一覧をJSONでキャッシュや管理画面へ流している処理は、エラーなしでキー名だけが変わります。未知のフィールドも保持されなくなったので、独自データは_metaへ移します。

2026-07-28仕様と旧クライアントを1つのサーバーで両対応させる構成

v2のリリースノートは、2026-07-28仕様に対応し、それ以前の版のクライアントにも同じサーバーで応じると説明しています。仕様側の変更点はMCP仕様2026-07-28版の変更点と移行手順で整理しています。ここで説明する対象は、Python SDKでの具体的な動き方です。

MCP-Protocol-Versionヘッダで自動で振り分けられる2世代の経路

旧クライアント対応のドキュメントによると、streamable_http_app()はリクエストごとにMCP-Protocol-Versionヘッダを見て振り分けます。2026-07-28を名乗るリクエストは新しい経路へ、2025-11-25以前かヘッダなしの最初のinitializeはセッションを持つ従来の経路へ進みます。設定は要りません。

裏返すと、旧世代を拒否するオプションはありません。旧経路のセッションはプロセス内のdictに置かれるため、複数ワーカーで動かすならスティッキールーティングが必要です。外すと別ワーカーが404(Session not found)を返します。アイドルセッションは既定1800秒で閉じ、上限の既定10,000セッションを超えると503です。トランスポート自体の仕組みはStreamable HTTPとSSEの違いで確認できます。

ctx.elicitのNoBackChannelErrorとResolveへの置き換え

2026-07-28仕様の接続では、サーバーからクライアントへ要求を送るチャネルがありません。ツールの途中でctx.elicit()やサンプリングを呼ぶとNoBackChannelErrorになります。v2の答えがResolveで、ツール引数に付けた関数がユーザーへの問いかけを担います。v2の新機能紹介の例を日本語の説明に置き換えたものです。

from typing import Annotated
from pydantic import BaseModel
from mcp.server.mcpserver import (
    MCPServer, AcceptedElicitation, Elicit, ElicitationResult, Resolve,
)

mcp = MCPServer("Bookshop")

class Quantity(BaseModel):
    copies: int

async def ask_quantity() -> Elicit[Quantity]:
    """取り置く冊数をユーザーに尋ねる"""
    return Elicit("How many copies?", Quantity)

@mcp.tool()
async def reserve(title: str, quantity: Annotated[ElicitationResult[Quantity], Resolve(ask_quantity)]) -> str:
    """書籍を取り置く(冊数はResolveで取得)"""
    if isinstance(quantity, AcceptedElicitation):
        return f"Reserved {quantity.data.copies} of {title!r}."
    return "Nothing reserved."

同じツールが、新世代の接続では入力要求の結果を返す往復で、旧世代の接続では従来のelicitation/createで動きます。ただし旧経路をstateless_http=Trueで動かしている場合は、旧世代でも問いかけが送れず失敗します。ctx.elicit()を多用するサーバーは、移行の工数をここに見積もってください。

テストのClient(server)をmode=legacyで旧世代に固定する場面

v1のテストヘルパーcreate_connected_server_and_client_sessionは削除され、Client(server)でインメモリ接続を作ります。注意点は、このClientが既定で2026-07-28を交渉することです。v1のテストでctx.elicit()やlist_roots()を通していた箇所はNoBackChannelErrorになります。

from mcp.client import Client

async def test_stock_legacy() -> None:
    # mcp は前掲の MCPServer。旧世代の接続に固定して v1 時代と同じ経路で検証する
    async with Client(mcp, mode="legacy") as client:
        result = await client.call_tool("stock", {"sku": "A-1"})
        assert not result.is_error

新旧両方の経路をテストで通すなら、同じテストをmode="legacy"と既定の2本で回します。pytestでfilterwarnings = ["error"]を使っている場合、非推奨のRootsやSamplingの呼び出しが例外になり、表面に現れるのはツールエラーです。"default::mcp.MCPDeprecationWarning"を足すと、警告を出しつつテストは通せます。

v2へ今すぐ移行する案件とmcp<2で据え置く案件を分ける判断基準

v1が保守モードでも、全案件を今月中に上げる必要はありません。条件で分けます。

移行を先送りしない条件と新仕様クライアントや水平スケールの要件

次のどれかに当たるなら、移行を先送りしないでください。1つ目は、利用者のクライアントが2026-07-28仕様へ移りつつある場合です。v2なら1つのエンドポイントで新旧に応じられ、v1のままだと新仕様のみを話すクライアントから接続できません。2つ目は、Streamable HTTPのサーバーを複数インスタンスで動かしたい場合です。新世代の経路はセッションに依存しないため、スティッキールーティングの制約は旧クライアントの分だけに縮みます。3つ目は、mcp本体を上限なしで依存している社内パッケージを配っている場合です。利用者の環境で勝手にv2が入るので、先にv2対応版を出すか上限を入れた版を出します。

mcp<2で据え置くほうが正解になる条件と見送りの判断基準

次の条件では、据え置きを選びます。第1に、stdioでローカル起動するツールを社内に配るだけの用途です。クライアントがv1系のプロトコルで話す限り、v2の利得はほぼありません。第2に、ctx.elicit()やサンプリングを多くのツールで使い、Resolveへの書き換えとテスト整備の工数を今期に取れない場合です。第3に、httpxの例外やクライアントを自前のリトライ処理やOAuth処理と深く結んでいる場合です。httpx2への置き換えは素通りの不具合を生むため、まとまった検証期間を確保してから着手します。

据え置く場合でも、v1は新機能を受け取りません。四半期以内の移行計画を立て、セキュリティ修正の版だけを取り込みます。なお、独立系のFastMCP(PyPIのfastmcp)は公式SDKとは別のパッケージです。そちらの移行はFastMCP 4のsessionless対応と3系からの移行判断で扱っています。TypeScriptと並行して移すならMCP TypeScript SDK v2の移行要点と比べてください。

MCPサーバー移行を外部へ任せる前にelicit利用数とデプロイ構成を棚卸しする範囲

移行を外部へ出すとき、見積りがぶれる原因は2つです。1つはctx.elicit()、サンプリング、rootsを使うツールの本数で、ここがResolveの書き換え量を決めます。もう1つはデプロイ構成で、複数ワーカーか、マウント型か、リバースプロキシがHostを書き換えるかによって確認項目が変わります。作業範囲を固定するには、この2点を棚卸ししてから依頼する方法が有効です。エージェント基盤ごと見直すなら、AIエージェント開発のように、MCPサーバーの設計から運用まで任せられる体制と社内で持つ範囲を先に分けておきます。

よくある質問

MCP Python SDK v2への移行でよく問われる点を、PyPIのメタデータと公式ドキュメントに沿って答えます。

MCP Python SDK v1はいつまで使えますか?

終了日は示されていません。v2.0.0のリリースノートでv1.xは保守モードとされ、今後はセキュリティ修正のみを受けます。1.30.0がリリースされた日付は2026年9月7日です。使い続けるなら依存をmcp<2で固定し、新機能や2026-07-28仕様への対応が必要になった時点でv2へ移ります。

FastMCPのコードはそのまま動きますか?

そのままでは動きません。mcp.server.fastmcpは削除され、ModuleNotFoundErrorになります。from mcp.server.mcpserver import MCPServerへ直せば、@mcp.tool()などのデコレータは同じ書き方で使えます。コンストラクタの2番目以降の位置引数と、host・portの渡し先は直してください。

v2はPythonのどのバージョンで動きますか?

2026年10月11日時点の最新版2.3.0は、PyPIのメタデータで要求PythonがPython 3.10以上です。Python 3.14ではanyioとstarletteの下限が一段上がります。依存にはpydantic 2.12以上とhttpx2が入るため、Pythonの版よりも、既存のpydanticやhttpxとの共存のほうが先に問題になります。

v2に上げると旧クライアントは接続できなくなりますか?

接続できます。streamable_http_app()はリクエストのMCP-Protocol-Versionヘッダで振り分け、ヘッダがないinitializeは従来の経路で処理します。旧世代だけを拒否する設定はありません。旧経路のセッションはプロセス内に保持されるので、複数ワーカーではスティッキールーティングが必要です。

移行後にテストが落ちる原因で多いものは何ですか?

既定の交渉先が変わったことです。Client(server)は既定で2026-07-28仕様を話すため、ctx.elicit()やroots、サンプリングを通すテストがNoBackChannelErrorで失敗します。旧動作を確かめるテストはmode="legacy"で固定し、新仕様側はResolveへ書き換えて別に検証します。

関連記事

お気に入りに入れた記事の一覧

この記事は以下の記事からリンクされています

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.09 テックブログ IDCFクラウド(IDCフロンティア)不正アクセス・ランサムウェア:影響先・復旧・データは戻るか
  2. 2026.10.09 テックブログ ニッスイのサイバー攻撃で日水物流の入出荷停止|委託先クラウド障害に荷主が備える手順
  3. 2026.07.15 コラム LINEミニアプリとは?未認証と認証済みの違い・LIFFとの使い分け・審査と開発費用【2026年版】
  4. 2026.07.23 コラム クラウド販売管理システムとは?オンプレとの違い・料金相場と5年総額の比べ方【2026年】
  5. 2026.10.09 テックブログ 京王電鉄のランサムウェア被害とグループ共通基盤:決済・ポイント・予約が止まった範囲と遮断の初動

RELATED POSTS 関連記事

目次