- Run Prettier on the 60 core/ files this PR touched — the deterministic import rewrite had collapsed multi-line imports and left throws unwrapped; core/ is in no format gate so it slipped past CI. Now Prettier-clean. - Default-path output-schema violation now throws ProtocolError(ProtocolErrorCode.InvalidParams, …) instead of a bare Error, matching the prior v1 contract (SDK threw a typed McpError with a code) so downstream `instanceof`/`.code` branching keeps working. - Document the "assumes serial construction" invariant on pollTaskToolCall's progress-subscription revival (the `_progressHandlers` new-key heuristic). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
70 lines
2.2 KiB
TypeScript
70 lines
2.2 KiB
TypeScript
import { selectClientAuthMethod } from "@modelcontextprotocol/client";
|
|
import type {
|
|
OAuthClientInformation,
|
|
OAuthMetadata,
|
|
} from "@modelcontextprotocol/client";
|
|
|
|
export async function parseOAuthTokenErrorResponse(
|
|
response: Response,
|
|
step: string,
|
|
): Promise<Error> {
|
|
let body: unknown;
|
|
try {
|
|
body = await response.json();
|
|
} catch {
|
|
return new Error(`${step}: token request failed (HTTP ${response.status})`);
|
|
}
|
|
if (typeof body === "object" && body !== null) {
|
|
const record = body as { error?: string; error_description?: string };
|
|
const parts = [step];
|
|
if (record.error) parts.push(`error=${record.error}`);
|
|
if (record.error_description) parts.push(record.error_description);
|
|
if (parts.length > 1) {
|
|
return new Error(parts.join(": "));
|
|
}
|
|
}
|
|
return new Error(`${step}: token request failed (HTTP ${response.status})`);
|
|
}
|
|
|
|
function applyClientAuth(
|
|
metadata: OAuthMetadata | undefined,
|
|
clientInformation: OAuthClientInformation,
|
|
headers: Headers,
|
|
body: URLSearchParams,
|
|
): void {
|
|
const supported = metadata?.token_endpoint_auth_methods_supported ?? [];
|
|
const method = selectClientAuthMethod(clientInformation, supported);
|
|
if (method === "client_secret_basic" && clientInformation.client_secret) {
|
|
const credentials = btoa(
|
|
`${clientInformation.client_id}:${clientInformation.client_secret}`,
|
|
);
|
|
headers.set("Authorization", `Basic ${credentials}`);
|
|
return;
|
|
}
|
|
if (method === "client_secret_post" && clientInformation.client_secret) {
|
|
body.set("client_id", clientInformation.client_id);
|
|
body.set("client_secret", clientInformation.client_secret);
|
|
return;
|
|
}
|
|
body.set("client_id", clientInformation.client_id);
|
|
}
|
|
|
|
/** POST application/x-www-form-urlencoded to an OAuth token endpoint. */
|
|
export async function postOAuthTokenRequest(
|
|
tokenUrl: URL,
|
|
body: URLSearchParams,
|
|
metadata: OAuthMetadata | undefined,
|
|
clientInformation: OAuthClientInformation,
|
|
fetchFn?: typeof fetch,
|
|
): Promise<Response> {
|
|
const headers = new Headers({
|
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
Accept: "application/json",
|
|
});
|
|
applyClientAuth(metadata, clientInformation, headers, body);
|
|
return (fetchFn ?? fetch)(tokenUrl, {
|
|
method: "POST",
|
|
headers,
|
|
body,
|
|
});
|
|
}
|