菜单

MCP server 连不上?按你看到的报错逐条排查

2026-10-12MCP Checkup

探测器像一个新客户端那样连接我们监测的每个 endpoint:没有账号,没有 token,每次 run 最多 8 个请求。连接失败时,报告页会写明停在哪一步,并给出原因。下面按连接依次经过的顺序列出这些原因、各自出现了多少次,以及在你自己的 server 上看到同样结果的命令。

2,980 个 endpoint 停在哪里

我们取了 2026-10-01 13:00Z 到 2026-10-08 13:00Z 之间探测器检查过的 2,980 个 endpoint,各取最近一次计划内 run,看每个连接最先停在哪一步。

停在哪里 报告上的原因 endpoint 数
没有停:握手完成,工具已列出 无 1,802
握手:需要密钥 credential_required 965
握手:initialize 收到非 200 状态 handshake_initialize_http_error 115
握手:第一个请求收到重定向或 5xx handshake_discover_http_error 61
握手:收尾的通知被拒 handshake_ack_http_error 6
握手:其他 两种原因,各 1 个 2
工具列表:需要密钥 credential_required 8
工具列表:其他 五种原因 6
第一次交换没有完成 四种原因,见下文 15

停在某一步的 1,178 个 endpoint 里,1,149 个停在握手,其中 965 个是有意为之。握手失败时,protocol_revision 会跟着失败,tools_list 通常也会。先修握手。

发送和探测器一样的请求

探测器先发当前修订版的握手 server/discover。如果回应是 4xx,或者是一个不是 discover 结果的 2xx,并且不带当前修订版定义的三个拒绝错误码之一,探测器就改用旧版的 initialize。两个请求你都可以自己发:

URL=https://your-server.example/mcp
INIT='{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl-check","version":"0.1.0"}}}'

curl -sS -i "$URL" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -H 'MCP-Protocol-Version: 2026-07-28' \
  -H 'Mcp-Method: server/discover' \
  -d '{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"curl-check","version":"0.1.0"},"io.modelcontextprotocol/clientCapabilities":{}}}}'

curl -sS -i "$URL" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d "$INIT"

使用旧修订版、工作正常的 server,对第二条命令会返回 HTTP 200,以及一个含 protocolVersion 字符串的 JSON-RPC result,可能是普通 JSON,也可能在事件流的 data: 行里。两条命令都不发送凭据,也不跟随重定向,和探测器一样。如果你的防火墙对陌生客户端另有规则,加上 -A 'MCPCheckup-Probe/1.0 (+https://mcpcheckup.com/probe)',这是探测器发送的 User-Agent。

需要密钥

你的 server 回了 401,并带有一个写明认证方案的 WWW-Authenticate 头。我们从不发送凭据,所以握手和工具列表显示为未检查(Not checked),而不是未通过(Failed),报告上也会说明这看起来是有意的访问控制。如果这正是你的本意,就没有什么要修的。没有密钥时我们还能观测到什么另有一篇文章。查看你的 challenge:

curl -sS -o /dev/null -D - "$URL" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d "$INIT" | grep -i -E '^(HTTP|www-authenticate)'

initialize 收到非 200 状态的 115 个 endpoint 里,有 65 个收到的是 401,却没有我们能读懂的 WWW-Authenticate challenge。没有它,401 就只是一句不带说明的拒绝,握手会记为未通过(Failed)。如果你的 server 需要密钥,请发出 challenge,例如 WWW-Authenticate: Bearer resource_metadata="https://your-server.example/.well-known/oauth-protected-resource"。我们怎么区分一次拒绝和一次失败里逐一讲了各种情况。

initialize 收到非 200 状态

报告上写的是:「我们的 server/discover 请求既没有得到可用的 discover 结果,也没有得到当前 MCP 规范定义的协议错误,因此我们改用旧版 initialize 握手。服务器对 initialize 返回 HTTP 404,而不是 200。」

状态 endpoint 数 通常意味着
401 65 不带 challenge 的拒绝
404 28 这个地址上没有东西响应 POST
403 6 防火墙或访问规则拒绝了请求
202 或 201 4 请求被接受,但响应体里没有回答
410 3 这个地址已经不在了
405 2 这个地址不接受 POST
402 或 451 4 你的 server 自己的策略回答
500 2 server 处理 initialize 时出错
400 1 请求被当作格式错误拒绝

404、405 和 410 说明登记的地址不是你的 MCP server 监听的地方:常见的是登记了站点根路径,而 server 在 /mcp 上,或者反过来。用你登记的那个确切地址跑一遍 initialize 命令;浏览器里能打开一个页面,说明不了什么。遇到 403,就加上我们的 User-Agent 再试:如果只因为这一项,200 就变成了 403,那是某条针对机器人的规则挡住了。202 或 201 且没有 JSON-RPC 回答,往往说明 server 是按旧的 HTTP+SSE 传输方式写的,它会在另一条事件流上发回答。我们的探测器要在 POST 本身的响应里拿到回答。

第一个请求收到重定向或 5xx

报告上写的是:「我们发送了 server/discover 请求。服务器返回 HTTP 502。运行这次检查的探测器版本既没有把这个状态当作 discover 结果,也没有据此改用旧版 initialize 握手,所以握手到此为止。」

这 61 个 endpoint 里,30 个收到 502,16 个收到重定向(307 有 7 个,308 有 6 个,301 有 3 个),11 个收到 523 到 530 之间的状态,4 个收到 500 或 503。

重定向。 探测器从不把 POST 重新发到新地址,不带 -L 的 curl 也一样。看看你被导向哪里:

curl -sS -o /dev/null -w '%{http_code} %{redirect_url}\n' "$URL" \
  -H 'Content-Type: application/json' -d "$INIT"

常见原因是结尾的斜杠多了或少了,或者 http:// 与 https:// 不一致。登记最终的地址,或者就在登记的地址上提供 MCP 服务。

502、500、503。 你的 server 前面的网关没有从它那里得到回答。查一下网关后面的进程当时是否在运行。

523 到 530。 CDN 连不上你的源站、或者无法与源站完成 TLS 握手时,会返回这些非标准状态。我们自己的 tls_certificate 检查在每次 run 上都是未检查(Not checked),因为校验证书链需要另一种执行器,不是我们基于 fetch 的探测器能做的,所以请你自己看:

curl -v -o /dev/null "$URL" 2>&1 | grep -E '^\*'

以 * 开头的行会显示 TLS 握手、证书的主体和到期时间,以及校验是否成功。如果前面有 CDN,也对源站自己的地址跑一遍。

收尾的通知被拒

报告上写的是:「服务器接受了我们的 initialize 请求(旧版握手),但对完成握手所需的 notifications/initialized 通知返回 HTTP 400,而不是 2xx。」共 6 个 endpoint:400 有 3 个,404 有 2 个,500 有 1 个。

如果 initialize 返回了 Mcp-Session-Id 头,探测器会在这条通知里把它带回去。这里出现 400 或 404,往往是会话没找到,例如几个实例之间没有共享的会话存储。对通知的常见回应是 202:

curl -sS -i "$URL" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -H 'Mcp-Session-Id: SESSION_ID_FROM_INITIALIZE' \
  -d '{"jsonrpc":"2.0","method":"notifications/initialized"}'

第一次交换没有完成

共 15 个 endpoint:11 个超时,1 个域名无法解析,1 个 server 要求我们稍后再来,还有 2 个是我们自己的 DNS 解析器没有应答,这与你无关。

时间。 一次 run 总共只有 10 秒。这类问题时有时无:在当前探测器版本的全部 run 里,有 25 个 endpoint 至少有一次在第一次交换完成前就超时,而在最近一次 run 上只有 11 个。常见原因是冷启动和首字节慢。等 server 闲置一段时间后再测,不要只在刚部署完时测:

curl -sS -o /dev/null -w 'tls %{time_appconnect}s, first byte %{time_starttransfer}s, total %{time_total}s\n' "$URL" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d "$INIT"

DNS。 dig +short your-server.example A,以及把 A 换成 AAAA 的同一条命令,都应该返回地址。

稍后再来。 报告上写的是:「对方要求我们稍后再来(HTTP 429,或带 Retry-After 的 503),我们在完成检查前停止了本轮。」我们看到的 Retry-After 从 5 秒到 43,169 秒(约 12 小时)不等。如果你按客户端限流,一次 run 最多 8 个请求。

再查一次之前

首页上的输入框会对你粘贴的任何地址运行同样的检查。结果不签名,同一个主机每 10 分钟最多做一次即时检查,所以改完、等一等、再查。那次检查具体跑了什么另有一篇文章,方法论页解释四种状态,探测说明页说明探测器发送什么。

我们怎么数的

  • 总体:在 2026-10-01T13:00Z 到 2026-10-08T13:00Z 之间至少有一次计划内 run(不含首页检查)的全部 endpoint,共 2,980 个,每个按其最近一次这样的 run 计一次。统计时间 2026-10-08T13:17Z。
  • 这 2,980 次最近的 run 都来自 2026-10-06T21:10Z 起上线的同一个探测器版本。这一周共有 36,702 次计划内 run,探测器换了四次版本;有三种握手原因只出现在 2026-10-03 之前,那时的探测器在这些情况下会停下,现在则会改用 initialize。
  • 「停在哪里」依次看可达性、握手、工具列表三行,第一个为未通过(Failed)或未检查(Not checked)的那一行决定归类。
  • 状态码是随每个原因一起记录的。「至少一次」的计数和 Retry-After 的范围,覆盖当前探测器版本截至 2026-10-08T13:00Z 的全部计划内 run。
  • 局限:一次 run 只是一个时刻,时有时无的问题只会出现在部分 run 上。我们看到的是从我们的网络看到的情况,也看不到 401 后面的东西。