MCPサーバーの水平スケール:セッション・負荷分散・ステートハンドルの設計【MCP Hosting・2026-07-28仕様対応】
MCPサーバーを1台から2台にすると何が壊れるのか。2026-07-28仕様を踏まえ、4つの状態モデル、Mcp-Session-Idのハッシュが失敗する理由、nginx・Kubernetesでの認証情報アフィニティ、SDKのステートレスモード、明示的ステートハンドル(SEP-2567)、Cloud Runのアフィニティ、stdioブリッジの挙動、レプリカのコストまで解説します。
最終更新:2026年10月16日 / 対象:MCP仕様 2026-07-28(ステートレス版)と 2025-03-26〜2025-11-25(セッションあり版)。2026年10月時点の公開情報に基づく
結論(30秒で読める要約)
MCP Hostingで MCPサーバーを水平スケールするとは、ロードバランサーの後ろでレプリカを2台以上動かし、どのリクエストもそれに答えられるレプリカに届くようにすることです。そのまま動くかどうかは、「呼び出しと呼び出しの間で、サーバーが状態をどこに持っているか」でほぼ決まります。
- 2026-07-28仕様のサーバーは、プロトコル上はステートレスです。
Mcp-Session-Id、initializeハンドシェイク、SSEの再開機能が削除されたので、ツールがプロセスのメモリに状態を持たない限り、普通のラウンドロビンで負荷分散できます。 - ただし、実際にホスティングするサーバーの多くは、まだセッションあり版です。
initialize時にSDKがMcp-Session-Idを発行し、セッションを1つのプロセスのメモリに保持します。アフィニティなしで2台にすると、400/404エラーか、もっと厄介な「状態が黙って消える」現象が起きます。 Mcp-Session-Idでハッシュしても直りません。IDはinitializeに答えたレプリカが発行するため、そのIDのハッシュ先はたいてい別のレプリカです。最初のリクエストから送られる値(Authorizationヘッダー)でルーティングするか、プラットフォームのセッションアフィニティを使うか、状態をプロセスの外に出します。- 根本的な解決は、明示的なステートハンドル(SEP-2567)です。
create_*ツールがbasket_idのようなIDを返し、以降の呼び出しは引数でそれを渡し、どのレプリカも共有ストアから状態を読みます。
判断表は3章、コピペできる設定は6章にあります。
目次
- MCPサーバーのスケールとは
- 2026-07-28で何が変わり、なぜまだ手元のサーバーには効かないのか
- サーバーの状態モデルを見分ける
- ルーティング方式の比較
- セッションIDでハッシュする落とし穴
- 設定例:nginx、Kubernetes、SDKのステートレスモード、明示的ハンドル
- ブリッジ経由のstdioサーバーは2台にすると何が起きるか
- スケールtoゼロ・スケールインで失われるもの
- MCP Hostingでレプリカを増やすコスト
- チェックリスト
- トラブルシューティング(症状→原因→対処)
- NodeFlareならこう解決できる
- よくある質問
1. MCPサーバーのスケールとは
MCPサーバーの水平スケールとは、同じMCPサーバーのコピー(レプリカ)を1つのURLの後ろで複数動かし、1プロセスでは捌けない数のクライアントや同時ツール呼び出しを処理することです。仕様はStreamable HTTPのサーバーを「複数のクライアント接続を処理できる独立したプロセス」と定義しており、クライアントのメッセージは1つずつ別々のHTTP POSTです。そのため、ロードバランサーはPOSTごとに別のレプリカへ振り分けられます。問題は、そのレプリカが前のレプリカの知っていたことを知っているかどうかです。
レプリカが2台以上必要になるのは、1プロセスのCPUやメモリが足りないとき、ヘッドレスブラウザや重いDBクエリでツール呼び出しが詰まるとき、無停止でローリングデプロイしたいとき、プラットフォームが同時実行数でオートスケールするときです。どれにも当てはまらなければ、1台のほうが単純です。
どこで動かすか(VPS、Cloud Run、Workers、MCP専用ホストなど)は、柱記事のMCPホスティング2026:MCPサーバーをリモートで動かす6つの方法で比較しています。この記事はその先、「2台目で何が壊れ、どう直すか」に絞ります。
2. 2026-07-28で何が変わり、なぜまだ手元のサーバーには効かないのか
2026-07-28版で、MCPはプロトコル層でステートレスになりました。公式changelogの主な変更のうち、スケールに関係するものは次のとおりです。
| 2026-07-28の変更 | スケールへの影響 |
|---|---|
プロトコルレベルのセッションと Mcp-Session-Id ヘッダーを削除(SEP-2567) |
固定すべきセッションがなく、どのレプリカでも応答できる |
initialize / notifications/initialized を削除し、バージョンと機能は各リクエストの _meta で送る(SEP-2575) |
接続ごとのネゴシエーション結果を覚えておく必要がない |
| サーバーからクライアントへのリクエストを Multi Round-Trip Requests に置き換え(SEP-2322) | sampling や elicitation の返答を同じレプリカで受ける必要がない |
| SSEストリームの再開と再送を削除 | Last-Event-ID 用の再送バッファをレプリカ間で共有しなくてよい |
すべてのPOSTに Mcp-Method と Mcp-Name ヘッダーが必須 |
ロードバランサーがJSONを解析せずにメソッドやツール名でルーティングできる |
GETストリームを subscriptions/listen に置き換え |
通知はPOSTのレスポンスストリーム1本で受ける |
Streamable HTTPのページは、新しいヘッダーの目的を「ロードバランサー、ゲートウェイ、可観測性ツールなどの中継者が、本文を解析せずにルーティング・検査できるようにするため」と明記しています。
それでも手元のサーバーにすぐ効かないのは、仕様が新しく、GitHubで見つかるサーバーや本番で動いているサーバーの大半が 2025-03-26〜2025-11-25 向けに作られているからです。この世代では、サーバーは initialize 時にセッションIDを割り当ててよく(MAY)、クライアントは以降のリクエストすべてにそれを付けなければならず(MUST)、サーバーが知らないセッションIDには 404 Not Found を返し(MUST)、クライアントは新しいセッションを始めます(MUST)。どのルールも「同じセッションのリクエストは同じプロセスが受ける」前提です。新旧はバージョンネゴシエーションで共存し、両方に対応したクライアントは相手が2026-07-28を話さなければ initialize にフォールバックします。つまり古い世代のサーバーは、新しいクライアントにつないでもセッションありの挙動のままです。
3. サーバーの状態モデルを見分ける
ルーティング方式は、言語やプラットフォームではなく「呼び出しの間に何を保持しているか」で決めます。SEP-2567には、オープンソースのMCPサーバーから無作為に選んだ1,000リポジトリの自動調査があり、各タイプの多さの目安になります。
| タイプ | 呼び出しの間に保持するもの | SEP-2567調査での割合 | ラウンドロビンでN台にできるか |
|---|---|---|---|
| A. ステートレス | 何もない。APIラッパー、検索、読み取り専用DBなど | 90.0%(アプリ側でセッションIDを参照していない) | できる。ただしSDKをステートレスモードにする(6章) |
| B. 定型コードだけのセッション | SDKが発行する Mcp-Session-Id と、メモリ上の Map<sessionId, transport>。アプリのデータはない |
3.5%(TSのルーティング定型コード)+2.8%(sessionIdGenerator を設定するだけ) |
セッションモードではできない。ステートレスモードにすればできる |
| C. セッションに紐づくアプリ状態 | カート、ブラウザコンテキスト、DBトランザクション、キャッシュなど | 2.5% | できない。アフィニティ、共有ストア、明示的ハンドルのどれかが必要 |
| D. セッションごとのゲートウェイ・ブリッジ | セッションごとに上流接続やstdio子プロセスを持つ | 0.7%(プロキシ/ゲートウェイのスティッキールーティング) | できない。アフィニティが必要 |
実務上のポイントは2つです。
- いちばん多い「誤報」はタイプBです。中身はステートレスなのに、SDKのセッションモードのせいで状態ありに見えています。ステートレスモードに切り替えるだけで、ツールには手を入れずに直ります。
- 「90%がステートレス」はコードの話で、デプロイの話ではありません。何も保存しないサーバーでも、トランスポートがセッションIDを発行していれば2台で壊れます。タイプAだと決めつける前に、トランスポートの設定を確認してください。
4. ルーティング方式の比較
以下の方式は、どれも「アフィニティを不要にする」か「アフィニティを保証する」かのどちらかです。違いは、ルーティングのキーが何で、いつ分かるかです。
| 方式 | ルーティングのキー | 使えるタイプ | 主な注意点 |
|---|---|---|---|
| ラウンドロビン/最小接続 | なし | A(ステートレスモードのBも) | メモリ上の状態はレプリカごとにバラバラ |
Mcp-Session-Id でハッシュ |
セッションIDヘッダー | 確実に使えるものはない | IDは initialize に答えたレプリカが発行する(5章) |
Authorization でコンシステントハッシュ |
呼び出し元の認証情報 | A・B・C・D(1つの認証情報→1台) | 1つのキーに負荷が集中すると1台が過負荷。台数変更で一部のキーが移る |
| プラットフォームのCookieアフィニティ(例:Cloud Run) | ロードバランサーのCookie | クライアントがCookieを返す場合のみ | Googleは「ベストエフォート」と明記。MCP仕様はCookieに触れておらず、対応はクライアント次第 |
プラットフォームの Mcp-Session-Id アフィニティ(例:Alibaba Cloud Function Compute) |
セッション作成時に紐づけたセッションID | 2025年版プロトコルのB・C・D | ベンダー固有。対応プロトコル版が限られる |
| 共有ステートストア(Redis、Postgres) | なし | 改修後のA・B・C | ストアの実装と運用が必要 |
| 明示的ステートハンドル(SEP-2567) | ツール引数で渡すハンドル | 改修後のC | ツールのスキーマ変更が必要。モデルがIDを持ち回る |
| 1台のまま | なし | すべて | 横ではなく縦(メモリ増)に伸ばす |
押さえておきたいプラットフォームの仕様:
- Google Cloud Runのセッションアフィニティは、TTL 30日のCookieを使い、オートスケールの性質上「ベストエフォート」です。「インスタンスが何らかの理由で終了した場合、またはリクエストの同時実行数やCPU使用率が上限に達した場合」はアフィニティが外れ、またインスタンスは他のクライアントのリクエストも受けます。
- Alibaba Cloud Function Computeには「MCP Streamable HTTP affinity」があり、同じ
Mcp-Session-Idを持つリクエストをセッションを作ったインスタンスへ送ります。対応はトランスポート仕様 2025-03-26 と 2025-06-18 で、インスタンスあたりの同時実行数は200に固定、同時セッション数の既定値は20、セッション寿命(既定6時間)やアイドルタイムアウト(既定30分)を過ぎるとアフィニティは保証されません。
5. セッションIDでハッシュする落とし穴
セッションありのサーバーで真っ先に思いつくのが「Mcp-Session-Id ヘッダーでハッシュして、同じセッションは同じレプリカへ送る」方法です。これは順序の問題でうまくいきません。
- クライアントはセッションIDなしで
initializeを送ります。ロードバランサーにはキーがないので、適当なレプリカ(R1とします)を選びます。 - R1がセッションを作ってメモリに保持し、
Mcp-Session-Id: 7f3a…を返します。 - クライアントは
Mcp-Session-Id: 7f3a…を付けてtools/callを送ります。ロードバランサーが7f3a…をハッシュすると、N台構成ならおよそ (N−1)/N の確率でR1以外に当たります。 - そのレプリカは
7f3a…を知りません。SDKによって400か404を返します。2025年版のルールでは404を受けたクライアントは新しいセッションを始め、それがまた別のレプリカに当たることもあります。
目に見える症状はエラーとは限りません。クライアントが再接続を繰り返したり、ツールの状態が「たまに」消えたりします。セッションIDでのハッシュが成立するのは、ロードバランサー自身がIDと発行元レプリカを紐づける場合だけで、Function Computeのアフィニティ機能はまさにそれをしています。nginxで素直に hash $http_mcp_session_id と書いても、そうはなりません。
普通のロードバランサーで確実に効くのは、最初のリクエストから付いてくる値でハッシュする方法です。代表は Authorization のBearer認証情報で、initialize もその後の呼び出しも同じヘッダーを持つため、同じレプリカに届きます。代わりに、同じ認証情報のセッションはすべて同じレプリカに乗ります(多くの場合は問題ありませんが、1本のAPIキーがトラフィックの大半を占めると偏ります)。また、台数を増減すると一部のキーの行き先が変わり、そのキーのセッションは切れます。コンシステントハッシュなら、移るキーは少数で済みます。
6. 設定例:nginx、Kubernetes、SDKのステートレスモード、明示的ハンドル
nginx:認証情報でアフィニティを取り、SSEを流す
upstream mcp_backend {
# 同じ認証情報 -> 同じレプリカ。consistent = ketamaハッシュで、
# 台数を増減しても移るキーは少数で済む
hash $http_authorization consistent;
server 10.0.0.11:8000;
server 10.0.0.12:8000;
server 10.0.0.13:8000;
}
server {
listen 443 ssl;
server_name mcp.example.com;
location /mcp {
proxy_pass http://mcp_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_buffering off; # SSEイベントを溜めずにすぐ流す
proxy_read_timeout 3600s; # 長いツール呼び出しやlistenストリーム用
}
}
nginxのドキュメントによれば、consistent を付けないと「サーバーの追加・削除でほとんどのキーが別のサーバーに割り当て直される」ことがあります。2026-07-28仕様も、proxy_buffering off と同じ理由で、SSEレスポンスに X-Accel-Buffering: no を付けるようサーバーに求めています。
Kubernetes(ingress-nginx)
metadata:
annotations:
nginx.ingress.kubernetes.io/upstream-hash-by: "$http_authorization"
nginx.ingress.kubernetes.io/proxy-buffering: "off"
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
タイプA、またはステートレスモードにしたタイプBなら、ハッシュのアノテーションは外して、Serviceの通常の負荷分散に任せてください。
SDKのステートレスモードを有効にする(タイプBの解決)
SEP-2567は「PHP以外の公式SDKはすべて、セッションIDを生成しないという形でステートレスモードを提供している」と書いています。自分で管理しているセッションありのサーバーなら、たいてい1行の変更で済みます。
TypeScript SDK:
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined, // ステートレス:Mcp-Session-Id を発行しない
});
Python SDK(公式 mcp パッケージのFastMCP):
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("orders", stateless_http=True, json_response=True)
@mcp.tool()
def get_order(order_id: str) -> dict:
return fetch_order(order_id) # セッションごとの状態を持たない
if __name__ == "__main__":
mcp.run(transport="streamable-http")
変更後は、2台のレプリカそれぞれに同じリクエストを送り、セッションヘッダーなしで tools/list と tools/call が通ることを確認します。
セッション状態を明示的ハンドルに置き換える(タイプCの解決)
SEP-2567のパターンは、作成用ツールが不透明なIDを返し、以降のツールはそれを引数で受け取り、状態は共有ストアに置くというものです。これでどのレプリカでも処理できます。SEPのガイダンスには、ハンドルを不透明にする、有効期限をツールの説明文に書く、期限切れのハンドルには分かりやすいエラーを返す、呼び出しごとにハンドルと呼び出し元の組み合わせを検証する、といった項目があります。
import json, os, secrets
import redis
from mcp.server.fastmcp import FastMCP
r = redis.Redis.from_url(os.environ["REDIS_URL"])
mcp = FastMCP("cart", stateless_http=True)
TTL = 24 * 3600
@mcp.tool()
def create_basket() -> dict:
"""Create a basket. Returns basket_id. Baskets expire after 24h idle."""
basket_id = "bsk_" + secrets.token_urlsafe(16) # 128ビット、不透明
r.set(basket_id, json.dumps({"items": []}), ex=TTL)
return {"basket_id": basket_id}
@mcp.tool()
def add_item(basket_id: str, sku: str) -> str:
"""Add an item to a basket created with create_basket."""
raw = r.get(basket_id)
if raw is None:
return f"basket {basket_id} has expired; call create_basket again"
basket = json.loads(raw)
basket["items"].append(sku)
r.set(basket_id, json.dumps(basket), ex=TTL) # アイドルTTLを延長
return f"Added {sku} to {basket_id} ({len(basket['items'])} items)"
本番では、カートに作成者の識別子も保存し、呼び出しのたびに照合してください。SEP-2567は、認証のあるサーバーでは「持っていることは認可ではない」と明記しています。
7. ブリッジ経由のstdioサーバーは2台にすると何が起きるか
GitHubで見つけたstdioサーバーは自前のHTTPトランスポートを持たないので、スケール時の挙動は前段のブリッジで決まります(ブリッジがどうプロセスを起動するかはstdio MCPサーバーをリモートで動かす方法で手順つきで解説しています)。
| ブリッジのモード | プロセスモデル | アフィニティなしで2台 | 認証情報でアフィニティを取って2台 |
|---|---|---|---|
| supergateway(ステートレス、既定) | POSTごとに子プロセス | 動く。もともと状態は1回で消える | 不要 |
supergateway --stateful |
セッションごとに子プロセス | 5章と同じセッションエラー | 動く |
| mcp-proxy | レプリカごとに共有の子プロセス1つ | レプリカごとにメモリ上の状態が別物になり、リクエストのたびに見えるデータが変わる | 認証情報単位では動くが、レプリカ同士の状態はずれたまま |
| SDKのネイティブトランスポート | 子プロセスなし | セッションモードかステートレスモードか次第(6章) | 動く |
SEP-2567もこれを残る課題として挙げ、「セッションごとにサブプロセスを起動してHTTPとstdioを橋渡しするゲートウェイ」には別の相関キーが必要で、例として「認証済みの主体でルーティングする」ことを挙げています。6章の Authorization ハッシュと同じ考え方です。
8. スケールtoゼロ・スケールインで失われるもの
レプリカのメモリやローカルファイルシステムにしかない状態は、そのレプリカが止まれば消えます。オートスケールのもとでは、これは例外ではなく日常です。
| イベント | 失われるもの | クライアントから見えること(セッションあり版) |
|---|---|---|
| スケールイン(レプリカ削除) | そのレプリカのセッションとメモリ上の状態 | 404 → 別レプリカで新しいセッション |
| スケールtoゼロ | メモリ上のすべて。多くの場合ローカルファイルも | コールドスタート後に新しいセッション |
| 再デプロイ/ローリング更新 | 古いレプリカ上のセッション | 更新中の再接続 |
| クラッシュと再起動 | そのプロセスのセッションと状態 | スケールインと同じ |
| プラットフォームがアフィニティを外す(例:Cloud Runで同時実行数が上限) | 何も失われないが、リクエストが別の台へ行く | 5章と同じセッションエラー |
ここから言えるのは、「失ったらバグになる状態」はレプリカより長生きするストア(データベース、Redis、オブジェクトストレージ、単一書き込みのサーバーに付けた永続ディスク)に置くべき、ということです。それ以外はキャッシュとしてメモリに置いて構いません。
9. MCP Hostingでレプリカを増やすコスト
メモリ×時間で課金されるプラットフォームでは、費用はレプリカ数に比例します。おおよそ「メモリ×レプリカ数×稼働時間」で、1か月は約730時間です。
| 構成 | 月あたりのGB時間 |
|---|---|
| 512 MB×1台、1日2時間稼働(約60時間) | 30 |
| 512 MB×2台、1日2時間稼働 | 60 |
| 512 MB×1台、常時稼働(約730時間) | 365 |
| 512 MB×3台、常時稼働 | 1,095 |
| 1 GB×1台、常時稼働(横ではなく縦に伸ばす) | 730 |
結論は2つです。1つ目に、固定台数より同時実行数でのオートスケールのほうが安く済みます。追加のレプリカは負荷の高い間しか課金されないからです。2つ目に、タイプCやDのサーバーでは、横に増やすより1台を大きくするほうがトータルで安いことが多いです。アフィニティや共有ストアの作業が丸ごと不要になります。横に増やすのは1台が本当に飽和したときにして、最初の選択肢にはしないのがおすすめです。
10. チェックリスト
- [ ] トランスポートの設定とツールの中身から、自分のサーバーがA〜Dのどれか把握している
- [ ] 自分で管理しているセッションありのサーバーは、可能な限りSDKのステートレスモードで動かしている
- [ ] 残るセッションありのサーバーには、
initializeの時点で分かる値(認証情報、またはプラットフォームがセッションIDを発行元に紐づける機能)でアフィニティを取っている。Mcp-Session-Idの単純なハッシュではない - [ ] レプリカより長生きすべき状態は、共有ストアか単一書き込みのディスクにある
- [ ] 状態を持つツールは明示的ハンドルを使い、有効期限の明記と「期限切れ」の明確なエラーがある
- [ ] プロキシがSSEを溜めずに流し(
proxy_buffering offまたはX-Accel-Buffering: no)、読み取りタイムアウトが最長のツール呼び出しより長い - [ ] セッションを開いたまま、スケールイン・再デプロイ・クラッシュを試した
- [ ] ログにセッションやハンドル単位の情報があり、失敗した呼び出しをどのレプリカが処理したか追える
- [ ] レプリカを足す前に、縦に伸ばす場合とのコストを比べた
11. トラブルシューティング(症状→原因→対処)
| 症状 | 原因 | 対処 |
|---|---|---|
| 1台では動くのに2台で失敗する | セッションありのサーバーでアフィニティがない | ステートレスモード(タイプB)か、認証情報ハッシュのアフィニティ |
一部の呼び出しで「no valid session ID」の 400 Bad Request |
セッションを発行していないレプリカに届いた | 同上 |
| スケールアウト後、クライアントが再接続を繰り返す | 未知のセッションに 404 → 新しいセッションが別の台に当たる |
セッションIDではなく認証情報でアフィニティを取る |
| ツールの状態がたまに消える | 状態がプロセスのメモリにあり、リクエストが分散するかレプリカが再起動した | 共有ストアか明示的ハンドル |
| 他のユーザーのデータが見える | レプリカごとに共有の子プロセス1つ(mcp-proxy) | テナントごとにデプロイを分けるか、セッションごとのプロセス+アフィニティ |
| 負荷が1台に集中する | ハッシュキーが共有の1本のAPIキー | ユーザーやエージェントごとにキーを発行するか、ステートレスにしてハッシュをやめる |
| 進捗通知がまとめて届く | プロキシがSSEをバッファしている | proxy_buffering off、X-Accel-Buffering: no |
| 長い呼び出しが60秒で切れる | プロキシの既定の読み取りタイムアウト | proxy_read_timeout を延ばす |
2026-07-28のリクエストで 400 とJSON-RPCエラー -32020(HeaderMismatch) |
途中の何かが Mcp-Method/Mcp-Name や本文を書き換えた |
プロキシでMCPのヘッダーや本文を変更しない |
| デプロイのたびにセッションが切れる | ローリング更新でセッションを持つレプリカが消える | 再接続を許容するか、状態をストアに移して新しいセッションで続きができるようにする |
NodeFlareならこう解決できる
NodeFlareは、GitHubのMCPサーバー(stdioのみのものを含む)を、認証つきのHTTPSエンドポイントとしてデプロイするMCPホスティングサービスです。この記事で挙げたスケールの課題には、次のように対応します。
| この記事の課題 | NodeFlareでの対応 |
|---|---|
| プラットフォームがレプリカを足すとセッションが壊れる | 同時実行数がソフト/ハードの上限を超えると、スナップショットからフォークしたレプリカを追加する水平オートスケール(プランとホストの上限まで)。セッションは元のレプリカに固定されたまま |
| stdioサーバーにはブリッジが必要で、ブリッジ次第でプロセスモデルが変わる | 組み込みのアダプター(stdio-adapter.cjs)がサーバーを子プロセスとして起動。同時に接続するクライアントは、リクエストごとのJSON-RPC IDの付け替えで1つの子プロセスを共有し、Mcp-Session-Id は initialize 時に発行 |
| 新旧のクライアントが同じサーバーにつなぐ | Streamable HTTPと、旧来のHTTP+SSEトランスポート(2024-11-05)の両方に対応 |
| レプリカがクラッシュする | 再起動予算の範囲内で、1秒から30秒までの指数バックオフで再起動。予算を使い切ると /health が503を返す |
| スケールtoゼロや再デプロイを越えて残すべき状態 | 永続ディスク(月額 ¥15/GB)をオプションで追加可能。ただし永続ディスク付きのサーバーは単一VM・単一書き込みで、並列のフォークはしない。永続化と複数レプリカの両方が必要なら外部データベースを使う |
| 冷えたレプリカ | スケールtoゼロでもスナップショットから1秒未満で復元。Always-On(Pro以上)なら温まったレプリカを1つ常駐 |
| 縦に伸ばすか横に増やすか | メモリは256 MB・512 MB・1 GB・2 GBの段階。約1か月の使用状況からサイズを提案し、メモリ不足が起きたら自動で元に戻す |
| どのセッションが失敗したか分からない | リクエストごとのログに session_id、mcp_method、tool_name、http_status、outcome、duration_ms、error_message を記録。保持期間はFreeで7日、Pro/Teamで30日 |
| レプリカのコスト | コンピュートは1 GB時間あたり ¥5で、動いている間だけ課金。月の支出上限に達するとサーバーを一時停止し、HTTP 402を返す |
料金:Freeは ¥0 でMCPサーバー1つ、月30 GB時間のコンピュート付き。Proは月額 ¥2,980 でサーバー数無制限、300 GB時間のコンピュートと10 GBのストレージ込みです。
最小の始め方:
- nodeflare.tech にGitHubでサインインし、MCPサーバーのリポジトリを選ぶ。
- 環境変数を入れてデプロイする。NodeFlareがMCPの
initializeを確認してから「Succeeded」にする。 /mcpのURLとAPIキーをクライアントに貼る。タイプCのサーバーなら、状態に頼る前に永続ディスクか外部ストアを用意する。
1サーバーまで無料で試せます → nodeflare.tech
よくある質問
MCPサーバーを水平スケールするには?
ロードバランサーの後ろでレプリカを複数動かし、どのレプリカに届いても処理できるようにします。2026-07-28仕様のサーバーやステートレスモードのサーバーなら、ラウンドロビンで足ります。セッションありのサーバーは、呼び出し元の認証情報でアフィニティを取るか、セッションの状態を共有ストアに移します。
MCPサーバーにスティッキーセッションは必要?
必要なのは、Mcp-Session-Id を発行するか、メモリに状態を持つセッションあり版(プロトコル 2025-03-26〜2025-11-25)のサーバーだけです。2026-07-28仕様のサーバーにはプロトコル上のセッションがないので、ツールがプロセスのメモリに状態を持たない限り、アフィニティなしで負荷分散できます。
Mcp-Session-Id ヘッダーで負荷分散できる?
単純なハッシュではできません。セッションIDは initialize に答えたレプリカが作りますが、そのレプリカはキーなしで選ばれているため、後からIDをハッシュすると別のレプリカを指すのが普通です。ロードバランサー自身がIDと発行元を紐づける、プラットフォーム固有のMCPアフィニティ機能を使う場合だけ成立します。
2026-07-28のMCP仕様でセッションはなくなった?
はい。changelogには、Streamable HTTPトランスポートから「プロトコルレベルのセッションと Mcp-Session-Id ヘッダーを削除」し、initialize ハンドシェイクも削除したとあります。呼び出しをまたぐ状態が必要なサーバーは、サーバーが発行する明示的なハンドルを通常のツール引数として渡す方式を使います。
既存のMCPサーバーをステートレスにするには?
公式SDKで作られていてアプリの状態を持たないなら、トランスポートをステートレスモードにします。TypeScript SDKは sessionIdGenerator: undefined、PythonのFastMCPは stateless_http=True です。ツールがセッションごとの状態を持っている場合は、その状態を共有ストアに移し、明示的ハンドル経由で扱います。
MCPの明示的ステートハンドルとは?
create_basket が返す basket_id のように、ツールが返すIDをモデルが以降の呼び出しで引数として渡す仕組みです。プロトコルの機能ではなく、SEP-2567が推奨するツール設計のパターンです。状態はハンドルで引くので、共有ストアにアクセスできればどのレプリカでも処理できます。
Cloud RunのセッションアフィニティはMCPサーバーで使える?
部分的には使えます。Cloud RunのアフィニティはCookieベースで、Google自身が「ベストエフォート」と書いており、インスタンスの終了や同時実行数・CPUの上限到達で外れます。MCP仕様はCookieに触れていないので、使うクライアントがCookieを返すか確認し、失ってはいけない状態をアフィニティだけに頼らないでください。
stdioのMCPサーバーを複数レプリカで動かせる?
動かせますが、結果はブリッジ次第です。リクエストごとにプロセスを起動するステートレスなブリッジなら自由に増やせます。セッションごとのブリッジはアフィニティが必要で、レプリカごとに1プロセスを共有するブリッジでは、レプリカごとに状態がずれます。状態を持つサーバーなら、認証情報でルーティングするか1台で運用します。
レプリカがスケールtoゼロや削除されたらMCPセッションはどうなる?
そのレプリカのセッションとメモリ上の状態は失われます。2025年版のルールでは、クライアントは古いセッションに 404 を受け取り、新しいセッションを始める必要があります。残すべき状態は、レプリカより長生きするデータベース、キャッシュ、永続ディスクに置きます。
MCPサーバーは縦に伸ばすのと横に増やすの、どちらが安い?
状態を持つサーバーなら、1台を大きくするほうがトータルで安いことが多いです。アフィニティや共有ストアの作業が不要で、メモリ×時間の課金はどのみちレプリカ数に比例するからです。横に増やすのは、1台が飽和したときや、無停止のローリングデプロイが必要なときにします。
まとめ
MCPサーバーを2台以上にするときの問題は、プロトコルではなく状態です。2026-07-28仕様のサーバーと、SDKのステートレスモードに切り替えたセッションあり版のサーバーは、どんなラウンドロビンのロードバランサーの後ろにも置けます。セッションの状態を持つサーバーには、最初のリクエストから分かる値(認証情報、またはセッションIDを発行元に紐づけるプラットフォーム機能)でアフィニティが必要で、Mcp-Session-Id を単純にハッシュすると別のレプリカに飛びます。長く使える解決策は、SEP-2567のとおり状態を共有ストアに移し、明示的ハンドルで扱うことです。レプリカを足す前に、1台を大きくすれば済まないかも確かめてください。
レプリカを動かすプラットフォーム選びは、MCPサーバーホスティングのランキングを参考にしてください。
参考資料
- MCP Specification 2026-07-28: Key Changes
- MCP Specification 2026-07-28: Streamable HTTP
- MCP Specification 2025-11-25: Transports(セッション管理)
- SEP-2567: Sessionless MCP via Explicit State Handles
- MCP TypeScript SDK: サーバーの例(ステートレスと複数ノード構成)
- Google Cloud Run: セッションアフィニティの構成
- Alibaba Cloud Function Compute: MCP Streamable HTTP affinity の構成
- nginx: ngx_http_upstream_module(hash)
