#7827·QwenPaw

[Bug]: `server/discover` 返回裸 HTTP 500(空响应体)未被判定为旧协议证据——DashScope 千问 MCP 商店的 streamable_http 驱动卡永远无法激活,Console 本地报 503

Author: McpyCreated Sep 17, 2026Updated Sep 17, 2026

QwenPaw Version

2.2.1(pip 安装,Linux x86_64);2.2.0 同样适用

Description

配置一个 transport: streamable_http 的 MCP 卡片,指向阿里云 DashScope / 千问AI平台 MCP 商店(One Key MCP)的托管服务时,驱动卡激活失败。Console 里看到的是本地产生的报错:

Failed to list tools for MCP client '<name>': 503: MCP client '<name>' is saved but not active yet (status=inactive)

驱动日志显示:

Driver '<name>' saved but not active yet: MCP server/discover rejected with HTTP 500

根因: DashScope 网关对未知方法 server/discover 的响应是裸 HTTP 500 + 空响应体(完全没有任何 JSON-RPC 信封)。这与 #7728 / #7729 是同一条断链,但载荷形状不同——而且是 #7729 修完之后依然会挂的那一种:

  1. _unwrap_jsonrpc_resultdrivers/handlers/mcp_streamable_http.py):status >= 400 且响应体无法解析为 JSON-RPC 信封 → 直接抛 httpx.HTTPStatusError
  2. _negotiate:旧协议证据白名单是 {400, 404, 405}不含 500 → 不会转成 _LegacyProtocolError → 驱动构建硬失败,legacy stateful client 的降级路径根本没有机会执行。

注意 #7729 的 PR 描述明确写着:"Real server failures (bare 500 ... ) still fail hard." —— 即"裸 500"是被有意保留为硬失败的,所以合并后本场景仍然无法连接。

同会话对照实验(均已验证):

  • 同一端点、同一 Key 走旧协议 initialize(POST,Accept: application/json, text/event-streamAuthorization: Bearer <key>)→ HTTP 200,握手正常完成;
  • 已知方法 tools/list → 200;而任意未知方法(包括瞎编的 some/totallyUnknownMethod)→ 一律裸 500 + 空 body。也就是说"HTTP 500 + 空 body"就是该网关表达 method not found 的方式,并不是服务器真的崩了;
  • 标题里的 503 是 QwenPaw 自己发出的(status=inactive),不是远端服务返回的。

这个问题值得优先处理,因为 DashScope MCP 商店是阿里自家的托管 MCP 服务,与 QwenPaw 同属一个生态——2.2.x 用户接入千问 MCP 商店(如 TextGenerateImage、WebFetch、EnhancedSearch 等)会大面积踩坑。

Component(s) Affected

  • Core / Backend (app, agents, config, providers, utils, local_models)

Environment

  • QwenPaw version: 2.2.1
  • OS: Linux 6.17 (x86_64)
  • Install method: pip
  • Python version: 3.11

Steps to Reproduce

  1. 获取千问AI平台 API Key(Token Plan 格式,sk-ws-...);
  2. 创建 streamable_http MCP 卡片:
    • URL:https://dashscope.aliyuncs.com/api/v1/mcps/TextGenerateImage/mcp
    • Header:Authorization: Bearer <key>
  3. 保存卡片 / 重启服务。驱动状态停留在 inactive;Console 列工具时返回本地 503

纯 curl 复现(不需要 QwenPaw):

bash
# 新协议探测方法 → 裸 HTTP 500,空 body
curl -s -o /dev/null -w "%{http_code}\n" -X POST \
  https://dashscope.aliyuncs.com/api/v1/mcps/TextGenerateImage/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -H 'mcp-protocol-version: 2026-07-28' \
  -H "Authorization: Bearer $KEY" \
  -d '{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{}}'
# → 500

# 同一 URL / 同一 Key 走旧协议 initialize → 正常
curl -s -w "\n%{http_code}\n" -X POST \
  https://dashscope.aliyuncs.com/api/v1/mcps/TextGenerateImage/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -H "Authorization: Bearer $KEY" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'
# → 200,{"jsonrpc":"2.0",...,"serverInfo":{"name":"BaiLianMcpServer","version":"1.0.0"}}

Actual vs Expected

  • Actual: server/discover 收到裸 HTTP 500 → 硬失败(MCP server/discover rejected with HTTP 500);legacy 降级永远不触发;驱动保持 inactive;列工具时返回具有误导性的本地 503,看起来像服务端故障,用户会先去排查服务而不是客户端。
  • Expected: server/discover 无论以何种形态失败,至少应先尝试一次旧协议 initialize 握手再判定。握手结果才是权威仲裁(多一次请求、约 100ms),且在 discover 成功时完全无额外开销。

修复建议(二选一)

  1. 简单方案: 仅在 discover 探测路径上,把 5xx 加入裸状态码的旧协议证据集合({400, 404, 405} → 追加 500, 502, 503)。理由:面对"未知方法"的响应,网关返回的状态码本身不可信——代码里已经对 401 承认了这一点("may be its gateway reporting an unsupported method rather than a real credential failure"),裸 500 是同一个陷阱。
  2. 稳妥方案: 只要 server/discover 失败(任何原因),就降级执行一次 legacy initialize,由它的结果裁决;仅当 initialize 也失败时才向上抛出原始 discover 错误。

另外建议:驱动卡 inactive 时的 503 提示文案里明确标注"这是本地状态,非远端服务错误",避免用户先往服务端排查。

Workaround confirmed

在不改动服务端的情况下,用 stdio 代理桥接可以正常工作:

json
{
  "transport": "stdio",
  "command": "npx",
  "args": ["-y", "mcp-remote",
           "https://dashscope.aliyuncs.com/api/v1/mcps/TextGenerateImage/mcp",
           "--header", "Authorization: Bearer <key>"]
}

mcp-remote 0.14.2 直接走 initialize 协商,返回 200,工具列表正常:bailian_image_gen / bailian_image_edit / bailian_image_fusion。)

Related: #7728(同一断链,但载荷是 Java jsonRpcError 信封形状)、#7729(PR——有意保留"裸 500"硬失败,所以本形状修完后依然挂)、#7330(引入双协议降级逻辑的源头)。