From 7175ee0e1dd6fe8c67d54761dbfd140287c07de7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20L=C3=BCdemann?= Date: Thu, 26 Feb 2026 12:25:43 +0100 Subject: [PATCH 1/2] fix(mcp): detect non-standard OAuth errors from servers returning HTTP 400 --- packages/api/src/mcp/MCPConnectionFactory.ts | 4 ++++ packages/api/src/mcp/connection.ts | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/packages/api/src/mcp/MCPConnectionFactory.ts b/packages/api/src/mcp/MCPConnectionFactory.ts index a8f631614d..bad0c9ae73 100644 --- a/packages/api/src/mcp/MCPConnectionFactory.ts +++ b/packages/api/src/mcp/MCPConnectionFactory.ts @@ -455,6 +455,10 @@ export class MCPConnectionFactory { if (message.includes('authentication required') || message.includes('unauthorized')) { return true; } + // Check for missing authorization values (e.g., Amazon Ads MCP returns HTTP 400 with this) + if (message.includes('no authorization')) { + return true; + } } return false; diff --git a/packages/api/src/mcp/connection.ts b/packages/api/src/mcp/connection.ts index 5744059708..5a3a5fb32c 100644 --- a/packages/api/src/mcp/connection.ts +++ b/packages/api/src/mcp/connection.ts @@ -1131,6 +1131,10 @@ export class MCPConnection extends EventEmitter { if (message.includes('authentication required') || message.includes('unauthorized')) { return true; } + // Check for missing authorization values (e.g., Amazon Ads MCP returns HTTP 400 with this) + if (message.includes('no authorization')) { + return true; + } } return false; From c966f14a6274b1dccac31867452b67dcdad92202 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20L=C3=BCdemann?= Date: Thu, 26 Feb 2026 16:31:49 +0100 Subject: [PATCH 2/2] add tests for oauth error check --- .../src/mcp/__tests__/MCPConnection.test.ts | 22 +++++++++++++ .../__tests__/MCPConnectionFactory.test.ts | 33 +++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/packages/api/src/mcp/__tests__/MCPConnection.test.ts b/packages/api/src/mcp/__tests__/MCPConnection.test.ts index 4cca1b3316..f665693001 100644 --- a/packages/api/src/mcp/__tests__/MCPConnection.test.ts +++ b/packages/api/src/mcp/__tests__/MCPConnection.test.ts @@ -82,6 +82,10 @@ describe('MCPConnection Error Detection', () => { if (message.includes('authentication required') || message.includes('unauthorized')) { return true; } + // Check for missing authorization values (e.g., Amazon Ads MCP returns HTTP 400 with this) + if (message.includes('no authorization')) { + return true; + } } return false; @@ -171,6 +175,24 @@ describe('MCPConnection Error Detection', () => { }; expect(isOAuthError(error)).toBe(true); }); + + it('should detect OAuth error for "no authorization" in message (HTTP 400)', () => { + const error = { + message: + 'Either no authorization values are specified or it could not be derived from the request', + }; + expect(isOAuthError(error)).toBe(true); + }); + + it('should detect OAuth error for "No authorization" with different casing', () => { + const error = { message: 'No Authorization header provided' }; + expect(isOAuthError(error)).toBe(true); + }); + + it('should not detect OAuth error for unrelated 400 errors', () => { + const error = { code: 400, message: 'Bad request: missing required field' }; + expect(isOAuthError(error)).toBe(false); + }); }); describe('error type differentiation', () => { diff --git a/packages/api/src/mcp/__tests__/MCPConnectionFactory.test.ts b/packages/api/src/mcp/__tests__/MCPConnectionFactory.test.ts index 263c84357a..599f19c32a 100644 --- a/packages/api/src/mcp/__tests__/MCPConnectionFactory.test.ts +++ b/packages/api/src/mcp/__tests__/MCPConnectionFactory.test.ts @@ -474,6 +474,39 @@ describe('MCPConnectionFactory', () => { expect.stringContaining('OAuth required, stopping connection attempts'), ); }); + + it('should identify "no authorization" errors as OAuth errors (HTTP 400)', async () => { + const basicOptions = { + serverName: 'test-server', + serverConfig: mockServerConfig, + }; + + const oauthOptions = { + useOAuth: true as const, + user: mockUser, + flowManager: mockFlowManager, + tokenMethods: { + findToken: jest.fn(), + createToken: jest.fn(), + updateToken: jest.fn(), + deleteTokens: jest.fn(), + }, + }; + + const noAuthError = new Error( + 'Either no authorization values are specified or it could not be derived from the request', + ); + + mockConnectionInstance.connect.mockRejectedValue(noAuthError); + mockConnectionInstance.isConnected.mockResolvedValue(false); + + await expect(MCPConnectionFactory.create(basicOptions, oauthOptions)).rejects.toThrow( + 'no authorization', + ); + expect(mockLogger.info).toHaveBeenCalledWith( + expect.stringContaining('OAuth required, stopping connection attempts'), + ); + }); }); describe('discoverTools static method', () => {