mcpField
M
MCP Stdio

MCP Stdio

本地服务
github.com/shigechika开发工具92 天前更新

关于 MCP Stdio

MCP Stdio 是双向 stdio 与 HTTP 网关,面向需要连接远程 MCP 服务或发布本地 stdio 服务的用户。它支持 OAuth 认证,并可在客户端网关和 HTTP 服务模式间切换。

主要功能

  1. 01双向 stdio 与 HTTP 网关
  2. 02支持 Streamable HTTP 和 SSE
  3. 03OAuth 2.1 客户端与服务端模式
  4. 04Bearer Token 和自定义请求头
  5. 05重试、分页与会话恢复

适用场景

  1. 01让 MCP 客户端访问远程 HTTP MCP 服务
  2. 02将本地 stdio 服务发布为 HTTP 端点
  3. 03为远程服务配置 OAuth 或 Bearer Token
  4. 04应对断流、会话过期和列表分页

MCP 相关

作为客户端网关时,它通过 stdio 接收 MCP 客户端请求并转发到远程 MCP 服务。serve 模式则将本地 stdio MCP 服务暴露为 Streamable HTTP 端点。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"shigechika-mcp-stdio": {
"command": "uvx",
"args": [
"mcp-stdio"
]
}
}
}
启动命令uvx mcp-stdio

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
pypimcp-stdio0.45.2

工具

还没有获取到工具列表

本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。

详细介绍

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 的服务器
    • 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 参数
    • RFC 7636 PKCE
      • §4.1–4.2 使用 S256 code_challenge_method,并采用 86 字符的 code_verifier
    • 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)
    • 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" 那样拒绝回环重定向
    • 客户端 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 标头(用于代码交换、令牌刷新和设备授权授权类型轮询)
    • RFC 6750 Bearer 令牌用法
      • §2.1 Authorization: Bearer <token> 请求标头
  • 兼容 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 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 顺序驱逐,直至数量降至 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 可处理这些问题。

工作原理

  1. 如果设置了 --oauth(浏览器)或 --oauth-device(无头模式,RFC 8628),则获取访问令牌(缓存 → 刷新 → 浏览器/设备流程)
  2. 从 stdin 读取 JSON-RPC 消息(由 MCP 客户端发送)
  3. 通过 HTTPS 将消息转发到远程 MCP 服务
  4. 解析响应并写入 stdout
  5. 仅在 OAuth 模式下,遇到 401 时刷新访问令牌并重试;如果刷新令牌本身被拒绝,则在后台重新授权,并在登录完成前返回 -32002(#471);使用静态 --bearer-token / -H 身份验证时,401 会直接传递给客户端
  6. 在 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

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。