Closes #1910 `_meta` was typed `Record<string, string>` end to end in the Inspector — on disk as a `{ key, value }[]` pair array, in the Server Settings form as key/value rows, and in every request verb's per-call metadata parameter. Nothing in the MCP spec restricts a `_meta` value to a string, and the SDK models the field as a `z.looseObject`, so the restriction was ours alone. Widen the whole path to `RequestMetadata` (`Record<string, JsonValue>`): - Per-server default metadata is a JSON **object** on disk and in memory. The pre-#1910 pair array is still read (`normalizeStoredMetadata`) so an existing `mcp.json` keeps working; it is never written back, so a save migrates the entry. - The Server Settings → Request Metadata section is a JSON editor (`JsonObjectInput`) instead of key/value rows — rows cannot express an object value. It keeps the typed text as the display source and emits only parsed JSON, the same draft split `SchemaJsonField` uses, so the box stays typeable; invalid text is flagged inline and not applied. - The CLI stops flattening `--metadata` / `--tool-metadata` through `metaValueToString`. Those values were already JSON-parsed, so `trace={"id":"abc"}` used to reach the wire as the *string* `{"id":"abc"}` and now reaches it as an object. - `servers/show` redaction replaces a sensitive key's whole value rather than a string, so a structured value cannot leak through its members. - `progressTokenOf` narrows the reserved `progressToken` key back to `string | number` before it reaches the SDK; any other JSON still travels inside `_meta` but wires up no progress callback. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0177mPHAdECD18nLLCTwR5rh Signed-off-by: cliffhall <cliff@futurescale.com>
75 lines
3 KiB
TypeScript
75 lines
3 KiB
TypeScript
import { vi } from "vitest";
|
|
import {
|
|
DEFAULT_MAX_FETCH_REQUESTS,
|
|
DEFAULT_TASK_TTL_MS,
|
|
} from "@inspector/core/mcp/types.js";
|
|
import type { InspectorServerSettings } from "@inspector/core/mcp/types.js";
|
|
import type { CliOAuthClient } from "../../src/cliOAuth.js";
|
|
|
|
/**
|
|
* Typed mock factories for the CLI OAuth tests. They exist so a test can supply
|
|
* only the surface a given code path exercises while `tsc` still sees a
|
|
* complete `CliOAuthClient` / `InspectorServerSettings` — avoiding a spray of
|
|
* `as unknown as` casts (see #1791 and the AGENTS.md `as`-cast policy).
|
|
*/
|
|
|
|
/**
|
|
* A full {@link CliOAuthClient} whose every method is a `vi.fn()` resolving to a
|
|
* benign value. Pass `overrides` to swap in the method(s) a test drives (e.g. a
|
|
* `connect` that rejects with `AuthRecoveryRequiredError`).
|
|
*/
|
|
export function makeFakeCliOAuthClient(
|
|
overrides: Partial<CliOAuthClient> = {},
|
|
): CliOAuthClient {
|
|
// The defaults are typed against the real CliOAuthClient signatures (not bare
|
|
// vi.fn()s) so a default with a wrong implementation/return would be rejected,
|
|
// the same way the tui App spies are typed. Note this does NOT extend to
|
|
// `overrides`: a caller passing a bare `vi.fn()` gets `Mock<(...args) => any>`,
|
|
// assignable to any member, so an override's implementation is unchecked — the
|
|
// factory guarantees the object's shape, not each override's signature. (And
|
|
// since `exactOptionalPropertyTypes` is off, `Partial<…>` even admits an
|
|
// explicit `undefined` for a required member.)
|
|
return {
|
|
connect: vi.fn<CliOAuthClient["connect"]>().mockResolvedValue(undefined),
|
|
disconnect: vi
|
|
.fn<CliOAuthClient["disconnect"]>()
|
|
.mockResolvedValue(undefined),
|
|
authenticate: vi
|
|
.fn<CliOAuthClient["authenticate"]>()
|
|
.mockResolvedValue(undefined),
|
|
beginInteractiveAuthorization: vi
|
|
.fn<CliOAuthClient["beginInteractiveAuthorization"]>()
|
|
.mockResolvedValue(undefined),
|
|
completeOAuthFlow: vi
|
|
.fn<CliOAuthClient["completeOAuthFlow"]>()
|
|
.mockResolvedValue(undefined),
|
|
checkAuthChallengeSatisfied: vi
|
|
.fn<CliOAuthClient["checkAuthChallengeSatisfied"]>()
|
|
.mockResolvedValue(false),
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* A full {@link InspectorServerSettings} with representative defaults for its
|
|
* required fields (the timeouts at 0 = "SDK default", `taskTtl` /
|
|
* `maxFetchRequests` at their product defaults, empty lists elsewhere). Pass
|
|
* `overrides` for the field(s) under test (e.g. `{ enterpriseManaged: true }`);
|
|
* as a `Partial<…>` with `exactOptionalPropertyTypes` off it also admits an
|
|
* explicit `undefined` for a required field, so don't rely on it to reject one.
|
|
*/
|
|
export function makeFakeServerSettings(
|
|
overrides: Partial<InspectorServerSettings> = {},
|
|
): InspectorServerSettings {
|
|
return {
|
|
headers: [],
|
|
metadata: {},
|
|
env: [],
|
|
connectionTimeout: 0,
|
|
requestTimeout: 0,
|
|
taskTtl: DEFAULT_TASK_TTL_MS,
|
|
maxFetchRequests: DEFAULT_MAX_FETCH_REQUESTS,
|
|
roots: [],
|
|
...overrides,
|
|
};
|
|
}
|