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

@ -880,6 +880,37 @@ describe("Metadata Tests", () => {
}
});
it.each([
["an overflowing literal", "n=1e400"],
["a negative overflow", "n=-1e400"],
["one nested in an object", 'o={"a":1e400}'],
])("rejects %s rather than sending null", async (_label, pair) => {
// `JSON.parse` accepts these and yields ±Infinity, which
// `JSON.stringify` writes as `null` — so accepting the flag would
// transmit a value the user did not ask for.
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
await server.start();
const result = await runCli([
server.url,
"--cli",
"--method",
"tools/list",
"--metadata",
pair,
"--transport",
"http",
]);
expect(result.exitCode).not.toBe(0);
expect(`${result.stderr}${result.stdout}`).toMatch(/finite/i);
} finally {
await server.stop();
}
});
it("should handle metadata parsing validation", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),