Summary
#2094 introduced JsonObjectInput — an Ace-backed JSON editor (syntax highlighting, line numbers, folding, brace auto-closing, per-line error annotation from Ace's JSON worker) — and wired it into one place: Server Settings → Request Metadata.
Everywhere else the user hand-writes JSON, they are still doing it in a bare box. ace-builds and react-ace already ship in clients/web, so the cost of the remaining sites is wiring and generalization, not a new dependency.
Two things are wanted:
- Use the Ace editor wherever JSON is typed today, in place of the plain
Textarea / Mantine JsonInput.
- Restore the v1 raw-JSON toggle: even a schema the form can render should offer a switch to edit the whole arguments object as JSON directly.
Where JSON is edited or displayed today
1. SchemaJsonField — the "can't render this" fallback (SchemaForm.tsx)
A Mantine JsonInput (formatOnBlur, autosize) is what an object-, array-, or union-typed argument falls through to. It is the single highest-value swap, because four surfaces share it through SchemaForm:
| Consumer |
What it edits |
ToolDetailPanel |
tool arguments |
AppDetailPanel |
MCP App tool arguments |
ElicitationFormPanel |
an elicitation's form fields |
InlineElicitationRequest |
the same, inline |
This box also has the worst history in the repo — #1853, #1856, #1928, #2026 were all about typing in it. It is where the Ace affordances (a located parse error rather than one message for the whole document, folding a nested payload, auto-closed braces) pay off most.
2. A raw-JSON toggle on the whole arguments object (v1 parity)
v1 let you flip a rendered form over to editing the arguments object as JSON. v2 has no such escape hatch, so a value the widgets can't express — or a payload you want to paste in whole — has no route in. Add the toggle at the SchemaForm level so all four consumers above inherit it.
Two existing behaviors it has to respect:
3. ImportServerJsonPanel — "File Contents"
A monospace Textarea holding a pasted or uploaded client config / registry server.json (reached via ServerImportJsonModal). Ace's gutter annotation is directly useful here: the panel already validates the document and reports why it failed, but cannot say where.
4. ExperimentalFeaturesPanel — the raw JSON-RPC "Request" box
A monospace Textarea the user types a whole JSON-RPC request into. This component is not wired into the app yet (it has tests and stories only), so the ask is that it lands on Ace rather than being migrated later.
5. Read-only JSON display — inside ContentViewer
Every read-only JSON payload in the app funnels through one element,
ContentViewer, which dispatches per MIME kind and falls back to a
looksLikeJson heuristic for untyped text. So this is a change to that one
JSON branch, and its eleven consumers inherit it: ProtocolEntry,
NetworkEntry, ToolResultPanel, StructuredOutputPanel,
ResourcePreviewPanel, ResourceLink, ServerCard, TaskCard,
ConnectionInfoContent, MessageBubble, AppsScreen.
Ace replaces the JSON branch only. Markdown, XML, CSS, YAML, CSV, HTML,
PDF and images keep the renderers they have — the user's framing exactly: not a
replacement for ContentViewer, a renderer it selects when the data is JSON.
Three things to get right:
- The copy button is already there and must stay.
ContentViewer overlays
it (CopyOverlay) above whichever renderer it picked, so copyable keeps
working across the swap rather than needing to be re-added.
- Colorization is not the win — folding is. JSON already highlights today
via CodeHighlight (lazily-imported Prism). What Ace adds is code folding,
line numbers and a gutter on a large payload. Decision to record on the
PR: does the Ace read-only mode replace the CodeHighlight JSON path
(letting the Prism json grammar loader be dropped), or do both stacks stay?
Two highlighters for one language is the kind of thing that rots.
wrap={false} callers must not get it. ServerCard passes it for a
fixed-height, single-line, ellipsis-clipped box, and pretty-printed JSON
collapsed onto one line is exactly what that prop is documented not to be
used for. That path keeps the current renderer.
6. Edit and replay (Protocol tab)
Replay is one-click today: ProtocolEntry shows a ReplayButton when
isReplayableProtocolMethod(method), and App.tsx calls
replayProtocolRequest(inspectorClient, method, params, tools) with the
entry's params verbatim.
Add an Edit and replay action beside it, gated on the same predicate, that
opens a modal with the request params in an editable Ace editor (seeded from
the entry), plus Send and Cancel. Send dispatches through the existing
replayProtocolRequest with the edited params — same code path, same
Can't replay failure toast — so this is a new way to supply params, not a
second replay implementation.
Invalid JSON disables Send rather than sending the last valid value: unlike the
Server Settings metadata box this modal has a commit gesture to gate, which
is the same reasoning SchemaJsonField uses.
Explicit non-goals
These look adjacent and are not JSON:
- Key/value row editors that really are string→string: custom headers, stdio
env, OAuth authorization params.
ServerConfigModal's Arguments (one per line) and Environment (KEY=VALUE per line) textareas.
SamplingRequestPanel's Response box, which is the sampled message text.
- The CLI and TUI. Ace is a browser editor; the terminal clients keep their own input.
- Non-JSON
ContentViewer kinds — markdown, XML, CSS, YAML, CSV, HTML, PDF, images. Only the JSON branch changes.
Generalization needed
JsonObjectInput cannot be reused as-is by SchemaForm — the two want different contracts, deliberately:
|
JsonObjectInput (#2094) |
SchemaJsonField |
| Value type |
StrictJsonObject (object only) |
any JsonValue — arrays and scalars included |
| While the draft is invalid |
parent is not told; last valid object stands (no Save button to gate) |
parent is told undefined, and invalidity is reported up via onValidityChange |
So this needs a generalized element — either widened props on JsonObjectInput or a shared Ace-based base with two thin wrappers — rather than a drop-in swap. Both already carry the same draft/value split (typed text is the display source, the parent only ever sees parsed JSON); that part is shared and should end up written once.
Also carry forward from #2094:
- Ace's JSON worker URL registration is module-scoped and idempotent — it must not move into a per-mount effect.
- Ace fires two change events for a replace (remove, then insert), so select-all-and-retype passes through a momentarily empty document.
JsonObjectInput coalesces the pair in a microtask and tracks what it last emitted; any new consumer inherits that hazard.
ariaLabel is load-bearing — Ace names its hidden textarea "Cursor at row N", which is a position readout, not a name.
Acceptance
Sequencing
Six surfaces is a lot for one PR. The shared element comes first; after that the
consumers are independent and can land separately, with the SchemaForm pair
(items 1 + 2) as the highest-value slice and ContentViewer (item 5) as the
widest-blast-radius one.
Summary
#2094 introduced
JsonObjectInput— an Ace-backed JSON editor (syntax highlighting, line numbers, folding, brace auto-closing, per-line error annotation from Ace's JSON worker) — and wired it into one place: Server Settings → Request Metadata.Everywhere else the user hand-writes JSON, they are still doing it in a bare box.
ace-buildsandreact-acealready ship inclients/web, so the cost of the remaining sites is wiring and generalization, not a new dependency.Two things are wanted:
Textarea/ MantineJsonInput.Where JSON is edited or displayed today
1.
SchemaJsonField— the "can't render this" fallback (SchemaForm.tsx)A Mantine
JsonInput(formatOnBlur,autosize) is what an object-, array-, or union-typed argument falls through to. It is the single highest-value swap, because four surfaces share it throughSchemaForm:ToolDetailPanelAppDetailPanelElicitationFormPanelInlineElicitationRequestThis box also has the worst history in the repo — #1853, #1856, #1928, #2026 were all about typing in it. It is where the Ace affordances (a located parse error rather than one message for the whole document, folding a nested payload, auto-closed braces) pay off most.
2. A raw-JSON toggle on the whole arguments object (v1 parity)
v1 let you flip a rendered form over to editing the arguments object as JSON. v2 has no such escape hatch, so a value the widgets can't express — or a payload you want to paste in whole — has no route in. Add the toggle at the
SchemaFormlevel so all four consumers above inherit it.Two existing behaviors it has to respect:
onValidityChangeso Execute / Open App / Submit stay disabled, rather than silently submitting the last valid object.anyOf/oneOfrender no input fields #2123). Switching branches drops the outgoing branch's values; a raw-JSON round trip must not resurrect them.3.
ImportServerJsonPanel— "File Contents"A monospace
Textareaholding a pasted or uploaded client config / registryserver.json(reached viaServerImportJsonModal). Ace's gutter annotation is directly useful here: the panel already validates the document and reports why it failed, but cannot say where.4.
ExperimentalFeaturesPanel— the raw JSON-RPC "Request" boxA monospace
Textareathe user types a whole JSON-RPC request into. This component is not wired into the app yet (it has tests and stories only), so the ask is that it lands on Ace rather than being migrated later.5. Read-only JSON display — inside
ContentViewerEvery read-only JSON payload in the app funnels through one element,
ContentViewer, which dispatches per MIME kind and falls back to alooksLikeJsonheuristic for untyped text. So this is a change to that oneJSON branch, and its eleven consumers inherit it:
ProtocolEntry,NetworkEntry,ToolResultPanel,StructuredOutputPanel,ResourcePreviewPanel,ResourceLink,ServerCard,TaskCard,ConnectionInfoContent,MessageBubble,AppsScreen.Ace replaces the JSON branch only. Markdown, XML, CSS, YAML, CSV, HTML,
PDF and images keep the renderers they have — the user's framing exactly: not a
replacement for
ContentViewer, a renderer it selects when the data is JSON.Three things to get right:
ContentVieweroverlaysit (
CopyOverlay) above whichever renderer it picked, socopyablekeepsworking across the swap rather than needing to be re-added.
via
CodeHighlight(lazily-imported Prism). What Ace adds is code folding,line numbers and a gutter on a large payload. Decision to record on the
PR: does the Ace read-only mode replace the
CodeHighlightJSON path(letting the Prism
jsongrammar loader be dropped), or do both stacks stay?Two highlighters for one language is the kind of thing that rots.
wrap={false}callers must not get it.ServerCardpasses it for afixed-height, single-line, ellipsis-clipped box, and pretty-printed JSON
collapsed onto one line is exactly what that prop is documented not to be
used for. That path keeps the current renderer.
6. Edit and replay (Protocol tab)
Replay is one-click today:
ProtocolEntryshows aReplayButtonwhenisReplayableProtocolMethod(method), andApp.tsxcallsreplayProtocolRequest(inspectorClient, method, params, tools)with theentry's params verbatim.
Add an Edit and replay action beside it, gated on the same predicate, that
opens a modal with the request params in an editable Ace editor (seeded from
the entry), plus Send and Cancel. Send dispatches through the existing
replayProtocolRequestwith the edited params — same code path, sameCan't replayfailure toast — so this is a new way to supply params, not asecond replay implementation.
Invalid JSON disables Send rather than sending the last valid value: unlike the
Server Settings metadata box this modal has a commit gesture to gate, which
is the same reasoning
SchemaJsonFielduses.Explicit non-goals
These look adjacent and are not JSON:
env, OAuth authorization params.ServerConfigModal's Arguments (one per line) and Environment (KEY=VALUEper line) textareas.SamplingRequestPanel's Response box, which is the sampled message text.ContentViewerkinds — markdown, XML, CSS, YAML, CSV, HTML, PDF, images. Only the JSON branch changes.Generalization needed
JsonObjectInputcannot be reused as-is bySchemaForm— the two want different contracts, deliberately:JsonObjectInput(#2094)SchemaJsonFieldStrictJsonObject(object only)JsonValue— arrays and scalars includedundefined, and invalidity is reported up viaonValidityChangeSo this needs a generalized element — either widened props on
JsonObjectInputor a shared Ace-based base with two thin wrappers — rather than a drop-in swap. Both already carry the same draft/value split (typed text is the display source, the parent only ever sees parsed JSON); that part is shared and should end up written once.Also carry forward from #2094:
JsonObjectInputcoalesces the pair in a microtask and tracks what it last emitted; any new consumer inherits that hazard.ariaLabelis load-bearing — Ace names its hidden textarea "Cursor at row N", which is a position readout, not a name.Acceptance
SchemaJsonFieldrenders it, so all fourSchemaFormconsumers get it.SchemaFormfor the whole arguments object, honoring SchemaForm: an invalid JSON draft in an optional field is silently dropped from the submission #2020 draft validity and Tool schemas with a root-levelanyOf/oneOfrender no input fields #2123 variant pruning.ImportServerJsonPanelrenders it.ExperimentalFeaturesPanel's Request box renders it.ContentViewer's JSON branch renders it read-only, keeping the copy overlay and leavingwrap={false}callers alone; the CodeHighlight-vs-Ace decision is stated on the PR.ProtocolEntryopening a modal (Ace editor + Send + Cancel) that dispatches through the existingreplayProtocolRequest, with Send disabled on invalid JSON.Sequencing
Six surfaces is a lot for one PR. The shared element comes first; after that the
consumers are independent and can land separately, with the
SchemaFormpair(items 1 + 2) as the highest-value slice and
ContentViewer(item 5) as thewidest-blast-radius one.