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>
This commit is contained in:
cliffhall 2026-07-15 11:08:54 -04:00
commit f9f79fff54
13 changed files with 296 additions and 95 deletions

View file

@ -1,4 +1,7 @@
import { discoverAuthorizationServerMetadata, discoverOAuthProtectedResourceMetadata } from "@modelcontextprotocol/client";
import {
discoverAuthorizationServerMetadata,
discoverOAuthProtectedResourceMetadata,
} from "@modelcontextprotocol/client";
import type { OAuthClientInformation } from "@modelcontextprotocol/client";
import { getAuthorizationServerUrl } from "./discovery.js";
import type { BaseOAuthClientProvider } from "./providers.js";

View file

@ -1,10 +1,18 @@
import type { OAuthClientInformation, OAuthTokens } from "@modelcontextprotocol/client";
import type {
OAuthClientInformation,
OAuthTokens,
} from "@modelcontextprotocol/client";
import type { EnterpriseManagedAuthIdpConfig } from "../client/types.js";
import { getEmaIdpLoginState, normalizeIdpIssuer } from "./ema/idpSession.js";
import { idpOAuthStorageKey } from "./ema/storage.js";
import { isJwtExpired } from "./ema/jwt.js";
import type { OAuthStorage } from "./storage.js";
import type { AuthProtocol, OAuthClientRegistrationKind, OAuthConnectionState, OAuthFlowState } from "./types.js";
import type {
AuthProtocol,
OAuthClientRegistrationKind,
OAuthConnectionState,
OAuthFlowState,
} from "./types.js";
import { authProtocolFromEnterpriseManaged } from "./types.js";
export interface BuildOAuthConnectionStateParams {
@ -17,9 +25,7 @@ export interface BuildOAuthConnectionStateParams {
}
/** True when persisted tokens include a non-expired access token (JWT exp when parseable). */
export function isAccessTokenUsable(
tokens: OAuthTokens | undefined,
): boolean {
export function isAccessTokenUsable(tokens: OAuthTokens | undefined): boolean {
if (!tokens?.access_token) return false;
return !isJwtExpired(tokens.access_token);
}
@ -27,10 +33,9 @@ export function isAccessTokenUsable(
function resolveClient(
preregistered: OAuthClientInformation | undefined,
dynamic: OAuthClientInformation | undefined,
dynamicRegistrationKind: Extract<
OAuthClientRegistrationKind,
"dcr" | "cimd"
> | undefined,
dynamicRegistrationKind:
| Extract<OAuthClientRegistrationKind, "dcr" | "cimd">
| undefined,
): OAuthConnectionState["client"] | undefined {
if (preregistered?.client_id) {
return {
@ -81,7 +86,7 @@ export async function buildOAuthConnectionState(
? flowTokens
: storedTokens && isAccessTokenUsable(storedTokens)
? storedTokens
: flowTokens ?? storedTokens;
: (flowTokens ?? storedTokens);
const preregistered = await storage.getClientInformation(serverUrl, true);
const dynamic = await storage.getClientInformation(serverUrl);
@ -120,7 +125,9 @@ export async function buildOAuthConnectionState(
const idp = params.enterpriseManagedAuth.idp;
const issuer = normalizeIdpIssuer(idp.issuer);
const idpSession = await getEmaIdpLoginState(storage, issuer);
const idpMetadata = await storage.getServerMetadata(idpOAuthStorageKey(issuer));
const idpMetadata = await storage.getServerMetadata(
idpOAuthStorageKey(issuer),
);
state.ema = {
idpIssuer: issuer,
idpClientId: idp.clientId,

View file

@ -1,5 +1,12 @@
import { discoverAuthorizationServerMetadata, exchangeAuthorization, startAuthorization } from "@modelcontextprotocol/client";
import type { OAuthClientInformation, OAuthMetadata } from "@modelcontextprotocol/client";
import {
discoverAuthorizationServerMetadata,
exchangeAuthorization,
startAuthorization,
} from "@modelcontextprotocol/client";
import type {
OAuthClientInformation,
OAuthMetadata,
} from "@modelcontextprotocol/client";
import { OAuthMetadataSchema } from "@modelcontextprotocol/core";
import type { OAuthStorage } from "../storage.js";
import type { EnterpriseManagedAuthIdpConfig } from "../../client/types.js";
@ -44,7 +51,9 @@ export async function discoverIdpMetadata(
fetchFn,
});
if (!metadata) {
throw new Error(`Failed to discover OIDC metadata for IdP issuer ${issuer}`);
throw new Error(
`Failed to discover OIDC metadata for IdP issuer ${issuer}`,
);
}
return OAuthMetadataSchema.parse(metadata);
}
@ -56,7 +65,11 @@ export async function startIdpOidcAuthorization(params: {
fetchFn?: typeof fetch;
}): Promise<{ authorizationUrl: URL }> {
const issuer = normalizeIdpIssuer(params.idp.issuer);
const metadata = await resolveIdpMetadata(issuer, params.storage, params.fetchFn);
const metadata = await resolveIdpMetadata(
issuer,
params.storage,
params.fetchFn,
);
const clientInformation = idpClientInformation(params.idp);
const storageKey = idpOAuthStorageKey(issuer);
const state = generateOAuthState();

View file

@ -1,4 +1,8 @@
import { discoverAuthorizationServerMetadata, discoverOAuthProtectedResourceMetadata, selectResourceURL } from "@modelcontextprotocol/client";
import {
discoverAuthorizationServerMetadata,
discoverOAuthProtectedResourceMetadata,
selectResourceURL,
} from "@modelcontextprotocol/client";
import type { OAuthClientProvider } from "@modelcontextprotocol/client";
import type { OAuthProtectedResourceMetadata } from "@modelcontextprotocol/client";
import { getAuthorizationServerUrl } from "../discovery.js";

View file

@ -1,5 +1,8 @@
import { selectClientAuthMethod } from "@modelcontextprotocol/client";
import type { OAuthClientInformation, OAuthMetadata } from "@modelcontextprotocol/client";
import type {
OAuthClientInformation,
OAuthMetadata,
} from "@modelcontextprotocol/client";
export async function parseOAuthTokenErrorResponse(
response: Response,

View file

@ -1,5 +1,9 @@
import type { OAuthClientProvider } from "@modelcontextprotocol/client";
import type { OAuthClientInformationMixed, OAuthClientMetadata, OAuthTokens } from "@modelcontextprotocol/client";
import type {
OAuthClientInformationMixed,
OAuthClientMetadata,
OAuthTokens,
} from "@modelcontextprotocol/client";
import type { BaseOAuthClientProvider } from "../providers.js";
import {
refreshEmaResourceTokens,
@ -8,10 +12,7 @@ import {
} from "./emaFlow.js";
import { isJwtExpired } from "./jwt.js";
function isAccessTokenUsable(
tokens: OAuthTokens,
skewMs = 60_000,
): boolean {
function isAccessTokenUsable(tokens: OAuthTokens, skewMs = 60_000): boolean {
if (!tokens.access_token) return false;
return !isJwtExpired(tokens.access_token, skewMs);
}
@ -28,10 +29,7 @@ export class EmaTransportOAuthProvider implements OAuthClientProvider {
private readonly inner: BaseOAuthClientProvider;
private readonly emaConfig: EmaFlowConfig;
constructor(
inner: BaseOAuthClientProvider,
emaConfig: EmaFlowConfig,
) {
constructor(inner: BaseOAuthClientProvider, emaConfig: EmaFlowConfig) {
this.inner = inner;
this.emaConfig = emaConfig;
}

View file

@ -1,5 +1,8 @@
import { discoverAuthorizationServerMetadata } from "@modelcontextprotocol/client";
import type { OAuthClientInformation, OAuthTokens } from "@modelcontextprotocol/client";
import type {
OAuthClientInformation,
OAuthTokens,
} from "@modelcontextprotocol/client";
import { OAuthTokensSchema } from "@modelcontextprotocol/core";
import type { EnterpriseManagedAuthIdpConfig } from "../../client/types.js";
import {

View file

@ -10,7 +10,14 @@
* (the same path v2 `auth()` takes when refresh cannot widen scope).
*/
import { auth as sdkAuth, discoverOAuthServerInfo, isHttpsUrl, registerClient, selectResourceURL, startAuthorization } from "@modelcontextprotocol/client";
import {
auth as sdkAuth,
discoverOAuthServerInfo,
isHttpsUrl,
registerClient,
selectResourceURL,
startAuthorization,
} from "@modelcontextprotocol/client";
import type { OAuthClientProvider } from "@modelcontextprotocol/client";
import type { FetchLike } from "@modelcontextprotocol/client";
/**

View file

@ -1,5 +1,12 @@
import type { OAuthClientInformation, OAuthTokens, OAuthMetadata } from "@modelcontextprotocol/client";
import { OAuthClientInformationSchema, OAuthTokensSchema } from "@modelcontextprotocol/core";
import type {
OAuthClientInformation,
OAuthTokens,
OAuthMetadata,
} from "@modelcontextprotocol/client";
import {
OAuthClientInformationSchema,
OAuthTokensSchema,
} from "@modelcontextprotocol/core";
import type { OAuthStorage } from "./storage.js";
import { type OAuthMemoryStore, type ServerOAuthState } from "./store.js";
import type { OAuthPersistBackend } from "./oauth-persist.js";
@ -173,7 +180,9 @@ export class OAuthStorageBase implements OAuthStorage {
async clearCodeVerifier(serverUrl: string): Promise<void> {
await this.ensureLoaded();
this.memory.getState().setServerState(serverUrl, { codeVerifier: undefined });
this.memory
.getState()
.setServerState(serverUrl, { codeVerifier: undefined });
await this.persist();
}
@ -206,7 +215,9 @@ export class OAuthStorageBase implements OAuthStorage {
metadata: OAuthMetadata,
): Promise<void> {
await this.ensureLoaded();
this.memory.getState().setServerState(serverUrl, { serverMetadata: metadata });
this.memory
.getState()
.setServerState(serverUrl, { serverMetadata: metadata });
await this.persist();
}

View file

@ -2,7 +2,12 @@ import type {
OAuthClientProvider,
OAuthClientInformationContext,
} from "@modelcontextprotocol/client";
import type { OAuthClientInformation, OAuthClientMetadata, OAuthTokens, OAuthMetadata } from "@modelcontextprotocol/client";
import type {
OAuthClientInformation,
OAuthClientMetadata,
OAuthTokens,
OAuthMetadata,
} from "@modelcontextprotocol/client";
import type { OAuthStorage, SaveClientInformationOptions } from "./storage.js";
import { generateOAuthState } from "./utils.js";
@ -210,9 +215,13 @@ export class BaseOAuthClientProvider implements OAuthClientProvider {
options && "registrationKind" in options
? options.registrationKind
: "dcr";
await this.storage.saveClientInformation(this.serverUrl, clientInformation, {
registrationKind,
});
await this.storage.saveClientInformation(
this.serverUrl,
clientInformation,
{
registrationKind,
},
);
}
async saveScope(scope: string | undefined): Promise<void> {

View file

@ -1,4 +1,8 @@
import type { OAuthClientInformation, OAuthTokens, OAuthMetadata } from "@modelcontextprotocol/client";
import type {
OAuthClientInformation,
OAuthTokens,
OAuthMetadata,
} from "@modelcontextprotocol/client";
import type { OAuthClientRegistrationKind } from "./types.js";
/**

View file

@ -3,7 +3,11 @@
* Persisted via `OAuthStorageBase` + `OAuthPersistBackend` backends.
*/
import type { OAuthClientInformation, OAuthTokens, OAuthMetadata } from "@modelcontextprotocol/client";
import type {
OAuthClientInformation,
OAuthTokens,
OAuthMetadata,
} from "@modelcontextprotocol/client";
import type {
IdpSessionState,
OAuthClientRegistrationKind,

View file

@ -1,4 +1,10 @@
import type { OAuthMetadata, OAuthClientInformation, OAuthClientInformationFull, OAuthTokens, OAuthProtectedResourceMetadata } from "@modelcontextprotocol/client";
import type {
OAuthMetadata,
OAuthClientInformation,
OAuthClientInformationFull,
OAuthTokens,
OAuthProtectedResourceMetadata,
} from "@modelcontextprotocol/client";
// OAuth flow steps
export type OAuthStep =