From a5d117f425d3fc8f572ca742bb36b94c96b721ac Mon Sep 17 00:00:00 2001 From: Olivier Chafik Date: Fri, 16 Jan 2026 14:08:04 +0000 Subject: [PATCH 1/2] fix(transcript-server): normalize Accept header for lenient MCP compatibility - Patch rawHeaders (not just req.headers) for @hono/node-server compatibility - The SDK reads from rawHeaders which is normally immutable - Add HTTP logging middleware for debugging - Use app.post() with explicit 405 for GET/DELETE (per SDK examples) --- examples/transcript-server/main.ts | 105 ++++++++++++++++++++++++++++- 1 file changed, 103 insertions(+), 2 deletions(-) diff --git a/examples/transcript-server/main.ts b/examples/transcript-server/main.ts index 2a927041..002a66c5 100644 --- a/examples/transcript-server/main.ts +++ b/examples/transcript-server/main.ts @@ -13,9 +13,91 @@ import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import cors from "cors"; -import type { Request, Response } from "express"; +import type { Request, Response, NextFunction } from "express"; import { createServer } from "./server.js"; +/** + * Normalize Accept header for lenient MCP compatibility. + * The SDK requires 'application/json, text/event-stream' but some clients send '*\/*'. + * We must patch rawHeaders because @hono/node-server reads from there, not req.headers. + */ +function normalizeAcceptHeader( + req: Request, + _res: Response, + next: NextFunction, +): void { + const accept = req.headers.accept; + if (!accept || accept === "*/*") { + const normalized = "application/json, text/event-stream"; + req.headers.accept = normalized; + + // Patch rawHeaders for @hono/node-server compatibility + const nodeReq = req as unknown as { rawHeaders: string[] }; + const newRawHeaders: string[] = []; + let found = false; + for (let i = 0; i < nodeReq.rawHeaders.length; i += 2) { + if (nodeReq.rawHeaders[i].toLowerCase() === "accept") { + newRawHeaders.push(nodeReq.rawHeaders[i], normalized); + found = true; + } else { + newRawHeaders.push(nodeReq.rawHeaders[i], nodeReq.rawHeaders[i + 1]); + } + } + if (!found) { + newRawHeaders.push("Accept", normalized); + } + Object.defineProperty(nodeReq, "rawHeaders", { value: newRawHeaders }); + } + next(); +} + +/** + * HTTP logging middleware - logs full request and response details. + */ +function httpLogger(req: Request, res: Response, next: NextFunction): void { + const startTime = Date.now(); + const reqId = Math.random().toString(36).slice(2, 8); + + // Log request + console.log(`\n[${reqId}] ← ${req.method} ${req.url}`); + console.log(`[${reqId}] Headers:`, JSON.stringify(req.headers, null, 2)); + if (req.body && Object.keys(req.body).length > 0) { + console.log(`[${reqId}] Body:`, JSON.stringify(req.body, null, 2)); + } + + // Capture response + const originalWrite = res.write.bind(res); + const originalEnd = res.end.bind(res); + const chunks: Buffer[] = []; + + res.write = function (chunk: any, ...args: any[]): boolean { + if (chunk) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); + return originalWrite(chunk, ...args); + }; + + res.end = function (chunk?: any, ...args: any[]): Response { + if (chunk) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); + const duration = Date.now() - startTime; + const body = Buffer.concat(chunks).toString("utf8"); + + console.log(`[${reqId}] → ${res.statusCode} (${duration}ms)`); + console.log( + `[${reqId}] Headers:`, + JSON.stringify(res.getHeaders(), null, 2), + ); + if (body) { + console.log( + `[${reqId}] Body:`, + body.length > 2000 ? body.slice(0, 2000) + "..." : body, + ); + } + + return originalEnd(chunk, ...args); + }; + + next(); +} + export interface ServerOptions { port: number; name?: string; @@ -32,8 +114,10 @@ export async function startServer( const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); + app.use(normalizeAcceptHeader); + app.use(httpLogger); - app.all("/mcp", async (req: Request, res: Response) => { + app.post("/mcp", async (req: Request, res: Response) => { const server = createServer(); const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, @@ -59,6 +143,23 @@ export async function startServer( } }); + // GET and DELETE not supported in stateless mode + app.get("/mcp", (_req: Request, res: Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed in stateless mode" }, + id: null, + }); + }); + + app.delete("/mcp", (_req: Request, res: Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed in stateless mode" }, + id: null, + }); + }); + const httpServer = app.listen(port, (err) => { if (err) { console.error("Failed to start server:", err); From 3c1cc34bb6279268ae1f72cbc7a930cce09570a2 Mon Sep 17 00:00:00 2001 From: Olivier Chafik Date: Fri, 16 Jan 2026 14:30:01 +0000 Subject: [PATCH 2/2] fix(examples): normalize Accept header for all MCP servers Apply the same Accept header normalization fix to all example servers: - Patch rawHeaders for @hono/node-server compatibility - Use app.post() with explicit 405 for GET/DELETE - Fixes 406 errors when clients send Accept: */* or no Accept header --- examples/basic-server-preact/main.ts | 57 ++++++++++++++++++- examples/basic-server-react/main.ts | 57 ++++++++++++++++++- examples/basic-server-solid/main.ts | 57 ++++++++++++++++++- examples/basic-server-svelte/main.ts | 57 ++++++++++++++++++- examples/basic-server-vanillajs/main.ts | 57 ++++++++++++++++++- examples/basic-server-vue/main.ts | 57 ++++++++++++++++++- examples/budget-allocator-server/main.ts | 57 ++++++++++++++++++- examples/cohort-heatmap-server/main.ts | 57 ++++++++++++++++++- examples/customer-segmentation-server/main.ts | 57 ++++++++++++++++++- examples/integration-server/main.ts | 57 ++++++++++++++++++- examples/map-server/main.ts | 57 ++++++++++++++++++- examples/pdf-server/main.ts | 57 ++++++++++++++++++- examples/scenario-modeler-server/main.ts | 57 ++++++++++++++++++- examples/shadertoy-server/main.ts | 57 ++++++++++++++++++- examples/sheet-music-server/main.ts | 57 ++++++++++++++++++- examples/system-monitor-server/main.ts | 57 ++++++++++++++++++- examples/threejs-server/main.ts | 57 ++++++++++++++++++- examples/video-resource-server/main.ts | 57 ++++++++++++++++++- examples/wiki-explorer-server/main.ts | 57 ++++++++++++++++++- 19 files changed, 1045 insertions(+), 38 deletions(-) diff --git a/examples/basic-server-preact/main.ts b/examples/basic-server-preact/main.ts index d9a51a2e..bc1f5c3a 100644 --- a/examples/basic-server-preact/main.ts +++ b/examples/basic-server-preact/main.ts @@ -13,9 +13,44 @@ import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import cors from "cors"; -import type { Request, Response } from "express"; +import type { Request, Response, NextFunction } from "express"; import { createServer } from "./server.js"; +/** + * Normalize Accept header for lenient MCP compatibility. + * The SDK requires 'application/json, text/event-stream' but some clients send wildcard Accept headers. + * We must patch rawHeaders because @hono/node-server reads from there, not req.headers. + */ +function normalizeAcceptHeader( + req: Request, + _res: Response, + next: NextFunction, +): void { + const accept = req.headers.accept; + if (!accept || accept === "*/*") { + const normalized = "application/json, text/event-stream"; + req.headers.accept = normalized; + + // Patch rawHeaders for @hono/node-server compatibility + const nodeReq = req as unknown as { rawHeaders: string[] }; + const newRawHeaders: string[] = []; + let found = false; + for (let i = 0; i < nodeReq.rawHeaders.length; i += 2) { + if (nodeReq.rawHeaders[i].toLowerCase() === "accept") { + newRawHeaders.push(nodeReq.rawHeaders[i], normalized); + found = true; + } else { + newRawHeaders.push(nodeReq.rawHeaders[i], nodeReq.rawHeaders[i + 1]); + } + } + if (!found) { + newRawHeaders.push("Accept", normalized); + } + Object.defineProperty(nodeReq, "rawHeaders", { value: newRawHeaders }); + } + next(); +} + export interface ServerOptions { port: number; name?: string; @@ -35,8 +70,9 @@ export async function startServer( const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); + app.use(normalizeAcceptHeader); - app.all("/mcp", async (req: Request, res: Response) => { + app.post("/mcp", async (req: Request, res: Response) => { const server = createServer(); const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, @@ -62,6 +98,23 @@ export async function startServer( } }); + // GET and DELETE not supported in stateless mode + app.get("/mcp", (_req: Request, res: Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed in stateless mode" }, + id: null, + }); + }); + + app.delete("/mcp", (_req: Request, res: Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed in stateless mode" }, + id: null, + }); + }); + const httpServer = app.listen(port, (err) => { if (err) { console.error("Failed to start server:", err); diff --git a/examples/basic-server-react/main.ts b/examples/basic-server-react/main.ts index 39840a13..38423140 100644 --- a/examples/basic-server-react/main.ts +++ b/examples/basic-server-react/main.ts @@ -9,9 +9,44 @@ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import cors from "cors"; -import type { Request, Response } from "express"; +import type { Request, Response, NextFunction } from "express"; import { createServer } from "./server.js"; +/** + * Normalize Accept header for lenient MCP compatibility. + * The SDK requires 'application/json, text/event-stream' but some clients send wildcard Accept headers. + * We must patch rawHeaders because @hono/node-server reads from there, not req.headers. + */ +function normalizeAcceptHeader( + req: Request, + _res: Response, + next: NextFunction, +): void { + const accept = req.headers.accept; + if (!accept || accept === "*/*") { + const normalized = "application/json, text/event-stream"; + req.headers.accept = normalized; + + // Patch rawHeaders for @hono/node-server compatibility + const nodeReq = req as unknown as { rawHeaders: string[] }; + const newRawHeaders: string[] = []; + let found = false; + for (let i = 0; i < nodeReq.rawHeaders.length; i += 2) { + if (nodeReq.rawHeaders[i].toLowerCase() === "accept") { + newRawHeaders.push(nodeReq.rawHeaders[i], normalized); + found = true; + } else { + newRawHeaders.push(nodeReq.rawHeaders[i], nodeReq.rawHeaders[i + 1]); + } + } + if (!found) { + newRawHeaders.push("Accept", normalized); + } + Object.defineProperty(nodeReq, "rawHeaders", { value: newRawHeaders }); + } + next(); +} + export interface ServerOptions { port: number; name?: string; @@ -31,8 +66,9 @@ export async function startServer( const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); + app.use(normalizeAcceptHeader); - app.all("/mcp", async (req: Request, res: Response) => { + app.post("/mcp", async (req: Request, res: Response) => { const server = createServer(); const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, @@ -58,6 +94,23 @@ export async function startServer( } }); + // GET and DELETE not supported in stateless mode + app.get("/mcp", (_req: Request, res: Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed in stateless mode" }, + id: null, + }); + }); + + app.delete("/mcp", (_req: Request, res: Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed in stateless mode" }, + id: null, + }); + }); + const httpServer = app.listen(port, (err) => { if (err) { console.error("Failed to start server:", err); diff --git a/examples/basic-server-solid/main.ts b/examples/basic-server-solid/main.ts index 23353486..72ab66c7 100644 --- a/examples/basic-server-solid/main.ts +++ b/examples/basic-server-solid/main.ts @@ -13,9 +13,44 @@ import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import cors from "cors"; -import type { Request, Response } from "express"; +import type { Request, Response, NextFunction } from "express"; import { createServer } from "./server.js"; +/** + * Normalize Accept header for lenient MCP compatibility. + * The SDK requires 'application/json, text/event-stream' but some clients send wildcard Accept headers. + * We must patch rawHeaders because @hono/node-server reads from there, not req.headers. + */ +function normalizeAcceptHeader( + req: Request, + _res: Response, + next: NextFunction, +): void { + const accept = req.headers.accept; + if (!accept || accept === "*/*") { + const normalized = "application/json, text/event-stream"; + req.headers.accept = normalized; + + // Patch rawHeaders for @hono/node-server compatibility + const nodeReq = req as unknown as { rawHeaders: string[] }; + const newRawHeaders: string[] = []; + let found = false; + for (let i = 0; i < nodeReq.rawHeaders.length; i += 2) { + if (nodeReq.rawHeaders[i].toLowerCase() === "accept") { + newRawHeaders.push(nodeReq.rawHeaders[i], normalized); + found = true; + } else { + newRawHeaders.push(nodeReq.rawHeaders[i], nodeReq.rawHeaders[i + 1]); + } + } + if (!found) { + newRawHeaders.push("Accept", normalized); + } + Object.defineProperty(nodeReq, "rawHeaders", { value: newRawHeaders }); + } + next(); +} + export interface ServerOptions { port: number; name?: string; @@ -35,8 +70,9 @@ export async function startServer( const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); + app.use(normalizeAcceptHeader); - app.all("/mcp", async (req: Request, res: Response) => { + app.post("/mcp", async (req: Request, res: Response) => { const server = createServer(); const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, @@ -62,6 +98,23 @@ export async function startServer( } }); + // GET and DELETE not supported in stateless mode + app.get("/mcp", (_req: Request, res: Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed in stateless mode" }, + id: null, + }); + }); + + app.delete("/mcp", (_req: Request, res: Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed in stateless mode" }, + id: null, + }); + }); + const httpServer = app.listen(port, (err) => { if (err) { console.error("Failed to start server:", err); diff --git a/examples/basic-server-svelte/main.ts b/examples/basic-server-svelte/main.ts index 762a40eb..6bbf0013 100644 --- a/examples/basic-server-svelte/main.ts +++ b/examples/basic-server-svelte/main.ts @@ -13,9 +13,44 @@ import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import cors from "cors"; -import type { Request, Response } from "express"; +import type { Request, Response, NextFunction } from "express"; import { createServer } from "./server.js"; +/** + * Normalize Accept header for lenient MCP compatibility. + * The SDK requires 'application/json, text/event-stream' but some clients send wildcard Accept headers. + * We must patch rawHeaders because @hono/node-server reads from there, not req.headers. + */ +function normalizeAcceptHeader( + req: Request, + _res: Response, + next: NextFunction, +): void { + const accept = req.headers.accept; + if (!accept || accept === "*/*") { + const normalized = "application/json, text/event-stream"; + req.headers.accept = normalized; + + // Patch rawHeaders for @hono/node-server compatibility + const nodeReq = req as unknown as { rawHeaders: string[] }; + const newRawHeaders: string[] = []; + let found = false; + for (let i = 0; i < nodeReq.rawHeaders.length; i += 2) { + if (nodeReq.rawHeaders[i].toLowerCase() === "accept") { + newRawHeaders.push(nodeReq.rawHeaders[i], normalized); + found = true; + } else { + newRawHeaders.push(nodeReq.rawHeaders[i], nodeReq.rawHeaders[i + 1]); + } + } + if (!found) { + newRawHeaders.push("Accept", normalized); + } + Object.defineProperty(nodeReq, "rawHeaders", { value: newRawHeaders }); + } + next(); +} + export interface ServerOptions { port: number; name?: string; @@ -35,8 +70,9 @@ export async function startServer( const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); + app.use(normalizeAcceptHeader); - app.all("/mcp", async (req: Request, res: Response) => { + app.post("/mcp", async (req: Request, res: Response) => { const server = createServer(); const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, @@ -62,6 +98,23 @@ export async function startServer( } }); + // GET and DELETE not supported in stateless mode + app.get("/mcp", (_req: Request, res: Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed in stateless mode" }, + id: null, + }); + }); + + app.delete("/mcp", (_req: Request, res: Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed in stateless mode" }, + id: null, + }); + }); + const httpServer = app.listen(port, (err) => { if (err) { console.error("Failed to start server:", err); diff --git a/examples/basic-server-vanillajs/main.ts b/examples/basic-server-vanillajs/main.ts index d53d53b5..78e35744 100644 --- a/examples/basic-server-vanillajs/main.ts +++ b/examples/basic-server-vanillajs/main.ts @@ -13,9 +13,44 @@ import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import cors from "cors"; -import type { Request, Response } from "express"; +import type { Request, Response, NextFunction } from "express"; import { createServer } from "./server.js"; +/** + * Normalize Accept header for lenient MCP compatibility. + * The SDK requires 'application/json, text/event-stream' but some clients send wildcard Accept headers. + * We must patch rawHeaders because @hono/node-server reads from there, not req.headers. + */ +function normalizeAcceptHeader( + req: Request, + _res: Response, + next: NextFunction, +): void { + const accept = req.headers.accept; + if (!accept || accept === "*/*") { + const normalized = "application/json, text/event-stream"; + req.headers.accept = normalized; + + // Patch rawHeaders for @hono/node-server compatibility + const nodeReq = req as unknown as { rawHeaders: string[] }; + const newRawHeaders: string[] = []; + let found = false; + for (let i = 0; i < nodeReq.rawHeaders.length; i += 2) { + if (nodeReq.rawHeaders[i].toLowerCase() === "accept") { + newRawHeaders.push(nodeReq.rawHeaders[i], normalized); + found = true; + } else { + newRawHeaders.push(nodeReq.rawHeaders[i], nodeReq.rawHeaders[i + 1]); + } + } + if (!found) { + newRawHeaders.push("Accept", normalized); + } + Object.defineProperty(nodeReq, "rawHeaders", { value: newRawHeaders }); + } + next(); +} + export interface ServerOptions { port: number; name?: string; @@ -35,8 +70,9 @@ export async function startServer( const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); + app.use(normalizeAcceptHeader); - app.all("/mcp", async (req: Request, res: Response) => { + app.post("/mcp", async (req: Request, res: Response) => { const server = createServer(); const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, @@ -62,6 +98,23 @@ export async function startServer( } }); + // GET and DELETE not supported in stateless mode + app.get("/mcp", (_req: Request, res: Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed in stateless mode" }, + id: null, + }); + }); + + app.delete("/mcp", (_req: Request, res: Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed in stateless mode" }, + id: null, + }); + }); + const httpServer = app.listen(port, (err) => { if (err) { console.error("Failed to start server:", err); diff --git a/examples/basic-server-vue/main.ts b/examples/basic-server-vue/main.ts index 0304600e..8daf1c92 100644 --- a/examples/basic-server-vue/main.ts +++ b/examples/basic-server-vue/main.ts @@ -13,9 +13,44 @@ import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import cors from "cors"; -import type { Request, Response } from "express"; +import type { Request, Response, NextFunction } from "express"; import { createServer } from "./server.js"; +/** + * Normalize Accept header for lenient MCP compatibility. + * The SDK requires 'application/json, text/event-stream' but some clients send wildcard Accept headers. + * We must patch rawHeaders because @hono/node-server reads from there, not req.headers. + */ +function normalizeAcceptHeader( + req: Request, + _res: Response, + next: NextFunction, +): void { + const accept = req.headers.accept; + if (!accept || accept === "*/*") { + const normalized = "application/json, text/event-stream"; + req.headers.accept = normalized; + + // Patch rawHeaders for @hono/node-server compatibility + const nodeReq = req as unknown as { rawHeaders: string[] }; + const newRawHeaders: string[] = []; + let found = false; + for (let i = 0; i < nodeReq.rawHeaders.length; i += 2) { + if (nodeReq.rawHeaders[i].toLowerCase() === "accept") { + newRawHeaders.push(nodeReq.rawHeaders[i], normalized); + found = true; + } else { + newRawHeaders.push(nodeReq.rawHeaders[i], nodeReq.rawHeaders[i + 1]); + } + } + if (!found) { + newRawHeaders.push("Accept", normalized); + } + Object.defineProperty(nodeReq, "rawHeaders", { value: newRawHeaders }); + } + next(); +} + export interface ServerOptions { port: number; name?: string; @@ -35,8 +70,9 @@ export async function startServer( const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); + app.use(normalizeAcceptHeader); - app.all("/mcp", async (req: Request, res: Response) => { + app.post("/mcp", async (req: Request, res: Response) => { const server = createServer(); const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, @@ -62,6 +98,23 @@ export async function startServer( } }); + // GET and DELETE not supported in stateless mode + app.get("/mcp", (_req: Request, res: Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed in stateless mode" }, + id: null, + }); + }); + + app.delete("/mcp", (_req: Request, res: Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed in stateless mode" }, + id: null, + }); + }); + const httpServer = app.listen(port, (err) => { if (err) { console.error("Failed to start server:", err); diff --git a/examples/budget-allocator-server/main.ts b/examples/budget-allocator-server/main.ts index 534e876d..0454fe37 100644 --- a/examples/budget-allocator-server/main.ts +++ b/examples/budget-allocator-server/main.ts @@ -13,9 +13,44 @@ import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import cors from "cors"; -import type { Request, Response } from "express"; +import type { Request, Response, NextFunction } from "express"; import { createServer } from "./server.js"; +/** + * Normalize Accept header for lenient MCP compatibility. + * The SDK requires 'application/json, text/event-stream' but some clients send wildcard Accept headers. + * We must patch rawHeaders because @hono/node-server reads from there, not req.headers. + */ +function normalizeAcceptHeader( + req: Request, + _res: Response, + next: NextFunction, +): void { + const accept = req.headers.accept; + if (!accept || accept === "*/*") { + const normalized = "application/json, text/event-stream"; + req.headers.accept = normalized; + + // Patch rawHeaders for @hono/node-server compatibility + const nodeReq = req as unknown as { rawHeaders: string[] }; + const newRawHeaders: string[] = []; + let found = false; + for (let i = 0; i < nodeReq.rawHeaders.length; i += 2) { + if (nodeReq.rawHeaders[i].toLowerCase() === "accept") { + newRawHeaders.push(nodeReq.rawHeaders[i], normalized); + found = true; + } else { + newRawHeaders.push(nodeReq.rawHeaders[i], nodeReq.rawHeaders[i + 1]); + } + } + if (!found) { + newRawHeaders.push("Accept", normalized); + } + Object.defineProperty(nodeReq, "rawHeaders", { value: newRawHeaders }); + } + next(); +} + export interface ServerOptions { port: number; name?: string; @@ -35,8 +70,9 @@ export async function startServer( const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); + app.use(normalizeAcceptHeader); - app.all("/mcp", async (req: Request, res: Response) => { + app.post("/mcp", async (req: Request, res: Response) => { const server = createServer(); const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, @@ -62,6 +98,23 @@ export async function startServer( } }); + // GET and DELETE not supported in stateless mode + app.get("/mcp", (_req: Request, res: Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed in stateless mode" }, + id: null, + }); + }); + + app.delete("/mcp", (_req: Request, res: Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed in stateless mode" }, + id: null, + }); + }); + const httpServer = app.listen(port, (err) => { if (err) { console.error("Failed to start server:", err); diff --git a/examples/cohort-heatmap-server/main.ts b/examples/cohort-heatmap-server/main.ts index 0b716d1d..be53e291 100644 --- a/examples/cohort-heatmap-server/main.ts +++ b/examples/cohort-heatmap-server/main.ts @@ -13,9 +13,44 @@ import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import cors from "cors"; -import type { Request, Response } from "express"; +import type { Request, Response, NextFunction } from "express"; import { createServer } from "./server.js"; +/** + * Normalize Accept header for lenient MCP compatibility. + * The SDK requires 'application/json, text/event-stream' but some clients send wildcard Accept headers. + * We must patch rawHeaders because @hono/node-server reads from there, not req.headers. + */ +function normalizeAcceptHeader( + req: Request, + _res: Response, + next: NextFunction, +): void { + const accept = req.headers.accept; + if (!accept || accept === "*/*") { + const normalized = "application/json, text/event-stream"; + req.headers.accept = normalized; + + // Patch rawHeaders for @hono/node-server compatibility + const nodeReq = req as unknown as { rawHeaders: string[] }; + const newRawHeaders: string[] = []; + let found = false; + for (let i = 0; i < nodeReq.rawHeaders.length; i += 2) { + if (nodeReq.rawHeaders[i].toLowerCase() === "accept") { + newRawHeaders.push(nodeReq.rawHeaders[i], normalized); + found = true; + } else { + newRawHeaders.push(nodeReq.rawHeaders[i], nodeReq.rawHeaders[i + 1]); + } + } + if (!found) { + newRawHeaders.push("Accept", normalized); + } + Object.defineProperty(nodeReq, "rawHeaders", { value: newRawHeaders }); + } + next(); +} + export interface ServerOptions { port: number; name?: string; @@ -35,8 +70,9 @@ export async function startServer( const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); + app.use(normalizeAcceptHeader); - app.all("/mcp", async (req: Request, res: Response) => { + app.post("/mcp", async (req: Request, res: Response) => { const server = createServer(); const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, @@ -62,6 +98,23 @@ export async function startServer( } }); + // GET and DELETE not supported in stateless mode + app.get("/mcp", (_req: Request, res: Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed in stateless mode" }, + id: null, + }); + }); + + app.delete("/mcp", (_req: Request, res: Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed in stateless mode" }, + id: null, + }); + }); + const httpServer = app.listen(port, (err) => { if (err) { console.error("Failed to start server:", err); diff --git a/examples/customer-segmentation-server/main.ts b/examples/customer-segmentation-server/main.ts index 7be715a4..2db3dde3 100644 --- a/examples/customer-segmentation-server/main.ts +++ b/examples/customer-segmentation-server/main.ts @@ -13,9 +13,44 @@ import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import cors from "cors"; -import type { Request, Response } from "express"; +import type { Request, Response, NextFunction } from "express"; import { createServer } from "./server.js"; +/** + * Normalize Accept header for lenient MCP compatibility. + * The SDK requires 'application/json, text/event-stream' but some clients send wildcard Accept headers. + * We must patch rawHeaders because @hono/node-server reads from there, not req.headers. + */ +function normalizeAcceptHeader( + req: Request, + _res: Response, + next: NextFunction, +): void { + const accept = req.headers.accept; + if (!accept || accept === "*/*") { + const normalized = "application/json, text/event-stream"; + req.headers.accept = normalized; + + // Patch rawHeaders for @hono/node-server compatibility + const nodeReq = req as unknown as { rawHeaders: string[] }; + const newRawHeaders: string[] = []; + let found = false; + for (let i = 0; i < nodeReq.rawHeaders.length; i += 2) { + if (nodeReq.rawHeaders[i].toLowerCase() === "accept") { + newRawHeaders.push(nodeReq.rawHeaders[i], normalized); + found = true; + } else { + newRawHeaders.push(nodeReq.rawHeaders[i], nodeReq.rawHeaders[i + 1]); + } + } + if (!found) { + newRawHeaders.push("Accept", normalized); + } + Object.defineProperty(nodeReq, "rawHeaders", { value: newRawHeaders }); + } + next(); +} + export interface ServerOptions { port: number; name?: string; @@ -35,8 +70,9 @@ export async function startServer( const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); + app.use(normalizeAcceptHeader); - app.all("/mcp", async (req: Request, res: Response) => { + app.post("/mcp", async (req: Request, res: Response) => { const server = createServer(); const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, @@ -62,6 +98,23 @@ export async function startServer( } }); + // GET and DELETE not supported in stateless mode + app.get("/mcp", (_req: Request, res: Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed in stateless mode" }, + id: null, + }); + }); + + app.delete("/mcp", (_req: Request, res: Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed in stateless mode" }, + id: null, + }); + }); + const httpServer = app.listen(port, (err) => { if (err) { console.error("Failed to start server:", err); diff --git a/examples/integration-server/main.ts b/examples/integration-server/main.ts index 5340e8a8..df06046c 100644 --- a/examples/integration-server/main.ts +++ b/examples/integration-server/main.ts @@ -13,9 +13,44 @@ import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import cors from "cors"; -import type { Request, Response } from "express"; +import type { Request, Response, NextFunction } from "express"; import { createServer } from "./server.js"; +/** + * Normalize Accept header for lenient MCP compatibility. + * The SDK requires 'application/json, text/event-stream' but some clients send wildcard Accept headers. + * We must patch rawHeaders because @hono/node-server reads from there, not req.headers. + */ +function normalizeAcceptHeader( + req: Request, + _res: Response, + next: NextFunction, +): void { + const accept = req.headers.accept; + if (!accept || accept === "*/*") { + const normalized = "application/json, text/event-stream"; + req.headers.accept = normalized; + + // Patch rawHeaders for @hono/node-server compatibility + const nodeReq = req as unknown as { rawHeaders: string[] }; + const newRawHeaders: string[] = []; + let found = false; + for (let i = 0; i < nodeReq.rawHeaders.length; i += 2) { + if (nodeReq.rawHeaders[i].toLowerCase() === "accept") { + newRawHeaders.push(nodeReq.rawHeaders[i], normalized); + found = true; + } else { + newRawHeaders.push(nodeReq.rawHeaders[i], nodeReq.rawHeaders[i + 1]); + } + } + if (!found) { + newRawHeaders.push("Accept", normalized); + } + Object.defineProperty(nodeReq, "rawHeaders", { value: newRawHeaders }); + } + next(); +} + export interface ServerOptions { port: number; name?: string; @@ -35,8 +70,9 @@ export async function startServer( const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); + app.use(normalizeAcceptHeader); - app.all("/mcp", async (req: Request, res: Response) => { + app.post("/mcp", async (req: Request, res: Response) => { const server = createServer(); const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, @@ -62,6 +98,23 @@ export async function startServer( } }); + // GET and DELETE not supported in stateless mode + app.get("/mcp", (_req: Request, res: Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed in stateless mode" }, + id: null, + }); + }); + + app.delete("/mcp", (_req: Request, res: Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed in stateless mode" }, + id: null, + }); + }); + const httpServer = app.listen(port, (err) => { if (err) { console.error("Failed to start server:", err); diff --git a/examples/map-server/main.ts b/examples/map-server/main.ts index 44956598..eb60fdc2 100644 --- a/examples/map-server/main.ts +++ b/examples/map-server/main.ts @@ -13,9 +13,44 @@ import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import cors from "cors"; -import type { Request, Response } from "express"; +import type { Request, Response, NextFunction } from "express"; import { createServer } from "./server.js"; +/** + * Normalize Accept header for lenient MCP compatibility. + * The SDK requires 'application/json, text/event-stream' but some clients send wildcard Accept headers. + * We must patch rawHeaders because @hono/node-server reads from there, not req.headers. + */ +function normalizeAcceptHeader( + req: Request, + _res: Response, + next: NextFunction, +): void { + const accept = req.headers.accept; + if (!accept || accept === "*/*") { + const normalized = "application/json, text/event-stream"; + req.headers.accept = normalized; + + // Patch rawHeaders for @hono/node-server compatibility + const nodeReq = req as unknown as { rawHeaders: string[] }; + const newRawHeaders: string[] = []; + let found = false; + for (let i = 0; i < nodeReq.rawHeaders.length; i += 2) { + if (nodeReq.rawHeaders[i].toLowerCase() === "accept") { + newRawHeaders.push(nodeReq.rawHeaders[i], normalized); + found = true; + } else { + newRawHeaders.push(nodeReq.rawHeaders[i], nodeReq.rawHeaders[i + 1]); + } + } + if (!found) { + newRawHeaders.push("Accept", normalized); + } + Object.defineProperty(nodeReq, "rawHeaders", { value: newRawHeaders }); + } + next(); +} + export interface ServerOptions { port: number; name?: string; @@ -35,8 +70,9 @@ export async function startServer( const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); + app.use(normalizeAcceptHeader); - app.all("/mcp", async (req: Request, res: Response) => { + app.post("/mcp", async (req: Request, res: Response) => { const server = createServer(); const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, @@ -62,6 +98,23 @@ export async function startServer( } }); + // GET and DELETE not supported in stateless mode + app.get("/mcp", (_req: Request, res: Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed in stateless mode" }, + id: null, + }); + }); + + app.delete("/mcp", (_req: Request, res: Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed in stateless mode" }, + id: null, + }); + }); + const httpServer = app.listen(port, () => { console.log(`${name} listening on http://localhost:${port}/mcp`); }); diff --git a/examples/pdf-server/main.ts b/examples/pdf-server/main.ts index ac0b8ccb..4cce1b13 100644 --- a/examples/pdf-server/main.ts +++ b/examples/pdf-server/main.ts @@ -13,10 +13,45 @@ import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import cors from "cors"; -import type { Request, Response } from "express"; +import type { Request, Response, NextFunction } from "express"; import { createServer, initializePdfIndex } from "./server.js"; import { isArxivUrl, toFileUrl, normalizeArxivUrl } from "./src/pdf-indexer.js"; +/** + * Normalize Accept header for lenient MCP compatibility. + * The SDK requires 'application/json, text/event-stream' but some clients send wildcard Accept headers. + * We must patch rawHeaders because @hono/node-server reads from there, not req.headers. + */ +function normalizeAcceptHeader( + req: Request, + _res: Response, + next: NextFunction, +): void { + const accept = req.headers.accept; + if (!accept || accept === "*/*") { + const normalized = "application/json, text/event-stream"; + req.headers.accept = normalized; + + // Patch rawHeaders for @hono/node-server compatibility + const nodeReq = req as unknown as { rawHeaders: string[] }; + const newRawHeaders: string[] = []; + let found = false; + for (let i = 0; i < nodeReq.rawHeaders.length; i += 2) { + if (nodeReq.rawHeaders[i].toLowerCase() === "accept") { + newRawHeaders.push(nodeReq.rawHeaders[i], normalized); + found = true; + } else { + newRawHeaders.push(nodeReq.rawHeaders[i], nodeReq.rawHeaders[i + 1]); + } + } + if (!found) { + newRawHeaders.push("Accept", normalized); + } + Object.defineProperty(nodeReq, "rawHeaders", { value: newRawHeaders }); + } + next(); +} + export interface ServerOptions { port: number; name?: string; @@ -36,8 +71,9 @@ export async function startServer( const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); + app.use(normalizeAcceptHeader); - app.all("/mcp", async (req: Request, res: Response) => { + app.post("/mcp", async (req: Request, res: Response) => { const server = createServer(); const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, @@ -63,6 +99,23 @@ export async function startServer( } }); + // GET and DELETE not supported in stateless mode + app.get("/mcp", (_req: Request, res: Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed in stateless mode" }, + id: null, + }); + }); + + app.delete("/mcp", (_req: Request, res: Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed in stateless mode" }, + id: null, + }); + }); + const httpServer = app.listen(port, (err) => { if (err) { console.error("Failed to start server:", err); diff --git a/examples/scenario-modeler-server/main.ts b/examples/scenario-modeler-server/main.ts index b7cc7d00..a4846ac9 100644 --- a/examples/scenario-modeler-server/main.ts +++ b/examples/scenario-modeler-server/main.ts @@ -13,9 +13,44 @@ import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import cors from "cors"; -import type { Request, Response } from "express"; +import type { Request, Response, NextFunction } from "express"; import { createServer } from "./server.js"; +/** + * Normalize Accept header for lenient MCP compatibility. + * The SDK requires 'application/json, text/event-stream' but some clients send wildcard Accept headers. + * We must patch rawHeaders because @hono/node-server reads from there, not req.headers. + */ +function normalizeAcceptHeader( + req: Request, + _res: Response, + next: NextFunction, +): void { + const accept = req.headers.accept; + if (!accept || accept === "*/*") { + const normalized = "application/json, text/event-stream"; + req.headers.accept = normalized; + + // Patch rawHeaders for @hono/node-server compatibility + const nodeReq = req as unknown as { rawHeaders: string[] }; + const newRawHeaders: string[] = []; + let found = false; + for (let i = 0; i < nodeReq.rawHeaders.length; i += 2) { + if (nodeReq.rawHeaders[i].toLowerCase() === "accept") { + newRawHeaders.push(nodeReq.rawHeaders[i], normalized); + found = true; + } else { + newRawHeaders.push(nodeReq.rawHeaders[i], nodeReq.rawHeaders[i + 1]); + } + } + if (!found) { + newRawHeaders.push("Accept", normalized); + } + Object.defineProperty(nodeReq, "rawHeaders", { value: newRawHeaders }); + } + next(); +} + export interface ServerOptions { port: number; name?: string; @@ -35,8 +70,9 @@ export async function startServer( const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); + app.use(normalizeAcceptHeader); - app.all("/mcp", async (req: Request, res: Response) => { + app.post("/mcp", async (req: Request, res: Response) => { const server = createServer(); const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, @@ -62,6 +98,23 @@ export async function startServer( } }); + // GET and DELETE not supported in stateless mode + app.get("/mcp", (_req: Request, res: Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed in stateless mode" }, + id: null, + }); + }); + + app.delete("/mcp", (_req: Request, res: Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed in stateless mode" }, + id: null, + }); + }); + const httpServer = app.listen(port, (err) => { if (err) { console.error("Failed to start server:", err); diff --git a/examples/shadertoy-server/main.ts b/examples/shadertoy-server/main.ts index 717f5e64..4bd93657 100644 --- a/examples/shadertoy-server/main.ts +++ b/examples/shadertoy-server/main.ts @@ -13,9 +13,44 @@ import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import cors from "cors"; -import type { Request, Response } from "express"; +import type { Request, Response, NextFunction } from "express"; import { createServer } from "./server.js"; +/** + * Normalize Accept header for lenient MCP compatibility. + * The SDK requires 'application/json, text/event-stream' but some clients send wildcard Accept headers. + * We must patch rawHeaders because @hono/node-server reads from there, not req.headers. + */ +function normalizeAcceptHeader( + req: Request, + _res: Response, + next: NextFunction, +): void { + const accept = req.headers.accept; + if (!accept || accept === "*/*") { + const normalized = "application/json, text/event-stream"; + req.headers.accept = normalized; + + // Patch rawHeaders for @hono/node-server compatibility + const nodeReq = req as unknown as { rawHeaders: string[] }; + const newRawHeaders: string[] = []; + let found = false; + for (let i = 0; i < nodeReq.rawHeaders.length; i += 2) { + if (nodeReq.rawHeaders[i].toLowerCase() === "accept") { + newRawHeaders.push(nodeReq.rawHeaders[i], normalized); + found = true; + } else { + newRawHeaders.push(nodeReq.rawHeaders[i], nodeReq.rawHeaders[i + 1]); + } + } + if (!found) { + newRawHeaders.push("Accept", normalized); + } + Object.defineProperty(nodeReq, "rawHeaders", { value: newRawHeaders }); + } + next(); +} + export interface ServerOptions { port: number; name?: string; @@ -35,8 +70,9 @@ export async function startServer( const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); + app.use(normalizeAcceptHeader); - app.all("/mcp", async (req: Request, res: Response) => { + app.post("/mcp", async (req: Request, res: Response) => { const server = createServer(); const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, @@ -62,6 +98,23 @@ export async function startServer( } }); + // GET and DELETE not supported in stateless mode + app.get("/mcp", (_req: Request, res: Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed in stateless mode" }, + id: null, + }); + }); + + app.delete("/mcp", (_req: Request, res: Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed in stateless mode" }, + id: null, + }); + }); + const httpServer = app.listen(port, (err) => { if (err) { console.error("Failed to start server:", err); diff --git a/examples/sheet-music-server/main.ts b/examples/sheet-music-server/main.ts index 4b206e72..70f305fb 100644 --- a/examples/sheet-music-server/main.ts +++ b/examples/sheet-music-server/main.ts @@ -13,9 +13,44 @@ import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import cors from "cors"; -import type { Request, Response } from "express"; +import type { Request, Response, NextFunction } from "express"; import { createServer } from "./server.js"; +/** + * Normalize Accept header for lenient MCP compatibility. + * The SDK requires 'application/json, text/event-stream' but some clients send wildcard Accept headers. + * We must patch rawHeaders because @hono/node-server reads from there, not req.headers. + */ +function normalizeAcceptHeader( + req: Request, + _res: Response, + next: NextFunction, +): void { + const accept = req.headers.accept; + if (!accept || accept === "*/*") { + const normalized = "application/json, text/event-stream"; + req.headers.accept = normalized; + + // Patch rawHeaders for @hono/node-server compatibility + const nodeReq = req as unknown as { rawHeaders: string[] }; + const newRawHeaders: string[] = []; + let found = false; + for (let i = 0; i < nodeReq.rawHeaders.length; i += 2) { + if (nodeReq.rawHeaders[i].toLowerCase() === "accept") { + newRawHeaders.push(nodeReq.rawHeaders[i], normalized); + found = true; + } else { + newRawHeaders.push(nodeReq.rawHeaders[i], nodeReq.rawHeaders[i + 1]); + } + } + if (!found) { + newRawHeaders.push("Accept", normalized); + } + Object.defineProperty(nodeReq, "rawHeaders", { value: newRawHeaders }); + } + next(); +} + export interface ServerOptions { port: number; name?: string; @@ -35,8 +70,9 @@ export async function startServer( const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); + app.use(normalizeAcceptHeader); - app.all("/mcp", async (req: Request, res: Response) => { + app.post("/mcp", async (req: Request, res: Response) => { const server = createServer(); const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, @@ -62,6 +98,23 @@ export async function startServer( } }); + // GET and DELETE not supported in stateless mode + app.get("/mcp", (_req: Request, res: Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed in stateless mode" }, + id: null, + }); + }); + + app.delete("/mcp", (_req: Request, res: Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed in stateless mode" }, + id: null, + }); + }); + const httpServer = app.listen(port, (err) => { if (err) { console.error("Failed to start server:", err); diff --git a/examples/system-monitor-server/main.ts b/examples/system-monitor-server/main.ts index 19624809..5176eaf7 100644 --- a/examples/system-monitor-server/main.ts +++ b/examples/system-monitor-server/main.ts @@ -13,9 +13,44 @@ import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import cors from "cors"; -import type { Request, Response } from "express"; +import type { Request, Response, NextFunction } from "express"; import { createServer } from "./server.js"; +/** + * Normalize Accept header for lenient MCP compatibility. + * The SDK requires 'application/json, text/event-stream' but some clients send wildcard Accept headers. + * We must patch rawHeaders because @hono/node-server reads from there, not req.headers. + */ +function normalizeAcceptHeader( + req: Request, + _res: Response, + next: NextFunction, +): void { + const accept = req.headers.accept; + if (!accept || accept === "*/*") { + const normalized = "application/json, text/event-stream"; + req.headers.accept = normalized; + + // Patch rawHeaders for @hono/node-server compatibility + const nodeReq = req as unknown as { rawHeaders: string[] }; + const newRawHeaders: string[] = []; + let found = false; + for (let i = 0; i < nodeReq.rawHeaders.length; i += 2) { + if (nodeReq.rawHeaders[i].toLowerCase() === "accept") { + newRawHeaders.push(nodeReq.rawHeaders[i], normalized); + found = true; + } else { + newRawHeaders.push(nodeReq.rawHeaders[i], nodeReq.rawHeaders[i + 1]); + } + } + if (!found) { + newRawHeaders.push("Accept", normalized); + } + Object.defineProperty(nodeReq, "rawHeaders", { value: newRawHeaders }); + } + next(); +} + export interface ServerOptions { port: number; name?: string; @@ -35,8 +70,9 @@ export async function startServer( const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); + app.use(normalizeAcceptHeader); - app.all("/mcp", async (req: Request, res: Response) => { + app.post("/mcp", async (req: Request, res: Response) => { const server = createServer(); const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, @@ -62,6 +98,23 @@ export async function startServer( } }); + // GET and DELETE not supported in stateless mode + app.get("/mcp", (_req: Request, res: Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed in stateless mode" }, + id: null, + }); + }); + + app.delete("/mcp", (_req: Request, res: Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed in stateless mode" }, + id: null, + }); + }); + const httpServer = app.listen(port, (err) => { if (err) { console.error("Failed to start server:", err); diff --git a/examples/threejs-server/main.ts b/examples/threejs-server/main.ts index 1f779a97..a6c92ecb 100644 --- a/examples/threejs-server/main.ts +++ b/examples/threejs-server/main.ts @@ -13,9 +13,44 @@ import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import cors from "cors"; -import type { Request, Response } from "express"; +import type { Request, Response, NextFunction } from "express"; import { createServer } from "./server.js"; +/** + * Normalize Accept header for lenient MCP compatibility. + * The SDK requires 'application/json, text/event-stream' but some clients send wildcard Accept headers. + * We must patch rawHeaders because @hono/node-server reads from there, not req.headers. + */ +function normalizeAcceptHeader( + req: Request, + _res: Response, + next: NextFunction, +): void { + const accept = req.headers.accept; + if (!accept || accept === "*/*") { + const normalized = "application/json, text/event-stream"; + req.headers.accept = normalized; + + // Patch rawHeaders for @hono/node-server compatibility + const nodeReq = req as unknown as { rawHeaders: string[] }; + const newRawHeaders: string[] = []; + let found = false; + for (let i = 0; i < nodeReq.rawHeaders.length; i += 2) { + if (nodeReq.rawHeaders[i].toLowerCase() === "accept") { + newRawHeaders.push(nodeReq.rawHeaders[i], normalized); + found = true; + } else { + newRawHeaders.push(nodeReq.rawHeaders[i], nodeReq.rawHeaders[i + 1]); + } + } + if (!found) { + newRawHeaders.push("Accept", normalized); + } + Object.defineProperty(nodeReq, "rawHeaders", { value: newRawHeaders }); + } + next(); +} + export interface ServerOptions { port: number; name?: string; @@ -35,8 +70,9 @@ export async function startServer( const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); + app.use(normalizeAcceptHeader); - app.all("/mcp", async (req: Request, res: Response) => { + app.post("/mcp", async (req: Request, res: Response) => { const server = createServer(); const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, @@ -62,6 +98,23 @@ export async function startServer( } }); + // GET and DELETE not supported in stateless mode + app.get("/mcp", (_req: Request, res: Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed in stateless mode" }, + id: null, + }); + }); + + app.delete("/mcp", (_req: Request, res: Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed in stateless mode" }, + id: null, + }); + }); + const httpServer = app.listen(port, (err) => { if (err) { console.error("Failed to start server:", err); diff --git a/examples/video-resource-server/main.ts b/examples/video-resource-server/main.ts index ff304639..09966ebf 100644 --- a/examples/video-resource-server/main.ts +++ b/examples/video-resource-server/main.ts @@ -13,9 +13,44 @@ import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import cors from "cors"; -import type { Request, Response } from "express"; +import type { Request, Response, NextFunction } from "express"; import { createServer } from "./server.js"; +/** + * Normalize Accept header for lenient MCP compatibility. + * The SDK requires 'application/json, text/event-stream' but some clients send wildcard Accept headers. + * We must patch rawHeaders because @hono/node-server reads from there, not req.headers. + */ +function normalizeAcceptHeader( + req: Request, + _res: Response, + next: NextFunction, +): void { + const accept = req.headers.accept; + if (!accept || accept === "*/*") { + const normalized = "application/json, text/event-stream"; + req.headers.accept = normalized; + + // Patch rawHeaders for @hono/node-server compatibility + const nodeReq = req as unknown as { rawHeaders: string[] }; + const newRawHeaders: string[] = []; + let found = false; + for (let i = 0; i < nodeReq.rawHeaders.length; i += 2) { + if (nodeReq.rawHeaders[i].toLowerCase() === "accept") { + newRawHeaders.push(nodeReq.rawHeaders[i], normalized); + found = true; + } else { + newRawHeaders.push(nodeReq.rawHeaders[i], nodeReq.rawHeaders[i + 1]); + } + } + if (!found) { + newRawHeaders.push("Accept", normalized); + } + Object.defineProperty(nodeReq, "rawHeaders", { value: newRawHeaders }); + } + next(); +} + export interface ServerOptions { port: number; name?: string; @@ -35,8 +70,9 @@ export async function startServer( const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); + app.use(normalizeAcceptHeader); - app.all("/mcp", async (req: Request, res: Response) => { + app.post("/mcp", async (req: Request, res: Response) => { const server = createServer(); const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, @@ -62,6 +98,23 @@ export async function startServer( } }); + // GET and DELETE not supported in stateless mode + app.get("/mcp", (_req: Request, res: Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed in stateless mode" }, + id: null, + }); + }); + + app.delete("/mcp", (_req: Request, res: Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed in stateless mode" }, + id: null, + }); + }); + const httpServer = app.listen(port, (err) => { if (err) { console.error("Failed to start server:", err); diff --git a/examples/wiki-explorer-server/main.ts b/examples/wiki-explorer-server/main.ts index d83ac427..2ed02439 100644 --- a/examples/wiki-explorer-server/main.ts +++ b/examples/wiki-explorer-server/main.ts @@ -13,9 +13,44 @@ import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import cors from "cors"; -import type { Request, Response } from "express"; +import type { Request, Response, NextFunction } from "express"; import { createServer } from "./server.js"; +/** + * Normalize Accept header for lenient MCP compatibility. + * The SDK requires 'application/json, text/event-stream' but some clients send wildcard Accept headers. + * We must patch rawHeaders because @hono/node-server reads from there, not req.headers. + */ +function normalizeAcceptHeader( + req: Request, + _res: Response, + next: NextFunction, +): void { + const accept = req.headers.accept; + if (!accept || accept === "*/*") { + const normalized = "application/json, text/event-stream"; + req.headers.accept = normalized; + + // Patch rawHeaders for @hono/node-server compatibility + const nodeReq = req as unknown as { rawHeaders: string[] }; + const newRawHeaders: string[] = []; + let found = false; + for (let i = 0; i < nodeReq.rawHeaders.length; i += 2) { + if (nodeReq.rawHeaders[i].toLowerCase() === "accept") { + newRawHeaders.push(nodeReq.rawHeaders[i], normalized); + found = true; + } else { + newRawHeaders.push(nodeReq.rawHeaders[i], nodeReq.rawHeaders[i + 1]); + } + } + if (!found) { + newRawHeaders.push("Accept", normalized); + } + Object.defineProperty(nodeReq, "rawHeaders", { value: newRawHeaders }); + } + next(); +} + export interface ServerOptions { port: number; name?: string; @@ -35,8 +70,9 @@ export async function startServer( const app = createMcpExpressApp({ host: "0.0.0.0" }); app.use(cors()); + app.use(normalizeAcceptHeader); - app.all("/mcp", async (req: Request, res: Response) => { + app.post("/mcp", async (req: Request, res: Response) => { const server = createServer(); const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, @@ -62,6 +98,23 @@ export async function startServer( } }); + // GET and DELETE not supported in stateless mode + app.get("/mcp", (_req: Request, res: Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed in stateless mode" }, + id: null, + }); + }); + + app.delete("/mcp", (_req: Request, res: Response) => { + res.status(405).json({ + jsonrpc: "2.0", + error: { code: -32000, message: "Method not allowed in stateless mode" }, + id: null, + }); + }); + const httpServer = app.listen(port, (err) => { if (err) { console.error("Failed to start server:", err);