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 , GetPromptResult , ReadResourceResult } from '../../types.js' ;
7
+
8
+ // Create an MCP server with implementation details
9
+ const server = new McpServer ( {
10
+ name : 'simple-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
+ // Register a simple prompt
34
+ server . prompt (
35
+ 'greeting-template' ,
36
+ 'A simple greeting prompt template' ,
37
+ {
38
+ name : z . string ( ) . describe ( 'Name to include in greeting' ) ,
39
+ } ,
40
+ async ( { name } ) : Promise < GetPromptResult > => {
41
+ return {
42
+ messages : [
43
+ {
44
+ role : 'user' ,
45
+ content : {
46
+ type : 'text' ,
47
+ text : `Please greet ${ name } in a friendly manner.` ,
48
+ } ,
49
+ } ,
50
+ ] ,
51
+ } ;
52
+ }
53
+ ) ;
54
+
55
+ // Create a simple resource at a fixed URI
56
+ server . resource (
57
+ 'greeting-resource' ,
58
+ 'https://example.com/greetings/default' ,
59
+ { mimeType : 'text/plain' } ,
60
+ async ( ) : Promise < ReadResourceResult > => {
61
+ return {
62
+ contents : [
63
+ {
64
+ uri : 'https://example.com/greetings/default' ,
65
+ text : 'Hello, world!' ,
66
+ } ,
67
+ ] ,
68
+ } ;
69
+ }
70
+ ) ;
71
+
72
+ const app = express ( ) ;
73
+ app . use ( express . json ( ) ) ;
74
+
75
+ // Map to store transports by session ID
76
+ const transports : { [ sessionId : string ] : StreamableHTTPServerTransport } = { } ;
77
+
78
+ app . post ( '/mcp' , async ( req : Request , res : Response ) => {
79
+ console . log ( 'Received MCP request:' , req . body ) ;
80
+ try {
81
+ // Check for existing session ID
82
+ const sessionId = req . headers [ 'mcp-session-id' ] as string | undefined ;
83
+ let transport : StreamableHTTPServerTransport ;
84
+
85
+ if ( sessionId && transports [ sessionId ] ) {
86
+ // Reuse existing transport
87
+ transport = transports [ sessionId ] ;
88
+ } else if ( ! sessionId && isInitializeRequest ( req . body ) ) {
89
+ // New initialization request
90
+ transport = new StreamableHTTPServerTransport ( {
91
+ sessionIdGenerator : ( ) => randomUUID ( ) ,
92
+ } ) ;
93
+
94
+ // Connect the transport to the MCP server BEFORE handling the request
95
+ // so responses can flow back through the same transport
96
+ await server . connect ( transport ) ;
97
+
98
+ // After handling the request, if we get a session ID back, store the transport
99
+ await transport . handleRequest ( req , res , req . body ) ;
100
+
101
+ // Store the transport by session ID for future requests
102
+ if ( transport . sessionId ) {
103
+ transports [ transport . sessionId ] = transport ;
104
+ }
105
+ return ; // Already handled
106
+ } else {
107
+ // Invalid request - no session ID or not initialization request
108
+ res . status ( 400 ) . json ( {
109
+ jsonrpc : '2.0' ,
110
+ error : {
111
+ code : - 32000 ,
112
+ message : 'Bad Request: No valid session ID provided' ,
113
+ } ,
114
+ id : null ,
115
+ } ) ;
116
+ return ;
117
+ }
118
+
119
+ // Handle the request with existing transport - no need to reconnect
120
+ // The existing transport is already connected to the server
121
+ await transport . handleRequest ( req , res , req . body ) ;
122
+ } catch ( error ) {
123
+ console . error ( 'Error handling MCP request:' , error ) ;
124
+ if ( ! res . headersSent ) {
125
+ res . status ( 500 ) . json ( {
126
+ jsonrpc : '2.0' ,
127
+ error : {
128
+ code : - 32603 ,
129
+ message : 'Internal server error' ,
130
+ } ,
131
+ id : null ,
132
+ } ) ;
133
+ }
134
+ }
135
+ } ) ;
136
+
137
+ // Helper function to detect initialize requests
138
+ function isInitializeRequest ( body : unknown ) : boolean {
139
+ if ( Array . isArray ( body ) ) {
140
+ return body . some ( msg => typeof msg === 'object' && msg !== null && 'method' in msg && msg . method === 'initialize' ) ;
141
+ }
142
+ return typeof body === 'object' && body !== null && 'method' in body && body . method === 'initialize' ;
143
+ }
144
+
145
+ // Start the server
146
+ const PORT = 3000 ;
147
+ app . listen ( PORT , ( ) => {
148
+ console . log ( `MCP Streamable HTTP Server listening on port ${ PORT } ` ) ;
149
+ console . log ( `Test with: curl -X POST -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" -d '{"jsonrpc":"2.0","method":"initialize","params":{"capabilities":{}},"id":"1"}' http://localhost:${ PORT } /mcp` ) ;
150
+ } ) ;
151
+
152
+ // Handle server shutdown
153
+ process . on ( 'SIGINT' , async ( ) => {
154
+ console . log ( 'Shutting down server...' ) ;
155
+ await server . close ( ) ;
156
+ process . exit ( 0 ) ;
157
+ } ) ;
0 commit comments