fix: exclude undefined from _meta values, and cover the JSON worker

- `JsonValue` admits `undefined`, so `RequestMetadata` accepted values that do
  not survive serialization: `JSON.stringify` drops an `undefined` object
  member and rewrites an `undefined` array element to `null`. The type
  contradicted the one promise this payload makes. Added `StrictJsonValue` /
  `StrictJsonObject` and used them for the metadata path.

  Deliberately not a change to the repo-wide `JsonValue`: for tool *arguments*
  `undefined` legitimately means "not supplied". The narrowing is sound
  wherever it lands here, since every one of these values comes from
  `JSON.parse`, which cannot produce `undefined`. The CLI's `parseKeyValuePair`
  needed the same tightening — caught by `clients/cli`'s typecheck, which the
  web `tsc -b` does not cover.

- Nothing asserted the JSON worker's annotations, and the wrapper's own "Not
  valid JSON" message comes from our parser rather than from Ace — so a broken
  worker URL would have left every test passing while the gutter diagnostics
  silently disappeared. Added a Storybook play function that waits for a real
  `session.getAnnotations()` error entry, and verified it fails when the worker
  module URL is pointed at a bad path.

- `aria-describedby` referenced only the error, so a supplied `description` was
  never announced and would have been dropped entirely once the draft went
  invalid. Both ids are referenced now, with the description surviving the
  error.

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 18:27:28 -04:00
commit 616dd9c196

View file

@ -24,6 +24,7 @@ import {
parseHeaderPair,
} 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 {
canonicalUrlHost,
isAllInterfacesHost,
@ -473,8 +474,8 @@ function buildHandoff(
function parseKeyValuePair(
value: string,
previous: Record<string, JsonValue> = {},
): Record<string, JsonValue> {
previous: Record<string, StrictJsonValue> = {},
): Record<string, StrictJsonValue> {
const parts = value.split("=");
const key = parts[0];
const val = parts.slice(1).join("=");
@ -485,9 +486,11 @@ function parseKeyValuePair(
);
}
let parsedValue: JsonValue;
// `StrictJsonValue`: `JSON.parse` cannot produce `undefined`, and these values
// become `_meta`, which must reach the wire exactly as written (#1910).
let parsedValue: StrictJsonValue;
try {
parsedValue = JSON.parse(val) as JsonValue;
parsedValue = JSON.parse(val) as StrictJsonValue;
} catch {
parsedValue = val;
}
@ -735,8 +738,8 @@ async function parseArgs(argv?: string[]): Promise<ParseResult> {
promptName?: string;
promptArgs?: Record<string, JsonValue>;
logLevel?: LoggingLevel;
metadata?: Record<string, JsonValue>;
toolMetadata?: Record<string, JsonValue>;
metadata?: Record<string, StrictJsonValue>;
toolMetadata?: Record<string, StrictJsonValue>;
cwd?: string;
transport?: "sse" | "http" | "stdio";
serverUrl?: string;