From 3678ce545e05ef2fe2b3efc2fadd10e3bb055f1d Mon Sep 17 00:00:00 2001 From: FU-max-boop Date: Thu, 28 May 2026 20:46:27 +0000 Subject: [PATCH] Continue auth discovery on non-JSON metadata --- .changeset/non-json-oauth-discovery.md | 5 +++++ packages/client/src/client/auth.ts | 12 +++++++++--- packages/client/test/client/auth.test.ts | 12 ++++++++++++ 3 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 .changeset/non-json-oauth-discovery.md diff --git a/.changeset/non-json-oauth-discovery.md b/.changeset/non-json-oauth-discovery.md new file mode 100644 index 0000000000..0fba766ff6 --- /dev/null +++ b/.changeset/non-json-oauth-discovery.md @@ -0,0 +1,5 @@ +--- +'@modelcontextprotocol/client': patch +--- + +Continue authorization server metadata discovery when a candidate well-known endpoint returns HTTP 200 with a non-JSON body, allowing fallback to the next OAuth/OIDC discovery URL. diff --git a/packages/client/src/client/auth.ts b/packages/client/src/client/auth.ts index 5f55fb7a08..56c1633637 100644 --- a/packages/client/src/client/auth.ts +++ b/packages/client/src/client/auth.ts @@ -1262,10 +1262,16 @@ export async function discoverAuthorizationServerMetadata( ); } + let metadata: unknown; + try { + metadata = await response.json(); + } catch { + await response.body?.cancel().catch(() => {}); + continue; + } + // Parse and validate based on type - return type === 'oauth' - ? OAuthMetadataSchema.parse(await response.json()) - : OpenIdProviderDiscoveryMetadataSchema.parse(await response.json()); + return type === 'oauth' ? OAuthMetadataSchema.parse(metadata) : OpenIdProviderDiscoveryMetadataSchema.parse(metadata); } return undefined; diff --git a/packages/client/test/client/auth.test.ts b/packages/client/test/client/auth.test.ts index 04d7f4a3fb..89871f2928 100644 --- a/packages/client/test/client/auth.test.ts +++ b/packages/client/test/client/auth.test.ts @@ -945,6 +945,18 @@ describe('OAuth Authorization', () => { expect(mockFetch).toHaveBeenCalledTimes(2); }); + it('continues when an authorization metadata endpoint returns non-JSON', async () => { + mockFetch.mockResolvedValueOnce(new Response('not metadata', { status: 200 })); + mockFetch.mockResolvedValueOnce(Response.json(validOpenIdMetadata, { status: 200 })); + + const metadata = await discoverAuthorizationServerMetadata('https://auth.example.com/tenant1'); + + expect(metadata).toEqual(validOpenIdMetadata); + expect(mockFetch).toHaveBeenCalledTimes(2); + expect(mockFetch.mock.calls[0]![0].toString()).toBe('https://auth.example.com/.well-known/oauth-authorization-server/tenant1'); + expect(mockFetch.mock.calls[1]![0].toString()).toBe('https://auth.example.com/.well-known/openid-configuration/tenant1'); + }); + it('throws on non-502 5xx errors', async () => { mockFetch.mockResolvedValueOnce({ ok: false,