agentscope-ai / agentscope-ai/QwenPaw
[Bug]: `server/discover` HTTP 500 with non-standard `jsonRpcError` envelope is not treated as legacy-protocol evidence — Driver build fails against Java MCP SDK servers
- Dominant language
- Python
- Stars
- 34.9k
- Forks
- 3.1k
- Avg merge
- 1d 15h
- Merged PRs (30d)
- 225
Description
## QwenPaw Version
2.2.0 (desktop, macOS arm64)
## Description
An MCP card of type `streamable_http` pointing at a Java/Kotlin MCP SDK server fails Driver construction with `Failed to build Driver '': MCP server/discover rejected with HTTP 500`.
Root cause: the server answers the unknown method `server/discover` with HTTP **500** plus a non-standard error envelope `{"jsonRpcError": {"code": -32603, "message": "Missing handler for request type: server/discover", ...}}` (not the standard `{"jsonrpc": "2.0", "error": {...}}`). Two break points in the chain (`mcp_streamable_http.py` → `_negotiate` → `_rpc`):
1. `_unwrap_jsonrpc_result` (~L395): on `status >= 400` it checks `_is_jsonrpc_envelope(data)` first; the non-standard envelope fails the check → raises `httpx.HTTPStatusError` directly.
2. `_negotiate` (~L710): its `except httpx.HTTPStatusError` branch only classifies bare 400/404/405 as legacy-protocol evidence → 500 is not in the set → exception propagates → entire Driver build fails.
Control group: endpoints returning 400 or a standard `-32601` for `server/discover` fall back to legacy and connect fine. The affected server's standard `initialize` handshake works (HTTP 200, protocol 2025-11-25) — it would work via the legacy `HttpStatefulClient` path if the fallback triggered.
## Component(s) Affected
- [x] Core / Backend (app, agents, config, providers, utils, local_models)
## Environment
- **QwenPaw version:** 2.2.0
- **OS:** macOS (arm64)
- **Install method:** desktop app
## Steps to Reproduce
1. Configure an MCP card with `streamable_http` transport pointing at a Java/Kotlin MCP SDK server.
2. Let QwenPaw build the Driver (startup or card hot-reload).
3. Driver construction fails; no tools are injected.
## Actual vs Expected
- **Actual:** `Failed to build Driver '': MCP server/discover rejected with HTTP 500`; legacy fallback never triggers because HTTP 500 with a `jsonRpcError` envelope is not classified as legacy-protocol evidence.
- **Expected:** the non-standard 500 response is recognized as "server does not support server/discover" and negotiation falls back to the legacy `HttpStatefulClient` path, which then connects successfully via standard `initialize`.
## Logs / Screenshots
```
Failed to build Driver 'mx-ds-mcp': MCP server/discover rejected with HTTP 500
# server response body:
{"jsonRpcError": {"code": -32603, "message": "Missing handler for request type: server/discover", ...}}
```
## Additional Notes
Suggested fix (any one suffices):
1. In `_negotiate`'s `HTTPStatusError` branch, also classify 5xx responses whose error body contains "Missing handler" / "server/discover" evidence as legacy-protocol evidence; or
2. In `_unwrap_jsonrpc_result`, recognize the `jsonRpcError` (Java MCP SDK variant) envelope and convert it to a standard RPC error (code=-32603); or
3. Add an explicit card-level config option such as `negotiation: legacy` / `skip_discover: true` to force the `HttpStatefulClient` path.
Local workaround (deployed and verified): switch the card to stdio transport with a local bridge process (official MCP SDK `streamable_http_client` → stdio server forwarding `list_tools`/`call_tool`), bypassing `server/discover` entirely. Verified: `initialize` OK, full tool list, real queries return.
Contributor guide
Assessment
This issue has not been assessed yet.