@@ -242,10 +242,37 @@ if (useHttp) {
242
242
logger . info ( `Starting HTTP server on port ${ port } ` ) ;
243
243
244
244
const httpServer = createServer ( async ( req , res ) => {
245
- // Enable CORS
246
- res . setHeader ( 'Access-Control-Allow-Origin' , '*' ) ;
245
+ // Validate Origin header as required by MCP spec
246
+ const origin = req . headers . origin ;
247
+ const allowedOrigins = [
248
+ 'http://localhost:3000' ,
249
+ 'http://127.0.0.1:3000' ,
250
+ 'https://mcp.socket.dev' ,
251
+ 'https://mcp.socket-staging.dev'
252
+ ] ;
253
+
254
+ const isValidOrigin = ! origin || allowedOrigins . includes ( origin ) ;
255
+
256
+ if ( origin && ! isValidOrigin ) {
257
+ logger . warn ( `Rejected request from invalid origin: ${ origin } ` ) ;
258
+ res . writeHead ( 403 , { 'Content-Type' : 'application/json' } ) ;
259
+ res . end ( JSON . stringify ( {
260
+ jsonrpc : '2.0' ,
261
+ error : { code : - 32000 , message : 'Forbidden: Invalid origin' } ,
262
+ id : null
263
+ } ) ) ;
264
+ return ;
265
+ }
266
+
267
+ // Set CORS headers for valid origins
268
+ if ( origin && isValidOrigin ) {
269
+ res . setHeader ( 'Access-Control-Allow-Origin' , origin ) ;
270
+ } else {
271
+ res . setHeader ( 'Access-Control-Allow-Origin' , 'http://localhost:3000' ) ;
272
+ }
247
273
res . setHeader ( 'Access-Control-Allow-Methods' , 'GET, POST, DELETE, OPTIONS' ) ;
248
- res . setHeader ( 'Access-Control-Allow-Headers' , 'Content-Type, mcp-session-id' ) ;
274
+ res . setHeader ( 'Access-Control-Allow-Headers' , 'Content-Type, mcp-session-id, Accept, Last-Event-ID' ) ;
275
+ res . setHeader ( 'Access-Control-Expose-Headers' , 'mcp-session-id' ) ;
249
276
250
277
if ( req . method === 'OPTIONS' ) {
251
278
res . writeHead ( 200 ) ;
@@ -269,6 +296,19 @@ if (useHttp) {
269
296
270
297
if ( url . pathname === '/' ) {
271
298
if ( req . method === 'POST' ) {
299
+ // Validate Accept header as required by MCP spec
300
+ const acceptHeader = req . headers . accept ;
301
+ if ( ! acceptHeader || ( ! acceptHeader . includes ( 'application/json' ) && ! acceptHeader . includes ( 'text/event-stream' ) ) ) {
302
+ logger . warn ( `Invalid Accept header: ${ acceptHeader } ` ) ;
303
+ res . writeHead ( 400 , { 'Content-Type' : 'application/json' } ) ;
304
+ res . end ( JSON . stringify ( {
305
+ jsonrpc : '2.0' ,
306
+ error : { code : - 32000 , message : 'Bad Request: Accept header must include application/json or text/event-stream' } ,
307
+ id : null
308
+ } ) ) ;
309
+ return ;
310
+ }
311
+
272
312
// Handle JSON-RPC messages
273
313
let body = '' ;
274
314
req . on ( 'data' , chunk => body += chunk ) ;
@@ -277,6 +317,18 @@ if (useHttp) {
277
317
const jsonData = JSON . parse ( body ) ;
278
318
const sessionId = req . headers [ 'mcp-session-id' ] as string ;
279
319
320
+ // Validate session ID format if provided (must contain only visible ASCII characters)
321
+ if ( sessionId && ! / ^ [ \x21 - \x7E ] + $ / . test ( sessionId ) ) {
322
+ logger . warn ( `Invalid session ID format: ${ sessionId } ` ) ;
323
+ res . writeHead ( 400 , { 'Content-Type' : 'application/json' } ) ;
324
+ res . end ( JSON . stringify ( {
325
+ jsonrpc : '2.0' ,
326
+ error : { code : - 32000 , message : 'Bad Request: Session ID must contain only visible ASCII characters' } ,
327
+ id : jsonData . id || null
328
+ } ) ) ;
329
+ return ;
330
+ }
331
+
280
332
let transport : StreamableHTTPServerTransport ;
281
333
282
334
if ( sessionId && transports [ sessionId ] ) {
@@ -298,6 +350,8 @@ if (useHttp) {
298
350
onsessioninitialized : ( id ) => {
299
351
transports [ id ] = transport ;
300
352
logger . info ( `Session initialized: ${ id } ` ) ;
353
+ // Set session ID in response headers as required by MCP spec
354
+ res . setHeader ( 'mcp-session-id' , id ) ;
301
355
}
302
356
} ) ;
303
357
@@ -343,14 +397,54 @@ if (useHttp) {
343
397
} ) ;
344
398
345
399
} else if ( req . method === 'GET' ) {
400
+ // Validate Accept header for SSE as required by MCP spec
401
+ const acceptHeader = req . headers . accept ;
402
+ if ( ! acceptHeader || ! acceptHeader . includes ( 'text/event-stream' ) ) {
403
+ logger . warn ( `GET request without text/event-stream Accept header: ${ acceptHeader } ` ) ;
404
+ res . writeHead ( 405 , { 'Content-Type' : 'application/json' } ) ;
405
+ res . end ( JSON . stringify ( {
406
+ jsonrpc : '2.0' ,
407
+ error : { code : - 32000 , message : 'Method Not Allowed: GET requires Accept: text/event-stream' } ,
408
+ id : null
409
+ } ) ) ;
410
+ return ;
411
+ }
412
+
346
413
// Handle SSE streams
347
414
const sessionId = req . headers [ 'mcp-session-id' ] as string ;
415
+
416
+ // Validate session ID format
417
+ if ( sessionId && ! / ^ [ \x21 - \x7E ] + $ / . test ( sessionId ) ) {
418
+ logger . warn ( `Invalid session ID format in GET request: ${ sessionId } ` ) ;
419
+ res . writeHead ( 400 , { 'Content-Type' : 'application/json' } ) ;
420
+ res . end ( JSON . stringify ( {
421
+ jsonrpc : '2.0' ,
422
+ error : { code : - 32000 , message : 'Bad Request: Session ID must contain only visible ASCII characters' } ,
423
+ id : null
424
+ } ) ) ;
425
+ return ;
426
+ }
427
+
348
428
if ( ! sessionId || ! transports [ sessionId ] ) {
349
- res . writeHead ( 400 ) ;
350
- res . end ( 'Invalid or missing session ID' ) ;
429
+ logger . warn ( `SSE request with invalid session ID: ${ sessionId } ` ) ;
430
+ res . writeHead ( 400 , { 'Content-Type' : 'application/json' } ) ;
431
+ res . end ( JSON . stringify ( {
432
+ jsonrpc : '2.0' ,
433
+ error : { code : - 32000 , message : 'Bad Request: Invalid or missing session ID for SSE stream' } ,
434
+ id : null
435
+ } ) ) ;
351
436
return ;
352
437
}
353
438
439
+ // Check for Last-Event-ID header for resumability (optional MCP feature)
440
+ const lastEventId = req . headers [ 'last-event-id' ] as string ;
441
+ if ( lastEventId ) {
442
+ logger . info ( `SSE resumability requested with Last-Event-ID: ${ lastEventId } ` ) ;
443
+ // Note: Actual resumability implementation would require message storage
444
+ // For now, we log the request but don't implement full resumability
445
+ }
446
+
447
+ // Let the transport handle SSE headers and response
354
448
const transport = transports [ sessionId ] ;
355
449
await transport . handleRequest ( req , res ) ;
356
450
0 commit comments