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