MRTR: drive input_required manually to keep pending-request UX (#1704)#1719
Conversation
Follow-up to #1627/#1705. Instead of letting the SDK auto-fulfil the modern (2026-07-28) MRTR retry loop, InspectorClient now drives it itself so the Inspector keeps its pending-request UX, owns the loop, and shows era-accurate semantics. Core (core/mcp/inspectorClient.ts): - Construct the SDK Client with `inputRequired: { autoFulfill: false }` (unconditional; a no-op on legacy, which never returns input_required). - New `requestWithInputRequired()` driver: issues the request with `allowInputRequired` + `withInputRequired(schema)`, and while the result is `input_required`, fulfils each embedded request through the SAME pending sampling/elicitation plumbing, then retries the original call with `inputResponses` + the echoed `requestState` on a fresh id, bounded by MRTR_MAX_ROUNDS. - Route tools/call, prompts/get, and resources/read through the driver. - Embedded roots/list is auto-answered from configured roots (no pending UX); decline/cancel is echoed to the server (not treated as an abort); a tool-call abort mid-round settles the pending request and surfaces ToolCallCancelledError. - Extract shared `enqueuePendingSample`/`enqueuePendingElicitation` helpers used by both the legacy inbound handlers and the driver; add a per-request `origin` ("server-request" | "input-required") to the pending wrappers. Web UI: - New `MrtrOriginNote` element shows an "input_required — your answer returns as a retry" note on a modern MRTR round; legacy panels are unchanged. - Thread `origin` App -> PendingClientRequestModal -> the sampling/elicitation panels (and the inline panels). Test servers: new MRTR presets (multi-round, roots, sampling, loop, edge-case param shaping). Integration tests cover the manual round-trip (asserting the pending UI pauses and the retry carries inputResponses + requestState on a new id), multi-round, roots auto-answer, sampling, decline-echoed, maxRounds bound, abort mid-round, and requestState-only/inputRequests-only param shaping. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
|
@claude review |
|
Claude finished @cliffhall's task in 2m 46s —— View job Review: MRTR manual driver (#1719)I read the core driver ( Detailed findingsCorrectness — no bugs foundThe driver logic is sound:
1.
|
) Address review nits on PR #1719 (comment-only, no behavior change): - enqueuePendingSample docstring: sampling has no decline/cancel action; the panel either sends a result (resolve) or Rejects (reject → fails the call). The shared "declined/cancelled resolves" wording was accurate only for the elicitation case. - callToolWithRetries abort comment: note the between-rounds case — when the abort lands while an MRTR round awaits an embedded pending request there is no in-flight SDK request, so nothing is sent on the wire; the outcome (ToolCallCancelledError, no failed-call record) is identical. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
|
Thanks for the thorough review! Addressed the two doc/comment nits in 2ce620b (comment-only, no behavior change): 1 — 2 — abort comment assumes an in-flight SDK request. Fixed. Expanded the 3 — abort listeners accumulate on the shared signal across rounds. Left as-is, per your "no functional impact" note. On a successful multi-round call the earlier rounds' Nits (no change): the missing abort signal on |
|
@claude review |
|
Claude finished @cliffhall's task in 1m 40s —— View job Re-review: MRTR manual driver (#1704)Re-read the full changed set at the current head ( Detailed findingsFixes from
|
…1704) A single modern-era streamable-HTTP server (port 3111) bundling all six MRTR presets — mrtr_confirm, mrtr_two_step, mrtr_sample, mrtr_roots, mrtr_edge, mrtr_loop — for manual testing of the manual-driving pending-request UX across single-round, multi-round, sampling, roots, param-shaping, and maxRounds paths. Document it in the README alongside modern-mrtr-http.json. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
Manual smoke test (Playwright + composable modern server)Verified the manual MRTR driving end to end in the browser against a real composable server running the modern (2026-07-28) leg. Setup
Steps exercised
Screenshots1. Pending Elicitation modal with the 2. Completed retry + grouped MRTR conversation ("MRTR complete", Protocol view): 3. Sampling panel showing the same era note (mrtr_sample): 4. Two-step multi-round (round 1 → round 2 → complete):
(Screenshots captured locally via Playwright; drop the PNGs in place of the placeholders above.) |




Closes #1704
Follow-up to #1627/#1705. Instead of letting the SDK auto-fulfil the modern (2026-07-28) MRTR retry loop,
InspectorClientnow drives it itself so the Inspector keeps its pending-request UX, owns the loop, and shows era-accurate semantics. Applies to all three clients (web/cli/tui) since the driver lives incore/mcp/inspectorClient.ts.What changed
Core (
core/mcp/inspectorClient.ts)ClientwithinputRequired: { autoFulfill: false }— unconditional and safe (a no-op on legacy, which never returnsinput_required; the negotiated era isn't known at construction time).requestWithInputRequired()driver: issues the request withallowInputRequired+withInputRequired(schema), and while the result isinput_required, fulfils each embedded request through the same pending sampling/elicitation plumbing, then retries the original call withinputResponses+ the echoedrequestStateon a fresh id, bounded byMRTR_MAX_ROUNDS.tools/call,prompts/get, andresources/readthrough the driver.roots/listis auto-answered from configured roots (no pending UX, consistent with the legacy roots handler); decline/cancel is echoed to the server (not an abort); a tool-call abort mid-round settles the pending request and surfacesToolCallCancelledError.enqueuePendingSample/enqueuePendingElicitationhelpers used by both the legacy inbound handlers and the driver; add a per-requestorigin("server-request"|"input-required") to the pending wrappers.Web UI
MrtrOriginNoteelement renders an "input_required — your answer is sent back as a retry (MRTR)" note on a modern MRTR round; legacy panels are visually unchanged.originfromApp→PendingClientRequestModal→ the sampling/elicitation panels (and the inline panels).Test servers
inputRequests-only /requestState-only rounds).Tests
inputResponses+requestStateon a new id), multi-round, roots auto-answer, embedded sampling, decline-echoed,MRTR_MAX_ROUNDSbound, abort mid-round, and param-shaping.MrtrOriginNoteelement tests/stories + panel/modalorigincoverage.callToolto mockrequest(the tool-call path now usesclient.request).npm run validate(web/cli/tui/launcher),npm run coverage(≥90 per-file gate), and Storybook play tests all green.Scope notes
callToolStream) path is intentionally not MRTR-driven here (out of scope).prompts/get/resources/readgo through the same method-agnostic driver astools/call; the MRTR-specific integration coverage exercises the driver viatools/call(the test server supports embedded requests on the tool path), while the existing prompt/resource integration tests validate the rewired call sites.Verified end-to-end
Smoke-tested in the browser (Playwright) against a composable modern-era server (
test-servers/configs/modern-mrtr-http.json): connecting in the Modern era and callingmrtr_confirmpauses at the pending modal with the era note, and answering completes the retry — shown as a grouped MRTR conversation in the Protocol view.🤖 Generated with Claude Code