fix(mcp): treat an unparseable session response as a transient auth failure

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
MaheshtheDev 2026-10-20 10:32:59 +00:00
parent f81fad00e4
commit 152a4e611d
2 changed files with 49 additions and 2 deletions

View file

@ -239,6 +239,48 @@ describe("MCP authentication", () => {
expect(warn).not.toHaveBeenCalled()
})
it("surfaces a 200 session body that fails the session contract as TransientAuthError", async () => {
const warn = vi.spyOn(console, "warn").mockImplementation(() => {})
const error = vi.spyOn(console, "error").mockImplementation(() => {})
vi.stubGlobal(
"fetch",
vi.fn().mockResolvedValue(
new Response(JSON.stringify({}), {
status: 200,
headers: { "content-type": "application/json" },
}),
),
)
await expect(
validateApiKey("sm_badbody_key_0123456789abcd", API_URL),
).rejects.toThrow(TransientAuthError)
expect(error).toHaveBeenCalledTimes(1)
expect(error.mock.calls[0]?.[0]).toBe(
"Auth backend unavailable during API key validation: Invalid session response",
)
expect(warn).not.toHaveBeenCalled()
})
it("surfaces a non-JSON 200 session body as TransientAuthError", async () => {
const warn = vi.spyOn(console, "warn").mockImplementation(() => {})
const error = vi.spyOn(console, "error").mockImplementation(() => {})
vi.stubGlobal(
"fetch",
vi
.fn()
.mockResolvedValue(
new Response("<html>upstream error</html>", { status: 200 }),
),
)
await expect(
validateApiKey("sm_htmlbody_key_0123456789abc", API_URL),
).rejects.toThrow(TransientAuthError)
expect(error).toHaveBeenCalledTimes(1)
expect(warn).not.toHaveBeenCalled()
})
it("surfaces a session-endpoint timeout as TransientAuthError", async () => {
const warn = vi.spyOn(console, "warn").mockImplementation(() => {})
const error = vi.spyOn(console, "error").mockImplementation(() => {})

View file

@ -44,9 +44,13 @@ export async function fetchSession(
)
}
const result = sessionInfoSchema.safeParse(await response.json())
const body = await response.json().catch((error: unknown) => {
if (error instanceof SyntaxError) return undefined
throw error
})
const result = sessionInfoSchema.safeParse(body)
if (!result.success) {
throw new Error("Invalid session response")
throw new TransientAuthError("Invalid session response")
}
return result.data
@ -86,6 +90,7 @@ const TRANSIENT_ERROR_NAMES = new Set([
const TRANSIENT_JOSE_CODES = new Set(["ERR_JWKS_TIMEOUT", "ERR_JOSE_GENERIC"])
function transientAuthErrorFor(error: unknown): TransientAuthError | null {
if (error instanceof TransientAuthError) return error
const status = (error as { status?: unknown } | null)?.status
if (typeof status === "number" && status !== 401 && status !== 403) {
return new TransientAuthError(`Session endpoint returned ${status}`, status)