MCP: notifications/initialized is not awaited before the first request, so stateful Streamable HTTP servers intermittently reject the connection
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 193k
- Forks
- 42.7k
- PR merge metrics
- PR metrics pending
Description
Does this issue occur when all extensions are disabled?: Yes (MCP client is core)
- VS Code Version: 1.138.0 (also reproduced on 1.137.0)
- OS Version: macOS 15 / Windows 11 ARM64
What happens
Against a stateful Streamable HTTP MCP server, connections intermittently fail on the first real request with:
Connection state: Error 400 status sending message to https://<gateway-host>/mcp:
{"jsonrpc":"2.0","id":2,"error":{"code":-32600,"message":"Session not initialized. Send notifications/initialized first."}}; will retry with new session ID
It then retries with a new session and can fail the same way indefinitely. Restarting the server does not help; repeatedly restarting eventually succeeds, which is what makes it look like a race rather than a misconfiguration. The failure rate appears to scale with network latency — rare on a fast local connection, near-constant for users on a higher-latency corporate network.
Why
notifications/initialized is sent fire-and-forget and is not awaited before the first real request goes out, so the two are concurrent HTTP POSTs with no ordering guarantee.
mcpServerRequestHandler.ts:281—sendNotificationreturnsvoid, discarding the promise fromsend().mcpServerRequestHandler.ts:144— the handshake calls it without awaiting, thencreate()returns and the caller immediately issuestools/list(id:2).extHostMcp.ts:385-390—send()only serializes through_requestSequencerwhile_mode.value === HttpMode.Unknown. Onceinitializereturns and the mode becomesHttpMode.Http, every subsequent send takes the unserializedelsebranch._sendStreamableHttpadditionally doesawait this._addAuthHeader(headers)before each fetch, adding enough jitter to flip the ordering.
So the sequencer that would have ordered these is switched off by the very act of recognising the server as a session-based Streamable HTTP server.
Server side
The server in question is an AWS Bedrock AgentCore Gateway with MCP sessions enabled, so this is likely to affect anyone using that managed gateway rather than one bespoke server. I verified its behaviour directly with raw HTTP:
| Request | Result |
|---|---|
initialize |
200, issues Mcp-Session-Id, < 1s |
request on a session before notifications/initialized |
400 -32600 Session not initialized |
notifications/initialized |
202 |
| request after the notification | 200 |
| request with an unknown session id | 404 -32004 (spec-correct) |
| request with no session header | 400 (spec-correct) |
Sequencing the same calls by hand always succeeds; only the concurrent ordering fails.
Proposed fix
Return the promise and await it at the single point where ordering matters:
- private sendNotification<N extends MCP.ClientNotification>(notification: Omit<N, 'jsonrpc'>): void {
- this.send({ ...notification, jsonrpc: MCP.JSONRPC_VERSION });
+ private sendNotification<N extends MCP.ClientNotification>(notification: Omit<N, 'jsonrpc'>): Promise<void> {
+ return this.send({ ...notification, jsonrpc: MCP.JSONRPC_VERSION });
}
- mcp.sendNotification<MCP.InitializedNotification>({
+ await mcp.sendNotification<MCP.InitializedNotification>({
method: 'notifications/initialized'
});
This costs one round trip once, at startup, and leaves the HttpMode.Unknown guard alone so normal traffic stays unserialized.
On the spec
To be upfront: I don't think VS Code is violating the spec. The only client-side ordering requirement in the lifecycle spec is "SHOULD NOT send requests other than pings before the server has responded to the initialize request", which VS Code honours, and the rule referencing the initialized notification binds the server. The transports spec also defines no server error for this state, and since every message is a separate POST, strict ordering isn't something the transport can guarantee — so I'm raising this with the server vendor too.
Filing it here because servers that do enforce it exist and are widely deployed, the failure is opaque from the user's side, and awaiting one notification at startup is a cheap way to be robust against them.
Related: #330022 was a similar Streamable HTTP interop fix in the same client.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Read mcpServerRequestHandler.ts around lines 144 and 281, then inspect extHostMcp.ts around lines 385-390 and _sendStreamableHttp. Trace the initialize handshake and request sequencing first; done means the initialized notification completes before the first real request, while normal HTTP traffic remains unserialized.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, networking
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 88/100