← 返回信息流

dev.to #ai短讯

无人记录的远程 MCP 客户端配置矩阵(以及类型静默失败的三种方式)

dev.to作者:TuringCorp教程AI评分:50/100

作者维护一个使用静态 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——传输层问题:路径错误、类型错误、代理问题,或客户端侧的桥接程序吞掉了请求
200200200服务器未对该调用进行保护,或者你访问的路由与你想象的不同
200401401凭证确实错误或已过期。这是唯一应该重新颁发凭证的情况
200401200服务器和凭证都正确。如果客户端仍然失败,则 bug 出在客户端配置中

还有一个值得牢记的陷阱:tools/list 不是凭证测试。用垃圾头运行 A 行,你仍然会获得包含完整工具列表的 200 响应,因为该方法对所有人开放,且从不检查头部。无论你在其中放入什么,“作为列出调用的认证检查”都会通过。请探测实际的调用路径。

矩阵

同一个端点,同一个密钥,七个客户端,四种截然不同的传输名称。父级键也不同。

客户端配置位置父级键传输字段值
Claude CodeCLI—--transporthttp
Cursor / Windsurf / Claude DesktopJSON 设置mcpServerstypehttp
Clinecline_mcp_settings.jsonmcpServerstypestreamableHttp
VS Code.vscode/mcp.jsonserverstypehttp
Codex~/.codex/config.toml[mcp_servers. ]url + http_headers表键
仅支持 stdio 的主机JSON 设置mcpServerscommand + argsnpx 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

三种类型错误导致的失败方式

这是我一个月前希望读到的部分,因为在这三种情况中,只有一种看起来像是拼写错误。

  1. 明确拒绝。客户端会根据一个封闭集合验证 transport 字段,并告知你该值无效。成本很低。你只需十秒钟即可修复。每个客户端都应该是这样做的。
  1. 静默地被视为 stdio。省略 type 而只留下裸 url,至少有一个客户端家族会将该条目读取为本地 stdio 服务器,然后要么不启动任何进程,要么报告生成失败。配置看起来是正确的,错误信息指向一个不存在的进程,而 URL 从未被拨号连接。如果你的客户端对远程端点显示“服务器已退出”,请在检查其他任何东西之前,先检查 type 是否存在。
  1. 连接成功,但在第一次调用时返回 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 服务器执行耗时数分钟的工作,请将超时时间设置在配置中,并在旁边写明检索路径,因为失败模式是“客户端放弃等待”,除非你去查看,否则这与“服务器失败”无法区分。

复制表格,而不是博客文章

用四步将其泛化到你的服务器中。

  1. 为你实际支持的客户端填写矩阵,使用每个客户端所需的精确传输协议名称。
  2. 将上述三个 curl 命令粘贴到你的安装文档中作为首个诊断步骤,以便用户在提交问题之前就能区分传输协议与凭据问题。
  3. 明确说明哪些方法是无需认证的。tools/list 公开而 tools/call 受保护,这是一个合理的默认发现策略——但这会让“已连接”状态成为一个无意义的信号,提前说明这一点可以节省一类支持工单。
  4. 将超时设置和按 ID 检索的路径放在配置部分,而不是放在无人阅读的独立性能页面中。

我在 https://mcp.turingcorp.net/mcp 的决策端点上运行此矩阵;该表格同样适用于任何具有静态 Bearer 头的远程 MCP 服务器。如果你的客户端不在表中,curl 块仍然能告诉你你属于三种失败类别中的哪一种,这通常就是完整的诊断结果。

译文已达到本站中文翻译的字数上限,剩余内容请查看原文。

阅读原文