Summary
Claude Code MCP clients (the official claude-code CLI / SDK) intermittently send tools/call requests directly after opening an SSE session, without first sending the initialize request and notifications/initialized notification. The server then rejects the request with RuntimeError("Received request before initialization was complete") (mcp/server/session.py:193), which surfaces to the client as -32602: Invalid request parameters.
Likely related to #1844, but we have a concrete reproduction with full server-side debug logs.
Environment
mcp 1.27.0 (also reproduces with 1.14.0, 1.27.1)
- Python 3.13 on Debian 14
- Transport: SSE via
SseServerTransport + Starlette/Uvicorn
- Client:
claude-code CLI (current version, agent-SDK based)
- Reproduces with multiple parallel SSE sessions from the same host (typical of a multi-agent setup)
Server-side debug log (raw)
[INFO] openproject-mcp-sse - POST /messages session=f3ee2d98… method=tools/call id=14 body={"method": "tools/call", "params": {"name": "test_connection", "arguments": {}, "_meta": {"claudecode/toolUseId": "toolu_01SqXkUVGvojypc6RaA4UriG", "progressToken": 14}}, "jsonrpc": "2.0", "id": 14}
[DEBUG] mcp.server.sse - Validated client message: root=JSONRPCRequest(method='tools/call', params={'name': 'test_connection', 'arguments': {}, '_meta': {'claudecode/toolUseId': 'toolu_01SqXkUVGvojypc6RaA4UriG', 'progressToken': 14}}, jsonrpc='2.0', id=14)
[DEBUG] mcp.server.sse - Sending session message to writer: SessionMessage(...)
INFO: POST /messages/?session_id=f3ee2d980809439b97eb35556d220eb1 HTTP/1.1" 202 Accepted
[WARNING] root - Failed to validate request: Received request before initialization was complete
[DEBUG] root - Message that failed validation: method='tools/call' params={'name': 'test_connection', 'arguments': {}, '_meta': {'claudecode/toolUseId': 'toolu_01SqXkUVGvojypc6RaA4UriG', 'progressToken': 14}} jsonrpc='2.0' id=14
Note: The session was just newly created by the same SSE handshake (session_id=f3ee2d98... was generated 11 seconds before). The client did not send initialize between SSE-connect and tools/call — this is observable in the raw POST body capture above.
Reproduction
- Have a Python SSE MCP server using the standard
mcp.server.sse.SseServerTransport + Server(...).run() pattern (see example in the repo).
- Connect with the Claude Code CLI as an MCP client (configure as a SSE-type MCP server in the user's
~/.claude/config).
- The server has been running and idle for >1 hour.
- From within a Claude Code session, invoke a tool. About half the time, the request comes in without prior
initialize.
The first session right after a service restart always works correctly. The problem manifests after the client has gone idle for some time and then resumes — strongly suggests the client is caching session state across what the server considers a session re-establishment.
What I'd like to know
- Is this expected client behavior — should the server tolerate
tools/call without preceding initialize?
- Or should the SDK make the server idempotently re-initialize on first non-init request (a kind of stateless mode for SSE, like
StreamableHTTPSessionManager(stateless=True) does for streamable_http)?
- Is there a workaround other than monkey-patching
_received_request?
Workaround we are using (not great, but it works)
We replaced the RuntimeError with an implicit state transition + warning, as a runtime monkey-patch:
async def _patched_received_request(self, responder):
match responder.request.root:
case types.InitializeRequest() | types.PingRequest():
return await _original(self, responder)
case _:
if self._initialization_state != InitializationState.Initialized:
logger.warning("Implicit initialization (client skipped initialize handshake)")
self._initialization_state = InitializationState.Initialized
return await _original(self, responder)
ServerSession._received_request = _patched_received_request
Full module: [linked in our deployment if helpful].
Possibly related
Thanks for the SDK!
Summary
Claude Code MCP clients (the official
claude-codeCLI / SDK) intermittently sendtools/callrequests directly after opening an SSE session, without first sending theinitializerequest andnotifications/initializednotification. The server then rejects the request withRuntimeError("Received request before initialization was complete")(mcp/server/session.py:193), which surfaces to the client as-32602: Invalid request parameters.Likely related to #1844, but we have a concrete reproduction with full server-side debug logs.
Environment
mcp1.27.0 (also reproduces with 1.14.0, 1.27.1)SseServerTransport+ Starlette/Uvicornclaude-codeCLI (current version, agent-SDK based)Server-side debug log (raw)
Note: The session was just newly created by the same SSE handshake (
session_id=f3ee2d98...was generated 11 seconds before). The client did not sendinitializebetween SSE-connect andtools/call— this is observable in the raw POST body capture above.Reproduction
mcp.server.sse.SseServerTransport+Server(...).run()pattern (see example in the repo).~/.claude/config).initialize.The first session right after a service restart always works correctly. The problem manifests after the client has gone idle for some time and then resumes — strongly suggests the client is caching session state across what the server considers a session re-establishment.
What I'd like to know
tools/callwithout precedinginitialize?StreamableHTTPSessionManager(stateless=True)does for streamable_http)?_received_request?Workaround we are using (not great, but it works)
We replaced the
RuntimeErrorwith an implicit state transition + warning, as a runtime monkey-patch:Full module: [linked in our deployment if helpful].
Possibly related
RuntimeError: Received request before initialization was completeLeading to Empty SSE Responses When Embedded in FastAPI #737 — FastMCP RuntimeError on pre-init in FastAPI embedsINVALID_REQUESTinstead of crashingstateless_sseoption PR (closed without merge?)Thanks for the SDK!