MCP 2026-07-28 でプロトコルがステートレス化 - セッション廃止を手元で確かめる

Model Context Protocol(MCP)の次期仕様 2026-07-28 のリリース候補が公開された。ローンチ以来最大の改訂と位置づけられており、目玉は プロトコル層がステートレスになる ことだ。最終仕様は 2026年7月28日に確定予定で、それまでの期間が SDK メンテナや実装者が実負荷で検証する猶予期間にあたる。

エージェント開発者にとって効いてくるのは「リモート MCP サーバの運用」の部分だ。この記事では何が変わったのかを整理し、現行の TypeScript SDK でステートレスな Streamable HTTP サーバを立てて、その挙動を手元で確かめる。

何が変わったのか

これまでの Streamable HTTP トランスポートは、接続の最初に initialize / initialized のハンドシェイクを行い、サーバが払い出した Mcp-Session-Id ヘッダを以降の全リクエストに付与する、という前提だった。これは水平スケール時に厄介で、ロードバランサでのスティッキールーティング、インスタンス間で共有するセッションストア、ゲートウェイでのボディ検査といった作りが必要になっていた。

2026-07-28 の主な変更点は次の通り。

  • セッションの廃止(SEP-2567): プロトコル層のセッションと Mcp-Session-Id ヘッダが Streamable HTTP から消える。任意のリクエストが任意のサーバインスタンスに着地してよくなる。
  • ハンドシェイクの廃止(SEP-2575): initialize / initialized の往復が不要になる。
  • ルーティング用ヘッダの必須化(SEP-2243): Mcp-Method(操作の種類)と Mcp-Name(操作名)が必須になり、LB やゲートウェイがボディを見ずに操作単位でルーティング・レート制限できる。
  • キャッシュメタデータ(SEP-2549): tools/list などのリスト系レスポンスに ttlMscacheScope が付く。クライアントは「この一覧が何ミリ秒鮮度を保つか」「ユーザ間で共有してよいか」を明示的に知れる。
  • プロトコルバージョン文字列: MCP-Protocol-Version: 2026-07-28(従来は 2025-11-25)。

要するに、これまで sticky session と共有セッションストアが要ったリモートサーバが、素のラウンドロビン LB の背後で動かせるようになる、という話だ。

現行 SDK で「ステートレス」を先取りする

仕様は 7月28日確定だが、ステートレスな Streamable HTTP 自体は現行の公式 TypeScript SDK でも sessionIdGenerator: undefined を指定することで既に使える。RC が正式化するのはこの運用モードを既定・前提にする方向性だ。ここでは現行 SDK でステートレスサーバを立て、「セッション ID なしで tools/list が単発 POST で返ってくる」ことを確認する。

実際に試す

前提

  • Node.js v20 以上(本稿は v20.17.0 で確認)
  • @modelcontextprotocol/sdk 1.29.0zod 3 系
  • 追加の HTTP フレームワーク不要(Node 標準の http を使う)
1
2
3
4
mkdir mcp-stateless && cd mcp-stateless
npm init -y
npm pkg set type=module
npm install @modelcontextprotocol/sdk@1.29.0 zod

ステートレスサーバ

server.mjs を作る。ポイントは sessionIdGenerator: undefined(セッション無効化)と、リクエストごとにトランスポート/サーバを生成する点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import { createServer } from "node:http";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { z } from "zod";

function buildServer() {
const server = new McpServer({ name: "echo-stateless", version: "1.0.0" });
server.registerTool(
"add",
{
description: "2つの整数を足す",
inputSchema: { a: z.number(), b: z.number() },
},
async ({ a, b }) => ({
content: [{ type: "text", text: String(a + b) }],
})
);
return server;
}

const http = createServer(async (req, res) => {
// ステートレス: リクエストごとにトランスポートを生成、sessionIdGenerator は undefined
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
enableJsonResponse: true,
});
const server = buildServer();
await server.connect(transport);

let body = "";
for await (const chunk of req) body += chunk;
await transport.handleRequest(req, res, body ? JSON.parse(body) : undefined);
});

http.listen(3000, () =>
console.log("stateless MCP server on http://127.0.0.1:3000/mcp")
);

起動する。

1
2
node server.mjs
# stateless MCP server on http://127.0.0.1:3000/mcp

セッション ID なしで叩く

別ターミナルから、Mcp-Session-Id を一切付けずに tools/list を単発の POST で投げる。Acceptapplication/json, text/event-stream の両方を含めるのが Streamable HTTP の作法だ。

1
2
3
4
curl -s -X POST http://127.0.0.1:3000/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

期待される出力(整形済み):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"result": {
"tools": [
{
"name": "add",
"description": "2つの整数を足す",
"inputSchema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": { "a": { "type": "number" }, "b": { "type": "number" } },
"required": ["a", "b"]
},
"execution": { "taskSupport": "forbidden" }
}
]
},
"jsonrpc": "2.0",
"id": 1
}

続けてツールを呼ぶ。これも独立した POST で、直前のリクエストと状態を共有していない。

1
2
3
4
curl -s -X POST http://127.0.0.1:3000/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"add","arguments":{"a":2,"b":40}}}'

出力:

1
{"result":{"content":[{"type":"text","text":"42"}]},"jsonrpc":"2.0","id":2}

initialize を送らず、セッション ID も持ち回らずに一覧取得とツール呼び出しが両方通った。各リクエストは互いに独立しているので、複数インスタンスに素のラウンドロビンで振り分けても成立する — これが 2026-07-28 がプロトコル既定にしようとしている姿だ。

対比: ステートフルだと弾かれる

違いを体感するため、sessionIdGenerator: () => randomUUID() にした「ステートフル」サーバを別ポートで立て、初期化なしでいきなり tools/list を投げると、こうなる。

1
{"jsonrpc":"2.0","error":{"code":-32000,"message":"Bad Request: Server not initialized"},"id":null}

Server not initialized で拒否される。ステートフルではハンドシェイクとセッションが前提になっているためで、ステートレス化がこの往復を丸ごと取り除くことがよく分かる。

まとめ

2026-07-28 の肝は、リモート MCP サーバをステートレスな HTTP サービスとして素直にスケールさせられるようにしたことだ。手元では現行 SDK の sessionIdGenerator: undefined で同じ運用形を先取りでき、Mcp-Session-Id なしの単発 POST で tools/list / tools/call が通ることを確認できる。正式版では Mcp-Method / Mcp-Name ヘッダの必須化や ttlMs / cacheScope によるキャッシュ制御も加わるため、リモート MCP サーバを運用しているなら 7月28日の確定前に自分の構成で挙動を確かめておくとよい。

出典: The 2026-07-28 MCP Specification Release Candidate(Model Context Protocol Blog)


MCP 2026-07-28 でプロトコルがステートレス化 - セッション廃止を手元で確かめる
https://blog.hashito.biz/2026/07/10/try-mcp-2026-07-28-stateless/
著者
hashito
作成日
2026年7月10日
著作権