ふたつの使い方¶
mcp-stdio はひとつのコマンドにふたつの明確に異なる役割があります。この ドキュメントの残り全部がこの選択にぶら下がっているので、まずここで 整理してください。
| クライアント側ゲートウェイ(デフォルト) | サーバーゲートウェイ(serve) |
|
|---|---|---|
| あなたは… | 誰かのリモート MCP サーバーを使う側 | 自分の MCP サーバーを公開する側 |
| MCP サーバーの居場所 | どこか別の場所、HTTPS の向こう | あなたのマシン上、stdio で動作 |
| mcp-stdio の居場所 | MCP クライアントの隣(手元のマシン) | MCP サーバーの隣(公開ホスト) |
| 変換の向き | stdio → Streamable HTTP / SSE | Streamable HTTP → stdio |
| OAuth での役割 | クライアント:ログインし、トークンを保存・リフレッシュ | 認可サーバー:クライアントを登録し、トークンを発行・検証 |
| 典型的な利用者 | Claude Desktop / Claude Code でリモートサーバーを使う人 | stdio MCP サーバーをリモートユーザーに届けたい運用者 |
| 次に読む | リモート MCP サーバーに接続する | stdio サーバーを公開する |
クライアント側ゲートウェイとして(デフォルトモード)¶
MCP クライアント(Claude Desktop、Claude Code など)はローカルの stdio プロセスしか起動できませんが、使いたいサーバーはネットワークの向こうに あります。mcp-stdio がそのローカルプロセスそのものになります: クライアントは mcp-stdio と stdio で会話し、mcp-stdio がすべての メッセージを HTTPS でリモートサーバーへ中継します——OAuth ログイン、 トークンキャッシュ、リフレッシュを引き受けるので、接続はアクセス トークンの寿命より長生きします。
graph TD
A["MCP クライアント<br/>(Claude Desktop / Claude Code)"]
B["mcp-stdio<br/>OAuth クライアント · トークンキャッシュ · トランスポート変換"]
C["リモート MCP サーバー<br/>(Streamable HTTP または legacy SSE)"]
A -- "stdio (JSON-RPC 行)" --> B
B -- "HTTPS (+ Bearer トークン)" --> C
C --> B
B --> A
mcp-stdio --oauth https://mcp.example.com/mcp
このモードが欲しくなるのは:
- ベンダーやチームがホストする MCP サーバーを Claude Desktop / Claude Code から使いたいとき;
- クライアント単体では完結できない OAuth ログインをサーバーが要求する とき;
- クライアントが対応をやめた legacy SSE トランスポートをサーバーが まだ話しているとき。
→ リモート MCP サーバーに接続する へ。
サーバーゲートウェイとして(serve モード)¶
手元のマシンで stdio を話す MCP サーバーを書いた(または動かしている)。
それをリモートユーザーが彼らの MCP クライアントから使えるようにしたい。
mcp-stdio serve はそれを本物の Streamable HTTP エンドポイントにします:
片側で HTTPS を受け、もう片側ではユーザーセッションごとに隔離された
stdio 子プロセスを spawn し、--enable-oauth を付ければクライアント登録と
トークン発行を担う OAuth 2.1 認可サーバーとしてすべてを守ります。
graph TD
A1["リモートユーザー A<br/>(Claude Desktop)"]
A2["リモートユーザー B<br/>(Claude.ai コネクタ)"]
B["mcp-stdio serve<br/>OAuth 認可サーバー · セッション管理"]
C1["A 用の stdio 子プロセス<br/>(あなたの MCP サーバー)"]
C2["B 用の stdio 子プロセス<br/>(あなたの MCP サーバー)"]
A1 -- "HTTPS + OAuth" --> B
A2 -- "HTTPS + OAuth" --> B
B -- "stdio" --> C1
B -- "stdio" --> C2
mcp-stdio serve --enable-oauth \
--public-url https://mcp.example.com \
--token-store /var/lib/mcp-stdio/state.json \
-- python -m my_mcp_server
このモードが欲しくなるのは:
- MCP サーバーが stdio 専用で、リモートクライアントから届かせたいとき;
- 複数ユーザーがひとつのデプロイをプロセスを共有せずに使う必要が あるとき——各セッションが専用の子プロセスを持ち、認証済みユーザーに 束縛されます;
- 前段に本物の OAuth が必要だが、エンドポイントひとつのために Keycloak を立てたくはないとき。
→ stdio サーバーを公開する へ。
新しい MCP(2026-07-28)のサーバーを使う¶
普段は何もしなくて構いません。 mcp-stdio はこれまでどおりの話し方で サーバーとやりとりするので、バージョンを上げても挙動は変わりません。
新しい MCP 仕様(2026-07-28)で作られたサーバーにつなぐときは、 フラグをひとつ足すだけで、あとは mcp-stdio が判断します。
mcp-stdio --protocol-era auto https://mcp.example.com/mcp
MCP クライアント側——Claude Desktop でも Claude Code でも——の設定変更は 一切要りません。クライアントは今までどおりの話し方のままで、あいだを mcp-stdio が通訳します。
いま、どちらで話しているか¶
起動時に stderr へ出ます。
[mcp-stdio] protocol era: modern (auto-detected)
| 表示 | 意味 |
|---|---|
protocol era: modern (auto-detected) |
相手は新しいサーバーで、新しいプロトコルで話している |
protocol era: legacy (auto-detected) |
相手は従来のサーバー。これまでと何も変わらない |
| 何も出ない | --protocol-era を付けていないので、従来のプロトコル |
フラグの選び方¶
| 値 | こんなとき |
|---|---|
legacy(デフォルト) |
とくに気にしないとき。これまでとまったく同じ挙動 |
auto |
相手がどちらか分からないとき。起動時に一度だけ尋ねて自動判別する |
modern |
新しいサーバーだと分かっていて、尋ねる手間を省きたいとき |
auto は起動時にリクエストが 1 回だけ増えます。デフォルトにしていない
理由はそれだけです。
古い SSE トランスポートでは効きません
--protocol-era はデフォルトのトランスポート専用です。
--transport sse では無視され、その旨が表示されます。
warning: --protocol-era auto is ignored on --transport sse
(always the pre-Streamable-HTTP legacy transport)
新しいサーバー相手に mcp-stdio が代わりにやること¶
気づかないのが理想ですが、中で何が変わっているか気になる方へ。
- ツール・リソース・プロンプトはこれまでどおり使えます。 新しい仕様は クライアントとサーバーの自己紹介のしかたを組み替えました。その両側を mcp-stdio が引き受けます。
- 通知もちゃんと届きます。 新しいサーバーは長時間つなぎっぱなしの 経路で通知を送ってくるので、mcp-stdio がその接続を保持します。購読中の リソースの更新も同じ経路で届きます。
- サーバーからの問い合わせも動きます。 処理の途中でサーバーが確認や 入力を求めてきたとき、mcp-stdio はそれをクライアントが表示できる いつもの形に変換し、あなたの答えを持って元の処理を続けます。
- キャンセルが本当に止まります。 これまでは応答を握りつぶすだけでしたが、 いまはサーバー側の処理そのものを中断します。ただし途中で打ち切れない 場面もわずかにあります——リクエストとほぼ同時にキャンセルが届いた場合、 応答を返し始める前に処理を終えてしまうサーバー、長いページ分割の一覧の 取得中。その場合でも応答は破棄されるので、キャンセルした結果が 表示されることはありません。
serve で自分のサーバーを公開する場合¶
mcp-stdio serve は同じアドレスで新旧どちらのクライアントにも応答します。
あなたの stdio サーバーはどちらが来たかを知る必要がありません。
mcp-stdio serve -- python -m my_mcp_server
- 新しいクライアントはハンドシェイクもセッションも使わずに接続し、
mcp-stdio があなたのサーバーに代わって応答します。結果をどれくらい
キャッシュしてよいかも含めて返します
(
--cache-ttl-msで調整可能)。 - 従来のクライアントはこれまでとまったく同じで、セッションごとに
隔離された子プロセスがつきます。
--modern-onlyを付けた場合だけは、 従来のクライアントを受け付けずに追い返します。
新しいクライアント向けには、認証ユーザーごとに 1 つ(認証なしで動かして いる場合は共有の 1 つ)あなたのサーバーを起動します。そうしたクライアント には、プロセスを紐づけるためのセッションが無いからです。
一覧が変わったことをクライアントに伝える¶
ツール・プロンプト・リソースの一覧が変わったことをあなたのサーバーが 知らせると、新しいクライアントにもそれが届くようになりました。 クライアントは長時間つなぎっぱなしの経路をひとつ開き、mcp-stdio が あなたのサーバーからの知らせをそこへ流します。あなたの stdio サーバーは これまでどおりの通知を送るだけで、変更は要りません。
この経路を通るのは次の 4 つです。
| サーバーが送るもの | クライアントの動き |
|---|---|
notifications/tools/list_changed |
ツール一覧を取り直す |
notifications/prompts/list_changed |
プロンプト一覧を取り直す |
notifications/resources/list_changed |
リソース一覧を取り直す |
notifications/resources/updated |
その 1 件のリソースを読み直す |
クライアントは必要なものだけを指定でき、指定したものだけが届きます。 同時に開ける本数は 4 本までで、5 本目は待たせずにその場で断ります。
mcp-stdio を停止した場合は「この経路はこれで終わり」とクライアントに 伝えるので、再接続を試みません。あなたのサーバープロセスのほうが落ちた 場合は経路がそのまま切れます。mcp-stdio 自体は生きているので、 クライアントはつなぎ直して取得し直す、という合図になります。
個別のリソースを監視する¶
最後の 1 行だけは少し毛色が違います。通知のオン・オフではなく、 クライアントが「どのリソースを見張りたいか」を名指しするからです。
あなたのサーバーが resources.subscribe を表明している必要が
あります。 表明していれば、mcp-stdio がクライアントの代わりに購読
します——あなたのサーバーには従来どおりの resources/subscribe を送り、
返ってきた notifications/resources/updated を、その URI を要求した
クライアントに届けます。表明していない場合は、その旨をクライアントへの
返答に示し、サーバーが扱えない購読を送りつけることはしません。
知っておくとよいこと:
- 同じリソースを何人が見張っていても、サーバーに伝えるのは 1 回だけ です。 mcp-stdio が人数を数え、最後の 1 人がいなくなったときにだけ 購読を解除します。
- URI は完全一致で照合します。
file:///aとfile:///a/は別々の 購読です。どちらの表記を指すのかをサーバーに問い合わせる手段が無いため です。 - 1 接続あたり 256 件まで。 それを超えた分は切り捨て、実際に受け付けた URI をクライアントに返します。
- 返答はサーバーを待ちません。 mcp-stdio はクライアントに即答し、 購読は裏で進めます。サーバーが遅くても接続が待たされません。その代わり、 サーバーに拒否された購読はログに残るだけでクライアントには伝わりません ——クライアントは「監視中」と受け取ったまま、更新が届かないことになります。
対象外:ログメッセージ
この経路に notifications/message を流すことはありません。仕様が
禁じているうえ、ログ機能は MCP 2026-07-28 で非推奨になったため、
未実装ではなく恒久的な方針です。
サーバーからの mid-call な問い合わせに答える¶
あなたのサーバーがまだ古いプロトコルバージョンのままでも、扱っている
呼び出しの途中で何かを尋ねたいことがあります——入力の要求、sampling
の依頼、クライアントの roots 一覧取得。それに新しいクライアントが答え
られるようにするのが MRTR 経由のこの機能で、クライアント側の
MCP_STDIO_MRTR_STRIP が
逃がしているパターンの逆方向にあたります。
既定では無効で、MCP_STDIO_MRTR_REVERSE_ENABLE
(リファレンス)の裏に隠れています。
未設定のままだと、mcp-stdio は最初からあなたのサーバーに「尋ねられない」
と伝えるので、行儀のよいサーバーは決して尋ねません。有効化すると、
mcp-stdio が起動するすべての子プロセスへのハンドシェイクが実際に変わ
ります——観測可能な変化です。このフラグはリクエストのたびに読み直され
(キャッシュはしません)が、実体は普通の環境変数なので、外部からの変更
が稼働中のゲートウェイに届くのは再起動を経由したときだけです。運用者が
取り下げるときも、変数を外して再起動する、という手段になります。
OAuth 認証済みの呼び出し元にしか使えません。 セッションを持たない
呼び出し元——認証なし、または共有の静的トークン——は他の呼び出し元と
区別する手段が無く、誤った相手の問い合わせに答えてしまうくらいなら
断るほうがましです。フラグを有効にしていても、そうした呼び出し元は
今までどおりの挙動のままです——あなたのサーバーの問い合わせには
-32601 が返ります。一度も尋ねられなかったのと同じです。
クライアント側から見えるもの:発行した呼び出しが、期待していた答えの
代わりに input_required 結果として返ってきます。あなたのサーバーの
問い合わせと、不透明な requestState を伴って。クライアントは
同じリクエストに inputResponses とその requestState を付けて
リトライすることで答えます。mcp-stdio がその答えをあなたのサーバーに
届け、サーバーが処理を終えたら、そのリトライに対して元の結果を返します。
知っておくとよいこと:
- 1つの子プロセスにつき、対象となる呼び出しは同時に1件まで。 1件が
処理中——問い合わせに答える前の in flight 状態でも、答え待ちで park
されている状態でも——の間に同じ子プロセスへ
tools/call・resources/read・prompts/getを送ると503が返ります。先の 1件が解決してからリトライしてください。 - クライアントが戻ってくる保証はありません。 リトライが来ないまま なら、mcp-stdio は最終的にあきらめ、あなたのサーバーの問い合わせに エラーで応答します。永久にブロックさせたままにはしません。
- ブリッジされるのはこの3種類だけです:
elicitation/create、sampling/createMessage、roots/list。それ以外をあなたのサーバーが 呼び出し途中で発行しても、これまでどおり-32601が返ります。
両方いっぺんに¶
ふたつの役割は組み合わせられます。よくあるパターン:運用者が社内サーバー
を serve で公開し、チームの全員が手元のラップトップからクライアント
モードの mcp-stdio で接続する——両端で同じパッケージが、それぞれ OAuth の
自分の側を担当します。
graph TD
A["Claude Desktop"] -- "stdio" --> B["mcp-stdio<br/>(クライアントモード)"]
B -- "HTTPS + OAuth" --> C["mcp-stdio serve<br/>(サーバーモード)"]
C -- "stdio" --> D["あなたの MCP サーバー"]