From 521bbc81d8c783f6fdb77fa94b1408e534d76218 Mon Sep 17 00:00:00 2001 From: Christopher Tso Date: Sun, 28 Dec 2025 12:48:57 +1100 Subject: [PATCH 1/4] fix: handle OAuth error responses returned with HTTP 200 status MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some OAuth servers (e.g., GitHub) return error responses with HTTP 200 status instead of 4xx. The SDK now checks for an `error` field in the JSON response before attempting to parse it as tokens. This provides users with meaningful error messages like: "The client_id and/or client_secret passed are incorrect." Instead of confusing Zod validation errors about missing access_token. Fixes #1342 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- packages/client/src/client/auth.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/client/src/client/auth.ts b/packages/client/src/client/auth.ts index 93048e4b3..52cda173e 100644 --- a/packages/client/src/client/auth.ts +++ b/packages/client/src/client/auth.ts @@ -1086,7 +1086,15 @@ async function executeTokenRequest( throw await parseErrorResponse(response); } - return OAuthTokensSchema.parse(await response.json()); + const json = await response.json(); + + // Some OAuth servers (e.g., GitHub) return error responses with HTTP 200 status. + // Check for error field before attempting to parse as tokens. + if (json.error) { + throw await parseErrorResponse(JSON.stringify(json)); + } + + return OAuthTokensSchema.parse(json); } /** From b8fdf1010b9e3092b299dfea7a5113539170ade2 Mon Sep 17 00:00:00 2001 From: Christopher Tso Date: Sun, 28 Dec 2025 12:56:34 +1100 Subject: [PATCH 2/4] fix: add type safety and changeset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix TypeScript error by properly typing json as unknown - Add changeset for the patch release Fixes #1342 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .changeset/oauth-error-http200.md | 7 +++++++ packages/client/src/client/auth.ts | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 .changeset/oauth-error-http200.md diff --git a/.changeset/oauth-error-http200.md b/.changeset/oauth-error-http200.md new file mode 100644 index 000000000..1ce4fdd9e --- /dev/null +++ b/.changeset/oauth-error-http200.md @@ -0,0 +1,7 @@ +--- +'@modelcontextprotocol/client': patch +--- + +Fix OAuth error handling for servers returning errors with HTTP 200 status + +Some OAuth servers (e.g., GitHub) return error responses with HTTP 200 status instead of 4xx. The SDK now checks for an `error` field in the JSON response before attempting to parse it as tokens, providing users with meaningful error messages. diff --git a/packages/client/src/client/auth.ts b/packages/client/src/client/auth.ts index 52cda173e..4f0e6a727 100644 --- a/packages/client/src/client/auth.ts +++ b/packages/client/src/client/auth.ts @@ -1086,11 +1086,11 @@ async function executeTokenRequest( throw await parseErrorResponse(response); } - const json = await response.json(); + const json: unknown = await response.json(); // Some OAuth servers (e.g., GitHub) return error responses with HTTP 200 status. // Check for error field before attempting to parse as tokens. - if (json.error) { + if (typeof json === 'object' && json !== null && 'error' in json) { throw await parseErrorResponse(JSON.stringify(json)); } From f98b92e9a64f7b60d31a3bdb0e5c242c0fd2ea02 Mon Sep 17 00:00:00 2001 From: Christopher Tso Date: Fri, 9 Jan 2026 07:47:04 +1100 Subject: [PATCH 3/4] check OAuth error field only on parse failure Co-authored-by: Paul Carleton --- packages/client/src/client/auth.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/client/src/client/auth.ts b/packages/client/src/client/auth.ts index 4f0e6a727..c8f09d822 100644 --- a/packages/client/src/client/auth.ts +++ b/packages/client/src/client/auth.ts @@ -1088,13 +1088,16 @@ async function executeTokenRequest( const json: unknown = await response.json(); - // Some OAuth servers (e.g., GitHub) return error responses with HTTP 200 status. - // Check for error field before attempting to parse as tokens. - if (typeof json === 'object' && json !== null && 'error' in json) { - throw await parseErrorResponse(JSON.stringify(json)); + try { + return OAuthTokensSchema.parse(json); + } catch (parseError) { + // Some OAuth servers (e.g., GitHub) return error responses with HTTP 200 status. + // Check for error field only if token parsing failed. + if (typeof json === 'object' && json !== null && 'error' in json) { + throw await parseErrorResponse(JSON.stringify(json)); + } + throw parseError; } - - return OAuthTokensSchema.parse(json); } /** From d6c3f65b0a8c8c9e0738494758fbd6eda39934a7 Mon Sep 17 00:00:00 2001 From: Felix Weinberger Date: Mon, 26 Jan 2026 14:08:30 +0000 Subject: [PATCH 4/4] test: add regression test for OAuth error with HTTP 200 status Adds test case for issue #1342 where OAuth servers like GitHub return error responses with HTTP 200 instead of 4xx. Ensures the fix properly surfaces the OAuth error message instead of a confusing Zod validation error. --- .../issues/test_1342OauthErrorHttp200.test.ts | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 test/integration/test/issues/test_1342OauthErrorHttp200.test.ts diff --git a/test/integration/test/issues/test_1342OauthErrorHttp200.test.ts b/test/integration/test/issues/test_1342OauthErrorHttp200.test.ts new file mode 100644 index 000000000..fd509e6d6 --- /dev/null +++ b/test/integration/test/issues/test_1342OauthErrorHttp200.test.ts @@ -0,0 +1,44 @@ +/** + * Regression test for https://github.com/modelcontextprotocol/typescript-sdk/issues/1342 + * + * Some OAuth servers (e.g., GitHub) return error responses with HTTP 200 status + * instead of 4xx. Previously, the SDK would try to parse these as tokens and fail + * with a confusing Zod validation error. This test verifies that the SDK properly + * detects the error field and surfaces the actual OAuth error message. + */ + +import { exchangeAuthorization } from '@modelcontextprotocol/client'; +import { describe, expect, it, vi } from 'vitest'; + +const mockFetch = vi.fn(); +vi.stubGlobal('fetch', mockFetch); + +describe('Issue #1342: OAuth error response with HTTP 200 status', () => { + const validClientInfo = { + client_id: 'test-client', + client_secret: 'test-secret', + redirect_uris: ['http://localhost:3000/callback'], + token_endpoint_auth_method: 'client_secret_post' as const + }; + + it('should throw OAuth error when server returns error with HTTP 200', async () => { + // GitHub returns errors with HTTP 200 instead of 4xx + mockFetch.mockResolvedValueOnce({ + ok: true, + status: 200, + json: async () => ({ + error: 'invalid_client', + error_description: 'The client_id and/or client_secret passed are incorrect.' + }) + }); + + await expect( + exchangeAuthorization('https://auth.example.com', { + clientInformation: validClientInfo, + authorizationCode: 'code123', + codeVerifier: 'verifier123', + redirectUri: 'http://localhost:3000/callback' + }) + ).rejects.toThrow('The client_id and/or client_secret passed are incorrect.'); + }); +});