Skip to content

fix(http): create a fresh MCP server per request in stateless HTTP mode - #118

Open
Defilan wants to merge 1 commit into
perplexityai:mainfrom
Defilan:fix/http-per-request-server
Open

fix(http): create a fresh MCP server per request in stateless HTTP mode#118
Defilan wants to merge 1 commit into
perplexityai:mainfrom
Defilan:fix/http-per-request-server

Conversation

@Defilan

@Defilan Defilan commented Jul 15, 2026

Copy link
Copy Markdown

Summary

In HTTP mode, createHttpApp builds a single McpServer at construction and calls mcpServer.connect(transport) on every /mcp request. With the stateless streamable-HTTP transport (sessionIdGenerator: undefined) there is no session to reuse, so once one request's transport is connected, any request arriving before it closes calls connect() on the already-connected server and throws Already connected to a transport … use a separate Protocol instance per connection → HTTP 500.

This breaks multi-request MCP clients: initialize succeeds, tools/list 500s, and no tools register. (A strictly-sequential curl dodges it because res.on("close") frees the shared server between requests, which is why it isn't obvious in one-shot testing.)

Fixes #117.

Change

  • Move createPerplexityServer() into the /mcp handler so each request gets its own server + transport (the SDK's stateless pattern), and close the per-request server on response close.
  • Add a regression test asserting a fresh server is created per request. It fails against the shared-server code (expected "createPerplexityServer" to be called 2 times, but got 0 times) and passes with the fix.

Verification

  • npm test — 91 passing (new test included).
  • npm run build — clean.
  • Verified end-to-end against a live MCP client: before, initialize succeeded but tools/list 500'd and no tools registered; after, the full initialize → tools/list → tools/call flow works.

createHttpApp built a single McpServer at construction and called
mcpServer.connect(transport) on every /mcp request. With the stateless
StreamableHTTP transport (sessionIdGenerator: undefined) there is no session to
reuse, so once one request's transport was connected, any request arriving
before it closed called connect() on the already-connected server and threw
"Already connected to a transport ... use a separate Protocol instance per
connection", returning HTTP 500.

This breaks multi-request MCP clients: a client sends initialize then
tools/list (and typically keeps a stream open), so its second call 500s and no
tools register. A strictly-sequential caller dodges it because res.on(close)
closes the transport and frees the shared server between requests.

Move server creation into the /mcp handler so each request gets its own
McpServer + transport (the SDK's stateless pattern) and close it on response
close. Add a regression test asserting a fresh server is created per request.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Stateless HTTP mode reuses one MCP server across requests -> "Already connected to a transport" (breaks multi-request clients)

1 participant