[Bug]: `server/discover` 返回裸 HTTP 500(空响应体)未被判定为旧协议证据——DashScope 千问 MCP 商店的 streamable_http 驱动卡永远无法激活,Console 本地报 503
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 修完之后依然会挂的那一种:
_unwrap_jsonrpc_result(drivers/handlers/mcp_streamable_http.py):status >= 400且响应体无法解析为 JSON-RPC 信封 → 直接抛httpx.HTTPStatusError;_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-stream,Authorization: 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
- 获取千问AI平台 API Key(Token Plan 格式,
sk-ws-...); - 创建
streamable_httpMCP 卡片:- URL:
https://dashscope.aliyuncs.com/api/v1/mcps/TextGenerateImage/mcp - Header:
Authorization: Bearer <key>
- URL:
- 保存卡片 / 重启服务。驱动状态停留在
inactive;Console 列工具时返回本地503。
纯 curl 复现(不需要 QwenPaw):
# 新协议探测方法 → 裸 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 成功时完全无额外开销。
修复建议(二选一)
- 简单方案: 仅在 discover 探测路径上,把 5xx 加入裸状态码的旧协议证据集合(
{400, 404, 405}→ 追加500, 502, 503)。理由:面对"未知方法"的响应,网关返回的状态码本身不可信——代码里已经对 401 承认了这一点("may be its gateway reporting an unsupported method rather than a real credential failure"),裸 500 是同一个陷阱。 - 稳妥方案: 只要
server/discover失败(任何原因),就降级执行一次 legacyinitialize,由它的结果裁决;仅当 initialize 也失败时才向上抛出原始 discover 错误。
另外建议:驱动卡 inactive 时的 503 提示文案里明确标注"这是本地状态,非远端服务错误",避免用户先往服务端排查。
Workaround confirmed
在不改动服务端的情况下,用 stdio 代理桥接可以正常工作:
{
"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(引入双协议降级逻辑的源头)。
Source: agentscope-ai/QwenPaw