@@ -12,15 +12,17 @@ import {
12
12
StdioClientTransport ,
13
13
getDefaultEnvironment ,
14
14
} from "@modelcontextprotocol/sdk/client/stdio.js" ;
15
- import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js" ;
16
- import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js" ;
17
15
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js" ;
16
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js" ;
17
+ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js" ;
18
+ import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js" ;
18
19
import express from "express" ;
19
20
import { findActualExecutable } from "spawn-rx" ;
20
21
import mcpProxy from "./mcpProxy.js" ;
22
+ import { randomUUID } from "node:crypto" ;
21
23
22
24
const SSE_HEADERS_PASSTHROUGH = [ "authorization" ] ;
23
- const STREAMABLE_HTTP_HEADERS_PASSTHROUGH = [ "authorization" ] ;
25
+ const STREAMABLE_HTTP_HEADERS_PASSTHROUGH = [ "authorization" , "mcp-session-id" ] ;
24
26
25
27
const defaultEnvironment = {
26
28
...getDefaultEnvironment ( ) ,
@@ -38,7 +40,7 @@ const { values } = parseArgs({
38
40
const app = express ( ) ;
39
41
app . use ( cors ( ) ) ;
40
42
41
- let webAppTransports : SSEServerTransport [ ] = [ ] ;
43
+ const webAppTransports : Map < string , Transport > = new Map < string , Transport > ( ) ; // Transports by sessionId
42
44
43
45
const createTransport = async ( req : express . Request ) : Promise < Transport > => {
44
46
const query = req . query ;
@@ -130,71 +132,89 @@ const createTransport = async (req: express.Request): Promise<Transport> => {
130
132
let backingServerTransport : Transport | undefined ;
131
133
132
134
app . get ( "/mcp" , async ( req , res ) => {
135
+ const sessionId = req . headers [ "mcp-session-id" ] as string ;
136
+ console . log ( `Received GET message for sessionId ${ sessionId } ` ) ;
133
137
try {
134
- console . log ( "New streamable-http connection" ) ;
138
+ const transport = webAppTransports . get (
139
+ sessionId ,
140
+ ) as StreamableHTTPServerTransport ;
141
+ if ( ! transport ) {
142
+ res . status ( 404 ) . end ( "Session not found" ) ;
143
+ return ;
144
+ } else {
145
+ await transport . handleRequest ( req , res ) ;
146
+ }
147
+ } catch ( error ) {
148
+ console . error ( "Error in /mcp route:" , error ) ;
149
+ res . status ( 500 ) . json ( error ) ;
150
+ }
151
+ } ) ;
135
152
153
+ app . post ( "/mcp" , async ( req , res ) => {
154
+ const sessionId = req . headers [ "mcp-session-id" ] as string | undefined ;
155
+ console . log ( `Received POST message for sessionId ${ sessionId } ` ) ;
156
+ if ( ! sessionId ) {
136
157
try {
137
- await backingServerTransport ?. close ( ) ;
138
- backingServerTransport = await createTransport ( req ) ;
139
- } catch ( error ) {
140
- if ( error instanceof SseError && error . code === 401 ) {
141
- console . error (
142
- "Received 401 Unauthorized from MCP server:" ,
143
- error . message ,
144
- ) ;
145
- res . status ( 401 ) . json ( error ) ;
146
- return ;
158
+ console . log ( "New streamable-http connection" ) ;
159
+ try {
160
+ await backingServerTransport ?. close ( ) ;
161
+ backingServerTransport = await createTransport ( req ) ;
162
+ } catch ( error ) {
163
+ if ( error instanceof SseError && error . code === 401 ) {
164
+ console . error (
165
+ "Received 401 Unauthorized from MCP server:" ,
166
+ error . message ,
167
+ ) ;
168
+ res . status ( 401 ) . json ( error ) ;
169
+ return ;
170
+ }
171
+
172
+ throw error ;
147
173
}
148
174
149
- throw error ;
150
- }
151
-
152
- console . log ( "Connected MCP client to backing server transport" ) ;
153
-
154
- const webAppTransport = new SSEServerTransport ( "/mcp" , res ) ;
155
- webAppTransports . push ( webAppTransport ) ;
156
- console . log ( "Created web app transport" ) ;
157
-
158
- await webAppTransport . start ( ) ;
175
+ console . log ( "Connected MCP client to backing server transport" ) ;
159
176
160
- if ( backingServerTransport instanceof StdioClientTransport ) {
161
- backingServerTransport . stderr ! . on ( "data" , ( chunk ) => {
162
- webAppTransport . send ( {
163
- jsonrpc : "2.0" ,
164
- method : "notifications/stderr" ,
165
- params : {
166
- content : chunk . toString ( ) ,
167
- } ,
168
- } ) ;
177
+ const webAppTransport = new StreamableHTTPServerTransport ( {
178
+ sessionIdGenerator : randomUUID ,
179
+ onsessioninitialized : ( sessionId ) => {
180
+ webAppTransports . set ( sessionId , webAppTransport ) ;
181
+ console . log ( "Created streamable web app transport " + sessionId ) ;
182
+ } ,
169
183
} ) ;
170
- }
171
-
172
- mcpProxy ( {
173
- transportToClient : webAppTransport ,
174
- transportToServer : backingServerTransport ,
175
- } ) ;
176
184
177
- console . log ( "Set up MCP proxy" ) ;
178
- } catch ( error ) {
179
- console . error ( "Error in /sse route:" , error ) ;
180
- res . status ( 500 ) . json ( error ) ;
181
- }
182
- } ) ;
185
+ await webAppTransport . start ( ) ;
183
186
184
- app . post ( "/mcp" , async ( req , res ) => {
185
- try {
186
- const sessionId = req . query . sessionId ;
187
- console . log ( `Received message for sessionId ${ sessionId } ` ) ;
187
+ mcpProxy ( {
188
+ transportToClient : webAppTransport ,
189
+ transportToServer : backingServerTransport ,
190
+ } ) ;
188
191
189
- const transport = webAppTransports . find ( ( t ) => t . sessionId === sessionId ) ;
190
- if ( ! transport ) {
191
- res . status ( 404 ) . end ( "Session not found" ) ;
192
- return ;
192
+ await ( webAppTransport as StreamableHTTPServerTransport ) . handleRequest (
193
+ req ,
194
+ res ,
195
+ req . body ,
196
+ ) ;
197
+ } catch ( error ) {
198
+ console . error ( "Error in /mcp POST route:" , error ) ;
199
+ res . status ( 500 ) . json ( error ) ;
200
+ }
201
+ } else {
202
+ try {
203
+ const transport = webAppTransports . get (
204
+ sessionId ,
205
+ ) as StreamableHTTPServerTransport ;
206
+ if ( ! transport ) {
207
+ res . status ( 404 ) . end ( "Transport not found for sessionId " + sessionId ) ;
208
+ } else {
209
+ await ( transport as StreamableHTTPServerTransport ) . handleRequest (
210
+ req ,
211
+ res ,
212
+ ) ;
213
+ }
214
+ } catch ( error ) {
215
+ console . error ( "Error in /mcp route:" , error ) ;
216
+ res . status ( 500 ) . json ( error ) ;
193
217
}
194
- await transport . handlePostMessage ( req , res ) ;
195
- } catch ( error ) {
196
- console . error ( "Error in /mcp route:" , error ) ;
197
- res . status ( 500 ) . json ( error ) ;
198
218
}
199
219
} ) ;
200
220
@@ -221,7 +241,7 @@ app.get("/stdio", async (req, res) => {
221
241
console . log ( "Connected MCP client to backing server transport" ) ;
222
242
223
243
const webAppTransport = new SSEServerTransport ( "/message" , res ) ;
224
- webAppTransports . push ( webAppTransport ) ;
244
+ webAppTransports . set ( webAppTransport . sessionId , webAppTransport ) ;
225
245
226
246
console . log ( "Created web app transport" ) ;
227
247
@@ -276,8 +296,7 @@ app.get("/sse", async (req, res) => {
276
296
console . log ( "Connected MCP client to backing server transport" ) ;
277
297
278
298
const webAppTransport = new SSEServerTransport ( "/message" , res ) ;
279
- webAppTransports . push ( webAppTransport ) ;
280
-
299
+ webAppTransports . set ( webAppTransport . sessionId , webAppTransport ) ;
281
300
console . log ( "Created web app transport" ) ;
282
301
283
302
await webAppTransport . start ( ) ;
@@ -299,7 +318,9 @@ app.post("/message", async (req, res) => {
299
318
const sessionId = req . query . sessionId ;
300
319
console . log ( `Received message for sessionId ${ sessionId } ` ) ;
301
320
302
- const transport = webAppTransports . find ( ( t ) => t . sessionId === sessionId ) ;
321
+ const transport = webAppTransports . get (
322
+ sessionId as string ,
323
+ ) as SSEServerTransport ;
303
324
if ( ! transport ) {
304
325
res . status ( 404 ) . end ( "Session not found" ) ;
305
326
return ;
0 commit comments