BaseSession.send_request fails when forwarding a request received via ServerSession (envelope-fields collision) #2548
Description
Repro
Any time a ServerSession.incoming_messages RequestResponder.request is forwarded to a ClientSession.send_request(...) — the canonical pattern for an MCP gateway/proxy — the SDK raises:
TypeError: mcp.types.JSONRPCRequest() got multiple values for keyword argument 'jsonrpc'
Root cause
-
In
ServerSession._receive_loop, the incoming wire message is parsed via:self._receive_request_type.model_validate( message.message.root.model_dump(by_alias=True, mode="json", exclude_none=True) )
The dump of a
JSONRPCRequestincludesjsonrpcandid. -
CallToolRequest,ListToolsRequest, etc. are configured withmodel_config = {'extra': 'allow'}, so the envelope fields are preserved on the validatedClientRequest. -
When that received
ClientRequestis then forwarded viaBaseSession.send_request, the SDK does:request_data = request.model_dump(by_alias=True, mode="json", exclude_none=True) # ... jsonrpc_request = JSONRPCRequest(jsonrpc="2.0", id=request_id, **request_data)
request_dataalready containsjsonrpcandid→TypeError.
Workaround in consumer code
def _strip_envelope_fields(request: ClientRequest) -> ClientRequest:
raw = request.model_dump(by_alias=True, mode="json", exclude_none=True)
raw.pop("jsonrpc", None)
raw.pop("id", None)
return ClientRequest.model_validate(raw)This is currently in production in MCP-IDS's ProxySession._handle_one.
Question
Is the extra='allow' on Request subtypes intentional? If yes, a non-breaking fix would be inside BaseSession.send_request:
request_data = request.model_dump(by_alias=True, mode="json", exclude_none=True)
request_data.pop("jsonrpc", None)
request_data.pop("id", None)
jsonrpc_request = JSONRPCRequest(jsonrpc="2.0", id=request_id, **request_data)This preserves consumer expectations (extra fields tolerated on receive) and fixes proxy use cases without breaking anything.
Affected use cases
- MCP gateways / proxies / IDS (a gateway is by definition a
ServerSession↔ClientSessionbridge) - Test harnesses that round-trip messages through both session types
- Anything relying on
ClientRequest's wire-form survival across the parse/forward boundary
SDK version observed
mcp==1.27.0. Likely reproduces on later 1.x — happy to confirm if there's a specific version you'd like tested.
Activity
charles-adedotun commentedon May 8, 2026
Confirmed the bug at
src/mcp/shared/session.py:285.request_datafromJSONRPCRequest.model_dump()carriesjsonrpcandid(the envelope fields are explicit on the class), which then collide with the explicit kwargs inJSONRPCRequest(jsonrpc="2.0", id=request_id, **request_data)— exact failure mode described.Happy to take this. Conservative approach:
request_data.pop("jsonrpc", None)andrequest_data.pop("id", None)before the unpack, plus a regression test for the proxy pattern (server→server forward viacreate_client_server_memory_streams, which doesn't appear to be covered yet intests/shared/test_session.py).If you'd rather address this at the model layer (e.g. excluding envelope fields from the dump on forwarded requests) I can take that direction instead. Will wait for a
ready for worklabel or maintainer reply per CONTRIBUTING before opening the PR.STiFLeR7 commentedon Jul 14, 2026
This is moot as of the v2 session rewrite (#2838, merged 2026-06-16). `BaseSession.send_request` — the method this issue and PR #2573 target — no longer exists; `src/mcp/shared/session.py` is now just a compat-name shim.
The new send path (`ClientSession.send_request` in `src/mcp/client/session.py`) only extracts `method` and `params` from the dumped request and passes those two values to `JSONRPCDispatcher.send_raw_request(method, params, opts)`, which assigns its own `id` internally. There's no longer a spot where a forwarded request's leftover `jsonrpc`/`id` fields get merged into explicit kwargs, so the `TypeError: got multiple values for keyword argument 'jsonrpc'` this issue describes can't reproduce on current `main`.
PR #2573 is showing CONFLICTING/DIRTY against main for the same reason. Both can probably be closed unless someone can repro the proxy/forwarding scenario against the new dispatcher-based session.
maxisbey commentedon Jul 29, 2026
Thanks for the report. Closing this as it's been fixed in v2, which is now released (see #2838). Feel free to reopen if you still hit it on v2.
AI Disclaimer