@@ -13,9 +13,91 @@ import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js
1313import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js" ;
1414import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js" ;
1515import cors from "cors" ;
16- import type { Request , Response } from "express" ;
16+ import type { Request , Response , NextFunction } from "express" ;
1717import { createServer } from "./server.js" ;
1818
19+ /**
20+ * Normalize Accept header for lenient MCP compatibility.
21+ * The SDK requires 'application/json, text/event-stream' but some clients send '*\/*'.
22+ * We must patch rawHeaders because @hono/node-server reads from there, not req.headers.
23+ */
24+ function normalizeAcceptHeader (
25+ req : Request ,
26+ _res : Response ,
27+ next : NextFunction ,
28+ ) : void {
29+ const accept = req . headers . accept ;
30+ if ( ! accept || accept === "*/*" ) {
31+ const normalized = "application/json, text/event-stream" ;
32+ req . headers . accept = normalized ;
33+
34+ // Patch rawHeaders for @hono /node-server compatibility
35+ const nodeReq = req as unknown as { rawHeaders : string [ ] } ;
36+ const newRawHeaders : string [ ] = [ ] ;
37+ let found = false ;
38+ for ( let i = 0 ; i < nodeReq . rawHeaders . length ; i += 2 ) {
39+ if ( nodeReq . rawHeaders [ i ] . toLowerCase ( ) === "accept" ) {
40+ newRawHeaders . push ( nodeReq . rawHeaders [ i ] , normalized ) ;
41+ found = true ;
42+ } else {
43+ newRawHeaders . push ( nodeReq . rawHeaders [ i ] , nodeReq . rawHeaders [ i + 1 ] ) ;
44+ }
45+ }
46+ if ( ! found ) {
47+ newRawHeaders . push ( "Accept" , normalized ) ;
48+ }
49+ Object . defineProperty ( nodeReq , "rawHeaders" , { value : newRawHeaders } ) ;
50+ }
51+ next ( ) ;
52+ }
53+
54+ /**
55+ * HTTP logging middleware - logs full request and response details.
56+ */
57+ function httpLogger ( req : Request , res : Response , next : NextFunction ) : void {
58+ const startTime = Date . now ( ) ;
59+ const reqId = Math . random ( ) . toString ( 36 ) . slice ( 2 , 8 ) ;
60+
61+ // Log request
62+ console . log ( `\n[${ reqId } ] ← ${ req . method } ${ req . url } ` ) ;
63+ console . log ( `[${ reqId } ] Headers:` , JSON . stringify ( req . headers , null , 2 ) ) ;
64+ if ( req . body && Object . keys ( req . body ) . length > 0 ) {
65+ console . log ( `[${ reqId } ] Body:` , JSON . stringify ( req . body , null , 2 ) ) ;
66+ }
67+
68+ // Capture response
69+ const originalWrite = res . write . bind ( res ) ;
70+ const originalEnd = res . end . bind ( res ) ;
71+ const chunks : Buffer [ ] = [ ] ;
72+
73+ res . write = function ( chunk : any , ...args : any [ ] ) : boolean {
74+ if ( chunk ) chunks . push ( Buffer . isBuffer ( chunk ) ? chunk : Buffer . from ( chunk ) ) ;
75+ return originalWrite ( chunk , ...args ) ;
76+ } ;
77+
78+ res . end = function ( chunk ?: any , ...args : any [ ] ) : Response {
79+ if ( chunk ) chunks . push ( Buffer . isBuffer ( chunk ) ? chunk : Buffer . from ( chunk ) ) ;
80+ const duration = Date . now ( ) - startTime ;
81+ const body = Buffer . concat ( chunks ) . toString ( "utf8" ) ;
82+
83+ console . log ( `[${ reqId } ] → ${ res . statusCode } (${ duration } ms)` ) ;
84+ console . log (
85+ `[${ reqId } ] Headers:` ,
86+ JSON . stringify ( res . getHeaders ( ) , null , 2 ) ,
87+ ) ;
88+ if ( body ) {
89+ console . log (
90+ `[${ reqId } ] Body:` ,
91+ body . length > 2000 ? body . slice ( 0 , 2000 ) + "..." : body ,
92+ ) ;
93+ }
94+
95+ return originalEnd ( chunk , ...args ) ;
96+ } ;
97+
98+ next ( ) ;
99+ }
100+
19101export interface ServerOptions {
20102 port : number ;
21103 name ?: string ;
@@ -32,8 +114,10 @@ export async function startServer(
32114
33115 const app = createMcpExpressApp ( { host : "0.0.0.0" } ) ;
34116 app . use ( cors ( ) ) ;
117+ app . use ( normalizeAcceptHeader ) ;
118+ app . use ( httpLogger ) ;
35119
36- app . all ( "/mcp" , async ( req : Request , res : Response ) => {
120+ app . post ( "/mcp" , async ( req : Request , res : Response ) => {
37121 const server = createServer ( ) ;
38122 const transport = new StreamableHTTPServerTransport ( {
39123 sessionIdGenerator : undefined ,
@@ -59,6 +143,23 @@ export async function startServer(
59143 }
60144 } ) ;
61145
146+ // GET and DELETE not supported in stateless mode
147+ app . get ( "/mcp" , ( _req : Request , res : Response ) => {
148+ res . status ( 405 ) . json ( {
149+ jsonrpc : "2.0" ,
150+ error : { code : - 32000 , message : "Method not allowed in stateless mode" } ,
151+ id : null ,
152+ } ) ;
153+ } ) ;
154+
155+ app . delete ( "/mcp" , ( _req : Request , res : Response ) => {
156+ res . status ( 405 ) . json ( {
157+ jsonrpc : "2.0" ,
158+ error : { code : - 32000 , message : "Method not allowed in stateless mode" } ,
159+ id : null ,
160+ } ) ;
161+ } ) ;
162+
62163 const httpServer = app . listen ( port , ( err ) => {
63164 if ( err ) {
64165 console . error ( "Failed to start server:" , err ) ;
0 commit comments