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