@@ -3,8 +3,15 @@ import { randomUUID } from 'node:crypto';
33import { z } from 'zod' ;
44import { McpServer } from '../../server/mcp.js' ;
55import { StreamableHTTPServerTransport } from '../../server/streamableHttp.js' ;
6+ import { getOAuthProtectedResourceMetadataUrl , mcpAuthMetadataRouter } from '../../server/auth/router.js' ;
7+ import { requireBearerAuth } from '../../server/auth/middleware/bearerAuth.js' ;
68import { CallToolResult , GetPromptResult , isInitializeRequest , ReadResourceResult } from '../../types.js' ;
79import { 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' ) ;
815
916// Create an MCP server with implementation details
1017const getServer = ( ) => {
@@ -40,7 +47,7 @@ const getServer = () => {
4047 name : z . string ( ) . describe ( 'Name to greet' ) ,
4148 } ,
4249 {
43- title : 'Multiple Greeting Tool' ,
50+ title : 'Multiple Greeting Tool' ,
4451 readOnlyHint : true ,
4552 openWorldHint : false
4653 } ,
@@ -159,14 +166,79 @@ const getServer = () => {
159166 return server ;
160167} ;
161168
169+ const MCP_PORT = 3000 ;
170+ const AUTH_PORT = 3001 ;
171+
162172const app = express ( ) ;
163173app . use ( express . json ( ) ) ;
164174
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+
165233// Map to store transports by session ID
166234const transports : { [ sessionId : string ] : StreamableHTTPServerTransport } = { } ;
167235
168- app . post ( '/mcp' , async ( req : Request , res : Response ) => {
236+ // MCP POST endpoint with optional auth
237+ const mcpPostHandler = async ( req : Request , res : Response ) => {
169238 console . log ( 'Received MCP request:' , req . body ) ;
239+ if ( useOAuth && req . auth ) {
240+ console . log ( 'Authenticated user:' , req . auth ) ;
241+ }
170242 try {
171243 // Check for existing session ID
172244 const sessionId = req . headers [ 'mcp-session-id' ] as string | undefined ;
@@ -234,16 +306,27 @@ app.post('/mcp', async (req: Request, res: Response) => {
234306 } ) ;
235307 }
236308 }
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+ }
238317
239318// 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 ) => {
241320 const sessionId = req . headers [ 'mcp-session-id' ] as string | undefined ;
242321 if ( ! sessionId || ! transports [ sessionId ] ) {
243322 res . status ( 400 ) . send ( 'Invalid or missing session ID' ) ;
244323 return ;
245324 }
246325
326+ if ( useOAuth && req . auth ) {
327+ console . log ( 'Authenticated SSE connection from user:' , req . auth ) ;
328+ }
329+
247330 // Check for Last-Event-ID header for resumability
248331 const lastEventId = req . headers [ 'last-event-id' ] as string | undefined ;
249332 if ( lastEventId ) {
@@ -254,10 +337,17 @@ app.get('/mcp', async (req: Request, res: Response) => {
254337
255338 const transport = transports [ sessionId ] ;
256339 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+ }
258348
259349// 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 ) => {
261351 const sessionId = req . headers [ 'mcp-session-id' ] as string | undefined ;
262352 if ( ! sessionId || ! transports [ sessionId ] ) {
263353 res . status ( 400 ) . send ( 'Invalid or missing session ID' ) ;
@@ -275,12 +365,17 @@ app.delete('/mcp', async (req: Request, res: Response) => {
275365 res . status ( 500 ) . send ( 'Error processing session termination' ) ;
276366 }
277367 }
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+ }
279376
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 } ` ) ;
284379} ) ;
285380
286381// Handle server shutdown
0 commit comments