dev.to #ai短讯
无人记录的远程 MCP 客户端配置矩阵(以及类型静默失败的三种方式)
作者维护一个使用静态 Authorization: Bearer 头进行认证的远程 MCP 服务器,并测试了 Cursor、Windsurf、Claude Desktop、VS Code 等多个客户端的连接情况。文章指出不同客户端对同一端点的处理方式各异,揭示了在缺乏 OAuth 等标准流程时,远程 MCP 配置中存在的类型检查静默失败问题及桥接 stdio-only 主机的实践。
我维护着一个远程 MCP 服务器,它通过静态的 Authorization: Bearer 头部进行身份验证。没有 OAuth,没有设备流程,也没有浏览器中转。这是一个枯燥的案例,结果证明每个客户端对此都有各自独特的看法。
在将同一个端点连接到 Cursor、Windsurf、Claude Desktop、Claude Code、Cline、VS Code 和 Codex 的一个多月里,我还为仅支持 stdio 的主机编写了一个两行的桥接程序,最终整理出了一张表格。如下所示,这张表的存在是因为相同的 HTTP 传输层根据读取 JSON 的客户端不同,拥有四种不同的名称。
有趣的地方不在于名称不同,而在于错误的名称会以三种完全不同的方式失败,其中两种看起来根本不像配置错误。
首先,在 HTTP 层面将传输层与凭证隔离开来
在接触任何客户端之前,先获取一个答案:该端点能否通过 curl 正常工作?如果可以,那么之后的所有问题都属于客户端配置范畴,你应该停止调试服务器。
export MCP_URL='https://mcp.turingcorp.net/mcp'
# A) 发现阶段,无凭证。设计上就是开放的。此处返回 200 = 传输层正常。
curl -s -o /dev/null -w 'open %{http_code}\n' -X POST "$MCP_URL" \
-H 'content-type: application/json' \
-H 'accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
# B) 故意使用错误的凭证,在调用路径上。必须返回 401。
curl -s -o /dev/null -w 'wrong %{http_code}\n' -X POST "$MCP_URL" \
-H 'content-type: application/json' \
-H 'accept: application/json, text/event-stream' \
-H 'authorization: Bearer definitely-not-a-real-pass' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"<your-tool>","arguments":{}}}'
# C) 真实的凭证,相同的调用。这是唯一能证明端到端正常的行。
curl -s -o /dev/null -w 'real %{http_code}\n' -X POST "$MCP_URL" \
-H 'content-type: application/json' \
-H 'accept: application/json, text/event-stream' \
-H "authorization: Bearer $AGENT_PASS" \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"<your-tool>","arguments":{}}}'将这三个结果视为决策表,而不是简单的通过/失败判断:
| A (开放) | B (错误凭证) | C (真实凭证) | 含义 |
|---|---|---|---|
| 非 200 | — | — | 传输层问题:路径错误、类型错误、代理问题,或客户端侧的桥接程序吞掉了请求 |
| 200 | 200 | 200 | 服务器未对该调用进行保护,或者你访问的路由与你想象的不同 |
| 200 | 401 | 401 | 凭证确实错误或已过期。这是唯一应该重新颁发凭证的情况 |
| 200 | 401 | 200 | 服务器和凭证都正确。如果客户端仍然失败,则 bug 出在客户端配置中 |
还有一个值得牢记的陷阱:tools/list 不是凭证测试。用垃圾头运行 A 行,你仍然会获得包含完整工具列表的 200 响应,因为该方法对所有人开放,且从不检查头部。无论你在其中放入什么,“作为列出调用的认证检查”都会通过。请探测实际的调用路径。
矩阵
同一个端点,同一个密钥,七个客户端,四种截然不同的传输名称。父级键也不同。
| 客户端 | 配置位置 | 父级键 | 传输字段 | 值 |
|---|---|---|---|---|
| Claude Code | CLI | — | --transport | http |
| Cursor / Windsurf / Claude Desktop | JSON 设置 | mcpServers | type | http |
| Cline | cline_mcp_settings.json | mcpServers | type | streamableHttp |
| VS Code | .vscode/mcp.json | servers | type | http |
| Codex | ~/.codex/config.toml | [mcp_servers. ] | url + http_headers | 表键 |
| 仅支持 stdio 的主机 | JSON 设置 | mcpServers | command + args | npx mcp-remote |
头部本身在所有地方都很普通,因此这里是针对 JSON 用户的全部配置:
{
"mcpServers": {
"MyServer": {
"type": "http",
"url": "https://mcp.turingcorp.net/mcp",
"headers": { "Authorization": "Bearer <your pass>" }
}
}
}Cline 需要且仅需要一个字符的差异,并且必须使用无连字符的 camelCase(小驼峰)命名:
{
"mcpServers": {
"MyServer": {
"type": "streamableHttp",
"url": "https://your-server.example.com/mcp",
"headers": { "Authorization": "Bearer <your pass>" },
"disabled": false,
"autoApprove": [],
"timeout": 300
}
}
}VS Code 在 servers 下使用相同的结构体,而不是 mcpServers。Codex 将凭据保留为 TOML 表结构,因此 UI 无需知晓其细节:
[mcp_servers.MyServer]
url = "https://your-server.example.com/mcp"
http_headers = { Authorization = "Bearer <your pass>" }
tool_timeout_sec = 300三种类型错误导致的失败方式
这是我一个月前希望读到的部分,因为在这三种情况中,只有一种看起来像是拼写错误。
- 明确拒绝。客户端会根据一个封闭集合验证 transport 字段,并告知你该值无效。成本很低。你只需十秒钟即可修复。每个客户端都应该是这样做的。
- 静默地被视为 stdio。省略 type 而只留下裸 url,至少有一个客户端家族会将该条目读取为本地 stdio 服务器,然后要么不启动任何进程,要么报告生成失败。配置看起来是正确的,错误信息指向一个不存在的进程,而 URL 从未被拨号连接。如果你的客户端对远程端点显示“服务器已退出”,请在检查其他任何东西之前,先检查 type 是否存在。
- 连接成功,但在第一次调用时返回 401。这是最糟糕的一种。客户端协商使用 SSE 而不是 streamable HTTP,不支持 SSE 的服务器会返回 405(或者客户端不断回退直到某个半可用的服务生效),存活指示器仅通过发现阶段就变绿,而第一次真正的调用失败。当 type 缺失或拼写为 streamable-http 时,Cline 正是如此行为:它回退到 SSE,由于我的端点不提供 SSE,连接以 405 终止,这看起来像是服务器故障,而非一个简单的单词配置修正。
贯穿这三种情况的统一问题是:type 是否存在?它的值是否是该特定客户端所使用的确切词汇?不存在跨客户端的统一拼写。这个词汇是客户端契约的一部分,它应该出现在配置文件中,而不是出现在提示词中,也不是出现在你从其他客户端复制过来的 README 里。
stdio 桥接有其特有的头部陷阱
对于仅支持 stdio 的主机,标准桥接可以工作,但凭据必须通过 --header 传入:
{
"mcpServers": {
"MyServer": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://your-server.example.com/mcp",
"--header", "Authorization:${MY_PASS}"],
"env": { "MY_PASS": "Bearer <your pass>" }
}
}
}两个让我每个都耗费了一晚上的细节。首先,mcp-remote 不会读取 AUTHORIZATION 环境变量,因此仅设置该变量的配置可以连接、列出工具,然后在第一次调用时因 401 失败——再次出现发现/执行分离的情况,这次只是换了个伪装。其次,Authorization: 后面缺少空格是刻意为之:某些主机可能会篡改 args 内部的空格,因此头部是从环境变量和一个无空格的前缀组装而成的。
两个在静默方向上失败的设置
这两者都不会在客户端产生错误消息。
| 设置项 | 错误值的表现形式 | 正确值 |
|---|---|---|
| 调用超时 | 类似 decide 风格的调用在约 60 秒时被截断,随后出现“服务器错误” | 300 秒或更高,单位需遵循客户端文档说明(Cline:秒) |
| 超时后检索 | 产生第二次单独计费的调用 | 使用相同凭据按 job_id 获取,通常保留期为 7 天 |
如果你的 MCP 服务器执行耗时数分钟的工作,请将超时时间设置在配置中,并在旁边写明检索路径,因为失败模式是“客户端放弃等待”,除非你去查看,否则这与“服务器失败”无法区分。
复制表格,而不是博客文章
用四步将其泛化到你的服务器中。
- 为你实际支持的客户端填写矩阵,使用每个客户端所需的精确传输协议名称。
- 将上述三个 curl 命令粘贴到你的安装文档中作为首个诊断步骤,以便用户在提交问题之前就能区分传输协议与凭据问题。
- 明确说明哪些方法是无需认证的。
tools/list公开而tools/call受保护,这是一个合理的默认发现策略——但这会让“已连接”状态成为一个无意义的信号,提前说明这一点可以节省一类支持工单。 - 将超时设置和按 ID 检索的路径放在配置部分,而不是放在无人阅读的独立性能页面中。
我在 https://mcp.turingcorp.net/mcp 的决策端点上运行此矩阵;该表格同样适用于任何具有静态 Bearer 头的远程 MCP 服务器。如果你的客户端不在表中,curl 块仍然能告诉你你属于三种失败类别中的哪一种,这通常就是完整的诊断结果。
译文已达到本站中文翻译的字数上限,剩余内容请查看原文。