Skip to content

Commit f34a8ff

Browse files
fix(shttpHandler): unknown Last-Event-ID returns 400, not 404 (404 means session terminated per spec)
1 parent 339211f commit f34a8ff

2 files changed

Lines changed: 8 additions & 2 deletions

File tree

packages/server/src/server/shttpHandler.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,12 @@ export function shttpHandler(
466466
}
467467
const eventStreamId = await eventStore.getStreamIdForEventId(lastEventId);
468468
if (eventStreamId === undefined) {
469-
return jsonError(404, -32_001, 'Event not found');
469+
// 400, not 404: per the Streamable HTTP spec a 404 on a request with
470+
// Mcp-Session-Id signals "session terminated, reinitialize". This branch
471+
// runs for a live session whose event store does not (or no longer) have
472+
// that event id; clients should retry without Last-Event-ID, not abandon
473+
// the session.
474+
return jsonError(400, -32_000, 'Unknown or expired Last-Event-ID');
470475
}
471476
if (!session.ownsStreamId(sessionId, eventStreamId)) {
472477
return jsonError(403, -32_000, 'Forbidden: event ID does not belong to this session');

packages/server/test/server/shttpHandler.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,8 @@ describe('shttpHandler — Last-Event-ID replay session binding', () => {
133133
await init.body?.cancel();
134134

135135
const res = await handler(getWithLastEventId(sid, 'no-such-stream::42'));
136-
expect(res.status).toBe(404);
136+
// 400, not 404: 404 with Mcp-Session-Id signals "session terminated" per spec.
137+
expect(res.status).toBe(400);
137138
});
138139

139140
test('fails closed when eventStore lacks getStreamIdForEventId', async () => {

0 commit comments

Comments
 (0)