1717 */
1818
1919import { randomUUID } from 'node:crypto' ;
20- import { AsgardeoMcpAuth , protectedRoute } from '@asgardeo/mcp-express' ;
20+ import { McpAuthServer } from '@asgardeo/mcp-express' ;
2121import { McpServer } from '@modelcontextprotocol/sdk/server/mcp' ;
2222import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp' ;
2323import { isInitializeRequest } from '@modelcontextprotocol/sdk/types' ;
@@ -28,12 +28,13 @@ import {z} from 'zod';
2828config ( ) ;
2929
3030const app : Express = express ( ) ;
31+
32+ const mcpAuthServer : McpAuthServer = new McpAuthServer ( {
33+ baseUrl : process . env . BASE_URL as string ,
34+ } ) ;
35+
3136app . use ( express . json ( ) ) ;
32- app . use (
33- AsgardeoMcpAuth ( {
34- baseUrl : process . env . BASE_URL as string ,
35- } ) ,
36- ) ;
37+ app . use ( mcpAuthServer . router ( ) ) ;
3738
3839interface TransportMap {
3940 [ sessionId : string ] : {
@@ -47,173 +48,167 @@ const SESSION_TIMEOUT_MS: number = 30 * 60 * 1000;
4748
4849const isSessionExpired = ( lastAccessTime : number ) : boolean => Date . now ( ) - lastAccessTime > SESSION_TIMEOUT_MS ;
4950
50- app . post (
51- '/mcp' ,
52- protectedRoute ( {
53- baseUrl : process . env . BASE_URL as string ,
54- } ) ,
55- async ( req : Request , res : Response ) : Promise < void > => {
56- try {
57- const sessionId : string | undefined = req . headers [ 'mcp-session-id' ] as string | undefined ;
58- let transport : StreamableHTTPServerTransport ;
59-
60- if ( sessionId && transports [ sessionId ] ) {
61- if ( isSessionExpired ( transports [ sessionId ] . lastAccess ) ) {
62- // eslint-disable-next-line no-console
63- console . log ( `Session expired: ${ sessionId } ` ) ;
64- transports [ sessionId ] . transport . close ( ) ;
65- delete transports [ sessionId ] ;
66-
67- res . status ( 401 ) . json ( {
68- error : {
69- code : - 32000 ,
70- message : 'Session expired' ,
71- } ,
72- id : null ,
73- jsonrpc : '2.0' ,
74- } ) ;
75- return ;
76- }
77-
78- transport = transports [ sessionId ] . transport ;
79- transports [ sessionId ] . lastAccess = Date . now ( ) ;
80- } else if ( ! sessionId && isInitializeRequest ( req . body ) ) {
81- let bearerToken : string | undefined ;
82- const authHeader : string | undefined = req . headers . authorization as string | undefined ;
83- if ( authHeader && authHeader . toLowerCase ( ) . startsWith ( 'bearer ' ) ) {
84- bearerToken = authHeader . substring ( 7 ) ;
85- // eslint-disable-next-line no-console
86- console . log ( `Bearer token captured for new session.` ) ;
87- } else {
88- // eslint-disable-next-line no-console
89- console . warn ( 'MCP session initialized: No Bearer token found in Authorization header.' ) ;
90- }
91- transport = new StreamableHTTPServerTransport ( {
92- onsessioninitialized : ( newSessionId : string ) : void => {
93- transports [ newSessionId ] = {
94- lastAccess : Date . now ( ) ,
95- transport,
96- } ;
97- // eslint-disable-next-line no-console
98- console . log ( `Session initialized: ${ newSessionId } ` ) ;
99- } ,
100- sessionIdGenerator : ( ) : string => randomUUID ( ) ,
101- } ) ;
102-
103- transport . onclose = ( ) : void => {
104- if ( transport . sessionId ) {
105- // eslint-disable-next-line no-console
106- console . log ( `Session closed: ${ transport . sessionId } ` ) ;
107- delete transports [ transport . sessionId ] ;
108- }
109- } ;
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 ;
11055
111- const server : McpServer = new McpServer ( {
112- name : 'example-server' ,
113- version : '1.0.0' ,
114- } ) ;
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 ] ;
11562
116- server . tool (
117- 'get_pet_vaccination_info' ,
118- 'Retrieves the vaccination history and upcoming vaccination dates for a specific pet. Requires user authentication and explicit consent via an authorization token.' ,
119- {
120- petId : z . string ( ) . describe ( 'The unique identifier for the pet.' ) ,
121- } ,
122- async ( { petId} : { petId : string } ) => {
123- try {
124- return {
125- content : [
126- {
127- text : `Retrieved vaccination info for pet ID: ${ petId } . Token was ${
128- bearerToken ? 'present' : 'absent'
129- } .`,
130- type : 'text' ,
131- } ,
132- ] ,
133- } ;
134- } catch ( error ) {
135- const errorMessage : string = error instanceof Error ? error . message : String ( error ) ;
136- throw new Error ( `Failed to retrieve vaccination information: ${ errorMessage } ` ) ;
137- }
138- } ,
139- ) ;
140-
141- server . tool (
142- 'book_vet_appointment' ,
143- 'Books a new veterinary appointment for a specific pet. Requires user authentication and explicit consent via an authorization token.' ,
144- {
145- date : z . string ( ) . describe ( 'Desired date for the appointment (e.g., YYYY-MM-DD).' ) ,
146- petId : z . string ( ) . describe ( 'The unique identifier for the pet.' ) ,
147- reason : z . string ( ) . describe ( 'The reason for the vet visit.' ) ,
148- time : z . string ( ) . describe ( 'Desired time for the appointment (e.g., HH:MM AM/PM).' ) ,
149- } ,
150- async ( { date, petId, reason, time} : { date : string ; petId : string ; reason : string ; time : string } ) => {
151- try {
152- return {
153- content : [
154- {
155- text : `Booked vet appointment for pet ID: ${ petId } on ${ date } at ${ time } for: ${ reason } . Token was ${
156- bearerToken ? 'present' : 'absent'
157- } .`,
158- type : 'text' ,
159- } ,
160- ] ,
161- } ;
162- } catch ( error ) {
163- const errorMessage : string = error instanceof Error ? error . message : String ( error ) ;
164- throw new Error ( `Failed to book appointment: ${ errorMessage } ` ) ;
165- }
63+ res . status ( 401 ) . json ( {
64+ error : {
65+ code : - 32000 ,
66+ message : 'Session expired' ,
16667 } ,
167- ) ;
68+ id : null ,
69+ jsonrpc : '2.0' ,
70+ } ) ;
71+ return ;
72+ }
16873
169- try {
170- 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+ } ;
17193 // eslint-disable-next-line no-console
172- console . log ( 'Server connected to transport' ) ;
173- } catch ( error ) {
94+ console . log ( `Session initialized: ${ newSessionId } ` ) ;
95+ } ,
96+ sessionIdGenerator : ( ) : string => randomUUID ( ) ,
97+ } ) ;
98+
99+ transport . onclose = ( ) : void => {
100+ if ( transport . sessionId ) {
174101 // eslint-disable-next-line no-console
175- console . error ( `Error connecting server to transport: ${ error } ` ) ;
176- res . status ( 500 ) . json ( {
177- error : {
178- code : - 32000 ,
179- message : 'Internal server error: Failed to connect to MCP server' ,
180- } ,
181- id : null ,
182- jsonrpc : '2.0' ,
183- } ) ;
184- return ;
185- }
186- } else {
187- let message : string = 'Bad Request: No valid session ID provided for existing session.' ;
188- if ( ! isInitializeRequest ( req . body ) ) {
189- message = 'Bad Request: Not an initialization request and no session ID found.' ;
102+ console . log ( `Session closed: ${ transport . sessionId } ` ) ;
103+ delete transports [ transport . sessionId ] ;
190104 }
191- 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 ( {
192173 error : {
193174 code : - 32000 ,
194- message,
175+ message : 'Internal server error: Failed to connect to MCP server' ,
195176 } ,
196- id : req . body ?. id || null ,
177+ id : null ,
197178 jsonrpc : '2.0' ,
198179 } ) ;
199180 return ;
200181 }
201-
202- await transport . handleRequest ( req , res , req . body ) ;
203- } catch ( error ) {
204- const requestId : string | number | null | undefined =
205- typeof req . body === 'object' && req . body !== null && 'id' in req . body ? req . body . id : null ;
206- 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 ( {
207188 error : {
208189 code : - 32000 ,
209- message : 'Internal server error' ,
190+ message,
210191 } ,
211- id : requestId ,
192+ id : req . body ?. id || null ,
212193 jsonrpc : '2.0' ,
213194 } ) ;
195+ return ;
214196 }
215- } ,
216- ) ;
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+ } ) ;
217212
218213const handleSessionRequest = async ( expressReq : Request , expressRes : Response ) : Promise < void > => {
219214 try {
0 commit comments