17
17
*/
18
18
19
19
import { randomUUID } from 'node:crypto' ;
20
- import { AsgardeoMcpAuth , protectedRoute } from '@asgardeo/mcp-express' ;
20
+ import { McpAuthServer } from '@asgardeo/mcp-express' ;
21
21
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp' ;
22
22
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp' ;
23
23
import { isInitializeRequest } from '@modelcontextprotocol/sdk/types' ;
@@ -28,12 +28,13 @@ import {z} from 'zod';
28
28
config ( ) ;
29
29
30
30
const app : Express = express ( ) ;
31
+
32
+ const mcpAuthServer : McpAuthServer = new McpAuthServer ( {
33
+ baseUrl : process . env . BASE_URL as string ,
34
+ } ) ;
35
+
31
36
app . use ( express . json ( ) ) ;
32
- app . use (
33
- AsgardeoMcpAuth ( {
34
- baseUrl : process . env . BASE_URL as string ,
35
- } ) ,
36
- ) ;
37
+ app . use ( mcpAuthServer . router ( ) ) ;
37
38
38
39
interface TransportMap {
39
40
[ sessionId : string ] : {
@@ -47,173 +48,167 @@ const SESSION_TIMEOUT_MS: number = 30 * 60 * 1000;
47
48
48
49
const isSessionExpired = ( lastAccessTime : number ) : boolean => Date . now ( ) - lastAccessTime > SESSION_TIMEOUT_MS ;
49
50
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 ;
110
55
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 ] ;
115
62
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' ,
166
67
} ,
167
- ) ;
68
+ id : null ,
69
+ jsonrpc : '2.0' ,
70
+ } ) ;
71
+ return ;
72
+ }
168
73
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
+ } ;
171
93
// 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 ) {
174
101
// 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 ] ;
190
104
}
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 ( {
192
173
error : {
193
174
code : - 32000 ,
194
- message,
175
+ message : 'Internal server error: Failed to connect to MCP server' ,
195
176
} ,
196
- id : req . body ?. id || null ,
177
+ id : null ,
197
178
jsonrpc : '2.0' ,
198
179
} ) ;
199
180
return ;
200
181
}
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 ( {
207
188
error : {
208
189
code : - 32000 ,
209
- message : 'Internal server error' ,
190
+ message,
210
191
} ,
211
- id : requestId ,
192
+ id : req . body ?. id || null ,
212
193
jsonrpc : '2.0' ,
213
194
} ) ;
195
+ return ;
214
196
}
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
+ } ) ;
217
212
218
213
const handleSessionRequest = async ( expressReq : Request , expressRes : Response ) : Promise < void > => {
219
214
try {
0 commit comments