From f84e54124b2c7e3556433e3893b1d5c4663522db Mon Sep 17 00:00:00 2001 From: Jerome Date: Mon, 7 Jul 2025 15:30:11 +0100 Subject: [PATCH] Implement DELETE request handling for session termination MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add explicit DELETE request handling in handleStreamableHTTP - Validate session existence before termination - Send shutdown control messages to clean up Redis resources - Return proper HTTP status codes (200 for success, 404 for not found) - Fix TypeScript issue with JSONRPCMessage id access in redisTransport - Add missing semicolon in startServerListeningToRedis call Follows MCP specification for explicit session termination. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/handlers/shttp.ts | 45 ++++++++++++++++++++++++++++++++-- src/services/redisTransport.ts | 2 +- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/handlers/shttp.ts b/src/handlers/shttp.ts index bc62990..c725882 100644 --- a/src/handlers/shttp.ts +++ b/src/handlers/shttp.ts @@ -1,7 +1,7 @@ import { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import { Request, Response } from "express"; -import { getFirstShttpTransport, getShttpTransport, isLive, startServerListeningToRedis } from "../services/redisTransport.js"; +import { getFirstShttpTransport, getShttpTransport, isLive, shutdownSession, startServerListeningToRedis } from "../services/redisTransport.js"; import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js"; import { randomUUID } from "crypto"; import { createMcpServer } from "../services/mcp.js"; @@ -20,6 +20,47 @@ export async function handleStreamableHTTP(req: Request, res: Response) { let shttpTransport: StreamableHTTPServerTransport | undefined = undefined; let cleanup: (() => Promise) | undefined = undefined; try { + // Handle DELETE requests for session termination + if (req.method === 'DELETE') { + const sessionId = req.headers['mcp-session-id'] as string | undefined; + + if (!sessionId) { + res.status(400).json({ + jsonrpc: '2.0', + error: { + code: -32000, + message: 'Bad Request: Session ID required for DELETE request', + }, + id: null, + }); + return; + } + + // Check if session exists + if (!(await isLive(sessionId))) { + res.status(404).json({ + jsonrpc: '2.0', + error: { + code: -32001, + message: 'Session not found', + }, + id: null, + }); + return; + } + + // Send shutdown control message to terminate the session + await shutdownSession(sessionId); + + // Respond with success + res.status(200).json({ + jsonrpc: '2.0', + result: { status: 'Session terminated successfully' }, + id: null, + }); + return; + } + // Check for existing session ID const sessionId = req.headers['mcp-session-id'] as string | undefined; @@ -32,7 +73,7 @@ export async function handleStreamableHTTP(req: Request, res: Response) { const server = createMcpServer(); - await startServerListeningToRedis(server, sessionId) + await startServerListeningToRedis(server, sessionId); ({ shttpTransport, cleanup } = await getFirstShttpTransport(sessionId)); } else { diff --git a/src/services/redisTransport.ts b/src/services/redisTransport.ts index 5fff564..1510c21 100644 --- a/src/services/redisTransport.ts +++ b/src/services/redisTransport.ts @@ -156,7 +156,7 @@ export class ServerRedisTransport implements Transport { } async send(message: JSONRPCMessage, options?: TransportSendOptions): Promise { - const relatedRequestId = options?.relatedRequestId?.toString() ?? "id" in message ? message.id : notificationStreamId; + const relatedRequestId = options?.relatedRequestId?.toString() ?? ("id" in message ? message.id?.toString() : notificationStreamId); const channel = getToClientChannel(this._sessionId, relatedRequestId) const redisMessage: RedisMessage = { type: 'mcp', message, options };