1
1
import express from "express" ;
2
2
import http from "http" ;
3
3
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js" ;
4
-
4
+ import { TransportRunnerBase } from "./base.js" ;
5
5
import { config } from "../common/config.js" ;
6
6
import logger , { LogId } from "../common/logger.js" ;
7
7
8
8
const JSON_RPC_ERROR_CODE_PROCESSING_REQUEST_FAILED = - 32000 ;
9
+ const JSON_RPC_ERROR_CODE_METHOD_NOT_ALLOWED = - 32601 ;
9
10
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
+ }
15
18
16
- const transport = new StreamableHTTPServerTransport ( {
17
- sessionIdGenerator : undefined ,
18
- } ) ;
19
+ export class StreamableHttpRunner extends TransportRunnerBase {
20
+ private httpServer : http . Server | undefined ;
19
21
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 ( ) ;
39
36
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 ( {
50
71
jsonrpc : "2.0" ,
51
72
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` ,
55
75
} ,
56
76
} ) ;
57
- }
58
- } ) ;
77
+ } ) ;
59
78
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 ( {
70
81
jsonrpc : "2.0" ,
71
82
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` ,
75
85
} ,
76
86
} ) ;
77
- }
78
- } ) ;
87
+ } ) ;
79
88
80
- try {
81
- const server = await new Promise < http . Server > ( ( resolve , reject ) => {
89
+ this . httpServer = await new Promise < http . Server > ( ( resolve , reject ) => {
82
90
const result = app . listen ( config . httpPort , config . httpHost , ( err ?: Error ) => {
83
91
if ( err ) {
84
92
reject ( err ) ;
@@ -93,31 +101,17 @@ export async function createHttpTransport(): Promise<StreamableHTTPServerTranspo
93
101
"streamableHttpTransport" ,
94
102
`Server started on http://${ config . httpHost } :${ config . httpPort } `
95
103
) ;
104
+ }
96
105
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 ) => {
100
109
if ( err ) {
101
- logger . error (
102
- LogId . streamableHttpTransportCloseFailure ,
103
- "streamableHttpTransport" ,
104
- `Error closing server: ${ err . message } `
105
- ) ;
110
+ reject ( err ) ;
106
111
return ;
107
112
}
108
- logger . info ( LogId . streamableHttpTransportCloseSuccess , "streamableHttpTransport" , `Server closed` ) ;
113
+ resolve ( ) ;
109
114
} ) ;
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
+ } ) ;
122
116
}
123
117
}
0 commit comments