@@ -41,14 +41,53 @@ const { values } = parseArgs({
41
41
} ,
42
42
} ) ;
43
43
44
+ // Function to get HTTP headers.
45
+ // Supports only "sse" and "streamable-http" transport types.
46
+ const getHttpHeaders = (
47
+ req : express . Request ,
48
+ transportType : string ,
49
+ ) : HeadersInit => {
50
+ const headers : HeadersInit = {
51
+ Accept :
52
+ transportType === "sse"
53
+ ? "text/event-stream"
54
+ : "text/event-stream, application/json" ,
55
+ } ;
56
+ const defaultHeaders =
57
+ transportType === "sse"
58
+ ? SSE_HEADERS_PASSTHROUGH
59
+ : STREAMABLE_HTTP_HEADERS_PASSTHROUGH ;
60
+
61
+ for ( const key of defaultHeaders ) {
62
+ if ( req . headers [ key ] === undefined ) {
63
+ continue ;
64
+ }
65
+
66
+ const value = req . headers [ key ] ;
67
+ headers [ key ] = Array . isArray ( value ) ? value [ value . length - 1 ] : value ;
68
+ }
69
+
70
+ // If the header "x-custom-auth-header" is present, use its value as the custom header name.
71
+ if ( req . headers [ "x-custom-auth-header" ] !== undefined ) {
72
+ const customHeaderName = req . headers [ "x-custom-auth-header" ] as string ;
73
+ const lowerCaseHeaderName = customHeaderName . toLowerCase ( ) ;
74
+ if ( req . headers [ lowerCaseHeaderName ] !== undefined ) {
75
+ const value = req . headers [ lowerCaseHeaderName ] ;
76
+ headers [ customHeaderName ] = value as string ;
77
+ }
78
+ }
79
+ return headers ;
80
+ } ;
81
+
44
82
const app = express ( ) ;
45
83
app . use ( cors ( ) ) ;
46
84
app . use ( ( req , res , next ) => {
47
85
res . header ( "Access-Control-Expose-Headers" , "mcp-session-id" ) ;
48
86
next ( ) ;
49
87
} ) ;
50
88
51
- const webAppTransports : Map < string , Transport > = new Map < string , Transport > ( ) ; // Transports by sessionId
89
+ const webAppTransports : Map < string , Transport > = new Map < string , Transport > ( ) ; // Web app transports by web app sessionId
90
+ const serverTransports : Map < string , Transport > = new Map < string , Transport > ( ) ; // Server Transports by web app sessionId
52
91
53
92
const createTransport = async ( req : express . Request ) : Promise < Transport > => {
54
93
const query = req . query ;
@@ -79,18 +118,8 @@ const createTransport = async (req: express.Request): Promise<Transport> => {
79
118
return transport ;
80
119
} else if ( transportType === "sse" ) {
81
120
const url = query . url as string ;
82
- const headers : HeadersInit = {
83
- Accept : "text/event-stream" ,
84
- } ;
85
-
86
- for ( const key of SSE_HEADERS_PASSTHROUGH ) {
87
- if ( req . headers [ key ] === undefined ) {
88
- continue ;
89
- }
90
121
91
- const value = req . headers [ key ] ;
92
- headers [ key ] = Array . isArray ( value ) ? value [ value . length - 1 ] : value ;
93
- }
122
+ const headers = getHttpHeaders ( req , transportType ) ;
94
123
95
124
console . log ( `SSE transport: url=${ url } , headers=${ Object . keys ( headers ) } ` ) ;
96
125
@@ -107,18 +136,7 @@ const createTransport = async (req: express.Request): Promise<Transport> => {
107
136
console . log ( "Connected to SSE transport" ) ;
108
137
return transport ;
109
138
} else if ( transportType === "streamable-http" ) {
110
- const headers : HeadersInit = {
111
- Accept : "text/event-stream, application/json" ,
112
- } ;
113
-
114
- for ( const key of STREAMABLE_HTTP_HEADERS_PASSTHROUGH ) {
115
- if ( req . headers [ key ] === undefined ) {
116
- continue ;
117
- }
118
-
119
- const value = req . headers [ key ] ;
120
- headers [ key ] = Array . isArray ( value ) ? value [ value . length - 1 ] : value ;
121
- }
139
+ const headers = getHttpHeaders ( req , transportType ) ;
122
140
123
141
const transport = new StreamableHTTPClientTransport (
124
142
new URL ( query . url as string ) ,
@@ -137,8 +155,6 @@ const createTransport = async (req: express.Request): Promise<Transport> => {
137
155
}
138
156
} ;
139
157
140
- let backingServerTransport : Transport | undefined ;
141
-
142
158
app . get ( "/mcp" , async ( req , res ) => {
143
159
const sessionId = req . headers [ "mcp-session-id" ] as string ;
144
160
console . log ( `Received GET message for sessionId ${ sessionId } ` ) ;
@@ -161,12 +177,12 @@ app.get("/mcp", async (req, res) => {
161
177
app . post ( "/mcp" , async ( req , res ) => {
162
178
const sessionId = req . headers [ "mcp-session-id" ] as string | undefined ;
163
179
console . log ( `Received POST message for sessionId ${ sessionId } ` ) ;
180
+ let serverTransport : Transport | undefined ;
164
181
if ( ! sessionId ) {
165
182
try {
166
183
console . log ( "New streamable-http connection" ) ;
167
184
try {
168
- await backingServerTransport ?. close ( ) ;
169
- backingServerTransport = await createTransport ( req ) ;
185
+ serverTransport = await createTransport ( req ) ;
170
186
} catch ( error ) {
171
187
if ( error instanceof SseError && error . code === 401 ) {
172
188
console . error (
@@ -180,12 +196,13 @@ app.post("/mcp", async (req, res) => {
180
196
throw error ;
181
197
}
182
198
183
- console . log ( "Connected MCP client to backing server transport" ) ;
199
+ console . log ( "Connected MCP client to server transport" ) ;
184
200
185
201
const webAppTransport = new StreamableHTTPServerTransport ( {
186
202
sessionIdGenerator : randomUUID ,
187
203
onsessioninitialized : ( sessionId ) => {
188
204
webAppTransports . set ( sessionId , webAppTransport ) ;
205
+ serverTransports . set ( sessionId , serverTransport ! ) ;
189
206
console . log ( "Created streamable web app transport " + sessionId ) ;
190
207
} ,
191
208
} ) ;
@@ -194,7 +211,7 @@ app.post("/mcp", async (req, res) => {
194
211
195
212
mcpProxy ( {
196
213
transportToClient : webAppTransport ,
197
- transportToServer : backingServerTransport ,
214
+ transportToServer : serverTransport ,
198
215
} ) ;
199
216
200
217
await ( webAppTransport as StreamableHTTPServerTransport ) . handleRequest (
@@ -229,10 +246,9 @@ app.post("/mcp", async (req, res) => {
229
246
app . get ( "/stdio" , async ( req , res ) => {
230
247
try {
231
248
console . log ( "New connection" ) ;
232
-
249
+ let serverTransport : Transport | undefined ;
233
250
try {
234
- await backingServerTransport ?. close ( ) ;
235
- backingServerTransport = await createTransport ( req ) ;
251
+ serverTransport = await createTransport ( req ) ;
236
252
} catch ( error ) {
237
253
if ( error instanceof SseError && error . code === 401 ) {
238
254
console . error (
@@ -250,26 +266,24 @@ app.get("/stdio", async (req, res) => {
250
266
251
267
const webAppTransport = new SSEServerTransport ( "/message" , res ) ;
252
268
webAppTransports . set ( webAppTransport . sessionId , webAppTransport ) ;
253
-
254
- console . log ( "Created web app transport " ) ;
269
+ serverTransports . set ( webAppTransport . sessionId , serverTransport ) ;
270
+ console . log ( "Created client/server transports " ) ;
255
271
256
272
await webAppTransport . start ( ) ;
257
- ( backingServerTransport as StdioClientTransport ) . stderr ! . on (
258
- "data" ,
259
- ( chunk ) => {
260
- webAppTransport . send ( {
261
- jsonrpc : "2.0" ,
262
- method : "notifications/stderr" ,
263
- params : {
264
- content : chunk . toString ( ) ,
265
- } ,
266
- } ) ;
267
- } ,
268
- ) ;
273
+
274
+ ( serverTransport as StdioClientTransport ) . stderr ! . on ( "data" , ( chunk ) => {
275
+ webAppTransport . send ( {
276
+ jsonrpc : "2.0" ,
277
+ method : "notifications/stderr" ,
278
+ params : {
279
+ content : chunk . toString ( ) ,
280
+ } ,
281
+ } ) ;
282
+ } ) ;
269
283
270
284
mcpProxy ( {
271
285
transportToClient : webAppTransport ,
272
- transportToServer : backingServerTransport ,
286
+ transportToServer : serverTransport ,
273
287
} ) ;
274
288
275
289
console . log ( "Set up MCP proxy" ) ;
@@ -284,10 +298,9 @@ app.get("/sse", async (req, res) => {
284
298
console . log (
285
299
"New SSE connection. NOTE: The sse transport is deprecated and has been replaced by streamable-http" ,
286
300
) ;
287
-
301
+ let serverTransport : Transport | undefined ;
288
302
try {
289
- await backingServerTransport ?. close ( ) ;
290
- backingServerTransport = await createTransport ( req ) ;
303
+ serverTransport = await createTransport ( req ) ;
291
304
} catch ( error ) {
292
305
if ( error instanceof SseError && error . code === 401 ) {
293
306
console . error (
@@ -305,13 +318,15 @@ app.get("/sse", async (req, res) => {
305
318
306
319
const webAppTransport = new SSEServerTransport ( "/message" , res ) ;
307
320
webAppTransports . set ( webAppTransport . sessionId , webAppTransport ) ;
308
- console . log ( "Created web app transport" ) ;
321
+ console . log ( "Created client transport" ) ;
322
+ serverTransports . set ( webAppTransport . sessionId , serverTransport ) ;
323
+ console . log ( "Created server transport" ) ;
309
324
310
325
await webAppTransport . start ( ) ;
311
326
312
327
mcpProxy ( {
313
328
transportToClient : webAppTransport ,
314
- transportToServer : backingServerTransport ,
329
+ transportToServer : serverTransport ,
315
330
} ) ;
316
331
317
332
console . log ( "Set up MCP proxy" ) ;
0 commit comments