inspector/core/auth/ema/tokenEndpoint.ts
cliffhall f9f79fff54 Address @claude review: format core/, typed output-validation error, document progress heuristic
- 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>
2026-07-15 11:08:54 -04:00

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,
});
}