关于 MCP Stdio
MCP Stdio 是双向 stdio 与 HTTP 网关,面向需要连接远程 MCP 服务或发布本地 stdio 服务的用户。它支持 OAuth 认证,并可在客户端网关和 HTTP 服务模式间切换。
主要功能
- 01双向 stdio 与 HTTP 网关
- 02支持 Streamable HTTP 和 SSE
- 03OAuth 2.1 客户端与服务端模式
- 04Bearer Token 和自定义请求头
- 05重试、分页与会话恢复
适用场景
- 01让 MCP 客户端访问远程 HTTP MCP 服务
- 02将本地 stdio 服务发布为 HTTP 端点
- 03为远程服务配置 OAuth 或 Bearer Token
- 04应对断流、会话过期和列表分页
MCP 相关
作为客户端网关时,它通过 stdio 接收 MCP 客户端请求并转发到远程 MCP 服务。serve 模式则将本地 stdio MCP 服务暴露为 Streamable HTTP 端点。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
mcp-stdio
English | 日本語
一个双向工作的 stdio↔HTTP MCP 网关——同一个二进制文件既是 MCP-over-HTTP 客户端网关,也可通过 serve 模式充当完整的 MCP-over-HTTP 服务(可选内嵌 OAuth 2.1 授权服务器),因此可以位于 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
Bearer 令牌、自定义标头和 OAuth 2.1 凭据都会转发到远程服务。
反向网关(mcp-stdio serve)— HTTP → stdio。 反向网关与上述模式相反:它接收本地 stdio MCP 服务(任何语言、任何框架),并将其公开为 Streamable HTTP 端点,支持可选的 Bearer 令牌或内嵌 OAuth 2.1 身份验证、启用 OAuth 时按用户隔离后端,以及可在重启后保留的令牌存储——详见下文反向网关:serve 模式。这样,当工具定义本身已能通过 stdio 正常运行时,mcp-stdio 就可以取代框架自带的 HTTP/OAuth 托管层(例如,无需仅为这一部分依赖 Python Web 框架自带的服务器)。
功能
- 支持两种 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=提示——在发现前探测服务器,以便无需猜测 well-known 路径即可找到在非标准 URL 发布 PRM 的服务器
- §3 通过
- RFC 8414 授权服务器元数据
- §3.1 构造 well-known URL,包括为包含路径组件的发行者插入路径
- §3.3
issuer验证——拒绝跨源发行者(防止 AS 混淆),同源不匹配(尾随斜杠/路径/大小写)时发出警告并继续 - §3 OpenID Connect Discovery 1.0 回退——OAuth well-known 返回 404 时,探测
/.well-known/openid-configuration(路径追加和路径插入),适用于只公开 OIDC 形式的 AS(Auth0、Okta、Azure AD、Google)
- RFC 8707 资源指示符
- §2 在授权、令牌交换和刷新请求中使用
resource参数
- §2 在授权、令牌交换和刷新请求中使用
- RFC 7636 PKCE
- §4.1–4.2 使用 S256
code_challenge_method,并采用 86 字符的code_verifier
- §4.1–4.2 使用 S256
- 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 客户端注册请求;根据 AS 元数据中的
token_endpoint_auth_methods_supported选择token_endpoint_auth_method(优先顺序为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 客户端注册请求;根据 AS 元数据中的
- 客户端 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 Bearer 令牌用法
- §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回收空闲后端。较新的客户端还可以保持一个连接打开,并在服务的工具、提示词或资源列表发生变化,或其指定的某个资源更新时收到通知(当你的服务声明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(请求过多)和 503(服务不可用)均遵照
Retry-After(秒数或 HTTP 日期),最长等待 60 秒;这两种状态是规范允许携带 Retry-After 的状态(RFC 9110 §10.2.3)。之后会向客户端返回状态,由客户端决定如何处理(参见 modelcontextprotocol/typescript-sdk#1892) - 自动分页(Streamable HTTP 传输)— 自动跟进
tools/list/resources/list/resources/templates/list/prompts/list的nextCursor,并将各页合并为一个响应,因此即使客户端丢弃第一页之后的内容,也能看到完整列表(参见 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时重新列出一次工具并重试调用;对于仍要求旧版协议会话携带这些标头的服务(GitHub 托管的 MCP 服务),--mcp-param-headers always也会执行相同操作;设为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回复该请求(“完成登录后重试”);之后的请求会使用新令牌,无需重启。同一时间只进行一次登录:同一服务的中继进程会互相等待并复用已存储的令牌;尝试失败后会进入 60 秒冷却期。使用--oauth/--oauth-device时默认开启;可通过--no-oauth-reauth关闭(#471) - 主动刷新令牌 — 后台计时器会在 OAuth 令牌过期前不久刷新令牌(提前时间:
--oauth-refresh-leeway),让长期运行的会话能够兼容将令牌过期报告为 HTTP 200 工具错误而非传输 401 的网关(例如 Atlassian 的 MCP 网关);OAuth 模式下默认开启,可通过--no-proactive-refresh关闭(#242) - 收到 403 时升级授权 — 收到
Bearer error="insufficient_scope"挑战时,针对已授予和所需范围的并集重新授权(RFC 9470 / MCP step-up;参见 anthropics/claude-code#44652) - 冷启动(
--oauth-eager) — 在本地响应initialize,并在后台线程运行交互式 OAuth 流程,因此耗时 30–180 秒的浏览器/SSO/MFA 登录不会超出客户端约 60 秒的初始化超时。在登录完成前,受限方法会返回-32002;登录完成后,notifications/*/list_changed会通知客户端获取现已可用的列表。仅限 Streamable HTTP;缓存有效或可刷新的情况下不受影响(#296) - Bearer 令牌身份验证 — 通过
--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 授权码、RFC 7591 动态客户端注册,并按 §3.2.2 返回invalid_redirect_uri错误、刷新、不透明的内存令牌,仅使用标准库)。https 颁发者会在授权响应中回显 RFC 9207iss参数(防范混淆攻击),并在元数据中发布该参数。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
顺序驱逐,直至数量降至 N,回收客户端重新连接却未发送 DELETE 留下的孤儿会话;0 =
禁用,默认值;静态令牌和开放网关会话不受限制);
--max-message-size BYTES(如果请求声明的 Content-Length
超过此值,则在读取任何请求正文前返回 413;默认 10 MiB,
0 表示禁用限制;#416);--user-env VAR(将经过身份验证的主体注入每个已启动
子进程的环境,并使用此名称,这样支持多用户的后端无需使用自己的 OAuth
堆栈即可读取调用者的身份——需要 --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
请求作为 2026-07-28 请求处理,而不是返回 400——Claude iOS 应用会以这种方式读取 MCP App ui:// 资源,
anthropics/claude-ai-mcp#1042,#469);
--allow-origin ORIGIN
(可重复;如果浏览器请求的 Origin 既不是回环地址,也不是 --public-url 的源,
且不在这些源之中,则返回 403,从而阻断指向回环网关的 DNS 重绑定路径;不带 Origin 标头的请求
(非浏览器 MCP 客户端会这样发送)不受影响;此选项不会添加 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 回调的 Web 应用,而非本地运行的 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 作为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文件会在启动时阻止第二个进程运行(共享同一个存储会悄然覆盖已签发的令牌),而且启动时会尝试写入该路径,因此目标配置错误会导致启动失败,而不会悄然禁用持久化功能。需要--enable-oauth(#277)。 - 重启后保留令牌,无需本地磁盘 —
--token-store-firestore COLLECTION/DOCUMENT会将与--token-store相同的状态(相同的 JSON 结构快照,以及启动时探测写入、失败即快速报错的行为)持久化到一个 Firestore 文档,而非本地文件,适用于没有持久本地磁盘的部署环境(如 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不同,多个进程共享同一文档时不会通过锁加以阻止,但每次写入都会通过读-合并-写事务进行,而非盲目覆盖(#406),因此,两个写入者短暂重叠(例如 Cloud Run 版本切换)时,任一方在重叠期间签发或轮换的令牌都不会被悄然丢弃。但仍存在一个缺口:在同一重叠窗口中被撤销(检测到重放)或因容量限制而被清出的令牌,仍可能被并发的过期写入者恢复,因为这些删除操作没有墓碑(#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限制每位用户可同时运行的子进程数量;若用户断开连接但未发送DELETE,--session-idle-ttl会回收相应子进程。--max-sessions-per-owner会在用户重新初始化时立即回收其之前的会话,因此,客户端重连时未发送DELETE(托管连接器中很常见)也不会留下僵尸会话。由于僵尸会话会在重连时回收,而不仅仅由空闲回收器处理,因此延长--session-idle-ttl不再会导致僵尸会话占满--max-sessions;这也让有暂停间隔的交互式客户端无需频繁重新初始化。
每位用户将自己的客户端指向网关,运行一次 OAuth 流程,即可由专属子进程提供服务:
mcp-stdio --oauth https://mcp.example.org/mcp
注意:
- 通过进程边界隔离——用户 A 和用户 B 永远不会共用同一个子进程,因此每个连接的后端状态(或 JSON-RPC ID 冲突)都不会泄漏给其他用户。
- 后端命令是一个模板:每个会话都会以全新的子进程运行相同的命令。身份验证由网关负责(会话→用户绑定);目前不会将身份信息注入子进程,因此需要按用户提供上下文的后端应从请求中获取,或者为每种后端配置运行一个网关(可按路径进行多路复用——参见路径范围的签发者)。
变通方案
有关 Claude Code、Claude Desktop、mcp-remote、MCP SDK 和 Windows 中已知的问题,请参见 WORKAROUNDS.md;mcp-stdio 可处理这些问题。
工作原理
- 如果设置了
--oauth(浏览器)或--oauth-device(无头模式,RFC 8628),则获取访问令牌(缓存 → 刷新 → 浏览器/设备流程) - 从 stdin 读取 JSON-RPC 消息(由 MCP 客户端发送)
- 通过 HTTPS 将消息转发到远程 MCP 服务
- 解析响应并写入 stdout
- 仅在 OAuth 模式下,遇到 401 时刷新访问令牌并重试;如果刷新令牌本身被拒绝,则在后台重新授权,并在登录完成前返回
-32002(#471);使用静态--bearer-token/-H身份验证时,401 会直接传递给客户端 - 在 OAuth 模式下,后台计时器还会在令牌过期前不久主动刷新令牌(
--oauth-refresh-leeway),且不依赖请求流程——这样可确保长会话在令牌过期被网关报告为 HTTP 200 工具错误而非 401 时仍能保持运行(使用--no-proactive-refresh可关闭此功能)
传输详情:
- Streamable HTTP(默认)——每条 stdin 消息对应一次 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 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。