fix: handle OAuth error responses returned with HTTP 200 status (#1343)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Paul Carleton <paulcarletonjr@gmail.com>
Co-authored-by: Felix Weinberger <fweinberger@anthropic.com>
Co-authored-by: Felix Weinberger <3823880+felixweinberger@users.noreply.github.com>
This commit is contained in:
Christopher Tso 2026-01-27 02:13:34 +11:00 • committed by GitHub
commit 4b5fdcba02
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 63 additions and 1 deletions

View file

@ -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.

View file

@ -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;
}
}
/**

View file

@ -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.');
});
});