1- import type { IncomingMessage } from 'node:http' ;
2- import { Readable } from 'node:stream' ;
3- import { URL } from 'node:url' ;
4-
5- import type { AuthMetadataOptions , AuthRouterOptions , WebHandlerContext } from '@modelcontextprotocol/server' ;
1+ import type { AuthMetadataOptions , AuthRouterOptions } from '@modelcontextprotocol/server' ;
62import {
3+ getParsedBody ,
74 mcpAuthMetadataRouter as createWebAuthMetadataRouter ,
85 mcpAuthRouter as createWebAuthRouter ,
96 TooManyRequestsError
107} from '@modelcontextprotocol/server' ;
11- import type { RequestHandler , Response as ExpressResponse } from 'express' ;
8+ import { createRequest , sendResponse } from '@remix-run/node-fetch-server' ;
9+ import type { RequestHandler } from 'express' ;
1210import express from 'express' ;
1311import { rateLimit } from 'express-rate-limit' ;
1412
15- type ExpressRequestLike = IncomingMessage & {
16- method : string ;
17- headers : Record < string , string | string [ ] | undefined > ;
18- originalUrl ?: string ;
19- url ?: string ;
20- protocol ?: string ;
21- // express adds this when trust proxy is enabled
22- ip ?: string ;
23- body ?: unknown ;
24- get ?: ( name : string ) => string | undefined ;
25- } ;
26-
27- function expressRequestUrl ( req : ExpressRequestLike ) : URL {
28- const host = req . get ?.( 'host' ) ?? req . headers . host ?? 'localhost' ;
29- const proto = req . protocol ?? 'http' ;
30- const path = req . originalUrl ?? req . url ?? '/' ;
31- return new URL ( path , `${ proto } ://${ host } ` ) ;
32- }
33-
34- function toHeaders ( req : ExpressRequestLike ) : Headers {
35- const headers = new Headers ( ) ;
36- for ( const [ key , value ] of Object . entries ( req . headers ) ) {
37- if ( value === undefined ) continue ;
38- if ( Array . isArray ( value ) ) {
39- headers . set ( key , value . join ( ', ' ) ) ;
40- } else {
41- headers . set ( key , value ) ;
42- }
43- }
44- return headers ;
45- }
46-
47- async function readBody ( req : IncomingMessage ) : Promise < Uint8Array > {
48- const chunks : Buffer [ ] = [ ] ;
49- for await ( const chunk of req ) {
50- chunks . push ( Buffer . isBuffer ( chunk ) ? chunk : Buffer . from ( chunk ) ) ;
51- }
52- return Buffer . concat ( chunks ) ;
53- }
54-
55- async function expressToWebRequest ( req : ExpressRequestLike , parsedBodyProvided : boolean ) : Promise < Request > {
56- const url = expressRequestUrl ( req ) ;
57- const headers = toHeaders ( req ) ;
58-
59- // If upstream body parsing ran, the Node stream is likely consumed.
60- if ( parsedBodyProvided ) {
61- return new Request ( url , { method : req . method , headers } ) ;
62- }
63-
64- if ( req . method === 'GET' || req . method === 'HEAD' ) {
65- return new Request ( url , { method : req . method , headers } ) ;
66- }
67-
68- const body = await readBody ( req ) ;
69- return new Request ( url , { method : req . method , headers, body } ) ;
70- }
71-
72- async function writeWebResponse ( res : ExpressResponse , webResponse : Response ) : Promise < void > {
73- res . status ( webResponse . status ) ;
74-
75- // eslint-disable-next-line @typescript-eslint/no-explicit-any
76- const getSetCookie = ( webResponse . headers as any ) . getSetCookie as ( ( ) => string [ ] ) | undefined ;
77- const setCookies = typeof getSetCookie === 'function' ? getSetCookie . call ( webResponse . headers ) : undefined ;
78-
79- for ( const [ key , value ] of webResponse . headers . entries ( ) ) {
80- if ( key . toLowerCase ( ) === 'set-cookie' && setCookies ?. length ) continue ;
81- res . setHeader ( key , value ) ;
82- }
83-
84- if ( setCookies ?. length ) {
85- res . setHeader ( 'set-cookie' , setCookies ) ;
86- }
87-
88- res . flushHeaders ?.( ) ;
89-
90- if ( ! webResponse . body ) {
91- res . end ( ) ;
92- return ;
93- }
94-
95- await new Promise < void > ( ( resolve , reject ) => {
96- const readable = Readable . fromWeb ( webResponse . body as unknown as ReadableStream ) ;
97- readable . on ( 'error' , err => {
98- try {
99- res . destroy ( err as Error ) ;
100- } catch {
101- // ignore
102- }
103- reject ( err ) ;
104- } ) ;
105- res . on ( 'error' , reject ) ;
106- res . on ( 'close' , ( ) => {
107- try {
108- readable . destroy ( ) ;
109- } catch {
110- // ignore
111- }
112- } ) ;
113- readable . pipe ( res ) ;
114- res . on ( 'finish' , ( ) => resolve ( ) ) ;
115- } ) ;
116- }
117-
118- function toHandlerContext ( req : ExpressRequestLike ) : WebHandlerContext {
119- return {
120- parsedBody : req . body
121- } ;
122- }
123-
12413export type ExpressAuthRateLimitOptions =
12514 | false
12615 | {
@@ -172,10 +61,10 @@ export function mcpAuthRouter(options: AuthRouterOptions & { rateLimit?: Express
17261 }
17362 handlers . push ( async ( req , res , next ) => {
17463 try {
175- const parsedBodyProvided = ( req as ExpressRequestLike ) . body !== undefined ;
176- const webReq = await expressToWebRequest ( req as ExpressRequestLike , parsedBodyProvided ) ;
177- const webRes = await route . handler ( webReq , toHandlerContext ( req as ExpressRequestLike ) ) ;
178- await writeWebResponse ( res , webRes ) ;
64+ const webReq = createRequest ( req , res ) ;
65+ const parsedBody = req . body !== undefined ? req . body : await getParsedBody ( webReq ) ;
66+ const webRes = await route . handler ( webReq , { parsedBody } ) ;
67+ await sendResponse ( res , webRes ) ;
17968 } catch ( err ) {
18069 next ( err ) ;
18170 }
@@ -198,10 +87,10 @@ export function mcpAuthMetadataRouter(options: AuthMetadataOptions): RequestHand
19887 for ( const route of web . routes ) {
19988 router . all ( route . path , async ( req , res , next ) => {
20089 try {
201- const parsedBodyProvided = ( req as ExpressRequestLike ) . body !== undefined ;
202- const webReq = await expressToWebRequest ( req as ExpressRequestLike , parsedBodyProvided ) ;
203- const webRes = await route . handler ( webReq , toHandlerContext ( req as ExpressRequestLike ) ) ;
204- await writeWebResponse ( res , webRes ) ;
90+ const webReq = createRequest ( req , res ) ;
91+ const parsedBody = req . body !== undefined ? req . body : await getParsedBody ( webReq ) ;
92+ const webRes = await route . handler ( webReq , { parsedBody } ) ;
93+ await sendResponse ( res , webRes ) ;
20594 } catch ( err ) {
20695 next ( err ) ;
20796 }
0 commit comments