v2 の新機能
v2 では 2 つのことが同時に起こりました。1 つは SDK の再構築です。クライアントとサーバーの両方の下に新しいエンジンが入り、第一級の Client が加わり、v1 のコードベースが最初のインポートでぶつかる一連の名前変更があります。もう 1 つはプロトコルの移行です。v2 が話すのは MCP の 2026-07-28 リビジョンで、このリビジョンは接続のハンドシェイク、セッション、そしてサーバー起点のリクエストをすべて取り除きます。それでも、すでに使われているクライアントを置き去りにはしません。
このページはその両方を巡るツアーです。見出しごとに 1 つのセクションを設け、それぞれの最後にそのトピックを扱うページを示します。移植の手順書ではありません。それは移行ガイドの役目で、すべての破壊的変更を変更前と変更後のコード付きで載せています。
v2 が安定版の系列
pip install mcp は 2.x をインストールします。コピーしてそのまま貼り付けられるインストールコマンドはインストールにあります。v2 で何かが壊れたり、意外な動きをしたり、作業の妨げになったりしたら、知らせてください。
SDK:v1 から v2 へ
FastMCP は MCPServer になった
高レベルのサーバークラスは名前が変わり、モジュールも一緒に変わりました。古いインポートパスは非推奨になったのではなく削除されたので、どの v1 サーバーも最初にここでつまずきます。
from mcp.server import MCPServer # v1: from mcp.server.fastmcp import FastMCP
mcp = MCPServer("Demo") # v1: FastMCP("Demo")
デコレーターで組み立てたサーバーなら、移植作業の大半もこれで終わりです。@mcp.tool()、@mcp.resource()、@mcp.prompt() は v1 で受け付けていたものをそのまま受け付け(@mcp.resource() には省略可能な security= キーワードが 1 つ加わりました)、入力スキーマも引き続き型ヒントから作られます。周辺の変更は次のとおりです。mcp.server.fastmcp.* の下にあったものはすべて mcp.server.mcpserver.* の下に移りました。ctx.fastmcp は ctx.mcp_server になり、get_context() は削除されました(代わりに ctx: Context パラメーターを宣言してください)。例外の基底クラス FastMCPError は MCPServerError です。インポートの対応表は移行ガイドにあります。
Resolve:ユーザーに入力を求める新しい方法
ツールが必要とするものを、すべてモデルから受け取るべきとは限りません。v2 の新機能として、Resolve(fn) で注釈したツールのパラメーターは、代わりに自分で書いた関数によってモデルからは見えない形で埋められます。その関数は Elicit(...) を返して、ユーザーに質問を提示できます。呼び出しの途中でクライアントから何かを得るには、これが推奨の方法です。SDK は接続が対応している仕組みに乗せて質問を運びます。レガシークライアントにはその場で送るエリシテーション(elicitation)リクエスト、2026-07-28 ではマルチラウンドトリップ(multi-round-trip)です。そのため、1 つのツール本体で両方の世代に対応できます。詳しくは依存関係を参照してください。
Note
必要なときのために、ほかの 2 つの形も残っています。ctx.elicit() はレガシー接続のクライアントに対して引き続き動作します(エリシテーション)。また、ハンドラーが自分で InputRequiredResult を返してラウンドを手動で進めることもでき、2026-07-28 でサンプリングやルート(roots)のリクエストが運ばれるのもこの方法です(マルチラウンドトリップリクエスト)。
第一級の Client
v1 では 3 つの層が入れ子になっていました。生のストリームを返すトランスポートのコンテキストマネージャー、それを包む ClientSession、そして手で呼び出す await session.initialize() です。v2 にあるのはオブジェクト 1 つです。
from mcp import Client
from mcp.server import MCPServer
mcp = MCPServer("Bookshop", instructions="Search the catalog before recommending a book.")
@mcp.tool()
def search_books(query: str) -> str:
"""Search the catalog by title or author."""
return f"Found 3 books matching {query!r}."
async def main() -> None:
async with Client(mcp) as client:
print(client.server_info)
print(client.server_capabilities)
print(client.protocol_version)
print(client.instructions)
Client が受け取るのは、サーバーオブジェクト(インメモリでトランスポートなし。テストで使う形です)、URL(Streamable HTTP)、または stdio_client(...) のような任意のトランスポートのコンテキストマネージャーです。async with に入ると接続し、サーバーがどの世代を話すかにかかわらずプロトコルバージョンをネゴシエートします。その後は client.server_capabilities と client.protocol_version がそのまま使え、サーバーが自身を名乗る場合は client.server_info も使えます(2026 年世代では識別情報が省略可能なので、Implementation | None になりました)。v1 で登録したサンプリングとエリシテーションのコールバックは引き続き動作します(コールバックの本体には、このページのほかの項目と同じ snake_case への属性名の変更が及びます)。加えて 2026 形式の「結果に埋め込まれたリクエスト」(後述)にも応答するようになり、1 つずつではなく並行して実行されます。低レベルのインターフェースが必要な人のために ClientSession は今も下にあり、client.session で取り出せます。ただしこちらも変わっています(新しいディスパッチャーエンジンの上で動き、自身のシグネチャも一部変わりました)。下りていく前に移行ガイドを読んでください。
Client で紹介し、クライアントのトランスポートで 3 つの接続形態を、クライアントのコールバックでコールバックそのものを扱います。テストでは、v1 の create_connected_server_and_client_session() ヘルパーに代わるインメモリのパターンを示します。
低レベルの Server は改名ではなく再構築
JSON-RPC の層で作業しているなら、ここが v2 の「すべてが違う」部分です。ツールが 1 つの同じサーバーを両方の書き方で示します。何が移ったかは、マーカーをクリックして確認してください。
from typing import Any
import mcp.types as types
from mcp.server.lowlevel import Server
server = Server("Bookshop")
@server.list_tools() # (1)!
async def list_tools() -> list[types.Tool]:
return [ # (2)!
types.Tool(
name="search_books",
description="Search the catalog by title or author.",
inputSchema={ # (3)!
"type": "object",
"properties": {"query": {"type": "string"}},
"required": ["query"],
},
)
]
@server.call_tool()
async def call_tool(name: str, arguments: dict[str, Any]) -> list[types.ContentBlock]: # (4)!
if name != "search_books":
raise ValueError(f"Unknown tool: {name}") # (5)!
ctx = server.request_context # (6)!
return [types.TextContent(type="text", text=f"Found 3 books matching {arguments['query']!r}.")] # (7)!
- ハンドラーはデコレーター(括弧を付けて呼び出す形)で登録します。サーバーができた後ならいつでもかまいません。
- 素の
list[Tool]を返すと、SDK がListToolsResultに包みます。 - フィールドは Python でも camelCase で、スキーマは強制されます。関数が動く前に、SDK が
call_toolの引数をこのスキーマに対して jsonschema で検証します。下のarguments["query"]が安全なのはそのためです。 - 1 つの
call_toolハンドラーがすべてのツールを受け持ち、ツール名と検証済みの引数を受け取ります。引数は展開済みで、Noneになることはありません。 - v1 のツールは例外の送出で失敗を伝えます。どんな例外も捕捉され、
str(e)をテキストにしたCallToolResult(isError=True)として返されるので、呼び出し側のモデルはこのメッセージを読んで再試行できます。 - コンテキストは暗黙の ContextVar から来ており、リクエストの途中でサーバーオブジェクトを通じて取り出します。
- 素のコンテンツブロックは自動で
CallToolResultに包まれます。
from mcp import MCPError
from mcp.server import Server, ServerRequestContext
from mcp.types import (
INVALID_PARAMS,
CallToolRequestParams,
CallToolResult,
ListToolsResult,
PaginatedRequestParams,
TextContent,
Tool,
)
SEARCH_BOOKS = Tool(
name="search_books",
description="Search the catalog by title or author.",
input_schema={ # (1)!
"type": "object",
"properties": {"query": {"type": "string"}},
"required": ["query"],
},
)
async def list_tools(ctx: ServerRequestContext, params: PaginatedRequestParams | None) -> ListToolsResult: # (2)!
return ListToolsResult(tools=[SEARCH_BOOKS]) # (3)!
async def call_tool(ctx: ServerRequestContext, params: CallToolRequestParams) -> CallToolResult: # (4)!
if params.name != "search_books":
raise MCPError(INVALID_PARAMS, f"Unknown tool: {params.name}") # (5)!
args = params.arguments or {} # (6)!
text = f"Found 3 books matching {args['query']!r}."
return CallToolResult(content=[TextContent(type="text", text=text)]) # (7)!
server = Server("Bookshop", on_list_tools=list_tools, on_call_tool=call_tool) # (8)!
- フィールドは snake_case になり、スキーマは公開されるだけで適用はされません。ハンドラーが動く前に引数を検査するものは何もありません。
- どのハンドラーも
async (ctx, params) -> resultという同じ形です。コンテキストは第 1 引数で(ctx.session、ctx.request_id、ctx.protocol_versionはここにあります)、server.request_contextの行き先はここです。 - 完全な
ListToolsResultを自分で組み立てます。素のリストを返しても SDK は包んでくれず、サーバー側のTypeErrorになります。 - 型付きの params が入り(
params.name、params.arguments)、完全な結果が出ていきます。展開も、包みも、変換も自動では行われません。 - 検査は同じで、手段が違います。ここで
ValueErrorを送出すると、モデルには中身の見えない-32603として届きます(後述)。そのため、意図した通信上のエラーはMCPErrorとして送出します。コードとメッセージはそのまま通り抜け、このテキストを添えた-32602は未知のツールに対する仕様自身の答えです。 params.argumentsはNoneのことがあります。v1 では、コードに届く前に既定値の{}が入っていました。ハンドラーの前に検証がないので、この行は欠かせません。- ここで送出された予期しない例外は、無害化されたプロトコルエラー
-32603"Internal server error"になり、モデルがメッセージを見ることはありません。モデルに読ませて対応させたい失敗には、CallToolResult(is_error=True, ...)を返してください。 - ハンドラーはコンストラクターの引数なので、サーバーのインターフェースはできた瞬間に完成しています。
add_request_handler()は構築後に使える抜け道であり、カスタムメソッドへの入り口でもあります。
この例がそのままパターンです。より一般的に言うと、次のとおりです。どのハンドラーも同じ形で、型付きの params が入り、完全な結果型が出ていきます。ツール引数に対する以前の jsonschema 検査はなくなりました。例外はプロトコルエラーであり、is_error=True のツール結果になることはありません。暗黙の server.request_context ContextVar もなくなりました。ベンダーの名前空間を持つカスタムメソッドは add_request_handler(method, params_type, handler) によって第一級の扱いになり、ハンドラーが動く前に、受信した params が渡したモデルに照らして検証されます。そして middleware リスト(意図的に暫定扱いとしています)がすべての受信メッセージを包み、これまで上書きの対象になっていた非公開の _handle_* メソッドを置き換えます。
その下では、v1 の BaseSession の受信ループが、クライアントとサーバーが共有するディスパッチャーエンジンに置き換わりました。このページのいくつかの事柄が同時に成り立つのは、このエンジンのおかげです。1 つの Server オブジェクトが両方のプロトコル世代を受け持ちます。Client(server) は JSON-RPC のフレーミングなしにプロセス内でディスパッチします。そしてタイムアウトしたクライアントのリクエストは、サーバー側のハンドラーを実際にキャンセルするようになりました。
詳しくは低レベルの Server を参照してください。削除されたフックは移行ガイドが 1 つずつたどります。MCPServer より下に下りたことがなければ、どれも影響しません。
通信用の型は mcp-types に移り、フィールドはすべて snake_case に
プロトコルの型は、独立したディストリビューション mcp-types に置かれるようになりました。依存するのは pydantic と typing-extensions だけなので、ゲートウェイやプロキシ、コードジェネレーターは HTTP スタックをインストールせずに MCP の通信上の形を扱えます。そうしたプロジェクトは mcp-types をインストールして mcp_types をインポートします。mcp 自体はそのパッケージに厳密に一致するバージョンで依存し、再公開しています。そのため SDK に依存するコードは、これまでどおり import mcp.types as types や from mcp.types import Tool と書き(恒久的なエイリアスで、どの名前も同じオブジェクトです)、本当の依存先である mcp だけを宣言します。目安は、実際に依存しているパッケージを通じてインポートすることです。
これらの型では、Python の属性がすべて snake_case になりました。result.is_error、tool.input_schema、listing.next_cursor のような形です。実際に送受信される JSON はこれまでとまったく同じ camelCase で、変わったのは属性のつづりだけです。より厳格なデフォルトも 2 つ付いてきます。未知のフィールドはそのまま往復させずに無視されます(追加の情報は _meta に入れてください)。そして両側とも、ネゴシエートしたプロトコルバージョンに照らして通信を検証します。名前変更の対応表は移行ガイドを参照してください。
トランスポートの設定は run() へ
MCPServer(...) が扱うのは、サーバーが「何であるか」です。名前、インストラクション、ライフスパン、認証がそうです。「どう配信するか」は run() とアプリビルダーの役目になりました。host、port、stateless_http、json_response、エンドポイントのパス、transport_security の移った先がそこです(MCPServer("x", port=9000) は TypeError です)。オーバーロードはトランスポートごとに型付けされているので、stdio が取るオプションと streamable-http が取るオプションはエディターが教えてくれます。知っておきたい削除が 1 つあります。mount_path はなくなりました。プレフィックスの下で配信するには、ASGI アプリをマウントするのがサポートされた方法です。
オプションはサーバーの実行、マウントは既存のアプリに追加するで扱います。
インポートエラーなしに変わる動作
名前の変更は自分から存在を知らせてくれます。次のものは知らせてくれません。
- 同期関数はワーカースレッドで動きます。
defのツール(リソース、プロンプト、リゾルバーも同様)はイベントループをブロックしなくなりました。その代わり、本体はイベントループのスレッド上では動かなくなったので、特定のスレッドに縛られたコードには影響します。async defのハンドラーはそのままです。詳しくは移行ガイドを参照してください。 - ツールの中で送出した
MCPError(v1 のMcpError)はプロトコルエラーになりました。 モデルがそれを見ることはありません。ほかの例外はすべて、これまでどおりモデルが読んで対応できるis_error=Trueの結果になります。この切り分けはエラーの処理で説明しています。 - 結果は送り出す前に検証されます。
input_schemaが{}の手組みのToolは、tools/listで失敗するようになりました(仕様は"type": "object"を要求します)。@mcp.tool()で作ったサーバーがこれに出会うことはありません。スキーマは SDK が書くからです。 - クライアントは受け取ったものを検証します。
list_tools()とcall_tool()は、ネゴシエートしたプロトコルバージョンに照らしてサーバーの応答を検査します。そのため、v1 の寛容なパースが見逃していた「少しだけ不正な」サーバーはpydantic.ValidationErrorを送出するようになりました。自分で管理していないサーバーに接続するなら、そうしたサーバーを見つけるのは自分だと思っておいてください。詳しくは移行ガイドを参照してください。 - URI テンプレートは本物の RFC 6570 になりました。
{+path}、{?query}などが使え、マッチングは正規表現的な緩さではなく厳密になり、取り出した値に含まれるパストラバーサルはデフォルトで拒否されます。厳格になったテンプレートは、最初のリクエストではなくデコレーターの適用時に失敗します。詳しくは URI テンプレートを参照してください。 - Streamable HTTP のライフスパンは 1 回だけ、起動時に実行され、その状態はすべてのセッションとリクエストで共有されます。v1 ではセッションごとに 1 回、
stateless_http=Trueではリクエストごとに 1 回実行されていました。ライフスパンで作るプールやキャッシュは劇的に安くなります。そこで接続ごとのリソースを取得していたものは、ハンドラー本体に移してください。詳しくはライフスパンを参照してください。 mcp devとmcp installは、起動する環境をインストール済みの SDK バージョンに固定します。どちらのコマンドもサーバーを新しいuv run --with ...環境で実行しますが、以前はその環境でmcpが開発対象のバージョンではなく最新の安定リリースに解決されていました。詳しくは移行ガイドを参照してください。- HTTP クライアントは
httpxではなくhttpx2になりました。 依存関係の入れ替えによって、コードが捕捉したり渡したりするもの(httpx2.AsyncClient、httpx2.ConnectError)が変わり、TLS 証明書の検証方法も変わります。httpx2は certifi 同梱の CA リストではなく、truststoreを通じてオペレーティングシステムのトラストストアに照らして検証します。ほとんどの環境では気づくこともありません。システムの CA ストアを持たない最小構成のコンテナや、certifi のバンドルだけが知っていたプライベート CA では、TLS ハンドシェイクが失敗し始めます。SSL_CERT_FILE/SSL_CERT_DIRを設定するか、クライアントにverify=ssl_contextを渡してください。詳しくは移行ガイドを参照してください。
完全に削除されたもの
次の項目には、それぞれ移行ガイドのセクションがあります。
- WebSocket トランスポート(クライアント側とサーバー側の両方)と
mcp[ws]extra です。MCP 仕様の一部だったことは一度もありません。 - 実験的な Tasks API(
mcp.*.experimental)です。2026-07-28 はタスクをコアプロトコルの外に出して公式の拡張(SEP-2663)に移しており、この SDK はまだそれを実装していません。 - インポートパスとしての
mcp.shared.version、mcp.shared.progress、mcp.shared.session(v1 のmessage_handlerの注釈がインポートしていたRequestResponderスタブを含む)です。(mcp.typesは削除されていません。独立したmcp_typesパッケージの恒久的なエイリアスとして残っています。) - 非推奨だった
streamablehttp_clientというつづりと、streamable_http_clientのget_session_idコールバックです(この関数が返すストリームはちょうど 2 つになりました)。 McpErrorです。MCPErrorに改名され、(code, message, data)を直接受け取るコンストラクターになりました。MCPServer.get_context()、mount_path=、そして低レベルServerのデコレーターメソッド、ContextVar、ハンドラーの辞書です。
プロトコル:2025-11-25 から 2026-07-28 へ
v2 は 2026-07-28 リビジョンを実装し、しかも両方のリビジョンを同時に扱います。同じ streamable_http_app()(と同じ stdio サーバー)が、2025 年世代のクライアントの initialize にも 2026 年世代のクライアントのリクエストにも応答します。設定するものも、切り替えるフラグも、別のデプロイも要りません。新しいリビジョンに対応しても、古いリビジョンのクライアントが置き去りになることはありません。ここから先は、新しいリビジョン自体が何を変えるのかを説明します。
ハンドシェイクもセッションもない
2026-07-28 のクライアントは、接続を開いてネゴシエートしてから話し始める、ということをしません。どのリクエストもプロトコルバージョン、クライアント情報、クライアントのケイパビリティを _meta に載せて運びます。唯一のディスカバリー呼び出しである server/discover も、ほかと変わらない普通のリクエストです。Client はデフォルトで正しく振る舞います。server/discover を一度試し、サーバーが古ければ initialize のハンドシェイクにフォールバックします。
Streamable HTTP では、2026 の経路に Mcp-Session-Id がありません。運用面での目玉はこれです。新世代のリクエストをワーカーに結び付けるものが何もないので、単純なラウンドロビンのロードバランサーの後ろにあるどのレプリカでも応答できます。正直に言っておくべき但し書きが 2 つあります。2025 年世代のクライアント(今日ではほとんどのクライアントがそうです)は引き続きセッションを開き、v1 で必要だったのと同じスティッキネスを引き続き必要とします。それらについては何も変わりません。そして、マルチラウンドトリップの再試行がワーカーをまたいで運ばなければならない唯一のものは封印された request_state で、そのデフォルトの鍵はプロセスごとに生成されます。そのため、スケールアウトしたデプロイでは RequestStateSecurity(keys=[...]) を渡します。(stateless_http=True は無関係です。2025 年世代のクライアントの扱い方にだけ影響し、2026 の通信がそれを読むことはありません。v1 ですでに設定しているなら、何も変わりません。)
クライアント側の話はプロトコルバージョン、運用者向けのチェックリスト(Host の許可リスト、request_state の鍵、レプリカをまたぐ通知)はデプロイとスケール、両方の世代を同時に扱う話はレガシークライアントへの対応にあります。
サーバーはクライアントを呼び出せない:マルチラウンドトリップリクエスト
2026-07-28 では、サーバー起点のリクエストはすべてなくなりました。プッシュ型のエリシテーション、サンプリング、roots/list です。2026 の接続にはそれらのためのチャネルがないので、ctx.elicit() と ctx.session.create_message() はそこでは NoBackChannelError で失敗します(レガシークライアントに対しては引き続き動作します)。
代わりの仕組みは呼び出しの向きを逆にします。ユーザーから何かを必要とするツールは質問を「返し」(InputRequiredResult)、クライアントはこれまでと同じコールバックでそれに答え、答えを添えて呼び出しが再試行されます。そのループは Client が回します。サーバー側で結果を自分で組み立てることはめったにありません。依存関係がやってくれるからです。パラメーターを Resolve(ask_quantity) で注釈します(ask_quantity は自分で書く普通の関数です)。すると SDK は接続が対応している仕組み、つまりレガシーセッションならその場で送るエリシテーションリクエスト、2026 ならマルチラウンドトリップで質問します。ツール本体は 1 つ、世代は両方です。
from typing import Annotated
from pydantic import BaseModel
from mcp import Client
from mcp.client import ClientRequestContext
from mcp.server import MCPServer
from mcp.server.mcpserver import AcceptedElicitation, Elicit, ElicitationResult, Resolve
from mcp.types import ElicitRequestParams, ElicitResult
mcp = MCPServer("Bookshop")
class Quantity(BaseModel):
copies: int
async def ask_quantity() -> Elicit[Quantity]:
"""Resolver: ask the user how many copies to put aside."""
return Elicit("How many copies?", Quantity)
@mcp.tool()
async def reserve(title: str, quantity: Annotated[ElicitationResult[Quantity], Resolve(ask_quantity)]) -> str:
"""Reserve copies of a book, asking the user how many."""
if isinstance(quantity, AcceptedElicitation):
return f"Reserved {quantity.data.copies} of {title!r}."
return "Nothing reserved."
async def answer(context: ClientRequestContext, params: ElicitRequestParams) -> ElicitResult:
return ElicitResult(action="accept", content={"copies": 2})
async def main() -> None:
async with (
Client(mcp, mode="legacy", elicitation_callback=answer) as legacy,
Client(mcp, elicitation_callback=answer) as modern,
):
for client in (legacy, modern):
result = await client.call_tool("reserve", {"title": "Dune"})
print(client.protocol_version, result.structured_content)
このファイル 1 つに要点が詰まっています。1 つのサーバー、Resolve に支えられた 1 つのツール、そしてレガシークライアントと新世代のクライアントの両方がインメモリで答えを受け取ります。仕組み(SDK が封印と検証を行う request_state を含む)はマルチラウンドトリップリクエストが説明し、質問のしかたはエリシテーションが扱います。
移植した v1 サーバーの動作が変わる唯一の場所
最初にぶつかるのは自分のテストです。Client(mcp) はデフォルトで v2 サーバーに対して 2026-07-28 をネゴシエートするので、ctx.elicit() を呼ぶツールは v1 で通っていたテストで失敗します。質問を Resolve(...) パラメーターに移す(世代をまたいで使えます)か、本当にプッシュ型の動作が欲しいならテストクライアントを mode="legacy" に固定してください。
ルート、サンプリング、プロトコルのロギングは非推奨、ping は削除
SEP-2577 は、すべてのプロトコルバージョンで 3 つの「ケイパビリティ」をまるごと非推奨にします。ルート、サンプリング、MCP レベルのロギング(ctx.info() など)です。これは上で述べたバックチャネル(back-channel)の欠如とは別の軸です。非推奨は勧告にすぎず、2025 年世代のセッションに対してはすべてが動き続け、通信上は何も変わりません。気づくのは MCPDeprecationWarning です。これは UserWarning なのでデフォルトで表示されます。アップグレード後の最初の ctx.info(...) がそう告げると思っておいてください。
ping はもっと厳しく、非推奨ではなくプロトコルから削除されました。非推奨になった機能の単独メソッドのうち 2 つ、logging/setLevel とクライアントの notifications/roots/list_changed も、2026-07-28 で同じように削除されています。また、進捗通知はサーバーからクライアントへの方向だけになりました。
完全な表、それぞれの代替、そしてレガシークライアントに対応しつつログを静かにしたい場合の 1 行のフィルターは非推奨の機能にあります。
変更通知は 1 本のストリームに
2026-07-28 では、単独の HTTP GET ストリームと resources/subscribe が subscriptions/listen に置き換わります。クライアントは長寿命のストリームを 1 本開き、欲しい通知の種類を指定します。MCPServer は追加の設定なしでこれに対応します。発行には await ctx.notify_resource_updated(uri)(や notify_tools_changed() など)を使い、ミドルウェアは呼び出し側ごとに listen リクエストを拒否でき、複数レプリカのデプロイでは共有の SubscriptionBus を差し込みます。クライアントでは async with client.listen(...) がストリームを開きます。フィルターはキーワード引数として渡し、型付きの変更イベントが返り、sub.honored はサーバーが配信に同意した部分集合です。
発行と配信はサブスクリプション、監視する側はクライアント編の対になるページ、バスはデプロイとスケールで扱います。
そのほかを手短に
- 識別情報は省略可能な、メッセージごとのメタデータです。 リクエスト側の
clientInfo_metaキーは省略可能で(必須の組はprotocolVersionとclientCapabilitiesです)、serverInfoはserver/discoverの結果本体の外に出ました。サーバーは代わりに、2026 年世代のすべての結果の_metaにそれを書き込みます(spec #3002)。SDK は常に書き込みます。サーバーが自身を名乗らない場合(たとえばミドルウェアがキーを取り除いた場合)、client.server_infoはNoneです。通信路上での書き込みの様子は低レベルの Server が示します。 - リクエストは本体をパースしなくてもルーティングできます。 新世代の HTTP リクエストは
Mcp-Method(と、ツール系の 3 つの呼び出しではMcp-Name)を運びます。x-mcp-headerで注釈したツールの入力スキーマのプロパティはMcp-Param-*ヘッダーに写され、サーバーが本体と突き合わせて検査します(SEP-2243)。ゲートウェイやレートリミッターはヘッダーだけでルーティングできます。ルールは移行ガイドにあります。 - 結果はキャッシュのヒントを運びます。 一覧と読み取りの結果は
ttlMsとcacheScopeを宣言します(SEP-2549)。メソッドごとにcache_hints=で設定し、Clientは組み込みのレスポンスキャッシュでそれに従います。ヒントを送らないサーバー(2026 より前のサーバーはすべてそうです)には、これまでと同じキャッシュされない通信が届きます。詳しくはキャッシュのヒントを参照してください。 - 拡張は第一級です。 サーバーとクライアントは、逆引き DNS 形式の識別子の下に省略可能なケイパビリティの束を宣言します(SEP-2133)。組み込みの
Apps拡張(MCP Apps)がそのリファレンスです。詳しくは拡張と MCP Apps を参照してください。 - エラーコードが標準化されました。 存在しないリソースは
-32602で、URI がerror.dataに入ります。仕様で新たに予約されたコードは-32020(ヘッダーの不一致)、-32021(必須のケイパビリティの欠如)、-32022(未対応のプロトコルバージョン)として現れます。トラブルシューティングは正確なメッセージで引けるようになっています。 - 認可は誤った使い方をしにくくなりました。 クライアントは認可コードとともに返される
issを検証し(RFC 9207。callback_handlerはAuthorizationCodeResultを返すようになりました)、登録時にapplication_typeを送り、別の認可サーバーに対して資格情報を使い回すことはありません。エンタープライズ方面の新機能は SEP-990 の ID アサーションフローです。OAuth の変更は移行ガイドにすべて載っています。該当するページはクライアント向けの OAuth と ID アサーションです。 - どのサーバーもトレースできます。 OpenTelemetry はミドルウェアとして、デフォルトで有効な状態で同梱されます。どのリクエストにもサーバースパンが付き、プロセスがエクスポーターを設定するまでコストはかかりません。両端が SDK を使っていれば、クライアントは W3C のトレースコンテキストも
_metaで伝播するので、トレースがつながります。詳しくは OpenTelemetry を参照してください。
v1 からアップグレードする場合
- 何を変えるかの完全で正確な一覧は移行ガイドです。このページはその「なぜ」を説明しました。
- v1.x はなくなりません。 メンテナンス段階に移り、重大な修正とセキュリティパッチを受け続けます。2026-07-28 の仕様リリースによって壊れることもありません。ドキュメントは /v1/ にあります。
mcpに依存するライブラリを公開していて、まだ移行の準備ができていないなら、固定していない依存解決が 1.x にとどまるように上限を付けてください(たとえばmcp>=1.28,<2)。 - 荒削りなところ、わかりにくいところ、壊れているところがあれば、v2 のフィードバックを送ってください。すべて目を通しています。