Skip to content

Commit 62656b4

Browse files
committed
oAuth metadata: switch to better-auth/plugins -> mcp plugin
1 parent d52565e commit 62656b4

6 files changed

Lines changed: 42 additions & 84 deletions

File tree

examples/server/src/elicitationUrlExample.ts

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@
1010
import { randomUUID } from 'node:crypto';
1111

1212
import {
13+
createProtectedResourceMetadataRouter,
1314
getOAuthProtectedResourceMetadataUrl,
14-
mcpAuthMetadataRouter,
1515
requireBearerAuth,
1616
setupAuthServer
1717
} from '@modelcontextprotocol/examples-shared';
18-
import type { CallToolResult, ElicitRequestURLParams, ElicitResult, OAuthMetadata } from '@modelcontextprotocol/server';
18+
import type { CallToolResult, ElicitRequestURLParams, ElicitResult } from '@modelcontextprotocol/server';
1919
import {
2020
isInitializeRequest,
2121
McpServer,
@@ -239,17 +239,11 @@ let authMiddleware = null;
239239
const mcpServerUrl = new URL(`http://localhost:${MCP_PORT}/mcp`);
240240
const authServerUrl = new URL(`http://localhost:${AUTH_PORT}`);
241241

242-
const oauthMetadata: OAuthMetadata = setupAuthServer({ authServerUrl, mcpServerUrl, strictResource: true });
242+
setupAuthServer({ authServerUrl, mcpServerUrl, strictResource: true });
243243

244-
// Add metadata routes to the main MCP server
245-
app.use(
246-
mcpAuthMetadataRouter({
247-
oauthMetadata,
248-
resourceServerUrl: mcpServerUrl,
249-
scopesSupported: ['mcp:tools'],
250-
resourceName: 'MCP Demo Server'
251-
})
252-
);
244+
// Add protected resource metadata route to the MCP server
245+
// This allows clients to discover the auth server
246+
app.use(createProtectedResourceMetadataRouter());
253247

254248
authMiddleware = requireBearerAuth({
255249
requiredScopes: [],

examples/server/src/simpleStreamableHttp.ts

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
import { randomUUID } from 'node:crypto';
22

33
import {
4+
createProtectedResourceMetadataRouter,
45
getOAuthProtectedResourceMetadataUrl,
5-
mcpAuthMetadataRouter,
66
requireBearerAuth,
77
setupAuthServer
88
} from '@modelcontextprotocol/examples-shared';
99
import type {
1010
CallToolResult,
1111
GetPromptResult,
12-
OAuthMetadata,
1312
PrimitiveSchemaDefinition,
1413
ReadResourceResult,
1514
ResourceLink
@@ -528,17 +527,11 @@ if (useOAuth) {
528527
const mcpServerUrl = new URL(`http://localhost:${MCP_PORT}/mcp`);
529528
const authServerUrl = new URL(`http://localhost:${AUTH_PORT}`);
530529

531-
const oauthMetadata: OAuthMetadata = setupAuthServer({ authServerUrl, mcpServerUrl, strictResource: strictOAuth });
530+
setupAuthServer({ authServerUrl, mcpServerUrl, strictResource: strictOAuth });
532531

533-
// Add metadata routes to the main MCP server
534-
app.use(
535-
mcpAuthMetadataRouter({
536-
oauthMetadata,
537-
resourceServerUrl: mcpServerUrl,
538-
scopesSupported: ['mcp:tools'],
539-
resourceName: 'MCP Demo Server'
540-
})
541-
);
532+
// Add protected resource metadata route to the MCP server
533+
// This allows clients to discover the auth server
534+
app.use(createProtectedResourceMetadataRouter());
542535

543536
authMiddleware = requireBearerAuth({
544537
requiredScopes: [],

examples/shared/src/auth.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
* For production use, configure a proper database and authentication flow.
88
*/
99

10-
import type { BetterAuthPlugin } from 'better-auth';
1110
import { betterAuth } from 'better-auth';
1211
import { mcp } from 'better-auth/plugins';
1312
import Database from 'better-sqlite3';
@@ -70,7 +69,7 @@ export function createDemoAuth(options: CreateDemoAuthOptions) {
7069
enabled: true,
7170
requireEmailVerification: false
7271
},
73-
plugins: [mcpPlugin as BetterAuthPlugin]
72+
plugins: [mcpPlugin]
7473
});
7574
}
7675

examples/shared/src/authMiddleware.ts

Lines changed: 2 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,10 @@
33
*
44
* 🚨 DEMO ONLY - NOT FOR PRODUCTION
55
*
6-
* This provides bearer auth middleware and metadata routes for MCP servers.
6+
* This provides bearer auth middleware for MCP servers.
77
*/
88

9-
import type { OAuthMetadata, OAuthProtectedResourceMetadata } from '@modelcontextprotocol/server';
10-
import type { NextFunction, Request, Response, Router } from 'express';
11-
import express from 'express';
9+
import type { NextFunction, Request, Response } from 'express';
1210

1311
import { verifyAccessToken } from './authServer.js';
1412

@@ -78,43 +76,6 @@ export function requireBearerAuth(
7876
};
7977
}
8078

81-
export interface McpAuthMetadataRouterOptions {
82-
oauthMetadata: OAuthMetadata;
83-
resourceServerUrl: URL;
84-
scopesSupported?: string[];
85-
resourceName?: string;
86-
}
87-
88-
/**
89-
* Creates an Express router that serves OAuth and Protected Resource metadata.
90-
*/
91-
export function mcpAuthMetadataRouter(options: McpAuthMetadataRouterOptions): Router {
92-
const { oauthMetadata, resourceServerUrl, scopesSupported = ['mcp:tools'], resourceName } = options;
93-
94-
const router = express.Router();
95-
96-
// OAuth Protected Resource Metadata (RFC 9728)
97-
const protectedResourceMetadata: OAuthProtectedResourceMetadata = {
98-
resource: resourceServerUrl.toString(),
99-
authorization_servers: [oauthMetadata.issuer],
100-
scopes_supported: scopesSupported,
101-
resource_name: resourceName
102-
};
103-
104-
// Serve protected resource metadata
105-
router.get('/.well-known/oauth-protected-resource', (req: Request, res: Response) => {
106-
res.json(protectedResourceMetadata);
107-
});
108-
109-
// Also serve at the MCP-specific path
110-
const mcpPath = new URL(resourceServerUrl.pathname, resourceServerUrl).pathname;
111-
router.get(`${mcpPath}/.well-known/oauth-protected-resource`, (req: Request, res: Response) => {
112-
res.json(protectedResourceMetadata);
113-
});
114-
115-
return router;
116-
}
117-
11879
/**
11980
* Helper to get the protected resource metadata URL from a server URL.
12081
*/

examples/shared/src/authServer.ts

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111

1212
import type { OAuthMetadata } from '@modelcontextprotocol/core';
1313
import { toNodeHandler } from 'better-auth/node';
14-
import type { Request, Response as ExpressResponse } from 'express';
14+
import { oAuthDiscoveryMetadata, oAuthProtectedResourceMetadata } from 'better-auth/plugins';
15+
import type { Request, Response as ExpressResponse, Router } from 'express';
1516
import express from 'express';
1617

1718
import type { DemoAuth } from './auth.js';
@@ -132,19 +133,10 @@ export function setupAuthServer(options: SetupAuthServerOptions): OAuthMetadata
132133
// This handles: authorization, token, client registration, etc.
133134
authApp.all('/api/auth/*', toNodeHandler(auth));
134135

135-
// OAuth metadata endpoints at well-known paths
136-
// Some clients may not parse WWW-Authenticate header and need these
137-
authApp.get('/.well-known/oauth-authorization-server', (_req, res) => {
138-
res.json(createOAuthMetadata(authServerUrl));
139-
});
140-
141-
authApp.get('/.well-known/oauth-protected-resource', (_req, res) => {
142-
res.json({
143-
resource: mcpServerUrl.toString(),
144-
authorization_servers: [authServerUrl.toString().replace(/\/$/, '')],
145-
scopes_supported: ['openid', 'profile', 'email', 'mcp:tools']
146-
});
147-
});
136+
// OAuth metadata endpoints using better-auth's built-in handlers
137+
// See: https://www.better-auth.com/docs/plugins/mcp#oauth-discovery-metadata
138+
authApp.get('/.well-known/oauth-authorization-server', toNodeHandler(oAuthDiscoveryMetadata(auth)));
139+
authApp.get('/.well-known/oauth-protected-resource', toNodeHandler(oAuthProtectedResourceMetadata(auth)));
148140

149141
// Start the auth server
150142
const authPort = parseInt(authServerUrl.port, 10);
@@ -162,6 +154,25 @@ export function setupAuthServer(options: SetupAuthServerOptions): OAuthMetadata
162154
return createOAuthMetadata(authServerUrl);
163155
}
164156

157+
/**
158+
* Creates an Express router that serves OAuth Protected Resource Metadata
159+
* on the MCP server using better-auth's built-in handler.
160+
*
161+
* This is needed because MCP clients discover the auth server by first
162+
* fetching protected resource metadata from the MCP server.
163+
*
164+
* See: https://www.better-auth.com/docs/plugins/mcp#oauth-protected-resource-metadata
165+
*/
166+
export function createProtectedResourceMetadataRouter(): Router {
167+
const auth = getAuth();
168+
const router = express.Router();
169+
170+
// Serve at the standard well-known path
171+
router.get('/.well-known/oauth-protected-resource', toNodeHandler(oAuthProtectedResourceMetadata(auth)));
172+
173+
return router;
174+
}
175+
165176
/**
166177
* Creates OAuth 2.0 Authorization Server Metadata (RFC 8414)
167178
*/

examples/shared/src/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ export type { CreateDemoAuthOptions, DemoAuth } from './auth.js';
33
export { createDemoAuth } from './auth.js';
44

55
// Auth middleware
6-
export type { McpAuthMetadataRouterOptions, RequireBearerAuthOptions } from './authMiddleware.js';
7-
export { getOAuthProtectedResourceMetadataUrl, mcpAuthMetadataRouter, requireBearerAuth } from './authMiddleware.js';
6+
export type { RequireBearerAuthOptions } from './authMiddleware.js';
7+
export { getOAuthProtectedResourceMetadataUrl, requireBearerAuth } from './authMiddleware.js';
88

99
// Auth server setup
1010
export type { AuthServerResult, SetupAuthServerOptions } from './authServer.js';
11-
export { getAuth, setupAuthServer, verifyAccessToken } from './authServer.js';
11+
export { createProtectedResourceMetadataRouter, getAuth, setupAuthServer, verifyAccessToken } from './authServer.js';

0 commit comments

Comments
 (0)