feat: bound SDK-internal OAuth on the transport chain too (#2319 round 11)
The transport chain carried the SDK's own OAuth work — the discovery it runs from inside the transport on the 401/refresh path — and that leg was left to the SDK's incidental per-request timeout, reporting a bare `Request timed out` naming no endpoint. On a direct (non-proxied) path the /api/fetch backstop cannot reach it either. `withOAuthRequestTimeout` takes an optional `isExempt` predicate; an exempt request passes through completely untouched — no deadline, no signal, and no body buffering, which is what a stream needs. `exemptMcpEndpoint` exempts the MCP endpoint by origin + pathname, and FAILS OPEN: no server URL, or either side unparseable, means exempt. The errors are not symmetric. Bounding what should not be bounded severs a long-running tool call withholding its headers, or an SSE body; failing to bound leaves the SDK's timeout as the backstop it already was. Two test corrections, both real: - The metadata-preservation case asserted url/redirected/type against a synthetic Response, whose values are the rebuild's own defaults — so it stayed green with the defineProperty loop deleted. It now seeds non-default values first; verified by mutation that removing the loop makes it fail. - Two signal assertions compared against the controller rather than `request.signal`. The Fetch standard gives a Request a dependent signal, so those encoded non-standard identity. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YFnM6RqB4nXqNysgUYmjmq Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
19718c5402
commit
f491c7fc9a
1 changed files with 181 additions and 14 deletions
|
|
@ -256,7 +256,11 @@ import {
|
|||
} from "../auth/challenge.js";
|
||||
import { withOAuthEndpointOverrides } from "../auth/endpointOverrides.js";
|
||||
import { withRfc8414OidcCompat } from "../auth/oidcDiscoveryCompat.js";
|
||||
import { withOAuthRequestTimeout } from "../auth/requestTimeout.js";
|
||||
import {
|
||||
DEFAULT_OAUTH_REQUEST_TIMEOUT_MS,
|
||||
exemptMcpEndpoint,
|
||||
withOAuthRequestTimeout,
|
||||
} from "../auth/requestTimeout.js";
|
||||
import type { TokenRevocationOutcome } from "../auth/revocation.js";
|
||||
import type { OAuthTokens } from "@modelcontextprotocol/client";
|
||||
import { silentLogger, type InspectorLogger } from "../logging/logger.js";
|
||||
|
|
@ -824,7 +828,21 @@ export class InspectorClient extends InspectorClientEventTarget {
|
|||
// still hit the upstream failure. The substituted response is stamped with
|
||||
// `COMPAT_SOURCE_HEADER` so a captured entry names the URL its body came
|
||||
// from rather than appearing to be a 200 from the RFC 8414 path.
|
||||
this.fetchFn = withRfc8414OidcCompat(withOverrides);
|
||||
// The transport chain is bounded too, but only for the requests on it that
|
||||
// are *not* MCP traffic — the OAuth work the SDK runs from inside the
|
||||
// transport on the 401/refresh path, which otherwise waits out the SDK's
|
||||
// incidental per-request timeout and reports a bare `Request timed out`
|
||||
// naming no endpoint (Copilot). The MCP endpoint itself is exempt, and the
|
||||
// predicate fails open: a long-running tool call withholding its response
|
||||
// headers for minutes, or an SSE body that stays open, must never be timed
|
||||
// here. Deadline innermost, for the same reason as the auth chain below.
|
||||
this.fetchFn = withRfc8414OidcCompat(
|
||||
withOAuthRequestTimeout(
|
||||
withOverrides,
|
||||
DEFAULT_OAUTH_REQUEST_TIMEOUT_MS,
|
||||
exemptMcpEndpoint(() => this.getServerUrl()),
|
||||
),
|
||||
);
|
||||
// #2319: the auth chain is composed separately so the deadline sits
|
||||
// *inside* the compat wrapper. Reusing `this.fetchFn` as the base would put
|
||||
// it outside, and then a stalled OIDC probe would be reported under the
|
||||
|
|
|
|||
Loading…
Reference in a new issue