microsoft / microsoft/vscode

MCP: notifications/initialized is not awaited before the first request, so stateful Streamable HTTP servers intermittently reject the connection

Closed Beginner friendly
#336,831 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

new release
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:281sendNotification returns void, discarding the promise from send().
  • mcpServerRequestHandler.ts:144 — the handshake calls it without awaiting, then create() returns and the caller immediately issues tools/list (id:2).
  • extHostMcp.ts:385-390send() only serializes through _requestSequencer while _mode.value === HttpMode.Unknown. Once initialize returns and the mode becomes HttpMode.Http, every subsequent send takes the unserialized else branch.
  • _sendStreamableHttp additionally does await 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.