From 59e3fb40ba932ce38bd566dc6daf53fde9c2853e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 14 Sep 2026 18:02:16 +0000 Subject: [PATCH 1/2] fix(mcp): classify auth-validation failures before logging Split the validateApiKey and validateOAuthToken catch blocks so transient auth-backend failures log at ERROR under a distinct 'Auth backend transient failure:' template while expected rejections log at debug. Return values and 401/503 status contract are unchanged. Co-authored-by: Dhravya Shah --- apps/mcp/src/server/auth/index.test.ts | 26 ++++++++++++++++++++------ apps/mcp/src/server/auth/index.ts | 14 ++++++++++---- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/apps/mcp/src/server/auth/index.test.ts b/apps/mcp/src/server/auth/index.test.ts index d551a7e2..ee85bc64 100644 --- a/apps/mcp/src/server/auth/index.test.ts +++ b/apps/mcp/src/server/auth/index.test.ts @@ -83,16 +83,19 @@ describe("MCP authentication", () => { }) it("rejects a token issued for a different audience", async () => { - vi.spyOn(console, "error").mockImplementation(() => {}) + const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}) + vi.spyOn(console, "debug").mockImplementation(() => {}) const token = await signToken({ audience: "https://api.example.com" }) await expect( validateOAuthToken(token, API_URL, MCP_RESOURCE, keySet), ).resolves.toBeNull() + expect(errorSpy).not.toHaveBeenCalled() }) it("rejects expired tokens and tokens without a subject", async () => { - vi.spyOn(console, "error").mockImplementation(() => {}) + const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}) + vi.spyOn(console, "debug").mockImplementation(() => {}) const expired = await signToken({ expiresIn: "-1s" }) const noSubject = await signToken({ subject: "" }) @@ -102,10 +105,11 @@ describe("MCP authentication", () => { await expect( validateOAuthToken(noSubject, API_URL, MCP_RESOURCE, keySet), ).resolves.toBeNull() + expect(errorSpy).not.toHaveBeenCalled() }) it("rejects opaque API keys without an API request", async () => { - vi.spyOn(console, "error").mockImplementation(() => {}) + vi.spyOn(console, "debug").mockImplementation(() => {}) const fetchSpy = vi.fn() vi.stubGlobal("fetch", fetchSpy) @@ -166,7 +170,8 @@ describe("MCP authentication", () => { }) it("rejects an API key the session endpoint refuses", async () => { - vi.spyOn(console, "error").mockImplementation(() => {}) + const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}) + vi.spyOn(console, "debug").mockImplementation(() => {}) vi.stubGlobal( "fetch", vi.fn().mockResolvedValue(new Response(null, { status: 401 })), @@ -175,6 +180,7 @@ describe("MCP authentication", () => { await expect( validateApiKey("sm_revoked_key_0123456789abcdef", API_URL), ).resolves.toBeNull() + expect(errorSpy).not.toHaveBeenCalled() }) it("rejects malformed API keys without an API request", async () => { @@ -187,7 +193,7 @@ describe("MCP authentication", () => { }) it("surfaces a 500 from the session endpoint as TransientAuthError, not invalid token", async () => { - vi.spyOn(console, "error").mockImplementation(() => {}) + const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}) vi.stubGlobal( "fetch", vi.fn().mockResolvedValue(new Response(null, { status: 500 })), @@ -196,10 +202,14 @@ describe("MCP authentication", () => { await expect( validateApiKey("sm_outage_key_0123456789abcdef", API_URL), ).rejects.toThrow(TransientAuthError) + expect(errorSpy).toHaveBeenCalledWith( + "Auth backend transient failure:", + expect.anything(), + ) }) it("surfaces a session-endpoint timeout as TransientAuthError", async () => { - vi.spyOn(console, "error").mockImplementation(() => {}) + const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}) vi.stubGlobal( "fetch", vi.fn().mockRejectedValue( @@ -212,5 +222,9 @@ describe("MCP authentication", () => { await expect( validateApiKey("sm_timeout_key_0123456789abcd", API_URL), ).rejects.toThrow(TransientAuthError) + expect(errorSpy).toHaveBeenCalledWith( + "Auth backend transient failure:", + expect.anything(), + ) }) }) diff --git a/apps/mcp/src/server/auth/index.ts b/apps/mcp/src/server/auth/index.ts index 3e2374a9..0ecdf8f3 100644 --- a/apps/mcp/src/server/auth/index.ts +++ b/apps/mcp/src/server/auth/index.ts @@ -132,9 +132,12 @@ export async function validateApiKey( }) return user } catch (error) { - console.error("API key validation error:", error) const transient = transientAuthErrorFor(error) - if (transient) throw transient + if (transient) { + console.error("Auth backend transient failure:", error) + throw transient + } + console.debug("API key validation rejected:", error) return null } } @@ -183,9 +186,12 @@ export async function validateOAuthToken( expiresAt: payload.exp, } } catch (error) { - console.error("OAuth token validation error:", error) const transient = transientAuthErrorFor(error) - if (transient) throw transient + if (transient) { + console.error("Auth backend transient failure:", error) + throw transient + } + console.debug("OAuth token validation rejected:", error) return null } } From cfcee9621c8d8ecb1b4dca78e4e992d7d074f474 Mon Sep 17 00:00:00 2001 From: Dhravya Shah Date: Tue, 22 Sep 2026 18:11:45 -0700 Subject: [PATCH 2/2] chore: remove test changes from polylane PR #1669 --- apps/mcp/src/server/auth/index.test.ts | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/apps/mcp/src/server/auth/index.test.ts b/apps/mcp/src/server/auth/index.test.ts index ee85bc64..d551a7e2 100644 --- a/apps/mcp/src/server/auth/index.test.ts +++ b/apps/mcp/src/server/auth/index.test.ts @@ -83,19 +83,16 @@ describe("MCP authentication", () => { }) it("rejects a token issued for a different audience", async () => { - const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}) - vi.spyOn(console, "debug").mockImplementation(() => {}) + vi.spyOn(console, "error").mockImplementation(() => {}) const token = await signToken({ audience: "https://api.example.com" }) await expect( validateOAuthToken(token, API_URL, MCP_RESOURCE, keySet), ).resolves.toBeNull() - expect(errorSpy).not.toHaveBeenCalled() }) it("rejects expired tokens and tokens without a subject", async () => { - const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}) - vi.spyOn(console, "debug").mockImplementation(() => {}) + vi.spyOn(console, "error").mockImplementation(() => {}) const expired = await signToken({ expiresIn: "-1s" }) const noSubject = await signToken({ subject: "" }) @@ -105,11 +102,10 @@ describe("MCP authentication", () => { await expect( validateOAuthToken(noSubject, API_URL, MCP_RESOURCE, keySet), ).resolves.toBeNull() - expect(errorSpy).not.toHaveBeenCalled() }) it("rejects opaque API keys without an API request", async () => { - vi.spyOn(console, "debug").mockImplementation(() => {}) + vi.spyOn(console, "error").mockImplementation(() => {}) const fetchSpy = vi.fn() vi.stubGlobal("fetch", fetchSpy) @@ -170,8 +166,7 @@ describe("MCP authentication", () => { }) it("rejects an API key the session endpoint refuses", async () => { - const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}) - vi.spyOn(console, "debug").mockImplementation(() => {}) + vi.spyOn(console, "error").mockImplementation(() => {}) vi.stubGlobal( "fetch", vi.fn().mockResolvedValue(new Response(null, { status: 401 })), @@ -180,7 +175,6 @@ describe("MCP authentication", () => { await expect( validateApiKey("sm_revoked_key_0123456789abcdef", API_URL), ).resolves.toBeNull() - expect(errorSpy).not.toHaveBeenCalled() }) it("rejects malformed API keys without an API request", async () => { @@ -193,7 +187,7 @@ describe("MCP authentication", () => { }) it("surfaces a 500 from the session endpoint as TransientAuthError, not invalid token", async () => { - const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}) + vi.spyOn(console, "error").mockImplementation(() => {}) vi.stubGlobal( "fetch", vi.fn().mockResolvedValue(new Response(null, { status: 500 })), @@ -202,14 +196,10 @@ describe("MCP authentication", () => { await expect( validateApiKey("sm_outage_key_0123456789abcdef", API_URL), ).rejects.toThrow(TransientAuthError) - expect(errorSpy).toHaveBeenCalledWith( - "Auth backend transient failure:", - expect.anything(), - ) }) it("surfaces a session-endpoint timeout as TransientAuthError", async () => { - const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}) + vi.spyOn(console, "error").mockImplementation(() => {}) vi.stubGlobal( "fetch", vi.fn().mockRejectedValue( @@ -222,9 +212,5 @@ describe("MCP authentication", () => { await expect( validateApiKey("sm_timeout_key_0123456789abcd", API_URL), ).rejects.toThrow(TransientAuthError) - expect(errorSpy).toHaveBeenCalledWith( - "Auth backend transient failure:", - expect.anything(), - ) }) })