All of the mid-session and step-up auth work

This commit is contained in:
Bob Dickinson 2026-07-02 16:35:31 -07:00
commit 562bdd00f1
2 changed files with 11437 additions and 1212 deletions

View file

@ -0,0 +1,34 @@
import {
AuthChallengeError,
parseAuthChallengeFromResponse,
} from "../../auth/challenge.js";
/**
* Wrap fetch so MCP HTTP 401/403 responses become {@link AuthChallengeError}
* before the SDK invokes `auth()` on a frozen remote token provider.
*/
export function createAuthChallengeInterceptFetch(
baseFetch: typeof fetch,
): typeof fetch {
return async (input, init) => {
const response = await baseFetch(input, init);
if (response.status !== 401 && response.status !== 403) {
return response;
}
const challenge = parseAuthChallengeFromResponse(response);
if (!challenge) {
return response;
}
// Release the connection before throwing so the SDK transport is not left
// with a half-read 401/403 body on streamable HTTP.
await response.body?.cancel().catch(() => {});
throw new AuthChallengeError(
challenge,
response.status,
`MCP auth challenge (${response.status})`,
);
};
}

View file

@ -12,6 +12,7 @@ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
import { createFetchTracker } from "../fetchTracking.js";
import { createAuthChallengeInterceptFetch } from "./authChallengeFetch.js";
/**
* Build the wire `headers` record from `settings.headers`, dropping rows with
@ -46,9 +47,13 @@ export function createTransportNode(
onFetchResponseBody,
authProvider,
settings,
interceptAuthChallenges = false,
} = options;
const baseFetch = optionsFetchFn ?? globalThis.fetch;
const fetchWithOptionalAuthIntercept = interceptAuthChallenges
? createAuthChallengeInterceptFetch(baseFetch)
: baseFetch;
if (serverType === "stdio") {
const stdioConfig = config as StdioServerConfig;
@ -79,7 +84,8 @@ export function createTransportNode(
const url = new URL(sseConfig.url);
const sseFetch =
(sseConfig.eventSourceInit?.fetch as typeof fetch) || baseFetch;
(sseConfig.eventSourceInit?.fetch as typeof fetch) ||
fetchWithOptionalAuthIntercept;
const trackedFetch = onFetchRequest
? createFetchTracker(sseFetch, {
trackRequest: onFetchRequest,
@ -101,11 +107,11 @@ export function createTransportNode(
};
const postFetch = onFetchRequest
? createFetchTracker(baseFetch, {
? createFetchTracker(fetchWithOptionalAuthIntercept, {
trackRequest: onFetchRequest,
updateResponseBody: onFetchResponseBody,
})
: baseFetch;
: fetchWithOptionalAuthIntercept;
const transport = new SSEClientTransport(url, {
authProvider,
@ -128,11 +134,11 @@ export function createTransportNode(
};
const transportFetch = onFetchRequest
? createFetchTracker(baseFetch, {
? createFetchTracker(fetchWithOptionalAuthIntercept, {
trackRequest: onFetchRequest,
updateResponseBody: onFetchResponseBody,
})
: baseFetch;
: fetchWithOptionalAuthIntercept;
const transport = new StreamableHTTPClientTransport(url, {
authProvider,