Skip to content

[MCP Bundle] Concurrent HTTP requests within the same MCP session can cause stale/unknown message IDs and hanging tool calls #1858

@tebaly

Description

@tebaly

Describe the bug

We are seeing unstable behavior when symfony/mcp-bundle handles multiple concurrent HTTP MCP requests within the same Mcp-Session-Id.

From our investigation, the underlying cause likely comes from the official PHP MCP SDK session model, where protocol state is stored in a shared session payload and mutated through a read -> modify -> write whole session flow. However, in practice this issue becomes visible at the Symfony bundle integration level, because the bundle exposes HTTP MCP request handling directly and does not serialize same-session concurrent requests.

What we expected:

  • multiple concurrent tools/call requests in the same MCP session should be handled safely.

What actually happens:

  • some requests succeed,
  • another response can be lost or overwritten,
  • the MCP client then reports stale or unknown message IDs,
  • and one or more tool calls may hang on the client side.

In our case this happens systematically with Cursor IDE when it sends several read-only MCP calls in parallel after discovery.

To Reproduce

Steps to reproduce the behavior:

  1. Create a Symfony app using symfony/mcp-bundle with HTTP transport enabled.
  2. Configure MCP session storage with a shared store, for example:
    mcp:
      http:
        session:
          store: cache
  3. Expose a few simple read-only MCP tools/resources.
  4. Connect a client that issues multiple concurrent MCP calls within the same session.
  5. Trigger 2-3 parallel tools/call requests for the same Mcp-Session-Id.
  6. Observe that:
    • some calls complete successfully,
    • another call may hang,
    • the client may log Received a response for an unknown message ID.

If needed, I can provide a minimal reproducer repository.

Expected behavior

Even if the underlying PHP MCP SDK currently has concurrency limitations, it would be very helpful if symfony/mcp-bundle could mitigate or document this problem more explicitly.

Possible improvements at the bundle level could be:

  • serializing HTTP MCP request handling per Mcp-Session-Id,
  • adding an optional lock around the full server->run($transport) lifecycle,
  • documenting that same-session parallel HTTP requests are currently unsafe unless externally serialized.

That would make the bundle much safer for real Symfony deployments.

Logs

Observed client-side symptom in Cursor IDE:

Ignoring stale response (unknown message ID): Received a response for an unknown message ID: {"jsonrpc":"2.0","id":10,"result":{...}}

In our case, several parallel MCP get_record calls were started in the same session. Two completed successfully, while another response appeared only as a stale/unknown-message-id payload on the client side.

Relevant bundle integration points:

The bundle injects the session store into the MCP server builder:

->call('setSession', [service('mcp.session.store')])

When store: cache is used, the bundle wires the SDK's Psr16SessionStore:

$container->register('mcp.session.store', Psr16SessionStore::class)
    ->setArguments([
        new Reference($sessionConfig['cache_pool']),
        $sessionConfig['prefix'],
        $sessionConfig['ttl'],
    ]);

The controller processes HTTP MCP requests directly:

public function handle(Request $request): Response
{
    $transport = new StreamableHttpTransport(
        $this->httpMessageFactory->createRequest($request),
        $this->responseFactory,
        $this->streamFactory,
        logger: $this->logger,
    );

    $psrResponse = $this->server->run($transport);
    $streamed = 'text/event-stream' === $psrResponse->getHeaderLine('Content-Type');

    return $this->httpFoundationFactory->createResponse($psrResponse, $streamed);
}

And the underlying SDK mutates outgoing queue state through a session read/modify/write pattern:

$queue = $session->get(self::SESSION_OUTGOING_QUEUE, []);
$queue[] = [
    'message' => $encoded,
    'context' => $context,
];
$session->set(self::SESSION_OUTGOING_QUEUE, $queue);

Later, the whole session payload is saved:

} finally {
    $session->save();
}

This seems consistent with a lost-update race when multiple HTTP requests mutate the same session concurrently.

Additional context

Versions:

  • symfony/mcp-bundle: v0.6.0
  • api-platform/mcp: ^4.3
  • modelcontextprotocol/php-sdk: version installed transitively by the bundle in our project
  • Symfony app: 7.4
  • Client where we observe the issue: Cursor IDE over HTTP MCP

I understand this may partly belong in the upstream PHP MCP SDK. Still, since symfony/mcp-bundle is the Symfony integration layer and is what application developers configure directly, it seems like a good place either to:

  • provide a mitigation,
  • expose a safe configuration option,
  • or clearly document the limitation.

modelcontextprotocol/php-sdk#275

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugSomething isn't workingMCP BundleIssues & PRs about the MCP SDK integration bundleStatus: Needs Review

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions