@@ -29,7 +29,7 @@ config();
2929
3030const app : Express = express ( ) ;
3131
32- const mcpAuthServer = new McpAuthServer ( {
32+ const mcpAuthServer : McpAuthServer = new McpAuthServer ( {
3333 baseUrl : process . env . BASE_URL as string ,
3434} ) ;
3535
@@ -48,170 +48,167 @@ const SESSION_TIMEOUT_MS: number = 30 * 60 * 1000;
4848
4949const isSessionExpired = ( lastAccessTime : number ) : boolean => Date . now ( ) - lastAccessTime > SESSION_TIMEOUT_MS ;
5050
51- app . post (
52- '/mcp' ,
53- mcpAuthServer . protect ( async ( req : Request , res : Response ) : Promise < void > => {
54- try {
55- const sessionId : string | undefined = req . headers [ 'mcp-session-id' ] as string | undefined ;
56- let transport : StreamableHTTPServerTransport ;
51+ app . post ( '/mcp' , mcpAuthServer . protect ( ) , async ( req : Request , res : Response ) : Promise < void > => {
52+ try {
53+ const sessionId : string | undefined = req . headers [ 'mcp-session-id' ] as string | undefined ;
54+ let transport : StreamableHTTPServerTransport ;
5755
58- if ( sessionId && transports [ sessionId ] ) {
59- if ( isSessionExpired ( transports [ sessionId ] . lastAccess ) ) {
60- // eslint-disable-next-line no-console
61- console . log ( `Session expired: ${ sessionId } ` ) ;
62- transports [ sessionId ] . transport . close ( ) ;
63- delete transports [ sessionId ] ;
64-
65- res . status ( 401 ) . json ( {
66- error : {
67- code : - 32000 ,
68- message : 'Session expired' ,
69- } ,
70- id : null ,
71- jsonrpc : '2.0' ,
72- } ) ;
73- return ;
74- }
56+ if ( sessionId && transports [ sessionId ] ) {
57+ if ( isSessionExpired ( transports [ sessionId ] . lastAccess ) ) {
58+ // eslint-disable-next-line no-console
59+ console . log ( `Session expired: ${ sessionId } ` ) ;
60+ transports [ sessionId ] . transport . close ( ) ;
61+ delete transports [ sessionId ] ;
7562
76- transport = transports [ sessionId ] . transport ;
77- transports [ sessionId ] . lastAccess = Date . now ( ) ;
78- } else if ( ! sessionId && isInitializeRequest ( req . body ) ) {
79- let bearerToken : string | undefined ;
80- const authHeader : string | undefined = req . headers . authorization as string | undefined ;
81- if ( authHeader && authHeader . toLowerCase ( ) . startsWith ( 'bearer ' ) ) {
82- bearerToken = authHeader . substring ( 7 ) ;
83- // eslint-disable-next-line no-console
84- console . log ( `Bearer token captured for new session.` ) ;
85- } else {
86- // eslint-disable-next-line no-console
87- console . warn ( 'MCP session initialized: No Bearer token found in Authorization header.' ) ;
88- }
89- transport = new StreamableHTTPServerTransport ( {
90- onsessioninitialized : ( newSessionId : string ) : void => {
91- transports [ newSessionId ] = {
92- lastAccess : Date . now ( ) ,
93- transport,
94- } ;
95- // eslint-disable-next-line no-console
96- console . log ( `Session initialized: ${ newSessionId } ` ) ;
63+ res . status ( 401 ) . json ( {
64+ error : {
65+ code : - 32000 ,
66+ message : 'Session expired' ,
9767 } ,
98- sessionIdGenerator : ( ) : string => randomUUID ( ) ,
99- } ) ;
100-
101- transport . onclose = ( ) : void => {
102- if ( transport . sessionId ) {
103- // eslint-disable-next-line no-console
104- console . log ( `Session closed: ${ transport . sessionId } ` ) ;
105- delete transports [ transport . sessionId ] ;
106- }
107- } ;
108-
109- const server : McpServer = new McpServer ( {
110- name : 'example-server' ,
111- version : '1.0.0' ,
68+ id : null ,
69+ jsonrpc : '2.0' ,
11270 } ) ;
71+ return ;
72+ }
11373
114- server . tool (
115- 'get_pet_vaccination_info' ,
116- 'Retrieves the vaccination history and upcoming vaccination dates for a specific pet. Requires user authentication and explicit consent via an authorization token.' ,
117- {
118- petId : z . string ( ) . describe ( 'The unique identifier for the pet.' ) ,
119- } ,
120- async ( { petId} : { petId : string } ) => {
121- try {
122- return {
123- content : [
124- {
125- text : `Retrieved vaccination info for pet ID: ${ petId } . Token was ${
126- bearerToken ? 'present' : 'absent'
127- } .`,
128- type : 'text' ,
129- } ,
130- ] ,
131- } ;
132- } catch ( error ) {
133- const errorMessage : string = error instanceof Error ? error . message : String ( error ) ;
134- throw new Error ( `Failed to retrieve vaccination information: ${ errorMessage } ` ) ;
135- }
136- } ,
137- ) ;
138-
139- server . tool (
140- 'book_vet_appointment' ,
141- 'Books a new veterinary appointment for a specific pet. Requires user authentication and explicit consent via an authorization token.' ,
142- {
143- date : z . string ( ) . describe ( 'Desired date for the appointment (e.g., YYYY-MM-DD).' ) ,
144- petId : z . string ( ) . describe ( 'The unique identifier for the pet.' ) ,
145- reason : z . string ( ) . describe ( 'The reason for the vet visit.' ) ,
146- time : z . string ( ) . describe ( 'Desired time for the appointment (e.g., HH:MM AM/PM).' ) ,
147- } ,
148- async ( { date, petId, reason, time} : { date : string ; petId : string ; reason : string ; time : string } ) => {
149- try {
150- return {
151- content : [
152- {
153- text : `Booked vet appointment for pet ID: ${ petId } on ${ date } at ${ time } for: ${ reason } . Token was ${
154- bearerToken ? 'present' : 'absent'
155- } .`,
156- type : 'text' ,
157- } ,
158- ] ,
159- } ;
160- } catch ( error ) {
161- const errorMessage : string = error instanceof Error ? error . message : String ( error ) ;
162- throw new Error ( `Failed to book appointment: ${ errorMessage } ` ) ;
163- }
164- } ,
165- ) ;
166-
167- try {
168- await server . connect ( transport ) ;
74+ transport = transports [ sessionId ] . transport ;
75+ transports [ sessionId ] . lastAccess = Date . now ( ) ;
76+ } else if ( ! sessionId && isInitializeRequest ( req . body ) ) {
77+ let bearerToken : string | undefined ;
78+ const authHeader : string | undefined = req . headers . authorization as string | undefined ;
79+ if ( authHeader && authHeader . toLowerCase ( ) . startsWith ( 'bearer ' ) ) {
80+ bearerToken = authHeader . substring ( 7 ) ;
81+ // eslint-disable-next-line no-console
82+ console . log ( `Bearer token captured for new session.` ) ;
83+ } else {
84+ // eslint-disable-next-line no-console
85+ console . warn ( 'MCP session initialized: No Bearer token found in Authorization header.' ) ;
86+ }
87+ transport = new StreamableHTTPServerTransport ( {
88+ onsessioninitialized : ( newSessionId : string ) : void => {
89+ transports [ newSessionId ] = {
90+ lastAccess : Date . now ( ) ,
91+ transport,
92+ } ;
16993 // eslint-disable-next-line no-console
170- console . log ( 'Server connected to transport' ) ;
171- } catch ( error ) {
94+ console . log ( `Session initialized: ${ newSessionId } ` ) ;
95+ } ,
96+ sessionIdGenerator : ( ) : string => randomUUID ( ) ,
97+ } ) ;
98+
99+ transport . onclose = ( ) : void => {
100+ if ( transport . sessionId ) {
172101 // eslint-disable-next-line no-console
173- console . error ( `Error connecting server to transport: ${ error } ` ) ;
174- res . status ( 500 ) . json ( {
175- error : {
176- code : - 32000 ,
177- message : 'Internal server error: Failed to connect to MCP server' ,
178- } ,
179- id : null ,
180- jsonrpc : '2.0' ,
181- } ) ;
182- return ;
102+ console . log ( `Session closed: ${ transport . sessionId } ` ) ;
103+ delete transports [ transport . sessionId ] ;
183104 }
184- } else {
185- let message : string = 'Bad Request: No valid session ID provided for existing session.' ;
186- if ( ! isInitializeRequest ( req . body ) ) {
187- message = 'Bad Request: Not an initialization request and no session ID found.' ;
188- }
189- res . status ( 400 ) . json ( {
105+ } ;
106+
107+ const server : McpServer = new McpServer ( {
108+ name : 'example-server' ,
109+ version : '1.0.0' ,
110+ } ) ;
111+
112+ server . tool (
113+ 'get_pet_vaccination_info' ,
114+ 'Retrieves the vaccination history and upcoming vaccination dates for a specific pet. Requires user authentication and explicit consent via an authorization token.' ,
115+ {
116+ petId : z . string ( ) . describe ( 'The unique identifier for the pet.' ) ,
117+ } ,
118+ async ( { petId} : { petId : string } ) => {
119+ try {
120+ return {
121+ content : [
122+ {
123+ text : `Retrieved vaccination info for pet ID: ${ petId } . Token was ${
124+ bearerToken ? 'present' : 'absent'
125+ } .`,
126+ type : 'text' ,
127+ } ,
128+ ] ,
129+ } ;
130+ } catch ( error ) {
131+ const errorMessage : string = error instanceof Error ? error . message : String ( error ) ;
132+ throw new Error ( `Failed to retrieve vaccination information: ${ errorMessage } ` ) ;
133+ }
134+ } ,
135+ ) ;
136+
137+ server . tool (
138+ 'book_vet_appointment' ,
139+ 'Books a new veterinary appointment for a specific pet. Requires user authentication and explicit consent via an authorization token.' ,
140+ {
141+ date : z . string ( ) . describe ( 'Desired date for the appointment (e.g., YYYY-MM-DD).' ) ,
142+ petId : z . string ( ) . describe ( 'The unique identifier for the pet.' ) ,
143+ reason : z . string ( ) . describe ( 'The reason for the vet visit.' ) ,
144+ time : z . string ( ) . describe ( 'Desired time for the appointment (e.g., HH:MM AM/PM).' ) ,
145+ } ,
146+ async ( { date, petId, reason, time} : { date : string ; petId : string ; reason : string ; time : string } ) => {
147+ try {
148+ return {
149+ content : [
150+ {
151+ text : `Booked vet appointment for pet ID: ${ petId } on ${ date } at ${ time } for: ${ reason } . Token was ${
152+ bearerToken ? 'present' : 'absent'
153+ } .`,
154+ type : 'text' ,
155+ } ,
156+ ] ,
157+ } ;
158+ } catch ( error ) {
159+ const errorMessage : string = error instanceof Error ? error . message : String ( error ) ;
160+ throw new Error ( `Failed to book appointment: ${ errorMessage } ` ) ;
161+ }
162+ } ,
163+ ) ;
164+
165+ try {
166+ await server . connect ( transport ) ;
167+ // eslint-disable-next-line no-console
168+ console . log ( 'Server connected to transport' ) ;
169+ } catch ( error ) {
170+ // eslint-disable-next-line no-console
171+ console . error ( `Error connecting server to transport: ${ error } ` ) ;
172+ res . status ( 500 ) . json ( {
190173 error : {
191174 code : - 32000 ,
192- message,
175+ message : 'Internal server error: Failed to connect to MCP server' ,
193176 } ,
194- id : req . body ?. id || null ,
177+ id : null ,
195178 jsonrpc : '2.0' ,
196179 } ) ;
197180 return ;
198181 }
199-
200- await transport . handleRequest ( req , res , req . body ) ;
201- } catch ( error ) {
202- const requestId : string | number | null | undefined =
203- typeof req . body === 'object' && req . body !== null && 'id' in req . body ? req . body . id : null ;
204- res . status ( 500 ) . json ( {
182+ } else {
183+ let message : string = 'Bad Request: No valid session ID provided for existing session.' ;
184+ if ( ! isInitializeRequest ( req . body ) ) {
185+ message = 'Bad Request: Not an initialization request and no session ID found.' ;
186+ }
187+ res . status ( 400 ) . json ( {
205188 error : {
206189 code : - 32000 ,
207- message : 'Internal server error' ,
190+ message,
208191 } ,
209- id : requestId ,
192+ id : req . body ?. id || null ,
210193 jsonrpc : '2.0' ,
211194 } ) ;
195+ return ;
212196 }
213- } ) ,
214- ) ;
197+
198+ await transport . handleRequest ( req , res , req . body ) ;
199+ } catch ( error ) {
200+ const requestId : string | number | null | undefined =
201+ typeof req . body === 'object' && req . body !== null && 'id' in req . body ? req . body . id : null ;
202+ res . status ( 500 ) . json ( {
203+ error : {
204+ code : - 32000 ,
205+ message : 'Internal server error' ,
206+ } ,
207+ id : requestId ,
208+ jsonrpc : '2.0' ,
209+ } ) ;
210+ }
211+ } ) ;
215212
216213const handleSessionRequest = async ( expressReq : Request , expressRes : Response ) : Promise < void > => {
217214 try {
0 commit comments