9月12日、xbillが「FastMCP Is Now MCPServer: Migrating a Python MCP Server to the MCP SDK 2.x」と題した記事を公開した。requirements.txtにバージョン指定なしでmcpと書いているだけで、ある日突然サーバーが起動しなくなる——この記事はその実体験から始まり、Python MCP ServerをMCP Python SDK 1.x(FastMCP)から2.x(MCPServer)へ移行するステップバイステップの手順を詳しく紹介している。
バージョン固定なしのpip installが引き起こした問題
requirements.txtにmcpとだけ書かれていて、バージョン指定がなかった。新規インストール時に2.x系が解決されてしまい、サーバーがインポートに失敗した。
python3 -c "from mcp.server.fastmcp import FastMCP"
ModuleNotFoundError: No module named 'mcp.server.fastmcp'.
This is mcp 2.x, where FastMCP was renamed to MCPServer
(from mcp.server.mcpserver import MCPServer) and other APIs changed;
see the migration guide at https://py.sdk.modelcontextprotocol.io/v2/migration/#fastmcp-renamed-to-mcpserver
or pin 'mcp<2' to keep running v1 code.
エラーメッセージが丁寧で、mcp/server/fastmcp.pyというファイル自体は2.xにも存在し、その唯一の役割がこのエラーを上げることだ。「モジュールが見つからない」で終わらずに移行先まで教えてくれる設計は評価できる。
対処は「固定」か「移行」か
2つの選択肢がある。mcp<2で固定するか、2.xに移行するかだ。固定も正当な選択だが、仮想環境を使わずシステムPythonに直接インストールする構成では、1プロジェクトのpinが他すべてのプロジェクトに影響する。xbillはリポジトリの中で変更を完結させるため移行を選んだ。
※編集部の考察:v1.x系がクリティカルなバグ修正やセキュリティパッチを受け続けているかどうかは元記事に明示されていない。固定を選ぶ前に公式リリースノートでメンテナンス状況を確認することを推奨する。
2.xで何が変わったか
移行に際して押さえるべき変更点は次の通りだ。
mcpがhttpxを依存として含まなくなった。 2.xはhttpxの代わりにフォーク版のhttpx2に依存している。httpx2はAnyIOとの互換性をはじめとする非同期ランタイムとの協調動作を改善するために派生したプロジェクトだ。import httpxしているサーバーがrequirements.txtにhttpxを独立して宣言していなければ、ModuleNotFoundErrorが発生する。エラーのトレースバックにmcpは一切登場しないため原因の特定が難しい。今すぐ宣言を追加すること。
同期ハンドラーがワーカースレッドで動くようになった。 1.xではdefで定義したツールはイベントループ上でインラインに実行されていたため、ブロッキング呼び出しがサーバー全体を止めていた。2.xではワーカースレッドで実行されるので、同期ツールは自動的に並行実行の恩恵を受ける。ただしdefハンドラー内でasyncio.get_running_loop()を呼んでいるコードは壊れる。
バージョン未指定サーバーの挙動変更。 1.xでは未バージョンのサーバーがmcpのインストール済みバージョンを自分のバージョンとして返していたが、2.xは空文字列を返す。動作自体は壊れないが、ハンドシェイクログに現れる。
変わらない点も多い。@mcp.tool()、@mcp.resource()、@mcp.prompt()の引数とハンドラーシグネチャ、list_tools()やlist_resources()の戻り値、lifespan=の挙動はそのままだ。ツール本体のコードは一切変更不要。
移行の実作業
Step 1 — 影響範囲を把握する
grep -c "^@mcp\.\(tool\|resource\)" server.py
grep -A1 "^@mcp\." server.py | grep -c "^def"
grep -n "get_running_loop\|asyncio.run(" server.py || echo "(no matches)"
grep -n "^import httpx" server.py; grep -n "^httpx" requirements.txt
コンストラクタの引数変更にも注意が必要だ。2.xはコンストラクタの位置引数にtitleとdescriptionを追加している。 FastMCP("Demo", "You answer questions…")という書き方は2.xでも動くが、2番目の文字列がtitleとして解釈されてしまい、モデルへの指示として届かなくなる。名前だけを位置引数で渡し、それ以外はキーワード引数にすること。
Step 2 — リネームする
変更箇所はここだけだ。
-from mcp.server.fastmcp import FastMCP
+from mcp.server.mcpserver import MCPServer
-mcp = FastMCP("Self-Hosted vLLM DevOps Agent")
+mcp = MCPServer("Self-Hosted vLLM DevOps Agent")
ruffを使っている場合は注意。 importの行だけ先に書き換えると、MCPServerが未使用としてruffに削除される。importと使用箇所を同時に変更するか、使用箇所を先に書き換えること。
Step 3 — バージョンの下限を明示する
-mcp
+mcp>=2
mcp.server.mcpserverをインポートするコードは1.xでは動かないので、依存に明示する。mcp>=2,<3とキャップすればより安全だ。
Step 4 — httpxを明示的に宣言する
2.xはhttpxを推移的依存として提供しなくなったため、import httpxしているコードがある場合はrequirements.txtに直接追加する。
+httpx
Step 1のgrepでhttpxの使用箇所が検出された場合は必須の対応だ。
Step 5 — プロトコルレベルで動作確認する
ユニットテストはPythonを呼ぶが、MCPクライアントはstdio越しにJSON-RPCで通信する。以下のコマンドで手動確認できる。**sleepでstdinを開いたままにするのがポイントで**、単純なパイプではサーバーが入力終端を検知してinitializeへの応答だけ返して終了してしまう。
{ printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"probe","version":"0"}}}' \
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \
'{"jsonrpc":"2.0","id":3,"method":"resources/list","params":{}}'; sleep 5; } \
| python3 server.py 2>/dev/null
このスニペットは「サーバーが壊れているのか、クライアントの設定が壊れているのか」を切り分ける最速の手段だと記事では説明している。
移行の全体像
今回の移行で実際に変更したのは実質2行(importとクラス名)だ。それ以外のツール定義、リソース定義、mcp.run()の呼び出しはすべてそのままだった。httpxの明示宣言とバージョン下限の追加を含めても、差分は4行に収まる。MCPサーバーを構築しているエンジニアにとって今後requirements.txtにバージョン指定なしでmcpと書くことのリスクが明確になった事例であり、同様の問題は他のSDKでも起こりうる。依存ライブラリのメジャーバージョンアップに備えてpip install時のバージョン境界を習慣的に明示しておくことが、最も低コストな予防策だ。
公式の移行ガイドはhttps://py.sdk.modelcontextprotocol.io/v2/migration/で参照できる。
詳細はFastMCP Is Now MCPServer: Migrating a Python MCP Server to the MCP SDK 2.xを参照していただきたい。