feat(web): URL elicitation — explicit completion step + error-path (-32042) handling (#1415)#1436
Conversation
Replace accept-on-open with an explicit completion step for URL elicitations. Opening the URL now only transitions the panel to its waiting state; the elicitation resolves with `accept` only when the user clicks the revealed "I've completed it" action, or with `cancel`. - ElicitationUrlPanel: add `onComplete`, reveal the completion button and relabel open→"Reopen in Browser" while `isWaiting`. - PendingClientRequestModal: wire local `isWaiting`; open no longer sends a response. Removed the accept-on-open comment that pointed here. - Tests cover open→waiting→complete and open→cancel, and assert opening alone does not resolve the elicitation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@claude review |
|
Claude finished @cliffhall's task in 1m 7s —— View job Review: two-step URL elicitation completion
Solid change — it fixes a real correctness bug (optimistic accept-on-open) cleanly. The two-step flow is wired correctly and the tests genuinely cover the new behavior. A few observations below; nothing blocking. ✅ What's right
💡 Suggestions (non-blocking)
Nit
Overall LGTM. The one I'd actually consider acting on is #1 (Copy-URL path can't confirm), since it's a reachable dead-end for a legitimate user flow. |
Implements the spec's "URL mode with elicitation required error" flow (2025-11-25). When a tools/call returns a `-32042` UrlElicitationRequired error, the client now surfaces the required URL elicitation(s) and retries the call once completed, instead of showing a bare error. Core (InspectorClient.callTool): - Detect `-32042` (typed UrlElicitationRequiredError or generic McpError) via the new `getUrlElicitationsFromError` helper. - Surface each carried elicitation in order through the existing pending- elicitation queue / modal (reusing the two-step "Open → I've completed it" UI), then re-issue the original call once all are accepted. A decline/cancel aborts with a clear message; a bounded retry count guards a server that keeps returning the error. - `ElicitationCreateMessage.completeIfPending()` lets the optional `notifications/elicitation/complete` auto-advance/auto-accept an open URL elicitation; the completion-notification handler now resolves (was: only removed) the pending entry. - Extracted `attemptToolCall` / `dispatchFailedToolCall` from the old callTool body to support the retry loop without duplicating bookkeeping. Web (App): - A non-spec `-32042` with no elicitations (nothing to open) shows a dedicated yellow toast linking to a new UrlElicitationErrorModal with the raw error body, mirroring the output-schema-validation toast. Tests: helper unit tests, completeIfPending unit tests, callTool error-path retry/cancel/no-list/ordinary-error tests (injected fake client), the new modal (test + stories), and an App toast test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Guard the -32042 retry loop: if the server's retry error re-requests a URL the user already completed earlier in the same call, completing it again can't make progress, so callTool aborts with a new typed UrlElicitationLoopError instead of re-prompting (which would loop until the retry cap). The web layer detects it and shows a "URL elicitation loop" toast naming the repeated URL rather than a generic failure. Tests: core abort-on-repeat test (URL presented once, two attempts, then loop error) and an App loop-toast test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address review of #1436: the "I've completed it" action was gated behind clicking "Open in Browser" (only that set isWaiting), so a user who used Copy URL to finish the flow externally could only Cancel. Copying the URL now also moves the panel into the waiting state, exposing the completion step. Also trims the now-redundant second sentence of the waiting hint. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks for the thorough review! Addressed in 1. Copy-URL dead-end — fixed. Good catch; that was a reachable dead-end. 2. Redundant waiting hint — fixed. Trimmed back to just 3. Nit (regex assertion) — agreed, keeping it. Note for context: this branch now also carries the error-path ( |
|
@claude review |
|
Claude finished @cliffhall's task in 2m 5s —— View job Review: URL elicitation — completion step + error-path (-32042)
This is a strong, well-documented PR. The two-step completion fix is correct and the Copy-URL dead-end from the prior round is properly closed. The new ✅ What's right
💡 Observations (all non-blocking)
Nit
Nothing here blocks merge — items 1–3 are the ones worth a moment if you're touching this again. |
Review follow-ups on #1436 (all non-blocking): - Remove the leftover bare block scope in attemptToolCall (extraction artifact); body de-indented one level. - Log a warning when callTool gives up after the URL-elicitation retry cap is hit, so a server that keeps demanding new elicitations is diagnosable rather than looking like an ordinary failure. - Assert the dispatch-once invariant on the loop-abort and cancel-abort paths (exactly one failed toolCallResultChange event). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks — great review. Addressed the actionable items in 1. Redundant block scope in 2. Retries-exhausted UX — added a warn. On hitting 3. Loop-path history assertion — added. Both the loop-abort and the cancel-abort tests now attach a 4. Nit (duplicate modal description strings) — leaving as-is for the explicit/readable form, as you suggested. Re-validated: |
|
@claude review |
|
Claude finished @cliffhall's task in 1m 58s —— View job Review: URL elicitation — completion step + error-path (-32042)
The follow-up commits land cleanly. Re-verified the items from the last round: the I found one new concrete issue while verifying #1. 🔧 One thing worth fixing
This is purely cosmetic, but it's not caught by
💡 Minor (non-blocking, unchanged from prior rounds)
Nothing blocks merge. The indentation cleanup is the only thing I'd act on. |
Run prettier over the extracted attemptToolCall method body, which was left over-indented by 2 spaces after the bare-block removal in 0754b43. core/ is outside clients/web's format:check scope, so npm run validate did not catch it. Whitespace-only; the pre-existing line 658 wrap (also outside the format scope) is left as-is, out of scope for this PR. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks — addressed the one actionable item in 🔧 Pre-existing line 658 + the Minor items (modal description strings, |
|
@claude review |
|
Claude finished @cliffhall's task in 1m 43s —— View job Review: URL elicitation — completion step + error-path (-32042)
This PR is in great shape after the prior three rounds — every actionable item from those rounds landed: the I did find one genuinely new edge case not raised before, plus a couple of small notes. 🔎 One new observation (non-blocking, edge case)Error-path elicitation promise never settles on disconnect → return new Promise<ElicitResult["action"]>((resolve) => {
const message = new ElicitationCreateMessage(
request,
(result) => resolve(result.action),
(id) => this.removePendingElicitation(id),
);
this.addPendingElicitation(message);
});But // inspectorClient.ts:1028
this.pendingElicitations = [];If the connection is torn down (server crash, manual disconnect, transport close) while an error-path URL elicitation is awaiting confirmation, the queue is emptied, the modal disappears, but the underlying promise never settles — so This is a real-but-narrow case (disconnect mid-elicitation). A clean fix would have 💡 Minor notes (non-blocking)
Prior-round minors (explicit modal description strings; Nothing here blocks merge — the disconnect-hang is the only item I'd consider, and only as a follow-up given how narrow it is. |
…l hang Address review of #1436: an error-path `awaitUrlElicitation` promise only settled via respond, but `disconnect()` cleared `pendingElicitations` wholesale — so a disconnect mid-elicitation (server crash, manual disconnect, transport close) left `callTool` (and the UI's in-flight toolCallState) hanging forever. - Add `ElicitationCreateMessage.cancel()`: settles a pending elicitation as `{ action: "cancel" }` without calling `onRemove` (the queue is cleared by the caller, so it must not splice mid-iteration). - `disconnect()` now cancels each pending elicitation before clearing the array, and dispatches `pendingElicitationsChange` (it already did so for samples) so the modal closes on a transport-close teardown too. Tests: disconnect-mid-elicitation settles callTool (no hang); cancel() unit tests (resolves-without-remove, no-op once responded). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks — and good find on the disconnect hang. Fixed in Disconnect mid-elicitation →
Scoped to elicitations since that's the path that blocks Multi-step re-list treated as a loop — yep, deliberate and documented; any repeat trips the guard even alongside a new URL. Left as-is; easy to relax to "abort only if every required URL is a repeat" if a real server ever needs it.
Prior-round minors (explicit modal strings, Re-validated: |
|
@claude review |
|
Claude finished @cliffhall's task in 1m 36s —— View job Review: URL elicitation — completion step + error-path (-32042)
The disconnect-hang fix from the last round landed correctly and the PR is now in solid shape across all five review passes. LGTM. ✅ Verified this round
💡 Two small observations (non-blocking, neither needs action)
Prior-round items remain as agreed: multi-step re-list is deliberately treated as a loop; Nothing blocks merge. |
Closes #1415. Builds the URL-elicitation experience out in two related parts.
Screen capture
Testing against the Everything server (on pending PR that fixes server behavior: modelcontextprotocol/servers#4285).
elicitation-error-path.mov
1. Two-step completion (the original #1415)
Open in Browserpreviously firedonRespond({ action: "accept" })synchronously the momentwindow.openreturned — optimistic, since the inspector can't observe an external flow. Replaced with the two-step flow the panel was already stubbed for:accept; Cancel sendscancel. While waiting the open button relabels to Reopen in Browser.ElicitationUrlPanelgainsonComplete;isWaitingis now wired (was hardcodedfalse).2. Error-path URL elicitation (
-32042)Handles the spec's "URL mode with elicitation required error" flow (2025-11-25). When a
tools/callreturns a-32042UrlElicitationRequirederror, the inspector now surfaces the required URL elicitation(s) and retries the call, instead of showing a bare error.getUrlElicitationsFromErrordetects-32042(typedUrlElicitationRequiredErroror genericMcpError).UrlElicitationLoopErrorand a "URL elicitation loop" toast rather than re-prompting forever.-32042with no elicitations shows a dedicated toast linking to a newUrlElicitationErrorModalwith the raw error body (mirrors the output-schema-validation toast).ElicitationCreateMessage.completeIfPending()lets the optionalnotifications/elicitation/completeauto-accept an open URL elicitation; the completion-notification handler now resolves (was: only removed) the pending entry — also fixes a latent request-path orphan where the server's request went unanswered.attemptToolCall/dispatchFailedToolCallfromcallToolto support the retry loop without duplicating history bookkeeping.Acceptance criteria (#1415)
accept.ElicitationUrlPanel'sisWaitingstate is wired.Testing
npm run validate— 2053 unit/integration tests pass; per-file coverage gate green.npm run test:storybook— 333 pass.🤖 Generated with Claude Code