Repository navigation
Description
Issue Origin
Observed or reproduced in a real environment
Bug Description
POST /mcp answers the 2026-07-28-era probe server/discover — and any unknown method —
with JSON-RPC -32602 Invalid request parameters instead of -32601 Method not found.
This is an upstream defect in the pinned MCP Python SDK, not an OpenViking logic bug:
- Malformed requests get -32602 where JSON-RPC 2.0 requires -32600 (non-Request body) and -32601 (unknown method) modelcontextprotocol/python-sdk#3557 (open,
bug/v1/v2) — case 2, still present on 2.2.0 - fix(queue): recover stale non-tree lock handoffs #3193 (closed 2026-07-29) and feat(examples): add Chroma + OpenViking minimal integration example #1561 (closed 2026-06-11) — earlier reports of the same path
- PR fix(server): default OAuth client scope for scope-less DCR (ChatGPT invalid_scope) #3210 implemented the fix and was closed by the
missing-issue-linkbot rule
Root cause: mcp/shared/session.py maps any request-union validation failure to
INVALID_PARAMS, so "method does not exist" never reaches the METHOD_NOT_FOUND branch.
OpenViking pins mcp<2 (open PR #4651), so 1.29.0 is affected.
Client-visible impact with an MCP 2.x client: because a probe has no parameters to fix, the
client does not treat -32602 as the definitive legacy-fallback signal and re-sends the
probe 17 times per connection before falling back to the 2025-era initialize
handshake. The connection then succeeds (era legacy, 15 tools), so this is a
compatibility/noise defect rather than a hard failure — but every connect costs 17 extra
round trips and 17 WARNING Failed to validate request lines.
Steps to Reproduce
# 1) modern probe
curl -s -i -X POST http://127.0.0.1:1933/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{}}'
# HTTP/1.1 200 ... text/event-stream
# data: {"jsonrpc":"2.0","id":1,"error":{"code":-32602,"message":"Invalid request parameters","data":""}}
# 2) any unknown method — same answer, proving it is one code path
curl -s -X POST http://127.0.0.1:1933/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":2,"method":"totally/unknown","params":{}}'
# 3) control: the legacy handshake down-negotiates correctly
curl -s -X POST http://127.0.0.1:1933/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":3,"method":"initialize","params":{"protocolVersion":"2026-07-28","capabilities":{},"clientInfo":{"name":"probe","version":"0"}}}'
# data: {"jsonrpc":"2.0","id":3,"result":{"protocolVersion":"2025-11-25",...,"serverInfo":{"name":"openviking","version":"1.29.0"}}}Expected Behavior
-32601 Method not found for methods outside the handler registry, keeping -32602 for
genuine parameter errors, so modern clients take the legacy path on the first response.
Actual Behavior
-32602 for every unknown method; the client retries the probe until it gives up and falls
back. Measured A/B with the same client and parameters: 17 probes on the wire with the
current server, 0 when the same method is answered -32601; both connect successfully
and list the same 15 tools.
Minimal Reproducible Example
### Minimal Reproducible Example
**A. Server-side: the defect itself (no dependencies, ~10 s)**
# 1) the modern-era probe → HTTP 200 + JSON-RPC -32602 (should be -32601)
curl -s -i -X POST http://127.0.0.1:1933/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{}}'
# HTTP/1.1 200 OK, content-type: text/event-stream
# data: {"jsonrpc":"2.0","id":1,"error":{"code":-32602,"message":"Invalid request parameters","data":""}}
# 2) any unknown method → the SAME code, i.e. this is one generic path, not a server/discover special case
curl -s -X POST http://127.0.0.1:1933/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":2,"method":"totally/unknown","params":{}}'
# data: {"jsonrpc":"2.0","id":2,"error":{"code":-32602,"message":"Invalid request parameters","data":""}}
# 3) control: the legacy handshake itself is fine and down-negotiates correctly
curl -s -X POST http://127.0.0.1:1933/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":3,"method":"initialize","params":{"protocolVersion":"2026-07-28","capabilities":{},"clientInfo":{"name":"probe","version":"0"}}}'
# data: {"jsonrpc":"2.0","id":3,"result":{"protocolVersion":"2025-11-25",...,"serverInfo":{"name":"openviking","version":"1.29.0"}}}
One `WARNING Failed to validate request` line is appended to the server log per attempt
(`PingRequest.method Input should be 'ping'`, `input_value='server/discover'`).
**B. Client-side: the impact, on the stdio transport (this is the topology DSH uses)**
Requires an MCP client SDK from the 2026-07-28 era; we used
`@modelcontextprotocol/client@2.0.0`. Any stdio↔HTTP bridge in front of `/mcp` reproduces
it; we used the OpenViking DSH plugin's own proxy
(`node_modules/@openviking/dsh-memory-plugin/servers/mcp-proxy.mjs`).
// repro.mjs — node repro.mjs
import { Client } from '@modelcontextprotocol/client';
import { StdioClientTransport } from '@modelcontextprotocol/client/stdio';
const PROXY = process.argv[2]; // path to @openviking/dsh-memory-plugin/servers/mcp-proxy.mjs
const client = new Client(
{ name: 'dsh-mcp-client', version: '0.0.1' },
{ capabilities: {}, versionNegotiation: { mode: 'auto' } }, // the mode DSH uses
);
await client.connect(new StdioClientTransport({
command: 'node', args: [PROXY], env: { ...process.env, HOME: process.env.HOME },
}));
console.log('era =', client.getProtocolEra?.(), 'tools =', (await client.listTools()).tools.length);
await client.close();
Measure the probe storm by counting the server-side log around one connect:
before=$(grep -c 'server/discover' /tmp/openviking-server.log)
node repro.mjs /path/to/@openviking/dsh-memory-plugin/servers/mcp-proxy.mjs
sleep 4 # let the server flush
after=$(grep -c 'server/discover' /tmp/openviking-server.log)
echo "sent $(($after - $before)) server/discover probes for ONE connect"
Observed on OpenViking 0.4.15 / mcp 1.29.0:
era = legacy tools = 15
sent 17 server/discover probes for ONE connect
The connection still succeeds (era `legacy`, all 15 tools), so this is a compatibility/noise
defect: 17 wasted round trips and 17 misleading `WARNING` lines per connect, with no
diagnostic in `error.data`.
**C. Control: the error code is the cause**
Insert a shim between the client and `/mcp` that answers `server/discover` locally with
`{"jsonrpc":"2.0","id":<same id>,"error":{"code":-32601,"message":"Method not found"}}` and
forwards everything else unchanged. Same client, same options:
era = legacy tools = 15
sent 0 server/discover probes for ONE connect
Same successful connection, zero probe retries — i.e. `-32602` (not the transport, not the
server's capabilities) is what makes the client keep re-probing.
**Scope note (verified):** the probe storm is transport-dependent. With the *same* client and
the *same* `versionNegotiation: {mode: 'auto'}` over `StreamableHTTPClientTransport` pointed
directly at `http://127.0.0.1:1933/mcp`, we measured **0** probes and a successful connect.
The stdio path is the affected one (and it is the path DSH uses); #4830 shows an HTTP-side
client (ChatGPT Secure MCP Tunnel) sending `server/discover` as well.Error Logs
### Error Logs
WARNING Failed to validate request
PingRequest.method Input should be 'ping'
input_value='server/discover'
session.py:383
One line per probe attempt; on one local install the `server/discover` occurrences grew
102 → 136 → 170 across a handful of client connects (~17 per connect).OpenViking Version
0.4.15 (reports serverInfo.version = "1.29.0", i.e. MCP Python SDK 1.29.0)
Python Version
3.10
Operating System
Linux
Model Backend
Other
Additional Context
Severity: connections still succeed, so this is a compatibility/noise defect. It becomes
user-visible for clients that pin the modern era, use tighter timeouts, or surface the last
negotiation error — and the log line actively misleads ("Failed to validate request" reads
like a malformed client request, not "unknown method").
Options
- Local workaround (no upstream dependency): resolve the method against the handler
registry before request validation and answer-32601for anything unknown. Suggested
regression tests:server/discoverand an arbitrary unknown method both return-32601;
a genuine parameter error still returns-32602;initializewith2026-07-28keeps
down-negotiating to2025-11-25andtools/liststill returns all 15 tools. - Upstream: follow refactor(pathlock):using rust implement instead python #3557; the fix already exists as PR fix(server): default OAuth client scope for scope-less DCR (ChatGPT invalid_scope) #3210 (closed for a missing issue
assignment). Alternatively relax the dependency bound tomcp>=1.27,<3(PR chore(deps): update mcp requirement from <2,>=1.27 to >=1.27,<3 #4651) — per
the SDK v2 notes, oneMCPServerthen serves the 2026-07-28 revision and every 2025-era
client from the same server.
Related
- Malformed requests get -32602 where JSON-RPC 2.0 requires -32600 (non-Request body) and -32601 (unknown method) modelcontextprotocol/python-sdk#3557 (open), fix(queue): recover stale non-tree lock handoffs #3193 / feat(examples): add Chroma + OpenViking minimal integration example #1561 (closed), PR fix(server): default OAuth client scope for scope-less DCR (ChatGPT invalid_scope) #3210 (closed)
- This repo: bug: X-Request-ID containing '/' is rejected with 400 INVALID_ARGUMENT, breaking OpenAI Secure MCP Tunnel connectors #4830 (closed) documented "ChatGPT sends server/discover then initialize",
but fixed theX-Request-IDmiddleware; feat(hermes-plugin): use OpenViking MCP tools #5646 validates real-service checks with an
MCP 2.0.0 client, so this client generation is already in use.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsIn progress
Activity
XHR666 commentedon Oct 11, 2026
1. Reporter environment (measured, not from memory)
Re-checked live on exactly this stack while writing this comment (still reproducible):
2. Impact on DSH (why this is not ignorable for us)
DSH reaches this endpoint over a stdio MCP proxy, i.e. the transport where the probe storm reproduces
(per the scope note above: StreamableHTTP straight to /mcp measured 0 probes, stdio measured 17). Concretely:
2025-era initialize handshake — a fixed latency tax on every session start / reconnect, multiplied by the
number of memory-plugin connections a session opens.
WARNING Failed to validate requestlines(PingRequest.method Input should be 'ping', input_value='server/discover'). They read like malformed client
requests, so they pollute our logs and make real client-side bugs harder to triage — and error.data is empty,
so nothing distinguishes "unknown method" from "bad parameters".
tighter timeouts, or surfaces the last negotiation error to the user turns this into a visible connect
failure, even though the connection would have succeeded after the fallback. We currently rely on
versionNegotiation: { mode: 'auto' }, so we sit on the "noise + latency" side of that line — but the margin is
set by the upstream SDK, not by us.
3. User-visible blocker observed on DSH (severity is higher than "noise")
Beyond the 17 probes, we have hit a state where the DSH chat itself becomes unusable while the OpenViking
plugin is active:
no reply appears, and the request never reaches the model (no "deep thinking" phase starts).
permanently disabled — the user can no longer send anything until the plugin is detached / the session is reset.
plugin restores normal sending.
Honest caveat (please treat as unconfirmed): we have not yet isolated whether this blocker is caused by
this negotiation defect specifically, by the probe storm exhausting a timeout/retry budget in the client, or by a
separate plugin-side issue. What we can state is the observed behavior above, on this exact stack, with the
client's versionNegotiation mode 'auto' going down the stdio path. If the client blocks session readiness on
negotiation completion, the extra 17 re-probes are a plausible amplifier — but that is a hypothesis, not a
confirmed causal chain, and we would rather report it plainly than overstate it.
Suggested next step for whoever picks this up: while the fix from Option 1 (answer
-32601for unknown methods)lands, it would help to know whether the client treats a repeated -32602 during negotiation as "keep waiting"
rather than "fall back now" — that is the part that would explain a queue that never drains.
r266-tech commentedon Oct 11, 2026
I checked the client fallback question independently with
@modelcontextprotocol/client@2.0.0, Node 26.8.2, and the production shared proxy core at4c9a755b(unchanged from the fetched checkout). The HTTP endpoint was a synthetic legacy server returning the exact error shape shown here, followed by a valid legacy initialize/tools response.For both
-32602and-32601, withprobe.maxRetriesset to either 0 or 16, the upstream trace was oneserver/discover→initialize→notifications/initialized→tools/list. All four controls resolvedconnect()with eralegacyand returned the fixture tool. In this published client,classifyRpcError()falls back for either code;negotiateEra()repeats on probe timeouts, not a received-32602.This does not reproduce your DSH UI or live OpenViking environment, and does not excuse the server's wrong JSON-RPC code. It does mean the 17 attempts / readiness blockage need the exact loaded client build and connection trace before attributing them to error-code retries. Could you provide the installed client package version/resolved path and whether those 17 requests come from one child PID/connection or repeated connections? A timeout or host-level retry could produce a different path.
AI-assisted investigation; no live DSH queue reproduction or server fix claimed.
XHR666 commentedon Oct 11, 2026
Thanks — your two readings check out on our side, and I'm withdrawing my earlier attribution.
Loaded build (exact): DSH 0.2.0-rc.2 on Node v24.19.0 (note: not 26.8.2) → @deepseek-ai/dsh-mcp-client@0.2.0-rc.2 (lib/index.js sha256 758d8fc473b700b5…) → @modelcontextprotocol/client@2.0.0, resolved to /opt/node/lib/node_modules/@deepseek-ai/dsh/node_modules/@modelcontextprotocol/client (nested under dsh, not a hoisted copy). dist/index.cjs sha256 60dc3a0f0f1cdafc… (246,150 B), dist/index.mjs af3ec9fcf3a70fb4…. Your probe.maxRetries-is-timeouts-only reading matches our installed dist/index.d.ts:1655-1657 and index.cjs:2693-2702.
Where the retries come from: DSH's own reconnect supervisor, not the SDK probe — our harness log shows mcp-client(openviking): connection failed; retrying in 500ms (attempt 1/10) → 1000ms (2/10) → 2000ms (3/10), with maxAttempts defaulting to 10 and exhaustion unregistering the tool (dsh-mcp-client/lib/types/connection.d.ts:24-29; one attempt = "fresh transport + client", lib/index.js:586).
One PID vs repeated connections: the transport is StreamableHTTP to
http://127.0.0.1:1933/mcp
— no command:/stdio — so there is no per-attempt child PID; each attempt is a new connection to the same URL. Your hypothesis about a timeout/host-level retry path is live on our side too: the server log is dominated by JSONDecodeError (6,136) and ExceptionGroup (3,068), so transport-level failures are plausible confounders.
On the "17": I can't corroborate it from our logs — our startup log shows 3 attempts and no "giving up", and my attempt to derive 17 from 34 server/discover hits failed (Failed to validate request occurs only twice; the rest are traceback echoes). To pin it down I need the host log from the run where the blockage happened, plus the effective reconnect/probe config.
Server-side trace we do have: the probe arrives as server/discover with only a _meta envelope, and pydantic's ClientRequest union rejects it (Input should be 'ping' / 'initialize') — so the wrong method/code on the server is confirmed; that part is not in dispute.
akushonkamen commentedon Oct 11, 2026
Confirmed the root cause — it's in the pinned MCP Python SDK, not OpenViking logic:
mcp/shared/session.py(v1.29.0,src/mcp/shared/session.py:360-395):_receive_request_type.model_validate(...)(:362) fails for any method outside theClientRequestunion; the genericexcept(:380) then always answersINVALID_PARAMS(:385-393), so theMETHOD_NOT_FOUNDbranch is unreachable. The streamable-HTTP transport (streamable_http.py:500-509) only validates the JSON-RPC envelope, so unknown methods do reach the session layer.mcp>=1.27,<2(pyproject.toml:84), so 1.29.0 is affected;/mcpis mounted atopenviking/server/app.py(_ScopedRoute("/mcp", ...)→create_mcp_app()inopenviking/server/mcp_endpoint.py), with no method pre-check before the SDK.I'm working on a repo-side compatibility layer: a small pure-ASGI middleware in front of the
/mcpmount that resolves the request method against the known set (derived dynamically from the pinned SDK's request types, so it tracks SDK changes) and answers-32601 Method not foundbefore the request hits the SDK union. Regression targets:server/discover/ unknown method →-32601; genuine parameter errors → still-32602;initializewith2026-07-28keeps down-negotiating to2025-11-25;tools/listunchanged.To be explicit on scope: this is a mitigation only — the proper fix is upstream in modelcontextprotocol/python-sdk#3557, and I won't touch the
mcp<2pin (that's #4651 territory). And agreeing with @r266-tech's client-side analysis above, the value here is spec-compliant error codes and removing probe noise/log spam, not a hard-failure rescue.AI-assisted contribution: investigation and patch prepared with AI assistance under human review; will be disclosed in the PR per the template.
akushonkamen commentedon Oct 12, 2026
Opened PR #5923 with a repo-side compatibility shim:
POST /mcpnow answers-32601 Method not foundfor methods outside the installed SDK's routable set (derived from theClientRequestunion at init, fail-open on introspection failure), while known methods, notifications, batch/unparseable bodies and non-JSONAcceptheaders keep the SDK's exact behavior. The proper fix remains upstream (python-sdk#3557), as noted in my claim comment. Live green run: both unknown methods →-32601,initialize→2025-11-25unchanged;tools/callbad params → SDK-32602; 12 new unit tests green.r266-tech commentedon Oct 12, 2026
Agreed with the correction. I also checked the revised direct-HTTP path with the real
@modelcontextprotocol/client@2.0.0against a synthetic legacy HTTP server. Both error codes, with probe retries set to 0 or 16, produced oneserver/discover, theninitialize,notifications/initialized, andtools/list; all four controls connected and returned the fixture tool. This used Node 26.8.2, so it does not reproduce your Node 24 / DSH UI environment.The published
@deepseek-ai/dsh-mcp-client@0.2.0-rc.2lib/index.jshas SHA-256758d8fc473b700b5d351158d06ce9f9e92c16e8b25497b08bb7bd2122a87f5dd, matching your prefix. Its connection attempt includes bothgeneration.connect(transport)and the initial tool sync. Thus a successful initialize followed by a failedtools/listcan also enter the supervisor retry path. It logs the caught error separately asconnection attempt failed: …beforeconnection failed; retrying ….For the blocked run, the most useful next evidence is that first
connection attempt failedline with its stack, the matching server traceback, whether initialize completed before it, and the effectivefailOnStartupError/ reconnect settings. Please redact credentials and session identifiers. The counts ofJSONDecodeError/ExceptionGroupalone cannot identify the failed request or establish why the UI queue stayed blocked. #5923 addresses the server error-code issue separately.AI-assisted investigation; no live DSH queue reproduction claimed.
XHR666 commentedon Oct 12, 2026
Correction to my own report. I ran the chain end-to-end and the causal claim in the title does not hold, so please treat this comment as superseding my earlier attribution.
Chain (corrected)
@openviking/dsh-memory-plugin@0.5.20builds{ transport: "stdio", serverName: "openviking", command: process.execPath, args: [servers/mcp-proxy.mjs], env }.http://127.0.0.1:1933/mcp, so the probe reaches/mcpthrough the proxy.reconnect, noprobe, nofailOnStartupError. Attempts run ondsh-mcp-clientdefaults (initialDelayMs500,maxDelayMs30000,maxAttempts10), which is exactly what ourattempt N/10lines show.ProtocolError: OpenViking MCP request failed. Check the configured URL …line our harness logged originates in that proxy, not in the client SDK.Measured
Method: the SDK DSH actually loads (
@modelcontextprotocol/client@2.0.0,versionNegotiation: { mode: "auto" }, sameClientconstruction asdsh-mcp-client) driving the real proxy; wire order observed at the endpoint wasserver/discover → initialize → notifications/initialized → tools/listin all three runs.-32602(mimics current behavior): 1 attempt,connect()succeeded, fixture tool returned.-32601: 1 attempt,connect()succeeded, fixture tool returned.-32602forserver/discover: 1 attempt,connect()succeeded, andtools/listreturned 15 real tools.Conclusion:
-32602forserver/discoverdoes not by itself break the client or cause repeated probing. The fallback to the legacyinitializehandshake runs either way, and this server answers that handshake correctly (protocolVersion: 2025-11-25,serverInfo: openviking/1.29.0).What did produce our retries: a startup race
Our DSH host log, verbatim:
17:29:14.522 mcp-client(openviking): connection attempt failed: ProtocolError: OpenViking MCP request failed. … 17:29:14.539 mcp-client(openviking): connection failed; retrying in 500ms (attempt 1/10) 17:29:15.358 mcp-client(openviking): connection attempt failed: ProtocolError: … 17:29:15.370 mcp-client(openviking): connection failed; retrying in 1000ms (attempt 2/10) 17:29:16.679 mcp-client(openviking): connection attempt failed: ProtocolError: … 17:29:16.691 mcp-client(openviking): connection failed; retrying in 2000ms (attempt 3/10)
giving upline — so attempt 4 (≈ 17:29:18.7) appears to have succeeded.server/discoverreaches it right after.On the two evidence requests
error.stackfor me to post.OPENVIKING_DEBUG=1plusOPENVIKING_DEBUG_LOG=<path>(the proxy also honoursOV_DEBUG_LOG). That is what I enabled for the runs above; I can post paired proxy/server traces if that is useful.initializehad completed before those earlier failures remains unanswered for that specific run, but it is no longer load-bearing given the race explanation.Server-side code
The error code is still wrong and still worth fixing — tracked in #5923. Verified locally on the installed
mcp 1.29.0:ClientRequestTypeexposes 17 routable methods andserver/discoveris not among them.AI-assisted investigation; measurements above are from this reporter's host, no server-side fix claimed.