* core: refactor serverList.ts object↔Record casts into typed helper (#1690) Remove the four pre-existing `as unknown as Record<string, unknown>` / `as unknown as MCPServerConfig` casts in core/mcp/serverList.ts: - stripInspectorFields: rewrite as clone + `delete` over INSPECTOR_FIELD_KEYS. Every Inspector-extension key is optional on StoredMCPServer, so the result is still (a subtype of) MCPServerConfig with no cast. - oauth-strip in extractSecretsFromStored: `oauth` is optional, so a plain `delete stripped.oauth` is legal — drop the cast. - mcpConfigToServerEntries: route the one genuinely-needed widening through a single documented `toRecord(value: object)` helper. Taking `object` makes the single `as Record<string, unknown>` legal (no TS2352), so the double cast disappears from the call site. Net: zero `as unknown as` in serverList.ts; the one remaining structural cast lives inside the audited toRecord helper. Behavior-neutral — existing coverage (79 serverList tests) unchanged; full `npm run ci` passes. Closes #1690 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * core+web+test-servers: eliminate/justify remaining `as unknown as` casts (#1690) Codebase-wide follow-on to the serverList refactor: audit every `as unknown as` double cast outside tests and either remove it or ensure it carries a justifying comment (the review bar is "no *unjustified* double cast"). Removed (widened via a shared helper or narrowed to a single cast): - Promote the serverList `toRecord(value: object)` widening helper to core/json/jsonUtils.ts and reuse it at core/mcp/node/config.ts (normalize loop), core/mcp/remote/node/server.ts (pino level dispatch), and clients/web/src/App.tsx (window global lookup). - inspectorClient.rawWireRequest: type the frame as `JSONRPCRequest` and narrow only `params` with a single structural cast (the SDK's `_meta`-typed params is what blocked direct assignment). - toolOutputValidation: SDK `outputSchema` → `JsonSchemaType` is a single cast. - test-server-fixtures: the optional `task` handle is reachable with a single cast (target field is optional). Justified in place (genuinely unavoidable — private SDK internals, partial interface stubs, third-party/nominal bridges): the inspectorClient private-map accesses, resourceContext + tokenAuthProvider partial `OAuthClientProvider` stubs, the ext-apps peer bridge, modern/legacy `_requestHandlers` bypasses, and the happy-dom `matchMedia` test mock. Behavior-neutral. Full `npm run ci` passes (validate, ≥90 coverage gate, smoke, Storybook). Refs #1690 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * address @claude review nits: consistent cast justifications + drop reflow noise (#1690) - test-servers/src/modern-tasks.ts: add an inline justification above the `_requestHandlers` double cast, mirroring the sibling in composable-test-server.ts (was leaning on the function JSDoc only). - Annotate the partial-`AppBridge` mock double casts in the story fixtures (AppsScreen ×2, AppRenderer, InspectorView) so no non-test-file double cast is unjustified. - inspectorClient.ts: revert unrelated Prettier reflow hunks (protocolVersion, task-content array, timeout-reject wrap, reconnect guard) so the diff is the single rawWireRequest cast change only. Behavior-neutral (comment/format-only). `npm run ci` green apart from a known flaky redaction test whose assertion collided with a random request id. Refs #1690 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * actually revert the reflow noise this time (#1690) The reflow reverts in cc187ea were silently undone by a `prettier --write` I ran on the same file right after, so the four Prettier-only hunks from 6a3afa4a were still in the diff (caught in re-review). Revert them for real now — and without re-running Prettier on the file: - core/mcp/inspectorClient.ts: restore the protocolVersion line, task-content array, timeout-reject call, and reconnect guard to their origin/v2/main form, so the file's diff is only the single rawWireRequest cast change. - test-servers/src/modern-tasks.ts: restore the ModernTaskStatus union to its one-line form (the leading-`|` reflow was also Prettier churn), leaving only the new `_requestHandlers` justification comment. Formatting-only; `npm run validate` green. Refs #1690 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
113 lines
3.3 KiB
TypeScript
113 lines
3.3 KiB
TypeScript
import type { OAuthClientProvider } from "@modelcontextprotocol/client";
|
|
import type {
|
|
OAuthClientInformation,
|
|
OAuthClientMetadata,
|
|
OAuthTokens,
|
|
} from "@modelcontextprotocol/client";
|
|
import type { RemoteAuthState } from "../types.js";
|
|
|
|
const REMOTE_OAUTH_STUB_METADATA: OAuthClientMetadata = {
|
|
redirect_uris: [],
|
|
scope: "",
|
|
};
|
|
|
|
export interface RemoteAuthProviderHandle {
|
|
provider: OAuthClientProvider;
|
|
setAuthState: (authState: RemoteAuthState) => void;
|
|
getAuthState: () => RemoteAuthState;
|
|
}
|
|
|
|
function cloneAuthState(state: RemoteAuthState): RemoteAuthState {
|
|
return {
|
|
...(state.oauthTokens && {
|
|
oauthTokens: { ...state.oauthTokens },
|
|
}),
|
|
...(state.oauthClient && {
|
|
oauthClient: { ...state.oauthClient },
|
|
}),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Mutable OAuth provider for the remote backend MCP transport.
|
|
* Browser (or CLI) owns interactive OAuth; this injects Bearer tokens and can
|
|
* be hot-updated via {@link RemoteAuthProviderHandle.setAuthState}.
|
|
*/
|
|
export function createRemoteAuthProvider(
|
|
initialAuthState?: RemoteAuthState,
|
|
): RemoteAuthProviderHandle | undefined {
|
|
if (!initialAuthState?.oauthTokens && !initialAuthState?.oauthClient) {
|
|
return undefined;
|
|
}
|
|
|
|
let authState = cloneAuthState(initialAuthState);
|
|
|
|
const provider = {
|
|
get clientMetadata(): OAuthClientMetadata {
|
|
return REMOTE_OAUTH_STUB_METADATA;
|
|
},
|
|
get redirectUrl(): string {
|
|
return "";
|
|
},
|
|
async tokens(): Promise<OAuthTokens | undefined> {
|
|
return authState.oauthTokens as OAuthTokens | undefined;
|
|
},
|
|
async clientInformation(): Promise<OAuthClientInformation | undefined> {
|
|
if (!authState.oauthClient?.client_id) {
|
|
return undefined;
|
|
}
|
|
return {
|
|
client_id: authState.oauthClient.client_id,
|
|
...(authState.oauthClient.client_secret && {
|
|
client_secret: authState.oauthClient.client_secret,
|
|
}),
|
|
};
|
|
},
|
|
async saveTokens(tokens: OAuthTokens) {
|
|
authState = {
|
|
...authState,
|
|
oauthTokens: {
|
|
access_token: tokens.access_token,
|
|
token_type: tokens.token_type,
|
|
expires_in: tokens.expires_in,
|
|
refresh_token: tokens.refresh_token,
|
|
scope: tokens.scope,
|
|
id_token: tokens.id_token,
|
|
},
|
|
};
|
|
},
|
|
codeVerifier() {
|
|
return undefined;
|
|
},
|
|
async saveCodeVerifier() {
|
|
// No-op
|
|
},
|
|
clear() {
|
|
authState = {};
|
|
},
|
|
async redirectToAuthorization() {
|
|
throw new Error(
|
|
"OAuth re-authorization must be performed in the Inspector web client (remote server cannot complete OAuth flows)",
|
|
);
|
|
},
|
|
state() {
|
|
return "";
|
|
},
|
|
// This provider implements only the members the remote backend transport
|
|
// exercises (token get/save, client info, non-interactive redirect); the
|
|
// interactive-only members the SDK never calls on the server side are stubbed
|
|
// with narrower shapes (e.g. `codeVerifier()` returns `undefined`, not the
|
|
// interface's `string | Promise<string>`), so it can't be assigned to the
|
|
// full `OAuthClientProvider` without the double cast.
|
|
} as unknown as OAuthClientProvider;
|
|
|
|
return {
|
|
provider,
|
|
setAuthState(next: RemoteAuthState) {
|
|
authState = cloneAuthState(next);
|
|
},
|
|
getAuthState() {
|
|
return cloneAuthState(authState);
|
|
},
|
|
};
|
|
}
|