@@ -3,8 +3,15 @@ import { randomUUID } from 'node:crypto';
3
3
import { z } from 'zod' ;
4
4
import { McpServer } from '../../server/mcp.js' ;
5
5
import { StreamableHTTPServerTransport } from '../../server/streamableHttp.js' ;
6
+ import { getOAuthProtectedResourceMetadataUrl , mcpAuthMetadataRouter } from '../../server/auth/router.js' ;
7
+ import { requireBearerAuth } from '../../server/auth/middleware/bearerAuth.js' ;
6
8
import { CallToolResult , GetPromptResult , isInitializeRequest , ReadResourceResult } from '../../types.js' ;
7
9
import { InMemoryEventStore } from '../shared/inMemoryEventStore.js' ;
10
+ import { setupAuthServer } from './demoInMemoryOAuthProvider.js' ;
11
+ import { OAuthMetadata } from 'src/shared/auth.js' ;
12
+
13
+ // Check for OAuth flag
14
+ const useOAuth = process . argv . includes ( '--oauth' ) ;
8
15
9
16
// Create an MCP server with implementation details
10
17
const getServer = ( ) => {
@@ -40,7 +47,7 @@ const getServer = () => {
40
47
name : z . string ( ) . describe ( 'Name to greet' ) ,
41
48
} ,
42
49
{
43
- title : 'Multiple Greeting Tool' ,
50
+ title : 'Multiple Greeting Tool' ,
44
51
readOnlyHint : true ,
45
52
openWorldHint : false
46
53
} ,
@@ -159,14 +166,79 @@ const getServer = () => {
159
166
return server ;
160
167
} ;
161
168
169
+ const MCP_PORT = 3000 ;
170
+ const AUTH_PORT = 3001 ;
171
+
162
172
const app = express ( ) ;
163
173
app . use ( express . json ( ) ) ;
164
174
175
+ // Set up OAuth if enabled
176
+ let authMiddleware = null ;
177
+ if ( useOAuth ) {
178
+ // Create auth middleware for MCP endpoints
179
+ const mcpServerUrl = new URL ( `http://localhost:${ MCP_PORT } ` ) ;
180
+ const authServerUrl = new URL ( `http://localhost:${ AUTH_PORT } ` ) ;
181
+
182
+ const oauthMetadata : OAuthMetadata = setupAuthServer ( authServerUrl ) ;
183
+
184
+ const tokenVerifier = {
185
+ verifyAccessToken : async ( token : string ) => {
186
+ const endpoint = oauthMetadata . introspection_endpoint ;
187
+
188
+ if ( ! endpoint ) {
189
+ throw new Error ( 'No token verification endpoint available in metadata' ) ;
190
+ }
191
+
192
+ const response = await fetch ( endpoint , {
193
+ method : 'POST' ,
194
+ headers : {
195
+ 'Content-Type' : 'application/x-www-form-urlencoded' ,
196
+ } ,
197
+ body : new URLSearchParams ( {
198
+ token : token
199
+ } ) . toString ( )
200
+ } ) ;
201
+
202
+
203
+ if ( ! response . ok ) {
204
+ throw new Error ( `Invalid or expired token: ${ await response . text ( ) } ` ) ;
205
+ }
206
+
207
+ const data = await response . json ( ) ;
208
+
209
+ // Convert the response to AuthInfo format
210
+ return {
211
+ token,
212
+ clientId : data . client_id ,
213
+ scopes : data . scope ? data . scope . split ( ' ' ) : [ ] ,
214
+ expiresAt : data . exp ,
215
+ } ;
216
+ }
217
+ }
218
+ // Add metadata routes to the main MCP server
219
+ app . use ( mcpAuthMetadataRouter ( {
220
+ oauthMetadata,
221
+ resourceServerUrl : mcpServerUrl ,
222
+ scopesSupported : [ 'mcp:tools' ] ,
223
+ resourceName : 'MCP Demo Server' ,
224
+ } ) ) ;
225
+
226
+ authMiddleware = requireBearerAuth ( {
227
+ verifier : tokenVerifier ,
228
+ requiredScopes : [ 'mcp:tools' ] ,
229
+ resourceMetadataUrl : getOAuthProtectedResourceMetadataUrl ( mcpServerUrl ) ,
230
+ } ) ;
231
+ }
232
+
165
233
// Map to store transports by session ID
166
234
const transports : { [ sessionId : string ] : StreamableHTTPServerTransport } = { } ;
167
235
168
- app . post ( '/mcp' , async ( req : Request , res : Response ) => {
236
+ // MCP POST endpoint with optional auth
237
+ const mcpPostHandler = async ( req : Request , res : Response ) => {
169
238
console . log ( 'Received MCP request:' , req . body ) ;
239
+ if ( useOAuth && req . auth ) {
240
+ console . log ( 'Authenticated user:' , req . auth ) ;
241
+ }
170
242
try {
171
243
// Check for existing session ID
172
244
const sessionId = req . headers [ 'mcp-session-id' ] as string | undefined ;
@@ -234,16 +306,27 @@ app.post('/mcp', async (req: Request, res: Response) => {
234
306
} ) ;
235
307
}
236
308
}
237
- } ) ;
309
+ } ;
310
+
311
+ // Set up routes with conditional auth middleware
312
+ if ( useOAuth && authMiddleware ) {
313
+ app . post ( '/mcp' , authMiddleware , mcpPostHandler ) ;
314
+ } else {
315
+ app . post ( '/mcp' , mcpPostHandler ) ;
316
+ }
238
317
239
318
// Handle GET requests for SSE streams (using built-in support from StreamableHTTP)
240
- app . get ( '/mcp' , async ( req : Request , res : Response ) => {
319
+ const mcpGetHandler = async ( req : Request , res : Response ) => {
241
320
const sessionId = req . headers [ 'mcp-session-id' ] as string | undefined ;
242
321
if ( ! sessionId || ! transports [ sessionId ] ) {
243
322
res . status ( 400 ) . send ( 'Invalid or missing session ID' ) ;
244
323
return ;
245
324
}
246
325
326
+ if ( useOAuth && req . auth ) {
327
+ console . log ( 'Authenticated SSE connection from user:' , req . auth ) ;
328
+ }
329
+
247
330
// Check for Last-Event-ID header for resumability
248
331
const lastEventId = req . headers [ 'last-event-id' ] as string | undefined ;
249
332
if ( lastEventId ) {
@@ -254,10 +337,17 @@ app.get('/mcp', async (req: Request, res: Response) => {
254
337
255
338
const transport = transports [ sessionId ] ;
256
339
await transport . handleRequest ( req , res ) ;
257
- } ) ;
340
+ } ;
341
+
342
+ // Set up GET route with conditional auth middleware
343
+ if ( useOAuth && authMiddleware ) {
344
+ app . get ( '/mcp' , authMiddleware , mcpGetHandler ) ;
345
+ } else {
346
+ app . get ( '/mcp' , mcpGetHandler ) ;
347
+ }
258
348
259
349
// Handle DELETE requests for session termination (according to MCP spec)
260
- app . delete ( '/mcp' , async ( req : Request , res : Response ) => {
350
+ const mcpDeleteHandler = async ( req : Request , res : Response ) => {
261
351
const sessionId = req . headers [ 'mcp-session-id' ] as string | undefined ;
262
352
if ( ! sessionId || ! transports [ sessionId ] ) {
263
353
res . status ( 400 ) . send ( 'Invalid or missing session ID' ) ;
@@ -275,12 +365,17 @@ app.delete('/mcp', async (req: Request, res: Response) => {
275
365
res . status ( 500 ) . send ( 'Error processing session termination' ) ;
276
366
}
277
367
}
278
- } ) ;
368
+ } ;
369
+
370
+ // Set up DELETE route with conditional auth middleware
371
+ if ( useOAuth && authMiddleware ) {
372
+ app . delete ( '/mcp' , authMiddleware , mcpDeleteHandler ) ;
373
+ } else {
374
+ app . delete ( '/mcp' , mcpDeleteHandler ) ;
375
+ }
279
376
280
- // Start the server
281
- const PORT = 3000 ;
282
- app . listen ( PORT , ( ) => {
283
- console . log ( `MCP Streamable HTTP Server listening on port ${ PORT } ` ) ;
377
+ app . listen ( MCP_PORT , ( ) => {
378
+ console . log ( `MCP Streamable HTTP Server listening on port ${ MCP_PORT } ` ) ;
284
379
} ) ;
285
380
286
381
// Handle server shutdown
0 commit comments