Skip to content

Commit 930ddd0

Browse files
dcramercodex
andauthored
fix(cloudflare): Treat OAuth probe 4xx as expired (#862)
Treat the refresh-token probe's client-side failures as an expected sign that the stored Sentry token is no longer usable. While investigating MCP-SERVER-F9Q, the upstream \/auth\/ probe was returning 400 for invalid or expired bearer tokens. The refresh path only treated 401 authentication failures as expected, so these 400 responses were being logged as issues even though they should fall through to re-auth. This broadens the probe guard to all ApiClientError responses and keeps the regression coverage focused on the 400 probe behavior without asserting on telemetry internals. Fixes MCP-SERVER-F9Q Co-authored-by: OpenAI Codex <noreply@openai.com>
1 parent b2adac5 commit 930ddd0

2 files changed

Lines changed: 23 additions & 6 deletions

File tree

packages/mcp-cloudflare/src/server/oauth/helpers.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,23 @@ describe("tokenExchangeCallback", () => {
7777
expect(result).toBeUndefined();
7878
});
7979

80+
it("should treat 400 probe failures as expired", async () => {
81+
const pastExpiry = Date.now() - 60 * 1000;
82+
const options = createRefreshOptions({
83+
accessTokenExpiresAt: pastExpiry,
84+
});
85+
86+
vi.spyOn(globalThis, "fetch").mockResolvedValue(
87+
new Response(JSON.stringify({ detail: "Invalid token" }), {
88+
status: 400,
89+
statusText: "Bad Request",
90+
}),
91+
);
92+
93+
const result = await tokenExchangeCallback(options, TEST_ENV);
94+
expect(result).toBeUndefined();
95+
});
96+
8097
it("should return undefined when upstream probe fails with network error", async () => {
8198
const pastExpiry = Date.now() - 60 * 1000;
8299
const options = createRefreshOptions({

packages/mcp-cloudflare/src/server/oauth/helpers.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,7 @@ import type {
33
TokenExchangeCallbackResult,
44
} from "@cloudflare/workers-oauth-provider";
55
import type { z } from "zod";
6-
import {
7-
ApiAuthenticationError,
8-
SentryApiService,
9-
} from "@sentry/mcp-core/api-client";
6+
import { ApiClientError, SentryApiService } from "@sentry/mcp-core/api-client";
107
import { logError, logIssue } from "@sentry/mcp-core/telem/logging";
118
import { TokenResponseSchema } from "./constants";
129
import type { WorkerProps } from "../types";
@@ -319,7 +316,10 @@ export async function tokenExchangeCallback(
319316
}
320317
}
321318

322-
// Probe upstream to check if the token is actually still valid
319+
// Probe upstream to check if the token is actually still valid. Sentry can
320+
// report invalid/expired bearer tokens here as 400 or 401, so treat any 4xx
321+
// as an expected probe failure and fall back to re-auth without creating an
322+
// issue.
323323
try {
324324
const api = new SentryApiService({
325325
accessToken: props.accessToken,
@@ -334,7 +334,7 @@ export async function tokenExchangeCallback(
334334
accessTokenTTL: 60 * 60,
335335
};
336336
} catch (error) {
337-
if (!(error instanceof ApiAuthenticationError)) {
337+
if (!(error instanceof ApiClientError)) {
338338
logIssue(error, {
339339
loggerScope: ["cloudflare", "oauth", "refresh"],
340340
});

0 commit comments

Comments
 (0)