fix(web): read the literal, not the parsed number; project cursors and tool args

Copilot's eighth pass, three comments, all real.

- The precision check now reads the **source text**. Inferring the literal from
  `String(value)` was wrong in both directions: it accepted every full-form
  integer at or above 1e21 (`1000000000000000000001` parses to `1e21` and is
  written back as `1e+21`), and refused `2^54`, which is written out in full
  and exactly representable. `hasImpreciseIntegerLiteral` scans the draft for
  numeric tokens — skipping strings, so a digit-string value is untouched — and
  compares `BigInt(literal)` against `BigInt(Number(literal))`, which is exact.

- `""` is now reported as a surviving cursor for `tools/list` only. It builds
  its params with `cursor !== undefined` and carries the empty string
  deliberately; the other four list adapters use a truthiness check and drop
  it, so the editor showed `{"cursor":""}` while `{}` went out. The asymmetry
  looks like a latent bug in those four, but this function's job is to describe
  what the dispatch does.

- `callTool` runs every string-valued argument through `convertToolParameters`,
  so `{"count":"2"}` against a numeric schema is sent as `{"count":2}`.
  `reshapedReplayParam` now takes the tool and detects that by running the same
  conversion and comparing, rather than restating its rules — the two cannot
  drift. `tools` is threaded to `ProtocolEntry` for it, optional throughout: an
  unknown tool skips the check rather than blocking a replay.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UL9retfvAXRgvi6EWk4SY4
Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-08-27 04:56:22 -04:00
commit fcdc65b289

View file

@ -1,4 +1,5 @@
import { useCallback, useMemo } from "react";
import type { Tool } from "@modelcontextprotocol/client";
import { Card, Flex, Stack } from "@mantine/core";
import type { ReplayParamsOverride } from "../../../lib/protocolReplay";
import type { ProtocolEra } from "@modelcontextprotocol/client";
@ -24,6 +25,12 @@ export interface ProtocolScreenProps {
onClearSection: (section: "pinned" | "history") => void;
onExportSection: (section: "pinned" | "history") => void;
onReplay: (id: string, overrideParams?: ReplayParamsOverride) => void;
/**
* The connected server's tools. Forwarded to each entry only so an edited
* `tools/call` replay can tell whether an argument would be coerced by the
* schema on the way out (#2151).
*/
tools?: Tool[];
onTogglePin: (id: string) => void;
sortDirection: SortDirection;
onSortChange: (next: SortDirection) => void;
@ -70,6 +77,7 @@ const SidebarCard = Card.withProps({
});
export function ProtocolScreen({
tools,
entries,
pinnedIds,
protocolEra,
@ -155,6 +163,7 @@ export function ProtocolScreen({
onClearSection={onClearSection}
onExportSection={onExportSection}
onReplay={onReplay}
tools={tools}
onTogglePin={onTogglePin}
sortDirection={sortDirection}
onSortChange={onSortChange}