Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
"zod": "^3.25.50"
},
"peerDependencies": {
"@modelcontextprotocol/sdk": "1.25.2",
"@modelcontextprotocol/sdk": ">=1.25.2",
"next": ">=13.0.0"
},
"peerDependenciesMeta": {
Expand Down
18 changes: 8 additions & 10 deletions src/handler/mcp-api-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,11 +277,6 @@ export function initializeMcpApiHandler(

let servers: McpServer[] = [];

let statelessServer: McpServer;
const statelessTransport = new StreamableHTTPServerTransport({
sessionIdGenerator: sessionIdGenerator,
});

// Start periodic cleanup if not already running
if (!cleanupInterval) {
cleanupInterval = setInterval(() => {
Expand Down Expand Up @@ -363,11 +358,14 @@ export function initializeMcpApiHandler(
config.onEvent
);

if (!statelessServer) {
statelessServer = new McpServer(serverInfo, mcpServerOptions);
await initializeServer(statelessServer);
await statelessServer.connect(statelessTransport);
}
// CVE-2026-25536: SDK >=1.26.0 requires a fresh transport+server per
// request in stateless mode to prevent cross-client data leaks.
const statelessTransport = new StreamableHTTPServerTransport({
sessionIdGenerator: sessionIdGenerator,
});
const statelessServer = new McpServer(serverInfo, mcpServerOptions);
await initializeServer(statelessServer);
await statelessServer.connect(statelessTransport);

// Parse the request body
let bodyContent: BodyType;
Expand Down
41 changes: 41 additions & 0 deletions tests/e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,47 @@ describe("e2e", () => {
);
});

// Regression test for CVE-2026-25536: the handler must create a fresh
// transport per request. Without the fix, the second client's initialize
// fails because the SDK rejects reuse of a stateless transport.
it("should handle multiple independent clients sequentially", async () => {
// First client — connects and calls a tool
const transport1 = new StreamableHTTPClientTransport(
new URL(`${endpoint}/mcp`)
);
const client1 = new Client(
{ name: "client-1", version: "1.0.0" },
{ capabilities: {} }
);
await client1.connect(transport1);
const result1 = await client1.callTool(
{ name: "echo", arguments: { message: "from client 1" } },
undefined,
{}
);
expect((result1.content as any)[0].text).toEqual(
"Tool echo: from client 1"
);

// Second client — must also succeed against the same server
const transport2 = new StreamableHTTPClientTransport(
new URL(`${endpoint}/mcp`)
);
const client2 = new Client(
{ name: "client-2", version: "1.0.0" },
{ capabilities: {} }
);
await client2.connect(transport2);
const result2 = await client2.callTool(
{ name: "echo", arguments: { message: "from client 2" } },
undefined,
{}
);
expect((result2.content as any)[0].text).toEqual(
"Tool echo: from client 2"
);
});

it("should return an invalid token error when verifyToken fails", async () => {
const authenticatedTransport = new StreamableHTTPClientTransport(
new URL(`${endpoint}/mcp`),
Expand Down