From d9a20d847c1a0436c5175a5a1024c152c614425d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Mar 2026 21:04:14 +0000 Subject: [PATCH 1/2] Initial plan From e73cad19a46957aed62cbed41fe1bd77dcced260 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Mar 2026 21:09:38 +0000 Subject: [PATCH 2/2] Fix OAuth: use authorization_servers URL from resource metadata for auth server discovery The discoverScopes function always used the MCP server URL to discover authorization server metadata, ignoring the authorization_servers field from the protected resource metadata. This caused failures when the authorization server is on a different domain than the MCP server. Now checks resourceMetadata.authorization_servers for a URL to use, falling back to the MCP server URL when not available. Fixes #675 Co-authored-by: cliffhall <871933+cliffhall@users.noreply.github.com> --- client/src/lib/__tests__/auth.test.ts | 33 +++++++++++++++++++++++++++ client/src/lib/auth.ts | 9 +++++--- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/client/src/lib/__tests__/auth.test.ts b/client/src/lib/__tests__/auth.test.ts index 329b7f027..101198054 100644 --- a/client/src/lib/__tests__/auth.test.ts +++ b/client/src/lib/__tests__/auth.test.ts @@ -85,6 +85,39 @@ describe("discoverScopes", () => { }, expected: "read", }, + { + name: "uses authorization_servers URL from resource metadata for discovery", + mockResolves: baseMetadata, + serverUrl: "https://mcp-server.com", + resourceMetadata: { + resource: "https://mcp-server.com", + authorization_servers: ["https://auth-server.com/"], + }, + expected: "read write", + expectedCallUrl: "https://auth-server.com/", + }, + { + name: "uses authorization_servers URL with path for discovery", + mockResolves: baseMetadata, + serverUrl: "https://mcp-server.com", + resourceMetadata: { + resource: "https://mcp-server.com", + authorization_servers: ["https://auth-server.com/realms/my-realm/"], + }, + expected: "read write", + expectedCallUrl: "https://auth-server.com/realms/my-realm/", + }, + { + name: "falls back to serverUrl when authorization_servers is empty", + mockResolves: baseMetadata, + serverUrl: "https://mcp-server.com", + resourceMetadata: { + resource: "https://mcp-server.com", + authorization_servers: [], + }, + expected: "read write", + expectedCallUrl: "https://mcp-server.com/", + }, ]; const undefinedCases = [ diff --git a/client/src/lib/auth.ts b/client/src/lib/auth.ts index 879936104..e1ed8d639 100644 --- a/client/src/lib/auth.ts +++ b/client/src/lib/auth.ts @@ -24,9 +24,12 @@ export const discoverScopes = async ( resourceMetadata?: OAuthProtectedResourceMetadata, ): Promise => { try { - const metadata = await discoverAuthorizationServerMetadata( - new URL("/", serverUrl), - ); + // Use the authorization server URL from resource metadata if available, + // otherwise fall back to the MCP server URL + const authServerUrl = resourceMetadata?.authorization_servers?.length + ? new URL(resourceMetadata.authorization_servers[0]) + : new URL("/", serverUrl); + const metadata = await discoverAuthorizationServerMetadata(authServerUrl); // Prefer resource metadata scopes, but fall back to OAuth metadata if empty const resourceScopes = resourceMetadata?.scopes_supported;