fix: bump SDK to 1.26.0, add session management to tools_call scenario (#134)

* fix: bump SDK to 1.26.0, add session management to tools_call scenario

SDK 1.26.0 made Protocol.connect() throw if already connected to a
transport. The tools_call scenario was calling server.connect(transport)
on every request with the same Server instance, which now throws.

Fixed by adding proper session management: each initialize request
creates a new Server + Transport pair, and subsequent requests are
routed to the correct transport via the mcp-session-id header.

* fix: return 404 for invalid session IDs in tools_call POST handler

The else branch was returning 400 for both missing-session and
invalid/stale-session cases. The MCP spec requires 404 for invalid
session IDs and 400 only for non-initialization requests without
any session ID.

* refactor: keep tools_call stateless, create fresh server per request

Instead of adding session management, simply create a new Server
instance per request. This preserves the original stateless design
while fixing the SDK 1.26.0 Protocol.connect() restriction.
This commit is contained in:
Paul Carleton 2026-02-05 15:43:21 +00:00 • committed by GitHub
commit 22196ca809
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 46 additions and 26 deletions

60
package-lock.json generated
View file

@ -9,7 +9,7 @@
"version": "0.1.12", "version": "0.1.12",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@modelcontextprotocol/sdk": "^1.25.2", "@modelcontextprotocol/sdk": "^1.26.0",
"commander": "^14.0.2", "commander": "^14.0.2",
"eventsource-parser": "^3.0.6", "eventsource-parser": "^3.0.6",
"express": "^5.1.0", "express": "^5.1.0",
@ -752,9 +752,9 @@
} }
}, },
"node_modules/@hono/node-server": { "node_modules/@hono/node-server": {
"version": "1.19.7", "version": "1.19.9",
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.7.tgz", "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz",
"integrity": "sha512-vUcD0uauS7EU2caukW8z5lJKtoGMokxNbJtBiwHgpqxEXokaHCBkQUmCHhjFB1VUTWdqj25QoMkMKzgjq+uhrw==", "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=18.14.1" "node": ">=18.14.1"
@ -855,12 +855,12 @@
} }
}, },
"node_modules/@modelcontextprotocol/sdk": { "node_modules/@modelcontextprotocol/sdk": {
"version": "1.25.2", "version": "1.26.0",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.25.2.tgz", "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz",
"integrity": "sha512-LZFeo4F9M5qOhC/Uc1aQSrBHxMrvxett+9KLHt7OhcExtoiRN9DKgbZffMP/nxjutWDQpfMDfP3nkHI4X9ijww==", "integrity": "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@hono/node-server": "^1.19.7", "@hono/node-server": "^1.19.9",
"ajv": "^8.17.1", "ajv": "^8.17.1",
"ajv-formats": "^3.0.1", "ajv-formats": "^3.0.1",
"content-type": "^1.0.5", "content-type": "^1.0.5",
@ -868,14 +868,15 @@
"cross-spawn": "^7.0.5", "cross-spawn": "^7.0.5",
"eventsource": "^3.0.2", "eventsource": "^3.0.2",
"eventsource-parser": "^3.0.0", "eventsource-parser": "^3.0.0",
"express": "^5.0.1", "express": "^5.2.1",
"express-rate-limit": "^7.5.0", "express-rate-limit": "^8.2.1",
"jose": "^6.1.1", "hono": "^4.11.4",
"jose": "^6.1.3",
"json-schema-typed": "^8.0.2", "json-schema-typed": "^8.0.2",
"pkce-challenge": "^5.0.0", "pkce-challenge": "^5.0.0",
"raw-body": "^3.0.0", "raw-body": "^3.0.0",
"zod": "^3.25 || ^4.0", "zod": "^3.25 || ^4.0",
"zod-to-json-schema": "^3.25.0" "zod-to-json-schema": "^3.25.1"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=18"
@ -3042,17 +3043,19 @@
} }
}, },
"node_modules/express": { "node_modules/express": {
"version": "5.1.0", "version": "5.2.1",
"resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
"integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
"license": "MIT",
"dependencies": { "dependencies": {
"accepts": "^2.0.0", "accepts": "^2.0.0",
"body-parser": "^2.2.0", "body-parser": "^2.2.1",
"content-disposition": "^1.0.0", "content-disposition": "^1.0.0",
"content-type": "^1.0.5", "content-type": "^1.0.5",
"cookie": "^0.7.1", "cookie": "^0.7.1",
"cookie-signature": "^1.2.1", "cookie-signature": "^1.2.1",
"debug": "^4.4.0", "debug": "^4.4.0",
"depd": "^2.0.0",
"encodeurl": "^2.0.0", "encodeurl": "^2.0.0",
"escape-html": "^1.0.3", "escape-html": "^1.0.3",
"etag": "^1.8.1", "etag": "^1.8.1",
@ -3083,10 +3086,13 @@
} }
}, },
"node_modules/express-rate-limit": { "node_modules/express-rate-limit": {
"version": "7.5.1", "version": "8.2.1",
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz",
"integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", "integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==",
"license": "MIT", "license": "MIT",
"dependencies": {
"ip-address": "10.0.1"
},
"engines": { "engines": {
"node": ">= 16" "node": ">= 16"
}, },
@ -3405,7 +3411,6 @@
"resolved": "https://registry.npmjs.org/hono/-/hono-4.11.7.tgz", "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.7.tgz",
"integrity": "sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw==", "integrity": "sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=16.9.0" "node": ">=16.9.0"
} }
@ -3501,6 +3506,15 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/ip-address": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz",
"integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==",
"license": "MIT",
"engines": {
"node": ">= 12"
}
},
"node_modules/ipaddr.js": { "node_modules/ipaddr.js": {
"version": "1.9.1", "version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
@ -3556,9 +3570,9 @@
} }
}, },
"node_modules/jose": { "node_modules/jose": {
"version": "6.1.2", "version": "6.1.3",
"resolved": "https://registry.npmjs.org/jose/-/jose-6.1.2.tgz", "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz",
"integrity": "sha512-MpcPtHLE5EmztuFIqB0vzHAWJPpmN1E6L4oo+kze56LIs3MyXIj9ZHMDxqOvkP38gBR7K1v3jqd4WU2+nrfONQ==", "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==",
"license": "MIT", "license": "MIT",
"funding": { "funding": {
"url": "https://github.com/sponsors/panva" "url": "https://github.com/sponsors/panva"

View file

@ -45,7 +45,7 @@
"vitest": "^4.0.16" "vitest": "^4.0.16"
}, },
"dependencies": { "dependencies": {
"@modelcontextprotocol/sdk": "^1.25.2", "@modelcontextprotocol/sdk": "^1.26.0",
"commander": "^14.0.2", "commander": "^14.0.2",
"eventsource-parser": "^3.0.6", "eventsource-parser": "^3.0.6",
"express": "^5.1.0", "express": "^5.1.0",

View file

@ -9,7 +9,7 @@ import express, { Request, Response } from 'express';
import { ScenarioUrls } from '../../types'; import { ScenarioUrls } from '../../types';
import { createRequestLogger } from '../request-logger'; import { createRequestLogger } from '../request-logger';
function createServer(checks: ConformanceCheck[]): express.Application { function createMcpServer(checks: ConformanceCheck[]): Server {
const server = new Server( const server = new Server(
{ {
name: 'add-numbers-server', name: 'add-numbers-server',
@ -84,6 +84,10 @@ function createServer(checks: ConformanceCheck[]): express.Application {
throw new Error(`Unknown tool: ${request.params.name}`); throw new Error(`Unknown tool: ${request.params.name}`);
}); });
return server;
}
function createServerApp(checks: ConformanceCheck[]): express.Application {
const app = express(); const app = express();
app.use(express.json()); app.use(express.json());
@ -96,6 +100,8 @@ function createServer(checks: ConformanceCheck[]): express.Application {
); );
app.post('/mcp', async (req: Request, res: Response) => { app.post('/mcp', async (req: Request, res: Response) => {
// Stateless: create a fresh server and transport per request
const server = createMcpServer(checks);
const transport = new StreamableHTTPServerTransport({ const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined sessionIdGenerator: undefined
}); });
@ -116,7 +122,7 @@ export class ToolsCallScenario implements Scenario {
async start(): Promise<ScenarioUrls> { async start(): Promise<ScenarioUrls> {
this.checks = []; this.checks = [];
this.app = createServer(this.checks); this.app = createServerApp(this.checks);
this.httpServer = this.app.listen(0); this.httpServer = this.app.listen(0);
const port = this.httpServer.address().port; const port = this.httpServer.address().port;
return { serverUrl: `http://localhost:${port}/mcp` }; return { serverUrl: `http://localhost:${port}/mcp` };