feat: allow complex JSON values in request _meta

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>
This commit is contained in:
cliffhall 2026-08-23 12:23:40 -04:00
commit b33532c024
20 changed files with 1004 additions and 495 deletions

View file

@ -76,7 +76,7 @@ describe("runner client auth options", () => {
taskTtl: 60000,
maxFetchRequests: 10,
autoRefreshOnListChanged: false,
metadata: [],
metadata: {},
headers: [],
env: [],
roots: [],
@ -99,7 +99,7 @@ describe("runner client auth options", () => {
taskTtl: 60000,
maxFetchRequests: 10,
autoRefreshOnListChanged: false,
metadata: [],
metadata: {},
headers: [],
env: [],
roots: [],
@ -116,7 +116,7 @@ describe("runner client auth options", () => {
taskTtl: 60000,
maxFetchRequests: 10,
autoRefreshOnListChanged: false,
metadata: [],
metadata: {},
headers: [],
env: [],
roots: [],
@ -135,7 +135,7 @@ describe("runner client auth options", () => {
taskTtl: 60000,
maxFetchRequests: 10,
autoRefreshOnListChanged: false,
metadata: [],
metadata: {},
headers: [],
env: [],
roots: [],
@ -156,7 +156,7 @@ describe("runner client auth options", () => {
taskTtl: 60000,
maxFetchRequests: 10,
autoRefreshOnListChanged: false,
metadata: [],
metadata: {},
headers: [],
env: [],
roots: [],
@ -187,7 +187,7 @@ describe("runner client auth options", () => {
taskTtl: 60000,
maxFetchRequests: 10,
autoRefreshOnListChanged: false,
metadata: [],
metadata: {},
headers: [],
env: [],
roots: [],

View file

@ -854,7 +854,7 @@ describe("InspectorClient peer-handler timing (#1797)", () => {
serverSettings: {
headers: [],
env: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
taskTtl: 0,

View file

@ -30,7 +30,7 @@ describe("headersToServerSettings", () => {
expect(settings?.headers).toEqual([
{ key: "Authorization", value: "Bearer t" },
]);
expect(settings?.metadata).toEqual([]);
expect(settings?.metadata).toEqual({});
expect(settings?.roots).toEqual([]);
});
});

View file

@ -361,7 +361,7 @@ describe("RemoteClientTransport", () => {
const settings = {
headers: [{ key: "X-Tenant", value: "acme" }],
env: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
taskTtl: 0,

View file

@ -11,6 +11,7 @@ import {
mcpConfigToServerEntries,
mergeSecretsIntoStored,
normalizeServerType,
normalizeStoredMetadata,
oauthAuthorizationParamsFromSettings,
oauthEndpointOverridesFromSettings,
serverEntriesToMcpConfig,
@ -240,7 +241,7 @@ describe("serverEntriesToMcpConfig", () => {
});
it("round-trips a populated set of Inspector-extension fields (post-#1358 flat shape)", () => {
// Disk shape: top-level `headers` (Record), `metadata` (pair-array),
// Disk shape: top-level `headers` (Record), `metadata` (JSON object),
// numeric timeouts, nested `oauth`. Round-trip must preserve the on-
// disk shape byte-equivalent so a hand-edited file is stable.
const original: MCPConfig = {
@ -249,7 +250,7 @@ describe("serverEntriesToMcpConfig", () => {
type: "streamable-http",
url: "https://x.test/mcp",
headers: { Authorization: "Bearer xyz" },
metadata: [{ key: "tenant", value: "acme" }],
metadata: { tenant: "acme", limits: { rps: 10 } },
connectionTimeout: 30000,
requestTimeout: 60000,
oauth: {
@ -581,7 +582,7 @@ describe("serverEntriesToMcpConfig", () => {
headers: [{ key: "X-Tenant", value: "acme" }],
// Non-stdio server → empty env mirror in memory (for the form)
env: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
// Absent taskTtl on disk → product default in memory (for the form)
@ -663,7 +664,7 @@ describe("serverEntriesToMcpConfig", () => {
{ key: " ", value: "whitespace" },
],
env: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
taskTtl: 0,
@ -690,7 +691,7 @@ describe("serverEntriesToMcpConfig", () => {
settings: {
headers: [],
env: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
taskTtl: 0,
@ -719,7 +720,7 @@ describe("serverEntriesToMcpConfig", () => {
settings: {
headers: [],
env: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
taskTtl: 0,
@ -758,7 +759,7 @@ describe("serverEntriesToMcpConfig", () => {
settings: {
headers: [],
env: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
taskTtl: 0,
@ -785,7 +786,7 @@ describe("serverEntriesToMcpConfig", () => {
settings: {
headers: [],
env: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
taskTtl: 0,
@ -1163,7 +1164,7 @@ describe("enterpriseManaged oauth settings", () => {
const stored = inspectorSettingsToStoredFields({
headers: [],
env: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
taskTtl: 60000,
@ -1180,7 +1181,7 @@ describe("enterpriseManaged oauth settings", () => {
const stored = inspectorSettingsToStoredFields({
headers: [],
env: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
taskTtl: 60000,
@ -1196,7 +1197,7 @@ describe("oauthOnInsufficientScope (SEP-2350)", () => {
const baseSettings = {
headers: [],
env: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
taskTtl: 60000,
@ -1374,6 +1375,109 @@ describe("oauthOnInsufficientScope (SEP-2350)", () => {
});
});
describe("normalizeStoredMetadata (#1910)", () => {
let warn: ReturnType<typeof vi.spyOn>;
beforeEach(() => {
warn = vi.spyOn(console, "warn").mockImplementation(() => {});
});
afterEach(() => {
warn.mockRestore();
});
it("passes an object through, values of any JSON type intact", () => {
const meta = {
s: "a",
n: 1,
b: false,
z: null,
arr: [1, { k: 2 }],
o: { deep: true },
};
expect(normalizeStoredMetadata(meta)).toEqual(meta);
});
it("copies rather than aliasing the stored object", () => {
// The settings object is edited in place by the form; sharing the
// reference with the parsed catalog would mutate it behind the caller.
const meta = { a: 1 };
const out = normalizeStoredMetadata(meta);
out.b = 2;
expect(meta).toEqual({ a: 1 });
});
it("reads the pre-#1910 pair array so an existing mcp.json keeps working", () => {
expect(
normalizeStoredMetadata([
{ key: "tenant", value: "acme" },
{ key: "n", value: 3 },
]),
).toEqual({ tenant: "acme", n: 3 });
});
it("skips a blank-key legacy pair", () => {
expect(
normalizeStoredMetadata([
{ key: "", value: "orphan" },
{ key: " ", value: "orphan" },
{ key: "kept", value: "yes" },
]),
).toEqual({ kept: "yes" });
});
it("reads a legacy pair with no value as an empty string", () => {
expect(normalizeStoredMetadata([{ key: "k" }])).toEqual({ k: "" });
});
it("drops a malformed legacy entry with a warning, keeping its siblings", () => {
expect(
normalizeStoredMetadata([
"nope",
null,
{ value: "no key" },
{ key: "kept", value: 1 },
]),
).toEqual({ kept: 1 });
expect(warn).toHaveBeenCalledTimes(3);
});
it.each([
["undefined", undefined],
["null", null],
])("reads %s as the empty object without warning", (_label, input) => {
expect(normalizeStoredMetadata(input)).toEqual({});
expect(warn).not.toHaveBeenCalled();
});
it.each([
["a string", "oops"],
["a number", 7],
["a boolean", true],
])("drops %s with a warning", (_label, input) => {
expect(normalizeStoredMetadata(input)).toEqual({});
expect(warn).toHaveBeenCalledTimes(1);
});
it("migrates a legacy file to the object shape on the next write", () => {
// Read side accepts the pair array; write side emits only the object, so
// one round-trip is the migration.
const entries = mcpConfigToServerEntries({
mcpServers: {
legacy: {
type: "streamable-http",
url: "https://x.test/mcp",
metadata: [{ key: "tenant", value: "acme" }] as unknown as Record<
string,
never
>,
},
},
});
expect(entries[0]?.settings?.metadata).toEqual({ tenant: "acme" });
const round = serverEntriesToMcpConfig(entries);
expect(round.mcpServers.legacy?.metadata).toEqual({ tenant: "acme" });
});
});
describe("envRecordToPairs / envPairsToRecord", () => {
it("envRecordToPairs preserves key insertion order", () => {
expect(envRecordToPairs({ B: "2", A: "1" })).toEqual([
@ -1469,7 +1573,7 @@ describe("stdio env / cwd mirroring", () => {
headers: [],
env: [{ key: "API_KEY", value: "secret" }],
cwd: "/srv/app",
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
taskTtl: 60000,

View file

@ -13,7 +13,7 @@ function prompt(name: string): Prompt {
const AUTO_REFRESH_SETTINGS: InspectorServerSettings = {
headers: [],
env: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
taskTtl: 60000,

View file

@ -13,7 +13,7 @@ function template(name: string): ResourceTemplate {
const AUTO_REFRESH_SETTINGS: InspectorServerSettings = {
headers: [],
env: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
taskTtl: 60000,

View file

@ -13,7 +13,7 @@ function resource(uri: string): Resource {
const AUTO_REFRESH_SETTINGS: InspectorServerSettings = {
headers: [],
env: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
taskTtl: 60000,

View file

@ -13,7 +13,7 @@ function tool(name: string): Tool {
const AUTO_REFRESH_SETTINGS: InspectorServerSettings = {
headers: [],
env: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
taskTtl: 60000,

View file

@ -11,7 +11,7 @@ function prompt(name: string): Prompt {
const PAGINATED_SETTINGS: InspectorServerSettings = {
headers: [],
env: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
taskTtl: 60000,

View file

@ -11,7 +11,7 @@ function resource(uri: string): Resource {
const PAGINATED_SETTINGS: InspectorServerSettings = {
headers: [],
env: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
taskTtl: 60000,

View file

@ -11,7 +11,7 @@ function tool(name: string): Tool {
const PAGINATED_SETTINGS: InspectorServerSettings = {
headers: [],
env: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
taskTtl: 60000,

View file

@ -412,7 +412,7 @@ describe("useServers", () => {
await result.current.updateServerSettings("alpha", {
headers: [{ key: "X-Tenant", value: "acme" }],
env: [],
metadata: [{ key: "trace", value: "abc" }],
metadata: { trace: "abc" },
connectionTimeout: 5000,
requestTimeout: 30000,
taskTtl: 30000,
@ -425,7 +425,7 @@ describe("useServers", () => {
expect(result.current.servers[0]?.settings).toEqual({
headers: [{ key: "X-Tenant", value: "acme" }],
env: [],
metadata: [{ key: "trace", value: "abc" }],
metadata: { trace: "abc" },
connectionTimeout: 5000,
requestTimeout: 30000,
taskTtl: 30000,
@ -444,7 +444,7 @@ describe("useServers", () => {
expect(stored.url).toBe("https://x.test/mcp");
expect(stored).not.toHaveProperty("settings");
expect(stored.headers).toEqual({ "X-Tenant": "acme" });
expect(stored.metadata).toEqual([{ key: "trace", value: "abc" }]);
expect(stored.metadata).toEqual({ trace: "abc" });
expect(stored.connectionTimeout).toBe(5000);
expect(stored.requestTimeout).toBe(30000);
expect(stored.taskTtl).toBe(30000);
@ -461,7 +461,7 @@ describe("useServers", () => {
await result.current.updateServerSettings("nonexistent", {
headers: [],
env: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
taskTtl: 0,
@ -511,7 +511,7 @@ describe("useServers", () => {
expect(result.current.servers[0]?.settings).toEqual({
headers: [{ key: "X-Keep", value: "yes" }],
env: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
// Absent taskTtl on disk reads back as the product default for the form.

View file

@ -389,7 +389,7 @@ describe("InspectorClient coverage backfill", () => {
serverSettings: {
headers: [],
env: [],
metadata: [{ key: "x-test", value: "1" }],
metadata: { "x-test": "1" },
connectionTimeout: 0,
requestTimeout: 0,
taskTtl: 0,
@ -655,7 +655,7 @@ describe("InspectorClient coverage backfill", () => {
serverSettings: {
headers: [],
env: [],
metadata: [],
metadata: {},
connectionTimeout: 1,
requestTimeout: 0,
taskTtl: 0,

View file

@ -506,7 +506,7 @@ describe("modern-era negotiation (2026-07-28)", () => {
versionNegotiation: eraToVersionNegotiation("modern"),
serverSettings: {
headers: [],
metadata: [],
metadata: {},
env: [],
connectionTimeout: 0,
requestTimeout: 0,
@ -535,7 +535,7 @@ describe("modern-era negotiation (2026-07-28)", () => {
versionNegotiation: eraToVersionNegotiation("modern"),
serverSettings: {
headers: [],
metadata: [],
metadata: {},
env: [],
connectionTimeout: 0,
requestTimeout: 0,
@ -586,7 +586,7 @@ describe("modern-era negotiation (2026-07-28)", () => {
versionNegotiation: eraToVersionNegotiation("modern"),
serverSettings: {
headers: [],
metadata: [],
metadata: {},
env: [],
connectionTimeout: 0,
requestTimeout: 0,

View file

@ -53,6 +53,7 @@ import type {
MessageEntry,
ConnectionStatus,
FetchRequestEntryBase,
RequestMetadata,
} from "@inspector/core/mcp/types.js";
import type { JsonValue } from "@inspector/core/json/jsonUtils.js";
import type {
@ -164,7 +165,7 @@ function settleInFlight(call: Promise<unknown>): InFlightCall {
/** Get all resources from the client via listResources() (paginates if needed). */
async function getAllResources(
client: InspectorClient,
metadata?: Record<string, string>,
metadata?: RequestMetadata,
): Promise<Resource[]> {
const collected: Resource[] = [];
let cursor: string | undefined;
@ -180,7 +181,7 @@ async function getAllResources(
/** Get all resource templates via listResourceTemplates() (paginates if needed). */
async function getAllResourceTemplates(
client: InspectorClient,
metadata?: Record<string, string>,
metadata?: RequestMetadata,
): Promise<ResourceTemplate[]> {
const collected: ResourceTemplate[] = [];
let cursor: string | undefined;
@ -196,7 +197,7 @@ async function getAllResourceTemplates(
/** Get all prompts via listPrompts() (paginates if needed). */
async function getAllPrompts(
client: InspectorClient,
metadata?: Record<string, string>,
metadata?: RequestMetadata,
): Promise<Prompt[]> {
const collected: Prompt[] = [];
let cursor: string | undefined;
@ -386,7 +387,7 @@ describe("InspectorClient", () => {
serverSettings: {
headers: [],
env: [],
metadata: [],
metadata: {},
connectionTimeout: 50,
requestTimeout: 0,
taskTtl: 0,
@ -756,7 +757,7 @@ describe("InspectorClient", () => {
const initial = {
headers: [],
env: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
taskTtl: 0,
@ -1372,6 +1373,45 @@ describe("InspectorClient", () => {
messageLogState.destroy();
});
it("sends a structured default _meta value as JSON, not as a string (#1910)", async () => {
// The whole point of #1910: a `_meta` value may be an object, an array,
// a number, a boolean or `null`. The Inspector used to type this map as
// `Record<string, string>`, so a nested payload could only be sent by
// stringifying it — which is a different wire message than the one the
// user configured.
const structured = {
trace: { id: "abc123", sampled: true, hops: [1, 2] },
retries: 3,
beta: false,
unset: null,
};
client = new InspectorClient(
{
type: "stdio",
command: serverCommand.command,
args: serverCommand.args,
},
{
environment: { transport: createTransportNode },
defaultMetadata: structured,
},
);
const messageLogState = new MessageLogState(client);
await client.connect();
await client.listTools();
const listToolsReq = messageLogState
.getMessages()
.find(
(m) =>
m.direction === "request" &&
(m.message as { method?: string }).method === "tools/list",
);
expect(listToolsReq).toBeDefined();
expect(metaOf(listToolsReq!)).toMatchObject(structured);
messageLogState.destroy();
});
it("call-time metadata overrides defaultMetadata on key collision", async () => {
client = new InspectorClient(
{
@ -4241,7 +4281,7 @@ describe("InspectorClient", () => {
timestamp: Date;
success: boolean;
error?: string;
metadata?: Record<string, string>;
metadata?: RequestMetadata;
}> = [];
client!.addEventListener(

View file

@ -218,7 +218,7 @@ describe("Transport", () => {
{ key: "", value: "ignored-empty-key" },
],
env: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
taskTtl: 0,
@ -267,7 +267,7 @@ describe("Transport", () => {
const baseSettings: InspectorServerSettings = {
headers: [],
env: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
taskTtl: 0,
@ -305,7 +305,7 @@ describe("Transport", () => {
const settings: InspectorServerSettings = {
headers: [{ key: "X-Tenant", value: "acme" }],
env: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
taskTtl: 0,
@ -350,7 +350,7 @@ describe("Transport", () => {
const settings: InspectorServerSettings = {
headers: [],
env: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
taskTtl: 0,

View file

@ -457,7 +457,7 @@ describe("server.ts supplemental coverage", () => {
const base = {
headers: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
};
@ -891,7 +891,7 @@ describe("server.ts supplemental coverage", () => {
config: { type: "streamable-http", url: "https://x.test/mcp" },
settings: {
headers: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
oauthClientSecret: "shh",
@ -1039,7 +1039,7 @@ describe("server.ts supplemental coverage", () => {
config: { type: "streamable-http", url: "https://x.test/mcp" },
settings: {
headers: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
oauthClientSecret: "new-secret",

View file

@ -425,10 +425,11 @@ describe("/api/servers routes", () => {
// Wire envelope unchanged from #1353: pair-array headers, flat
// oauth* fields. Backend splats these into the flat disk shape:
// object headers, nested oauth, plus the inspector-only fields
// at the top level.
// at the top level. Metadata is the exception — it crosses as a
// JSON object, since a `_meta` value may be any JSON (#1910).
settings: {
headers: [{ key: "Authorization", value: "Bearer xyz" }],
metadata: [{ key: "tenant", value: "acme" }],
metadata: { tenant: "acme", limits: { rps: 10 } },
connectionTimeout: 30000,
requestTimeout: 60000,
oauthClientId: "client-abc",
@ -442,7 +443,7 @@ describe("/api/servers routes", () => {
// Disk shape: flat, no `settings` wrapper, object headers, nested oauth.
expect(stored).not.toHaveProperty("settings");
expect(stored.headers).toEqual({ Authorization: "Bearer xyz" });
expect(stored.metadata).toEqual([{ key: "tenant", value: "acme" }]);
expect(stored.metadata).toEqual({ tenant: "acme", limits: { rps: 10 } });
expect(stored.connectionTimeout).toBe(30000);
expect(stored.requestTimeout).toBe(60000);
expect(stored.oauth).toEqual({
@ -462,7 +463,7 @@ describe("/api/servers routes", () => {
config: { type: "streamable-http", url: "https://x.test/mcp" },
settings: {
headers: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
oauthAuthorizationParams: [
@ -488,7 +489,7 @@ describe("/api/servers routes", () => {
config: { type: "streamable-http", url: "https://x.test/mcp" },
settings: {
headers: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
oauthAuthorizationParams: { kc_idp_hint: "corp" },
@ -509,7 +510,7 @@ describe("/api/servers routes", () => {
config: { type: "streamable-http", url: "https://x.test/mcp" },
settings: {
headers: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
oauthAuthorizationUrl: "https://staging.test/authorize",
@ -534,7 +535,7 @@ describe("/api/servers routes", () => {
config: { type: "streamable-http", url: "https://x.test/mcp" },
settings: {
headers: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
oauthAuthorizationUrl: "",
@ -557,7 +558,7 @@ describe("/api/servers routes", () => {
config: { type: "streamable-http", url: "https://x.test/mcp" },
settings: {
headers: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
oauthTokenUrl: 42,
@ -583,7 +584,7 @@ describe("/api/servers routes", () => {
config: { type: "streamable-http", url: "https://x.test/mcp" },
settings: {
headers: [{ key: "X-Tenant", value: "acme" }],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 45000,
},
@ -617,7 +618,7 @@ describe("/api/servers routes", () => {
body: JSON.stringify({
settings: {
headers: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
autoRefreshOnListChanged: true,
@ -649,7 +650,7 @@ describe("/api/servers routes", () => {
body: JSON.stringify({
settings: {
headers: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
autoRefreshOnListChanged: false,
@ -671,7 +672,7 @@ describe("/api/servers routes", () => {
config: { type: "stdio", command: "node" },
settings: {
headers: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
autoRefreshOnListChanged: "yes",
@ -696,7 +697,7 @@ describe("/api/servers routes", () => {
body: JSON.stringify({
settings: {
headers: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
maxFetchRequests: 5000,
@ -724,7 +725,7 @@ describe("/api/servers routes", () => {
body: JSON.stringify({
settings: {
headers: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
maxFetchRequests: 0,
@ -756,7 +757,7 @@ describe("/api/servers routes", () => {
body: JSON.stringify({
settings: {
headers: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
maxFetchRequests: 1000,
@ -778,7 +779,7 @@ describe("/api/servers routes", () => {
config: { type: "stdio", command: "node" },
settings: {
headers: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
maxFetchRequests: -5,
@ -884,7 +885,7 @@ describe("/api/servers routes", () => {
// headers should be an array of {key, value}; "oops" is a string.
settings: {
headers: "oops",
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
},
@ -915,7 +916,7 @@ describe("/api/servers routes", () => {
// write lock and apply only the settings patch.
settings: {
headers: [{ key: "X-Tenant", value: "acme" }],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
},
@ -957,7 +958,7 @@ describe("/api/servers routes", () => {
config: { type: "streamable-http", url: "https://x.test/mcp" },
settings: {
headers: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
oauthClientId: "",
@ -983,7 +984,7 @@ describe("/api/servers routes", () => {
config: { type: "stdio", command: "node" },
settings: {
headers: [{ key: "X-A", value: "1" }],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
// Unknown stowaway — must not survive the validator.
@ -1101,7 +1102,7 @@ describe("/api/servers routes", () => {
url: "https://x.test/mcp",
settings: {
headers: [{ key: "X-Tenant", value: "acme" }],
metadata: [],
metadata: {},
connectionTimeout: 30000,
requestTimeout: 0,
oauthClientId: "client-abc",
@ -1207,7 +1208,7 @@ describe("/api/servers routes", () => {
config: { type: "stdio", command: "node" },
settings: {
headers: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
roots: [
@ -1255,7 +1256,7 @@ describe("/api/servers routes", () => {
config: { type: "stdio", command: "node" },
settings: {
headers: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
roots: [{ uri: " " }],
@ -1285,7 +1286,7 @@ describe("/api/servers routes", () => {
config: { type: "stdio", command: "node" },
settings: {
headers: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
// uri must be a string.
@ -1336,7 +1337,7 @@ describe("/api/servers routes", () => {
{ key: "Authorization", value: "Bearer the-token" },
{ key: "X-Tenant", value: "acme" },
],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
},
@ -1368,7 +1369,7 @@ describe("/api/servers routes", () => {
body: JSON.stringify({
settings: {
headers: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
env: [{ key: "API_KEY", value: "abc-123" }],
@ -1416,7 +1417,7 @@ describe("/api/servers routes", () => {
body: JSON.stringify({
settings: {
headers: [],
metadata: [],
metadata: {},
connectionTimeout: 5000,
requestTimeout: 0,
},
@ -1484,7 +1485,7 @@ describe("/api/servers routes", () => {
body: JSON.stringify({
settings: {
headers: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
env: [],
@ -1516,7 +1517,7 @@ describe("/api/servers routes", () => {
body: JSON.stringify({
settings: {
headers: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
env: [
@ -1587,7 +1588,7 @@ describe("/api/servers routes", () => {
body: JSON.stringify({
settings: {
headers: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
env: "nope",
@ -1614,7 +1615,7 @@ describe("/api/servers routes", () => {
body: JSON.stringify({
settings: {
headers: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
env: [],
@ -1642,7 +1643,7 @@ describe("/api/servers routes", () => {
body: JSON.stringify({
settings: {
headers: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
env: [],
@ -1675,7 +1676,7 @@ describe("/api/servers routes", () => {
body: JSON.stringify({
settings: {
headers: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
env: [{ key: "API_KEY", value: "abc" }],
@ -1775,7 +1776,7 @@ describe("/api/servers routes", () => {
config: { type: "streamable-http", url: "https://x.test/mcp" },
settings: {
headers: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
oauthClientId: "cid",
@ -1935,7 +1936,7 @@ describe("/api/servers routes", () => {
body: JSON.stringify({
settings: {
headers: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
oauthClientId: "cid",
@ -2327,7 +2328,7 @@ describe("/api/servers routes", () => {
config: { type: "streamable-http", url: "https://x.test/mcp" },
settings: {
headers: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
oauthClientId: "cid",
@ -2361,7 +2362,7 @@ describe("/api/servers routes", () => {
config: { type: "streamable-http", url: "https://x.test/mcp" },
settings: {
headers: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
oauthClientId: "cid",
@ -2658,7 +2659,7 @@ describe("a config-only PUT must not delete stored secrets (#2083)", () => {
settings: {
headers: [],
env: [],
metadata: [],
metadata: {},
connectionTimeout: 30000,
requestTimeout: 60000,
taskTtl: 60000,
@ -2726,7 +2727,7 @@ describe("a config-only PUT must not delete stored secrets (#2083)", () => {
settings: {
headers: [],
env: [],
metadata: [],
metadata: {},
connectionTimeout: 30000,
requestTimeout: 60000,
taskTtl: 60000,
@ -2879,7 +2880,7 @@ describe("plaintext migration against a session-scoped store (#1950)", () => {
settings: {
headers: [],
env: [],
metadata: [],
metadata: {},
connectionTimeout: 45000,
requestTimeout: 60000,
taskTtl: 60000,
@ -2927,7 +2928,7 @@ describe("plaintext migration against a session-scoped store (#1950)", () => {
settings: {
headers: [],
env: [],
metadata: [],
metadata: {},
connectionTimeout: 30000,
requestTimeout: 60000,
taskTtl: 60000,

View file

@ -412,7 +412,7 @@ describe("Remote transport e2e", () => {
{ key: "X-Trace", value: "abc123" },
],
env: [],
metadata: [],
metadata: {},
connectionTimeout: 0,
requestTimeout: 0,
taskTtl: 0,