fix: reject non-finite numbers before they reach _meta

`StrictJsonValue` removed `undefined`, but the type cannot express the rest of
the gap: TypeScript has one `number`, and JSON has no encoding for `NaN` or
`±Infinity`, so `JSON.stringify` rewrites them to `null`.

This is reachable from ordinary input, not a careless caller.
`JSON.parse('{"a":1e400}')` returns `Infinity` on overflow — so text a user
types parses cleanly, satisfies the type, and reaches the server as `null`
while the editor still shows `1e400`.

`isSerializableJson` in `core/json` is the runtime half of `StrictJsonValue`,
applied at both boundaries that turn text into metadata:

- the editor's draft parser, which now reports "Numbers must be finite" inline
  and withholds the value rather than emitting one it cannot send;
- the CLI's `--metadata` / `--tool-metadata` parser, which throws. Silently
  sending `null` would be wrong, and so would falling back to the literal
  string — neither is what was asked for.

Tests cover the validator directly (including that `JSON.stringify` really does
write `null`, which is the reason it matters), the editor parser, and the CLI
flag.

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 19:43:39 -04:00
commit 7f5992081e

View file

@ -25,6 +25,7 @@ import {
} from "@inspector/core/mcp/node/index.js";
import type { JsonValue } from "@inspector/core/mcp/index.js";
import type { StrictJsonValue } from "@inspector/core/json/jsonUtils.js";
import { isSerializableJson } from "@inspector/core/json/jsonUtils.js";
import {
canonicalUrlHost,
isAllInterfacesHost,
@ -492,9 +493,22 @@ function parseKeyValuePair(
try {
parsedValue = JSON.parse(val) as StrictJsonValue;
} catch {
// Not JSON at all — a bare word or an unquoted string. Sent as a string,
// which is what the user plainly meant.
parsedValue = val;
}
// Valid JSON syntax is not the same as sendable JSON: `1e400` parses to
// `Infinity`, which `JSON.stringify` writes as `null`. Rejecting is better
// than accepting the flag and silently transmitting a different value —
// and better than falling back to the literal string, which would also not
// be what was asked for.
if (!isSerializableJson(parsedValue)) {
throw new Error(
`Invalid parameter value: ${value}. Numbers must be finite (a value like 1e400 overflows and cannot be sent).`,
);
}
return { ...previous, [key as string]: parsedValue };
}