From 3341d58e057e18dd32a02924c736181ae87da375 Mon Sep 17 00:00:00 2001 From: Felix Weinberger Date: Mon, 22 Jun 2026 11:19:35 +0000 Subject: [PATCH 1/2] =?UTF-8?q?fix(auth):=20make=202025-03-26=20backcompat?= =?UTF-8?q?=20mock=20RFC=208414=20=C2=A73.3-compliant?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop the routePrefix from auth/2025-03-26-oauth-metadata-backcompat. The prefix caused createAuthServer to report issuer:'/oauth' while serving the metadata at the root /.well-known/oauth-authorization-server — an RFC 8414 §3.3 mismatch a conforming client must reject. The MCP server in this scenario only mounts /mcp, so the unprefixed /authorize, /token, /register routes do not collide (the sibling endpoint-fallback scenario already mounts them at root). The scenario continues to test exactly what it's meant to: 2025-03-26-spec discovery with no PRM, OAuth metadata at the root well-known path. --- src/scenarios/client/auth/march-spec-backcompat.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/scenarios/client/auth/march-spec-backcompat.ts b/src/scenarios/client/auth/march-spec-backcompat.ts index 3e543882..94d746ba 100644 --- a/src/scenarios/client/auth/march-spec-backcompat.ts +++ b/src/scenarios/client/auth/march-spec-backcompat.ts @@ -24,9 +24,13 @@ export class Auth20250326OAuthMetadataBackcompatScenario implements Scenario { // same URL as the main server (rather than separating AS / RS). const authApp = createAuthServer(ctx, this.checks, this.server.getUrl, { // Disable logging since the main server will already have logging enabled - loggingEnabled: false, - // Add a prefix to auth endpoints to avoid being caught by auth fallbacks - routePrefix: '/oauth' + loggingEnabled: false + // No routePrefix: the AS metadata is served at the root well-known path, + // so the metadata `issuer` must be the bare origin (RFC 8414 §3.3). A + // routePrefix here would make `createAuthServer` report + // `issuer: /oauth`, which an RFC 8414-conforming client correctly + // rejects. The MCP server only mounts `/mcp`, so the unprefixed + // `/authorize`, `/token`, `/register` routes do not collide. }); const app = createServer( ctx, From 7640176f93cebb531d7b32af8e878afbc907f46b Mon Sep 17 00:00:00 2001 From: Paul Carleton Date: Tue, 23 Jun 2026 11:09:23 +0000 Subject: [PATCH 2/2] fix(auth): keep /oauth prefix in 2025-03-26 backcompat, override issuer instead MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dropping routePrefix made the metadata-advertised endpoints land on the 2025-03-26 hardcoded fallback paths, so a client that fetches the well-known doc but ignores its body would now pass. Restore the prefix and pin the metadata issuer to the bare origin via a lazy metadataIssuer override so the mock is RFC 8414 §3.3-compliant without losing the fallback-vs-metadata distinction. --- .../client/auth/helpers/createAuthServer.ts | 9 +++++++-- src/scenarios/client/auth/march-spec-backcompat.ts | 14 +++++++------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/scenarios/client/auth/helpers/createAuthServer.ts b/src/scenarios/client/auth/helpers/createAuthServer.ts index d7d1fc44..4dde4794 100644 --- a/src/scenarios/client/auth/helpers/createAuthServer.ts +++ b/src/scenarios/client/auth/helpers/createAuthServer.ts @@ -59,8 +59,10 @@ export interface AuthServerOptions { * Override the `issuer` value served in the AS metadata document. Used to * test that clients validate the metadata issuer against the issuer * identifier used to construct the well-known URL (RFC 8414 §3.3). + * Accepts a lazy getter for callers that don't know the server URL until + * after `start()`. */ - metadataIssuer?: string; + metadataIssuer?: string | (() => string); tokenVerifier?: MockTokenVerifier; onTokenRequest?: (requestData: { scope?: string; @@ -156,7 +158,10 @@ export function createAuthServer( }); const metadata: any = { - issuer: metadataIssuer ?? `${getAuthBaseUrl()}${routePrefix}`, + issuer: + typeof metadataIssuer === 'function' + ? metadataIssuer() + : (metadataIssuer ?? `${getAuthBaseUrl()}${routePrefix}`), authorization_endpoint: `${getAuthBaseUrl()}${authRoutes.authorization_endpoint}`, token_endpoint: `${getAuthBaseUrl()}${authRoutes.token_endpoint}`, ...(!disableDynamicRegistration && { diff --git a/src/scenarios/client/auth/march-spec-backcompat.ts b/src/scenarios/client/auth/march-spec-backcompat.ts index 94d746ba..48f9170a 100644 --- a/src/scenarios/client/auth/march-spec-backcompat.ts +++ b/src/scenarios/client/auth/march-spec-backcompat.ts @@ -24,13 +24,13 @@ export class Auth20250326OAuthMetadataBackcompatScenario implements Scenario { // same URL as the main server (rather than separating AS / RS). const authApp = createAuthServer(ctx, this.checks, this.server.getUrl, { // Disable logging since the main server will already have logging enabled - loggingEnabled: false - // No routePrefix: the AS metadata is served at the root well-known path, - // so the metadata `issuer` must be the bare origin (RFC 8414 §3.3). A - // routePrefix here would make `createAuthServer` report - // `issuer: /oauth`, which an RFC 8414-conforming client correctly - // rejects. The MCP server only mounts `/mcp`, so the unprefixed - // `/authorize`, `/token`, `/register` routes do not collide. + loggingEnabled: false, + // Keep auth endpoints off the 2025-03-26 fallback paths so a client that + // fetches metadata but ignores the advertised endpoints still 404s. + routePrefix: '/oauth', + // Metadata is served at the root well-known path, so per RFC 8414 §3.3 + // the `issuer` must be the bare origin — not `/oauth`. + metadataIssuer: () => this.server.getUrl() }); const app = createServer( ctx,