1
+ import express , { Request , Response } from 'express' ;
2
+ import { randomUUID } from "node:crypto" ;
3
+ import { McpServer } from '../../server/mcp.js' ;
4
+ import { SSEServerTransport } from '../../server/sse.js' ;
5
+ import { z } from 'zod' ;
6
+ import { CallToolResult } from '../../types.js' ;
7
+
8
+ /**
9
+ * This example server demonstrates the deprecated HTTP+SSE transport
10
+ * (protocol version 2024-11-05). It mainly used for testing backward compatible clients.
11
+ *
12
+ * The server exposes two endpoints:
13
+ * - /sse: For establishing the SSE stream (GET)
14
+ * - /messages: For receiving client messages (POST)
15
+ *
16
+ */
17
+
18
+ // Create an MCP server instance
19
+ const server = new McpServer ( {
20
+ name : 'simple-sse-server' ,
21
+ version : '1.0.0' ,
22
+ } , { capabilities : { logging : { } } } ) ;
23
+
24
+ server . tool (
25
+ 'start-notification-stream' ,
26
+ 'Starts sending periodic notifications' ,
27
+ {
28
+ interval : z . number ( ) . describe ( 'Interval in milliseconds between notifications' ) . default ( 1000 ) ,
29
+ count : z . number ( ) . describe ( 'Number of notifications to send' ) . default ( 10 ) ,
30
+ } ,
31
+ async ( { interval, count } , { sendNotification } ) : Promise < CallToolResult > => {
32
+ const sleep = ( ms : number ) => new Promise ( resolve => setTimeout ( resolve , ms ) ) ;
33
+ let counter = 0 ;
34
+
35
+ // Send the initial notification
36
+ await sendNotification ( {
37
+ method : "notifications/message" ,
38
+ params : {
39
+ level : "info" ,
40
+ data : `Starting notification stream with ${ count } messages every ${ interval } ms`
41
+ }
42
+ } ) ;
43
+
44
+ // Send periodic notifications
45
+ while ( counter < count ) {
46
+ counter ++ ;
47
+ await sleep ( interval ) ;
48
+
49
+ try {
50
+ await sendNotification ( {
51
+ method : "notifications/message" ,
52
+ params : {
53
+ level : "info" ,
54
+ data : `Notification #${ counter } at ${ new Date ( ) . toISOString ( ) } `
55
+ }
56
+ } ) ;
57
+ }
58
+ catch ( error ) {
59
+ console . error ( "Error sending notification:" , error ) ;
60
+ }
61
+ }
62
+
63
+ return {
64
+ content : [
65
+ {
66
+ type : 'text' ,
67
+ text : `Completed sending ${ count } notifications every ${ interval } ms` ,
68
+ }
69
+ ] ,
70
+ } ;
71
+ }
72
+ ) ;
73
+
74
+ const app = express ( ) ;
75
+ app . use ( express . json ( ) ) ;
76
+
77
+ // Store transports by session ID
78
+ const transports : Record < string , SSEServerTransport > = { } ;
79
+
80
+ // SSE endpoint for establishing the stream
81
+ app . get ( '/sse' , async ( req : Request , res : Response ) => {
82
+ console . log ( 'Received GET request to /sse (establishing SSE stream)' ) ;
83
+
84
+ try {
85
+ // Create a new SSE transport for the client
86
+ // The endpoint for POST messages is '/messages'
87
+ const transport = new SSEServerTransport ( '/messages' , res ) ;
88
+
89
+ // Store the transport by session ID
90
+ const sessionId = transport . sessionId ;
91
+ transports [ sessionId ] = transport ;
92
+
93
+ // Set up onclose handler to clean up transport when closed
94
+ transport . onclose = ( ) => {
95
+ console . log ( `SSE transport closed for session ${ sessionId } ` ) ;
96
+ delete transports [ sessionId ] ;
97
+ } ;
98
+
99
+ // Connect the transport to the MCP server
100
+ await server . connect ( transport ) ;
101
+
102
+ // Start the SSE transport to begin streaming
103
+ // This sends an initial 'endpoint' event with the session ID in the URL
104
+ await transport . start ( ) ;
105
+
106
+ console . log ( `Established SSE stream with session ID: ${ sessionId } ` ) ;
107
+ } catch ( error ) {
108
+ console . error ( 'Error establishing SSE stream:' , error ) ;
109
+ if ( ! res . headersSent ) {
110
+ res . status ( 500 ) . send ( 'Error establishing SSE stream' ) ;
111
+ }
112
+ }
113
+ } ) ;
114
+
115
+ // Messages endpoint for receiving client JSON-RPC requests
116
+ app . post ( '/messages' , async ( req : Request , res : Response ) => {
117
+ console . log ( 'Received POST request to /messages' ) ;
118
+
119
+ // Extract session ID from URL query parameter
120
+ // In the SSE protocol, this is added by the client based on the endpoint event
121
+ const sessionId = req . query . sessionId as string | undefined ;
122
+
123
+ if ( ! sessionId ) {
124
+ console . error ( 'No session ID provided in request URL' ) ;
125
+ res . status ( 400 ) . send ( 'Missing sessionId parameter' ) ;
126
+ return ;
127
+ }
128
+
129
+ const transport = transports [ sessionId ] ;
130
+ if ( ! transport ) {
131
+ console . error ( `No active transport found for session ID: ${ sessionId } ` ) ;
132
+ res . status ( 404 ) . send ( 'Session not found' ) ;
133
+ return ;
134
+ }
135
+
136
+ try {
137
+ // Handle the POST message with the transport
138
+ await transport . handlePostMessage ( req , res , req . body ) ;
139
+ } catch ( error ) {
140
+ console . error ( 'Error handling request:' , error ) ;
141
+ if ( ! res . headersSent ) {
142
+ res . status ( 500 ) . send ( 'Error handling request' ) ;
143
+ }
144
+ }
145
+ } ) ;
146
+
147
+ // Start the server
148
+ const PORT = 3000 ;
149
+ app . listen ( PORT , ( ) => {
150
+ console . log ( `Simple SSE Server (deprecated protocol version 2024-11-05) listening on port ${ PORT } ` ) ;
151
+ } ) ;
152
+
153
+ // Handle server shutdown
154
+ process . on ( 'SIGINT' , async ( ) => {
155
+ console . log ( 'Shutting down server...' ) ;
156
+
157
+ // Close all active transports to properly clean up resources
158
+ for ( const sessionId in transports ) {
159
+ try {
160
+ console . log ( `Closing transport for session ${ sessionId } ` ) ;
161
+ await transports [ sessionId ] . close ( ) ;
162
+ delete transports [ sessionId ] ;
163
+ } catch ( error ) {
164
+ console . error ( `Error closing transport for session ${ sessionId } :` , error ) ;
165
+ }
166
+ }
167
+ await server . close ( ) ;
168
+ console . log ( 'Server shutdown complete' ) ;
169
+ process . exit ( 0 ) ;
170
+ } ) ;
0 commit comments