All of the mid-session and step-up auth work
This commit is contained in:
parent
1f31e0f1f8
commit
562bdd00f1
2 changed files with 11437 additions and 1212 deletions
34
core/mcp/node/authChallengeFetch.ts
Normal file
34
core/mcp/node/authChallengeFetch.ts
Normal 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})`,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue