1+ import express , { Request , Response } from 'express' ;
2+ import { randomUUID } from 'node:crypto' ;
3+ import { McpServer } from '../../server/mcp.js' ;
4+ import { StreamableHTTPServerTransport } from '../../server/streamableHttp.js' ;
5+ import { z } from 'zod' ;
6+ import { CallToolResult } from '../../types.js' ;
7+
8+ // Create an MCP server with implementation details
9+ const server = new McpServer ( {
10+ name : 'json-response-streamable-http-server' ,
11+ version : '1.0.0' ,
12+ } ) ;
13+
14+ // Register a simple tool that returns a greeting
15+ server . tool (
16+ 'greet' ,
17+ 'A simple greeting tool' ,
18+ {
19+ name : z . string ( ) . describe ( 'Name to greet' ) ,
20+ } ,
21+ async ( { name } ) : Promise < CallToolResult > => {
22+ return {
23+ content : [
24+ {
25+ type : 'text' ,
26+ text : `Hello, ${ name } !` ,
27+ } ,
28+ ] ,
29+ } ;
30+ }
31+ ) ;
32+
33+ const app = express ( ) ;
34+ app . use ( express . json ( ) ) ;
35+
36+ // Map to store transports by session ID
37+ const transports : { [ sessionId : string ] : StreamableHTTPServerTransport } = { } ;
38+
39+ app . post ( '/mcp' , async ( req : Request , res : Response ) => {
40+ console . log ( 'Received MCP request:' , req . body ) ;
41+ try {
42+ // Check for existing session ID
43+ const sessionId = req . headers [ 'mcp-session-id' ] as string | undefined ;
44+ let transport : StreamableHTTPServerTransport ;
45+
46+ if ( sessionId && transports [ sessionId ] ) {
47+ // Reuse existing transport
48+ transport = transports [ sessionId ] ;
49+ } else if ( ! sessionId && isInitializeRequest ( req . body ) ) {
50+ // New initialization request - use JSON response mode
51+ transport = new StreamableHTTPServerTransport ( {
52+ sessionIdGenerator : ( ) => randomUUID ( ) ,
53+ enableJsonResponse : true , // Enable JSON response mode
54+ } ) ;
55+
56+ // Connect the transport to the MCP server BEFORE handling the request
57+ await server . connect ( transport ) ;
58+
59+ // After handling the request, if we get a session ID back, store the transport
60+ await transport . handleRequest ( req , res , req . body ) ;
61+
62+ // Store the transport by session ID for future requests
63+ if ( transport . sessionId ) {
64+ transports [ transport . sessionId ] = transport ;
65+ }
66+ return ; // Already handled
67+ } else {
68+ // Invalid request - no session ID or not initialization request
69+ res . status ( 400 ) . json ( {
70+ jsonrpc : '2.0' ,
71+ error : {
72+ code : - 32000 ,
73+ message : 'Bad Request: No valid session ID provided' ,
74+ } ,
75+ id : null ,
76+ } ) ;
77+ return ;
78+ }
79+
80+ // Handle the request with existing transport - no need to reconnect
81+ await transport . handleRequest ( req , res , req . body ) ;
82+ } catch ( error ) {
83+ console . error ( 'Error handling MCP request:' , error ) ;
84+ if ( ! res . headersSent ) {
85+ res . status ( 500 ) . json ( {
86+ jsonrpc : '2.0' ,
87+ error : {
88+ code : - 32603 ,
89+ message : 'Internal server error' ,
90+ } ,
91+ id : null ,
92+ } ) ;
93+ }
94+ }
95+ } ) ;
96+
97+ // Helper function to detect initialize requests
98+ function isInitializeRequest ( body : unknown ) : boolean {
99+ if ( Array . isArray ( body ) ) {
100+ return body . some ( msg => typeof msg === 'object' && msg !== null && 'method' in msg && msg . method === 'initialize' ) ;
101+ }
102+ return typeof body === 'object' && body !== null && 'method' in body && body . method === 'initialize' ;
103+ }
104+
105+ // Start the server
106+ const PORT = 3000 ;
107+ app . listen ( PORT , ( ) => {
108+ console . log ( `MCP Streamable HTTP Server with JSON responses listening on port ${ PORT } ` ) ;
109+ console . log ( `Server is running. Press Ctrl+C to stop.` ) ;
110+ console . log ( `Initialize with: curl -X POST -H "Content-Type: application/json" -H "Accept: application/json" -d '{"jsonrpc":"2.0","method":"initialize","params":{"capabilities":{}},"id":"1"}' http://localhost:${ PORT } /mcp` ) ;
111+ console . log ( `Then call tool with: curl -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "mcp-session-id: YOUR_SESSION_ID" -d '{"jsonrpc":"2.0","method":"mcp.call_tool","params":{"name":"greet","arguments":{"name":"World"}},"id":"2"}' http://localhost:${ PORT } /mcp` ) ;
112+ } ) ;
113+
114+ // Handle server shutdown
115+ process . on ( 'SIGINT' , async ( ) => {
116+ console . log ( 'Shutting down server...' ) ;
117+ await server . close ( ) ;
118+ process . exit ( 0 ) ;
119+ } ) ;
0 commit comments