11import express from "express" ;
22import http from "http" ;
33import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js" ;
4-
4+ import { TransportRunnerBase } from "./base.js" ;
55import { config } from "../common/config.js" ;
66import logger , { LogId } from "../common/logger.js" ;
77
88const JSON_RPC_ERROR_CODE_PROCESSING_REQUEST_FAILED = - 32000 ;
9+ const JSON_RPC_ERROR_CODE_METHOD_NOT_ALLOWED = - 32601 ;
910
10- export async function createHttpTransport ( ) : Promise < StreamableHTTPServerTransport > {
11- const app = express ( ) ;
12- app . enable ( "trust proxy" ) ; // needed for reverse proxy support
13- app . use ( express . urlencoded ( { extended : true } ) ) ;
14- app . use ( express . json ( ) ) ;
11+ function promiseHandler (
12+ fn : ( req : express . Request , res : express . Response , next : express . NextFunction ) => Promise < void >
13+ ) {
14+ return ( req : express . Request , res : express . Response , next : express . NextFunction ) => {
15+ fn ( req , res , next ) . catch ( next ) ;
16+ } ;
17+ }
1518
16- const transport = new StreamableHTTPServerTransport ( {
17- sessionIdGenerator : undefined ,
18- } ) ;
19+ export class StreamableHttpRunner extends TransportRunnerBase {
20+ private httpServer : http . Server | undefined ;
1921
20- app . post ( "/mcp" , async ( req : express . Request , res : express . Response ) => {
21- try {
22- await transport . handleRequest ( req , res , req . body ) ;
23- } catch ( error ) {
24- logger . error (
25- LogId . streamableHttpTransportRequestFailure ,
26- "streamableHttpTransport" ,
27- `Error handling request: ${ error instanceof Error ? error . message : String ( error ) } `
28- ) ;
29- res . status ( 400 ) . json ( {
30- jsonrpc : "2.0" ,
31- error : {
32- code : JSON_RPC_ERROR_CODE_PROCESSING_REQUEST_FAILED ,
33- message : `failed to handle request` ,
34- data : error instanceof Error ? error . message : String ( error ) ,
35- } ,
36- } ) ;
37- }
38- } ) ;
22+ async start ( ) {
23+ const app = express ( ) ;
24+ app . enable ( "trust proxy" ) ; // needed for reverse proxy support
25+ app . use ( express . urlencoded ( { extended : true } ) ) ;
26+ app . use ( express . json ( ) ) ;
27+
28+ app . post (
29+ "/mcp" ,
30+ promiseHandler ( async ( req : express . Request , res : express . Response ) => {
31+ const transport = new StreamableHTTPServerTransport ( {
32+ sessionIdGenerator : undefined ,
33+ } ) ;
34+
35+ const server = this . setupServer ( ) ;
3936
40- app . get ( "/mcp" , async ( req : express . Request , res : express . Response ) => {
41- try {
42- await transport . handleRequest ( req , res , req . body ) ;
43- } catch ( error ) {
44- logger . error (
45- LogId . streamableHttpTransportRequestFailure ,
46- "streamableHttpTransport" ,
47- `Error handling request: ${ error instanceof Error ? error . message : String ( error ) } `
48- ) ;
49- res . status ( 400 ) . json ( {
37+ await server . connect ( transport ) ;
38+
39+ res . on ( "close" , ( ) => {
40+ Promise . all ( [ transport . close ( ) , server . close ( ) ] ) . catch ( ( error : unknown ) => {
41+ logger . error (
42+ LogId . streamableHttpTransportCloseFailure ,
43+ "streamableHttpTransport" ,
44+ `Error closing server: ${ error instanceof Error ? error . message : String ( error ) } `
45+ ) ;
46+ } ) ;
47+ } ) ;
48+
49+ try {
50+ await transport . handleRequest ( req , res , req . body ) ;
51+ } catch ( error ) {
52+ logger . error (
53+ LogId . streamableHttpTransportRequestFailure ,
54+ "streamableHttpTransport" ,
55+ `Error handling request: ${ error instanceof Error ? error . message : String ( error ) } `
56+ ) ;
57+ res . status ( 400 ) . json ( {
58+ jsonrpc : "2.0" ,
59+ error : {
60+ code : JSON_RPC_ERROR_CODE_PROCESSING_REQUEST_FAILED ,
61+ message : `failed to handle request` ,
62+ data : error instanceof Error ? error . message : String ( error ) ,
63+ } ,
64+ } ) ;
65+ }
66+ } )
67+ ) ;
68+
69+ app . get ( "/mcp" , ( req : express . Request , res : express . Response ) => {
70+ res . status ( 405 ) . json ( {
5071 jsonrpc : "2.0" ,
5172 error : {
52- code : JSON_RPC_ERROR_CODE_PROCESSING_REQUEST_FAILED ,
53- message : `failed to handle request` ,
54- data : error instanceof Error ? error . message : String ( error ) ,
73+ code : JSON_RPC_ERROR_CODE_METHOD_NOT_ALLOWED ,
74+ message : `method not allowed` ,
5575 } ,
5676 } ) ;
57- }
58- } ) ;
77+ } ) ;
5978
60- app . delete ( "/mcp" , async ( req : express . Request , res : express . Response ) => {
61- try {
62- await transport . handleRequest ( req , res , req . body ) ;
63- } catch ( error ) {
64- logger . error (
65- LogId . streamableHttpTransportRequestFailure ,
66- "streamableHttpTransport" ,
67- `Error handling request: ${ error instanceof Error ? error . message : String ( error ) } `
68- ) ;
69- res . status ( 400 ) . json ( {
79+ app . delete ( "/mcp" , ( req : express . Request , res : express . Response ) => {
80+ res . status ( 405 ) . json ( {
7081 jsonrpc : "2.0" ,
7182 error : {
72- code : JSON_RPC_ERROR_CODE_PROCESSING_REQUEST_FAILED ,
73- message : `failed to handle request` ,
74- data : error instanceof Error ? error . message : String ( error ) ,
83+ code : JSON_RPC_ERROR_CODE_METHOD_NOT_ALLOWED ,
84+ message : `method not allowed` ,
7585 } ,
7686 } ) ;
77- }
78- } ) ;
87+ } ) ;
7988
80- try {
81- const server = await new Promise < http . Server > ( ( resolve , reject ) => {
89+ this . httpServer = await new Promise < http . Server > ( ( resolve , reject ) => {
8290 const result = app . listen ( config . httpPort , config . httpHost , ( err ?: Error ) => {
8391 if ( err ) {
8492 reject ( err ) ;
@@ -93,31 +101,17 @@ export async function createHttpTransport(): Promise<StreamableHTTPServerTranspo
93101 "streamableHttpTransport" ,
94102 `Server started on http://${ config . httpHost } :${ config . httpPort } `
95103 ) ;
104+ }
96105
97- transport . onclose = ( ) = > {
98- logger . info ( LogId . streamableHttpTransportCloseRequested , "streamableHttpTransport" , `Closing server` ) ;
99- server . close ( ( err ?: Error ) => {
106+ async close ( ) : Promise < void > {
107+ await new Promise < void > ( ( resolve , reject ) => {
108+ this . httpServer ?. close ( ( err ) => {
100109 if ( err ) {
101- logger . error (
102- LogId . streamableHttpTransportCloseFailure ,
103- "streamableHttpTransport" ,
104- `Error closing server: ${ err . message } `
105- ) ;
110+ reject ( err ) ;
106111 return ;
107112 }
108- logger . info ( LogId . streamableHttpTransportCloseSuccess , "streamableHttpTransport" , `Server closed` ) ;
113+ resolve ( ) ;
109114 } ) ;
110- } ;
111-
112- return transport ;
113- } catch ( error : unknown ) {
114- const err = error instanceof Error ? error : new Error ( String ( error ) ) ;
115- logger . info (
116- LogId . streamableHttpTransportStartFailure ,
117- "streamableHttpTransport" ,
118- `Error starting server: ${ err . message } `
119- ) ;
120-
121- throw err ;
115+ } ) ;
122116 }
123117}
0 commit comments