MCP Stdio について
MCP Stdioは、リモートMCPサーバーへの接続やローカルのstdioサーバー公開に使う双方向ゲートウェイです。OAuthに対応し、クライアントゲートウェイとHTTPサーバーの両方として動作します。
主な機能
- 01stdioとHTTPの双方向ゲートウェイ
- 02Streamable HTTPとSSEに対応
- 03OAuth 2.1クライアント/サーバーモード
- 04Bearerトークンとカスタムヘッダー
- 05リトライ、ページネーション、セッション復旧
活用シーン
- 01MCPクライアントからリモートHTTP MCPサーバーへ接続
- 02ローカルstdioサーバーをHTTPエンドポイントとして公開
- 03リモートサーバーのOAuthやBearer認証を設定
- 04ストリーム切断、期限切れセッション、一覧ページに対処
MCP 対応
ゲートウェイモードでは、stdio経由でMCPクライアントの要求を受け、リモートMCPサーバーへ中継します。serveモードでは、ローカルのstdio MCPサーバーをStreamable HTTPエンドポイントとして公開します。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
ツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
mcp-stdio
English | 日本語
双方向で動作する stdio↔HTTP MCP ゲートウェイです。同じバイナリが MCP-over-HTTP クライアントゲートウェイとして機能し、serve モードでは(オプションの OAuth 2.1 認可サーバーを組み込んで)完全な MCP-over-HTTP サーバーとしても機能するため、MCP 接続のどちら側にも配置できます。
📖 初めての方は、ユーザーガイドから始めてください — クライアントの接続やサーバーの公開に関する、タスク別のドキュメントです。この README は完全なリファレンスです。
概要
クライアントゲートウェイ(デフォルトモード)— stdio → HTTP。 Claude Desktop、Claude Code、OpenAI Codex などの MCP クライアントからは、mcp-stdio はローカルで実行されるセルフホスト可能な MCP サーバーとして認識されます。一方、さまざまな認証方式に対応しながら、すべてのリクエストをリモート MCP サーバーに中継します。
flowchart BT
A[MCP client<br>Claude / Codex / …] <-- stdio --> B(mcp-stdio)
B <== "<b>HTTPS</b><br>Streamable HTTP / SSE<br>Bearer Token<br>Header<br>OAuth" ==> C[Remote<br>MCP Server]
B -. "OAuth 2.1<br>(PKCE)" .-> D[Authorization<br>Server]
D -. callback .-> B
style B fill:#4a5,stroke:#333,color:#fff
ベアラートークン、カスタムヘッダー、OAuth 2.1 の認証情報がリモートサーバーに転送されます。
リバースゲートウェイ(mcp-stdio serve)— HTTP → stdio。 逆方向の動作です。ローカルの stdio MCP サーバー(言語やフレームワークは問いません)を、Streamable HTTP エンドポイントとして公開します。オプションで、ベアラートークン認証または OAuth 2.1 の組み込み認証、OAuth 有効時のユーザーごとのバックエンド分離、再起動後も保持されるトークンストアに対応します。詳しくは、以下のリバースゲートウェイ:serve モードを参照してください。ツール定義がすでに stdio 経由で問題なく動作する場合に、フレームワーク独自の HTTP/OAuth ホスティングレイヤー(たとえば、その部分だけを提供する Python の Web フレームワーク内蔵サーバー)に依存せず、mcp-stdio をその代わりとして使えます。
機能
- 両方のMCPトランスポートに対応 — Streamable HTTP(現行仕様、デフォルト)とSSE(MCP 2024-11-05のレガシー仕様)をサポートし、
--transportで選択できます。SSEパーサーはWHATWG Server-Sent Events仕様に準拠しています。 - OAuth 2.1クライアント — PKCEを使用した組み込みの認可コードフロー、動的クライアント登録、トークン更新、安全なトークン永続化を備えています。MCP認可仕様をセクション単位で完全に実装しています。
- RFC 9728 保護リソースメタデータ
- §3
/.well-known/oauth-protected-resourceを介した認可サーバーの検出 - §3.1 パスベースのリバースプロキシ環境に対応した、パスを考慮するwell-known URLの構築と、ホストルートへのフォールバック。構築したメタデータURLではリソースURLのクエリーコンポーネントを保持します
- §3.3
resourceフィールドの検証 — 不一致の場合は警告して続行します - §5.1
WWW-Authenticate: Bearer resource_metadata=ヒント — 検出の前にサーバーをプローブするため、非標準URLでPRMを公開しているサーバーも、well-knownパスを推測せずに見つけられます
- §3
- RFC 8414 認可サーバーメタデータ
- §3.1 パスコンポーネントを含むissuerのパス挿入を含む、well-known URLの構築
- §3.3
issuerの検証 — クロスオリジンのissuerは拒否し(AS混同ガード)、同一オリジンでの不一致(末尾のスラッシュ/パス/大文字小文字)は警告して続行します - §3 OpenID Connect Discovery 1.0へのフォールバック — OAuthのwell-known URLが404を返した場合、OIDC形式のみを公開するAS(Auth0、Okta、Azure AD、Google)を対象に、
/.well-known/openid-configuration(パス末尾追加とパス挿入)をプローブします
- RFC 8707 リソースインジケーター
- §2 認可、トークン交換、および更新リクエストでの
resourceパラメーター
- §2 認可、トークン交換、および更新リクエストでの
- RFC 7636 PKCE
- §4.1–4.2 86文字の
code_verifierを使用したS256のcode_challenge_method
- §4.1–4.2 86文字の
- RFC 8628 デバイス認可グラント
- §3.1
resourceインジケーター(RFC 8707)を含むデバイス認可リクエスト - §3.4–3.5
authorization_pending/slow_down(間隔を5秒延長)/expired_token/access_deniedを処理するトークンポーリング - DCRでは
grant_typesにurn:ietf:params:oauth:grant-type:device_codeを登録します(RFC 7591 §2)
- §3.1
- RFC 7591 動的クライアント登録
- §3 クライアント登録リクエスト。
token_endpoint_auth_methodはASメタデータのtoken_endpoint_auth_methods_supportedから選択します(none→client_secret_post→client_secret_basicの順に優先) - §3.2.1
client_secret_expires_atの処理 — 有効期限切れ時に自動で再登録します - DCRでの
application_type: "native"(RFC 8252 §8.4/MCP SEP-837):ループバック認可コードフローとヘッドレスデバイスフローはネイティブクライアントであるため、ループバックリダイレクトがRFC 7591のデフォルト"web"として拒否されることはありません
- §3 クライアント登録リクエスト。
- クライアントIDメタデータドキュメント(MCP 2025-11-25/draft-ietf-oauth-client-id-metadata-document-00)
--client-metadata-urlを指定すると、運用者がホストするHTTPSドキュメントURLをclient_idとして提示し、動的クライアント登録を省略します。ASメタデータでclient_id_metadata_document_supportedが(まだ)広告されていなくても、設定されていれば使用されます(暗黙にフォールバックせず警告します)。事前登録済みのclient_id(--client-idまたはMCP_OAUTH_CLIENT_ID)がある場合は、そちらが優先されます(#60)- ホストされたドキュメントの
redirect_urisには、mcp-stdioのループバックコールバックをポート番号なしで含める必要があります(http://127.0.0.1/callback)。実際のコールバックは毎回新しい一時ポートにバインドされ、ASはループバックリダイレクトURIの任意のポートを受け入れる必要があります(RFC 8252 §7.3/§8.4)
- RFC 6749 OAuth 2.0
- §2.3.1
client_secret_basic:パーセントエンコードされた認証情報を含むAuthorization: Basicヘッダー(コード交換、トークン更新、デバイス認可グラントのポーリングに適用)
- §2.3.1
- RFC 6750 ベアラートークンの使用
- §2.1
Authorization: Bearer <token>リクエストヘッダー
- §2.1
- RFC 9728 保護リソースメタデータ
- MCP 2026-07-28サーバーに対応 —
--protocol-era autoを追加すると、mcp-stdioがサーバーに使用しているプロトコルを問い合わせて適応するため、MCPクライアントに変更は不要です。このフラグを指定しなければ動作は変わらないため、安全にアップグレードできます。mcp-stdio serveは、同じアドレスで新旧両方のクライアントに自動で応答します。または--modern-onlyで新しいクライアントのみに限定でき、--modern-idle-ttlでアイドル状態のバックエンドを解放できます。新しいクライアントは、1つの接続を開いたまま、サーバーのツール、プロンプト、リソースのリストの変更や、名前を指定した特定のリソースの更新を受け取ることもできます(サーバーがresources.subscribeを宣言している場合、mcp-stdioがクライアントに代わって購読します)。python-sdk v2.0.0との双方向のエンドツーエンド検証済みです。→ MCP 2026-07-28サーバーとの連携 - バックオフ付き再試行 — 接続エラー時に最大3回再試行します。配信後に発生した可能性のあるトランスポートエラー(低速な
tools/callの読み取りタイムアウトなど)の場合は、既知の再実行安全なメソッド(tools/listやresources/readなどの読み取り専用または冪等なメソッド)のみを再試行するため、そのようなエラーによってtools/callが再実行されることはありません - HTTP 429/503の処理 — 429(Too Many Requests)と503(Service Unavailable)の両方で、
Retry-After(秒数またはHTTP日付)を最大60秒まで尊重します。これらは仕様で認められたRetry-Afterの適用先です(RFC 9110 §10.2.3)。その後、クライアントが判断できるようステータスを返します(modelcontextprotocol/typescript-sdk#1892を参照) - 自動ページネーション(Streamable HTTPトランスポート)—
nextCursorを透過的にたどってtools/list/resources/list/resources/templates/list/prompts/listのページを取得し、1つのレスポンスに統合します。最初のページ以降を破棄するクライアントでも、リスト全体を取得できます(anthropics/claude-code#39586を参照) - ストリーミングの堅牢性 — SSEレスポンスをリアルタイムでストリーミングし、ストリーム途中の切断時には自動再接続します
- 行区切り文字の安全性 — 上流レスポンス内の生の
U+2028/U+2029(JSONでは合法ですが、JavaScriptでは行終端文字)をエスケープし、これらを改行として扱うクライアントによる出力の誤フレーミングを防ぎます。情報は失われません(modelcontextprotocol/typescript-sdk#2155を参照) - 引数の正規化 —
argumentsがnullのtools/callリクエストを{}に書き換え、null形式を拒否する厳格なサーバーでも呼び出しを受け入れられるようにします。デフォルトで有効です。無効にするには--no-normalize-argumentsを指定します(modelcontextprotocol/typescript-sdk#2012を参照) Mcp-Param-*ヘッダー — 2026-07-28サーバーに対して、サーバーがx-mcp-headerでマークしたすべてのツール引数を、仕様で必須のMcp-Param-*ヘッダーに反映します(python-sdk v2サーバーは、これがないtools/callを拒否します)。アノテーションが無効なツールは非表示にし、-32020が返された場合はツールを一度再リストしてから呼び出しを再試行します。--mcp-param-headers alwaysを指定すると、古いプロトコルのセッションでも同様の処理を行います(GitHubのホスト型MCPサーバーなど、そこでヘッダーを必要とするサーバー向け)。offで無効にできます(#459)- キャンセルを考慮したフィルタリング — stdin経由で
notifications/cancelledによりキャンセルされたリクエストIDを追跡し、対応するIDを持つ上流レスポンスが遅れて届いた場合は、クライアントに届く前に破棄します。MCPキャンセル仕様に準拠しています。デフォルトで有効(TTLは60秒)です。無効にするには--no-cancel-filterを指定します(anthropics/claude-code#51073を参照) - SSE処理中エラーの合成 — レガシーSSEトランスポートでは、レスポンスは長時間接続のGETストリームでのみ返されます。そのストリームが切断されると、すでにPOST済みのリクエストはそのままでは永久に応答を待ち続けます。mcp-stdioは現在のストリーム上で処理中のIDを追跡し、切断時に各IDに対するJSON-RPC
-32000エラーを合成します。これにより、クライアントは待ち続けずに再試行できます。また、自動再接続も行います。キャンセル済みのIDはスキップします(anthropics/claude-code#60061を参照) - セッションの復旧 — 404時にMCPセッションIDをリセットして再試行します
- プロトコルバージョンヘッダー —
initializeレスポンスからネゴシエート済みのprotocolVersionを取得し、それ以降のすべてのStreamable HTTPリクエストにMCP-Protocol-Versionを挿入します(MCP仕様改訂版2025-06-18)。このヘッダーを必須とするサーバーでは、初期化後のリクエストが400 Bad Requestで拒否されるのを防ぎます - 401時のトークン更新 — セッション中に期限切れとなったOAuthトークンを自動更新します(OAuthモードのみ)
- リフレッシュトークン失効時の再認可 — リフレッシュ自体が拒否された場合(グラントの取り消しまたは期限切れ、リフレッシュトークンなし)、リレーは起動時と同じ対話型サインインをバックグラウンドスレッドで実行し、そのリクエストに
-32002(「サインインを完了してから再試行してください」)を返します。その後のリクエストでは新しいトークンが使われるため、再起動は不要です。一度にサインインできるのは1つだけです。同じサーバーへのリレープロセス同士は待機し、保存済みトークンを再利用します。失敗した場合は60秒間のクールダウンが始まります。--oauth/--oauth-deviceを指定した場合はデフォルトで有効です。無効にするには--no-oauth-reauthを指定します(#471) - プロアクティブなトークン更新 — バックグラウンドタイマーが、有効期限が切れる直前にOAuthトークンを更新します(先行時間:
--oauth-refresh-leeway)。これにより、トークンの期限切れをトランスポートの401ではなくHTTP 200のツールエラーとして通知するゲートウェイ(AtlassianのMCPゲートウェイなど)でも、長時間のセッションを維持できます。OAuthモードではデフォルトで有効です。無効にするには--no-proactive-refreshを指定します(#242) - 403時のステップアップ認可 —
Bearer error="insufficient_scope"チャレンジに対し、付与済みスコープと必要なスコープの和集合を使って再認可します(RFC 9470/MCPステップアップ。anthropics/claude-code#44652を参照) - コールドスタート(
--oauth-eager) —initializeにローカルで応答し、対話型OAuthフローをバックグラウンドスレッドで実行します。これにより、ブラウザー/SSO/MFAでの30~180秒のログインが、クライアントの約60秒の初期化タイムアウトを超えることを防ぎます。ログイン完了まで、ゲート対象のメソッドは-32002を返し、完了後にnotifications/*/list_changedでクライアントに通知して、利用可能になったリストを取得させます。Streamable HTTPのみが対象です。キャッシュが有効または更新可能な場合は影響しません(#296) - ベアラートークン認証 —
--bearer-tokenフラグまたはMCP_BEARER_TOKEN環境変数で設定できます - カスタムヘッダー —
-H/--headerで任意のヘッダーを渡せます - 正常なシャットダウン — SIGTERM/SIGINTを処理します
- プロキシ対応 — httpxを介して
HTTP_PROXY、HTTPS_PROXY、NO_PROXY環境変数を尊重します - 最小限の依存関係 — httpxのみです。OAuthには標準ライブラリのみを使用します
インストール
pip install mcp-stdio
または、uv を使用します。
uv tool install mcp-stdio
または、インストールせずに直接実行します。
uvx mcp-stdio https://your-server.example.com:8080/mcp
または、Homebrew を使用します。
brew install shigechika/tap/mcp-stdio
クイックスタート
mcp-stdio https://your-server.example.com:8080/mcp
Bearer トークン認証を使用する場合:
# Recommended: use env var (token is hidden from `ps`)
MCP_BEARER_TOKEN=YOUR_TOKEN mcp-stdio https://your-server.example.com:8080/mcp
# Or pass directly (token is visible in `ps` output)
mcp-stdio https://your-server.example.com:8080/mcp --bearer-token YOUR_TOKEN
カスタムヘッダーを使用する場合:
mcp-stdio https://your-server.example.com:8080/mcp --header "X-API-Key: YOUR_KEY"
OAuth 2.1 認証を使用する場合(認証が必要なサーバー向け):
mcp-stdio --oauth https://your-server.example.com:8080/mcp
# With a pre-registered client ID (skips dynamic registration)
mcp-stdio --oauth --client-id YOUR_CLIENT_ID https://your-server.example.com:8080/mcp
OAuth 2.1 デバイス認可グラントを使用する場合(RFC 8628、ヘッドレス/SSH 環境向け):
mcp-stdio --oauth-device https://your-server.example.com:8080/mcp
2024-11-05 SSE トランスポートを使用する従来の MCP サーバーの場合:
mcp-stdio --transport sse https://your-server.example.com:8080/sse
使用前に接続を確認します。
mcp-stdio --check https://your-server.example.com:8080/mcp
# For an SSE server, pass --transport sse so --check runs the legacy
# GET/endpoint/POST handshake instead of a Streamable HTTP probe:
mcp-stdio --check --transport sse https://your-server.example.com:8080/sse
Claude Desktop の設定
claude_desktop_config.json に追加します。
{
"mcpServers": {
"my-remote-server": {
"command": "mcp-stdio",
"args": ["https://your-server.example.com:8080/mcp"],
"env": {
"MCP_BEARER_TOKEN": "YOUR_TOKEN"
}
}
}
}
設定ファイルの場所:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json - Linux:
~/.config/Claude/claude_desktop_config.json
Claude Code の設定
claude mcp add my-remote-server \
-e MCP_BEARER_TOKEN=YOUR_TOKEN \
-- mcp-stdio https://your-server.example.com:8080/mcp
Codex の設定
codex mcp add my-remote-server \
--env MCP_BEARER_TOKEN=YOUR_TOKEN \
-- mcp-stdio https://your-server.example.com:8080/mcp
同じ方法は、stdio サーバーを起動するあらゆる MCP クライアントで使用できます。コマンドに mcp-stdio を指定し、引数にリモート URL(および必要なオプション)を渡します。
使用方法
mcp-stdio [OPTIONS] URL
Arguments:
URL Remote MCP server URL
Options:
--bearer-token TOKEN Bearer token (or set MCP_BEARER_TOKEN env var)
--oauth Enable OAuth 2.1 authentication (browser flow)
--oauth-device Enable OAuth 2.1 Device Authorization Grant (RFC 8628, headless)
--client-id ID Pre-registered OAuth client ID (or set MCP_OAUTH_CLIENT_ID)
--client-metadata-url URL
HTTPS URL of a Client ID Metadata Document you host
(draft-ietf-oauth-client-id-metadata-document-00), used
as client_id instead of Dynamic Client Registration.
Ignored if --client-id is also given (#60)
--oauth-scope SCOPE OAuth scope to request
--oauth-use-id-token Present the OIDC id_token as the Bearer credential
instead of the access_token (AWS Bedrock AgentCore /
Cognito); falls back to access_token if none is returned (#59)
--oauth-eager Cold-start: answer initialize locally and run the
interactive OAuth flow in the background, so a long
browser/SSO/MFA login does not blow the client's ~60 s
initialize timeout. Streamable HTTP only; ignored on
--transport sse. Warm cache unaffected (#296)
--oauth-refresh-leeway SECONDS
Proactively refresh tokens this many seconds before
expiry (default: 60, or MCP_OAUTH_REFRESH_LEEWAY)
--no-proactive-refresh
Disable the background timer that refreshes the OAuth
token before it expires. On by default in OAuth mode;
keeps long sessions alive against gateways that signal
expiry as an HTTP 200 tool-error rather than a 401 (#242)
--no-oauth-reauth Do not sign in again in the background when the refresh
token stops working mid-session (on by default; #471)
--oauth-timeout SECONDS
Seconds to wait for the interactive OAuth flow (browser
callback / device-code confirmation) before giving up
(default: 120; OAuth only)
--no-resource-indicator
Omit the RFC 8707 resource parameter from all OAuth
requests. Required for AS that reject it, such as
Microsoft Entra ID v2 with api:// scopes (AADSTS9010010).
Persisted in the token store so proactive refreshes
and step-up flows stay consistent
--oauth-resource URI Send this exact RFC 8707 resource value on every OAuth
request instead of the server-URL-derived one. Required
for AS that demand a specific resource identifier, e.g.
Microsoft Entra ID's App ID URI api://<app-id>. Persisted
in the token store. Mutually exclusive with
--no-resource-indicator
-H, --header 'Key: Value' Custom header (can be repeated)
--transport {streamable-http,sse}
Transport type (default: streamable-http)
--timeout-connect SEC Connection timeout (default: 10)
--timeout-read SEC Read timeout (default: 120)
--sse-read-timeout SEC Idle read timeout on the SSE GET stream
(default: 300; 0 disables; SSE transport only)
--no-tcp-keepalive Disable TCP keepalive on the HTTP socket
--max-message-size BYTES
Cap on a single upstream response body (JSON or
cumulative SSE stream) buffered before parsing
(default: 10 MiB; 0 disables the cap; #416).
Sends Accept-Encoding: identity by default (#417);
-H 'Accept-Encoding: gzip'/deflate opts back in
with a genuinely size-bounded decoder (#418) —
anything else needs --max-message-size 0.
Also covers this relay's own OAuth traffic (#419)
--no-cancel-filter Disable the cancel-aware response filter (drops late
responses for ids cancelled via notifications/cancelled)
--no-normalize-arguments
Disable rewriting a tools/call request's
arguments:null to {} before forwarding
--check Check connection and exit
-V, --version Show version
-h, --help Show help
フラグごとの詳細は mcp-stdio --help を実行して確認してください(プラットフォームに関する注意事項や課題への参照は、この表より詳しく記載されています)。
リバースゲートウェイ:serve モード
デフォルトのモードでは stdio → HTTP(クライアント側)をブリッジします。serve サブコマンドはその逆の HTTP → stdio で、ローカルの stdio MCP サーバーを Streamable HTTP MCP エンドポイントとして公開します。ローカルでサーバーを起動できないクライアントも、ネットワーク経由でアクセスできます。
flowchart BT
A["MCP client<br>Claude / claude.ai / Codex /<br>Meta Muse<br>(or mcp-stdio --oauth)"]
B("mcp-stdio serve<br><b>HTTP → stdio</b> gateway<br>auth: none / static token /<br>embedded OAuth 2.1 AS")
C["local stdio<br>MCP server"]
A <== "Streamable HTTP<br>Bearer / OAuth 2.1 (PKCE)" ==> B
B <-- "stdio (spawned child)" --> C
これは、冒頭のクライアント側の図を逆にしたものです。そちらでは mcp-stdio は stdio → HTTP、こちらでは HTTP → stdio です。
mcp-stdio serve --port 8080 -- python -m my_mcp_server
その後、任意の MCP クライアント(mcp-stdio 自身を含む)から接続します。
mcp-stdio --check http://127.0.0.1:8080/mcp
- 標準ライブラリのみ(
http.server)を使用するため、実行時の依存関係が増えません。 - Streamable HTTP のリクエスト/レスポンスと通知のセマンティクス、セッション管理に加え、サーバー起点のメッセージ用に GET SSE チャネルを実装しています。
- 認証はオプションで、段階的に設定できます。
- トークンなし — エンドポイントは公開されます(TLS を終端するプロキシの背後で実行してください)。
- 静的トークン(
--auth-token/MCP_STDIO_SERVE_TOKEN)— OAuth リソースサーバーとして動作します。MCP リクエストにはAuthorization: Bearer <token>が必要で、401 レスポンスでは/.well-known/oauth-protected-resourceにある RFC 9728 保護リソースメタデータを通知します。 - 組み込み OAuth AS(
--enable-oauth)— 最小限の OAuth 2.1 認可サーバーです(PKCE 認可コードフロー、§3.2.2 に基づくinvalid_redirect_uriエラーを伴う RFC 7591 動的クライアント登録、更新、メモリー内の不透明なトークンを実装し、標準ライブラリのみを使用します)。https のイシュアーは認可レスポンスに RFC 9207 のissパラメーターを反映し(ミックスアップ攻撃を防御)、メタデータでも通知します。mcp-stdio クライアントの--oauthフローは、このゲートウェイで動作します。
- 組み込み AS のトークンセキュリティ:リソースサーバーはトークンの対象者を検証します(RFC 8707/MCP — 別のリソース向けに発行されたトークンは拒否されます)。提示されたトークンが無効な場合は
error="invalid_token"を返します(RFC 6750 §3.1)。認可コードまたはローテーション済みのリフレッシュトークンが再利用された場合は、猶予期間を短く設け、善意のクライアントによる再試行が罰せられないようにしつつ、許可されたトークン一式を失効させます(RFC 6749 §4.1.2/RFC 9700 §4.14.2)。 - セッションごとのマルチクライアント分離 — MCP セッションごとにバックエンドの子プロセスを起動するため、同時接続するクライアントはプロセス境界によって分離されます(クライアント間で JSON-RPC ID が衝突しても、レスポンスが混在することはありません)。MCP Streamable HTTP 仕様に従い、
initializeでMcp-Session-Idを発行し、以降の各リクエストにはこの ID を含めます。不明または終了済みの ID には404を返し(その後、クライアントは再初期化します)、DELETEで該当セッションの子プロセスを終了します。同時セッション数の上限により、公開ゲートウェイで子プロセスが際限なく起動されるのを防ぎます。OAuth が有効な場合、各セッションは認証済みユーザーに紐付けられます。別のユーザーのトークンで提示されたセッション ID は拒否される(404を返す)ため、ID が漏洩してもテナント間で共有されることはありません。
静的トークンの例(ps に表示されないよう、トークンは環境変数で指定します):
MCP_STDIO_SERVE_TOKEN=your-secret mcp-stdio serve --port 8080 -- python -m my_mcp_server
mcp-stdio --bearer-token your-secret --check http://127.0.0.1:8080/mcp
組み込み OAuth の例です。ユーザー認証は、ログイン済みのユーザーをヘッダーで示す前段のリバースプロキシに委任されます(--trusted-user-header。クライアントから送信された同名のヘッダーを除去するプロキシの背後でのみ信頼してください)。--dev-user はローカルテスト用の安全でないループバック限定のショートカットです。
mcp-stdio serve --enable-oauth --public-url http://127.0.0.1:8080 \
--dev-user alice --port 8080 -- python -m my_mcp_server
mcp-stdio --oauth http://127.0.0.1:8080/mcp
オプション:--host(デフォルトは127.0.0.1)、--port(デフォルトは8080)、--path
(デフォルトは/mcp)、--auth-token TOKEN(またはMCP_STDIO_SERVE_TOKEN、こちらを推奨);
セッションの制限には、--max-sessions N(デフォルトは100。上限を超えたinitializeには
503を返します)、--session-idle-ttl SECONDS(この時間だけ操作のないセッションとその子プロセスを
削除します。クライアントがDELETEを送信せずに切断しても、スロットが占有されたままになりません。
0=無効、デフォルト)、
--max-sessions-per-owner N(新しいinitialize時に、OAuthユーザーの古いセッションをLRU方式で削除し、
DELETEを送信せずに再接続したクライアントが残したゴーストを回収して、セッション数をN以下にします。
0=無効、デフォルト。静的トークンのセッションとオープンゲートウェイのセッションは対象外);
--max-message-size BYTES(宣言されたContent-Lengthがこの値を超えるリクエストを、本文を読み取る前に
413で拒否します。デフォルトは10 MiB、0で上限を無効化。#416);
--user-env VAR(認証済みプリンシパルをこの名前で起動する各子プロセスの環境変数に設定します。
複数ユーザー対応のバックエンドが独自のOAuthスタックなしで呼び出し元のIDを読み取れるようにします。
--enable-oauthが必要です。オープンゲートウェイと共有静的トークンのプリンシパルは、
--max-sessions-per-ownerと同様に対象外です。PATH、LD_PRELOAD、PYTHONPATHなど、
子プロセス自身のランタイムの起動に必要な検索パス/動的リンカ変数名は指定できません);
--drop-client-capability CAP(複数回指定可能。roots、elicitation、samplingのいずれか。
ケイパビリティを宣言しているにもかかわらず応答しないレガシークライアント向けに、
バックエンドが受け取る前にクライアントのinitializeから削除します。claude.aiはrootsを宣言し、
その場合にバックエンドがroots/listを送信するとタイムアウトします。
anthropics/claude-ai-mcp#708、#466);
--accept-claimless-resource-read(プロトコルクレームを含まないセッションレスのresources/readを、
400ではなく2026-07-28リクエストとして処理します。Claude iOSアプリはMCP Appのui://リソースを
この方法で読み取ります。anthropics/claude-ai-mcp#1042、#469);
--allow-origin ORIGIN
(複数回指定可能。Originがループバック、--public-urlのオリジン、またはここで指定したいずれでもない
ブラウザーリクエストには403を返し、ループバックゲートウェイへのDNSリバインディング経路を遮断します。
ブラウザー以外のMCPクライアントが送信するようなOriginヘッダーのないリクエストには影響しません。
CORSヘッダーは追加しません。#449);組み込みAS向けには、
--enable-oauth、--public-url URL(発行者を固定します。プロキシの背後での使用を推奨)、
--trusted-user-header HEADER、--dev-user USER(安全ではありません。テスト専用)、
--access-token-ttl SECONDS、--allow-redirect-uri URL(複数回指定可能。以下を参照)、
--allow-client-id-url URL(複数回指定可能。以下を参照)、
--token-store PATHまたは--token-store-firestore COLLECTION/DOCUMENT
(相互排他的。以下を参照)を指定できます。いずれも指定しない場合、トークンはメモリー内にのみ保存され、
再起動すると無効になります(クライアントは--oauthを再実行します)。バックエンドのコマンドは
オプションの後に続けます(区切りの--は省略可能です)。
- ループバック以外のリモートクライアント — DCR はデフォルトで、RFC 8252 に準拠したループバックの
http://redirect_uriのみを受け付けます。そのため、ブラウザーベースのリモート MCP クライアント(固定の HTTPS OAuth コールバックを持つウェブアプリで、ローカルで実行される CLI/ネイティブアプリではないもの)はこの条件を満たせません。--allow-redirect-uri URL(複数指定可) は、追加のredirect_uriを バイト単位で完全一致する場合に限り 信頼します。ホスト、プレフィックス、 ポートによる照合は行いません。実際に信頼しているクライアントに属すると確認済みの URL のみを追加してください。 各エントリは、ハードコードされたリダイレクト先とまったく同じように信頼されます。ループバックのパスとは 独立しているため、一方を追加しても他方の許容範囲は広がりません。また、--enable-oauthが必要です。 例:https://claude.ai/api/mcp/auth_callback(claude.ai)、https://agent.meta.ai/api/hatch/oauth/callback(Meta Muse)。 - クライアント ID メタデータドキュメント —
--allow-client-id-url URL(複数指定可)は、その HTTPS URL と 完全一致する URL をclient_idとして受け付けます(draft-ietf-oauth-client-id-metadata-document。 MCP 2026-07-28 では DCR よりもこちらが優先されます)。serve は/authorizeでドキュメントを取得し、 そのredirect_urisを登録情報として使用するため、このクライアントには--allow-redirect-uriは不要です。claude.ai の「サインインなし」コネクターには、--allow-client-id-url https://claude.ai/oauth/mcp-oauth-client-metadataが必要です。 許可リストに登録された URL のみを、リダイレクトを行わず、パブリックアドレスからのみ取得します。--enable-oauthが必要です(#463)。 - 再起動後も保持されるトークン —
--token-store PATHは、発行済みトークン、ローテーションの墓標、 クライアント登録情報を JSON ファイルに永続化します(0600で作成され、状態が変わるたびに アトミックに書き込まれます)。これにより、再起動前に有効なトークンを保持していたクライアントは、 新たに対話型の認可を行わなくてもツールを呼び出し続けられ、再起動後に提示されたリフレッシュも 有効になります。これにより、401/invalid_grantで再認可しないリモートクライアントでも、 デプロイが透過的に行われます(そうでなければ、無効になったトークンを延々と再送し、ツールが 知らないうちに使えなくなっても接続中に見えてしまいます)。リフレッシュトークンの再利用検出と グラントファミリーの失効も再起動後に維持されます。消費台帳は永続化された状態の一部です。 このファイルには認証情報が含まれるため、秘密鍵と同様に保護し、各serveプロセスには個別の パスを指定してください。サイドカーの.lockファイルにより、起動時に2つ目のプロセスは拒否されます (1つのストアを共有すると、発行済みトークンが気付かないうちに上書きされます)。また、起動時に 指定先へ試験書き込みを行うため、設定ミスがあれば永続化を黙って無効にするのではなく、起動に失敗します。--enable-oauthが必要です(#277)。 - ローカルディスクを使わずに再起動後も保持されるトークン —
--token-store-firestore COLLECTION/DOCUMENTは、ローカルファイルの代わりに、1つの Firestore ドキュメントへ--token-storeと同じ状態を永続化します(同じ JSON 形式のスナップショットを使用し、 起動時の試験書き込みによるフェイルファスト動作も同じです)。Cloud Run など、永続的なローカルディスクが ないデプロイ向けです。GCP プロジェクトは通常の google-cloud の方法で解決されます (GOOGLE_CLOUD_PROJECT環境変数、または Cloud Run 上の ADC)。個別の--projectフラグはありません。google-cloud-firestoreパッケージ(pip install mcp-stdio[firestore]でインストールする オプションの追加パッケージ。通常のpip install mcp-stdioでは不要です)と--enable-oauthが必要です。--token-storeと異なり、1つのドキュメントを複数のプロセスで共有する場合のロックはありませんが、 各書き込みは無条件の上書きではなく、読み取り・マージ・書き込みのトランザクションを通じて行われます (#406)。そのため、2つの書き込みプロセスが短時間重複しても(たとえば Cloud Run のリビジョン切り替え時)、 重複期間中にどちらかが発行またはローテーションしたトークンが黙って破棄されることはありません。 ただし、残る問題が1つあります。同じ重複期間中に失効された(リプレイが検出された)トークンや、 容量上限のために削除されたトークンは、並行して動作する古い状態の書き込みによって復元される可能性が あります。これらの削除には墓標が付かないためです(#428)。--token-storeとは併用できません。 - パススコープの発行者 —
--public-urlはパスを保持するため、リバースプロキシの背後で複数の--enable-oauthバックエンドを、それぞれ独自のプレフィックス配下で同じホストに配置できます (例:--public-url https://gw.example.org/team-aでhttps://gw.example.org/team-a/mcpを提供)。発行者はhttps://gw.example.org/team-aとなり、AS エンドポイントはプレフィックス配下に置かれます (/team-a/authorize、/token、/register)。また、既知のドキュメントは、 クライアントのパス対応ディスカバリーとバイト単位で対称となる、RFC 8414 §3.1/RFC 9728 §3.1 のルート挿入位置に配置されます (/.well-known/oauth-authorization-server/team-a、/.well-known/oauth-protected-resource/team-a/mcp)。オリジンのみの--public-urlの動作は これまでとまったく同じです(#245)。claude.ai のカスタムコネクターは例外です。claude.ai は現在、 パスが正確に/mcpでないエンドポイントへの接続を完了できません。トークン発行後に MCP リクエストの 送信を停止し(anthropics/claude-ai-mcp#878)、複数セグメントのパスではリクエストが一切送信されません (#738)。そのため、この場合はパスプレフィックスではなく、各バックエンドに個別のホスト名を割り当ててください。
マルチユーザー環境へのデプロイ
serve は複数ユーザーの同時利用を想定して構築されています。各 MCP セッションには、個別に起動されたバックエンドの子プロセスが割り当てられます。また、OAuth が有効な場合は認証済みユーザーに紐づけられるため、ユーザーはプロセス境界によって分離され、漏えいしたセッション ID でテナントをまたぐことはできません。
エンドユーザーのログインは、実際の SSO を実行し、--trusted-user-header を使ってユーザーを通知する前段のリバースプロキシに委任されます(プロキシがクライアントから送信された同名のヘッダーを削除するため、このヘッダーが信頼されます)。組み込みの AS がユーザーごとのトークンを発行し、ゲートウェイは各セッションをそのユーザーに紐づけます。
flowchart TD
UA["User A<br>mcp-stdio --oauth"]
UB["User B<br>mcp-stdio --oauth"]
RP["Reverse proxy<br>SSO login, sets X-Forwarded-User<br>strips any client-supplied copy"]
GW["mcp-stdio serve --enable-oauth<br>--trusted-user-header X-Forwarded-User"]
CA["stdio child<br>session of A"]
CB["stdio child<br>session of B"]
UA == "Streamable HTTP<br>OAuth 2.1 (PKCE)" ==> RP
UB == "Streamable HTTP<br>OAuth 2.1 (PKCE)" ==> RP
RP ==> GW
GW -- "spawn per session" --> CA
GW -- "spawn per session" --> CB
ゲートウェイ(ループバックにバインドし、プロキシの背後に配置):
mcp-stdio serve --enable-oauth \
--public-url https://mcp.example.org \
--trusted-user-header X-Forwarded-User \
--max-sessions 200 --session-idle-ttl 900 --max-sessions-per-owner 1 \
--host 127.0.0.1 --port 8080 -- python -m my_mcp_server
--public-urlは、プロキシが提供する外部 HTTPS URL に発行者を固定します。--trusted-user-headerは、ログイン後にプロキシが設定するヘッダーです。ゲートウェイがこれを信頼するのは、プロキシがクライアントから送信された同名のヘッダーを削除するためです。--max-sessionsはユーザーごとの同時子プロセス数を制限します。--session-idle-ttlは、ユーザーがDELETEを送信せずに切断した後、子プロセスを回収します。--max-sessions-per-ownerは、ユーザーが再初期化した時点で、そのユーザーの以前のセッションを回収します。これにより、DELETEなしで再接続するクライアント(ホステッドコネクターでよくあります)が、残存セッションを残さなくなります。アイドル状態のセッションを回収するだけでなく、再接続時にも回収されるため、--session-idle-ttlを長くしても、残存セッションが--max-sessionsを使い切る心配がありません。そのため、一時停止を挟みながら使うインタラクティブなクライアントでも、頻繁な再初期化を避けられます。
各ユーザーはクライアントの接続先をゲートウェイに設定し、OAuth フローを一度実行します。その後は専用の子プロセスからサービスを受けます。
mcp-stdio --oauth https://mcp.example.org/mcp
補足:
- プロセス境界による分離 — ユーザー A とユーザー B が子プロセスを共有することはありません。そのため、接続ごとのバックエンド状態(または JSON-RPC ID の衝突)がユーザー間で漏れることはありません。
- バックエンドのコマンドはテンプレートです。各セッションでは、同じコマンドを新しい子プロセスとして起動します。ユーザーの識別情報はゲートウェイで適用されます(セッション→ユーザーの紐づけ)。現時点では子プロセスに渡されないため、ユーザーごとのコンテキストが必要なバックエンドでは、リクエストから識別情報を取得するか、バックエンド設定ごとにゲートウェイを実行してください(必要に応じてパスで多重化できます。パススコープの発行者を参照)。
回避策
mcp-stdio が対処する Claude Code、Claude Desktop、mcp-remote、MCP SDK、Windows の既知の問題については、WORKAROUNDS.md を参照してください。
仕組み
--oauth(ブラウザー)または--oauth-device(ヘッドレス、RFC 8628)が設定されている場合、アクセストークンを取得します(キャッシュ済み → 更新 → ブラウザー/デバイスフロー)。- 標準入力から JSON-RPC メッセージを読み取ります(MCP クライアントから送信)。
- HTTPS 経由でリモート MCP サーバーに中継します。
- 応答を解析し、標準出力に書き込みます。
- 401 の場合(OAuth モードのみ)、アクセストークンを更新して再試行します。トークンの更新自体が拒否された場合は、バックグラウンドで再認証を行い、サインインが完了するまで
-32002を返します(#471)。静的な--bearer-token/-H認証の場合は、401 がクライアントにそのまま返されます。 - OAuth モードでは、バックグラウンドタイマーもトークンの有効期限が切れる直前に更新します(
--oauth-refresh-leeway)。これはリクエストの流れとは独立しており、トークンの期限切れを 401 ではなく HTTP 200 のツールエラーとして返すゲートウェイに対して、長時間のセッションを維持します(無効にするには--no-proactive-refreshを指定)。
トランスポートの詳細:
- Streamable HTTP(デフォルト) — 標準入力の各メッセージを単一の POST として送信します。セッション状態は
Mcp-Session-Idヘッダーで追跡され、404 の場合は自動的に再初期化されます。ネゴシエートされたMCP-Protocol-Versionヘッダーは、初期化後のすべてのリクエストで送信されます(仕様リビジョン 2025-06-18)。 - SSE(MCP 2024-11-05 のレガシー方式) — 持続的な
GETストリームで応答と、POST URL を含む最初のendpointイベントを受信します。切断時にはストリームが自動的に再接続します。
OAuth トークンは ~/.config/mcp-stdio/tokens.json に保存されます(アクセス許可 0600)。
ライセンス
MIT
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。