From 9ca242e2d8f46327ffaebb7b48a53b40e6d36010 Mon Sep 17 00:00:00 2001 From: Ryan Buchmayer Date: Wed, 22 Jul 2026 15:24:18 -0700 Subject: [PATCH] fix(http): create a server instance per request in stateless HTTP mode The stateless streamable HTTP path reused a single McpServer across requests, but a Protocol instance only supports one transport. The second POST in any session failed with "Already connected to a transport" (HTTP 500), which breaks initialize -> tools/list for every client. Follow the SDK's stateless pattern: build a fresh server + transport pair per request and close both when the response closes. --- src/http.config.test.ts | 43 +++++++++++++++++++++++++++++++++++++++++ src/http.ts | 6 ++++-- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/http.config.test.ts b/src/http.config.test.ts index c899821..a0d6205 100644 --- a/src/http.config.test.ts +++ b/src/http.config.test.ts @@ -350,4 +350,47 @@ describe("HTTP transport configuration", () => { expect(status).toBe(200); }); }); + + describe("stateless request handling", () => { + it("serves multiple sequential requests on one app instance", async () => { + await start(); + + const listTools = () => + fetch(`${baseUrl}/mcp`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json, text/event-stream", + }, + body: JSON.stringify({ + jsonrpc: "2.0", + id: 1, + method: "tools/list", + params: {}, + }), + }); + + // A single server instance can only bind one transport, so the app must + // build a fresh pair per request; before that fix the second request + // failed with a 500 "Already connected to a transport". + const first = await listTools(); + expect(first.status).toBe(200); + + const second = await listTools(); + expect(second.status).toBe(200); + const data = await second.json(); + expect(data.result.tools).toHaveLength(4); + }); + + it("serves the health endpoint", async () => { + await start(); + + const response = await fetch(`${baseUrl}/health`); + expect(response.ok).toBe(true); + expect(await response.json()).toEqual({ + status: "ok", + service: "perplexity-mcp-server", + }); + }); + }); }); diff --git a/src/http.ts b/src/http.ts index e9923ad..8c96715 100644 --- a/src/http.ts +++ b/src/http.ts @@ -124,17 +124,19 @@ export function createHttpApp(options: HttpAppOptions): Express { app.use(express.json()); - const mcpServer = createPerplexityServer(); - app.all("/mcp", async (req, res) => { try { + // Stateless mode: a Protocol instance supports one transport, so build + // a fresh server + transport pair per request. const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, enableJsonResponse: true, }); + const mcpServer = createPerplexityServer(); res.on("close", () => { transport.close(); + mcpServer.close(); }); await mcpServer.connect(transport);