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 2946f3f41..4589fcda2 100644 --- a/packages/client/src/client/auth.ts +++ b/packages/client/src/client/auth.ts @@ -1088,7 +1088,18 @@ async function executeTokenRequest( throw await parseErrorResponse(response); } - return OAuthTokensSchema.parse(await response.json()); + const json: unknown = await response.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; + } } /** 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.'); + }); +});