- Run Prettier on the 60 core/ files this PR touched — the deterministic
import rewrite had collapsed multi-line imports and left throws unwrapped;
core/ is in no format gate so it slipped past CI. Now Prettier-clean.
- Default-path output-schema violation now throws
ProtocolError(ProtocolErrorCode.InvalidParams, …) instead of a bare Error,
matching the prior v1 contract (SDK threw a typed McpError with a code) so
downstream `instanceof`/`.code` branching keeps working.
- Document the "assumes serial construction" invariant on pollTaskToolCall's
progress-subscription revival (the `_progressHandlers` new-key heuristic).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Migrate all three clients + core + test-servers from
@modelcontextprotocol/sdk@1.29 to SDK v2 (2.0.0-beta.4), keeping behavior
identical against legacy (2025-11-25) servers. Foundation for the SDK V2 +
New Spec stack.
Packages
- Split @modelcontextprotocol/sdk -> @modelcontextprotocol/client (Client,
transports, OAuth, error taxonomy, protocol types) + core (Zod schemas);
stdio -> client/stdio, Ajv -> client/validators/ajv. Test-servers ->
@modelcontextprotocol/server (+ server-legacy/sse). Deterministic import
rewrite (schemas -> core, everything else -> client); the codemod misroutes
client code to /server so it was not used for path routing.
- new Client(..., { versionNegotiation: { mode: 'legacy' } }) — nothing
2026-era goes on the wire.
Runtime (core/mcp/inspectorClient.ts)
- Error taxonomy: McpError -> ProtocolError, ErrorCode -> ProtocolErrorCode;
RequestTimeout -> SdkError/SdkErrorCode.
- Handlers take method strings; task methods (tasks/*, notifications/tasks/*)
use the 3-arg custom form (excluded from v2's spec-method set).
- Legacy tasks kept working (per project requirement): requestor tasks re-
implemented over raw client.request + explicit deprecated schemas;
experimental.tasks.callToolStream replaced by a local pollTaskToolCall
async generator (with _progressHandlers revival so task-execution progress
still reaches the caller). Receiver (server-initiated) tasks: the v2 Client
validates a sampling/elicit handler result and rejects the legacy { task }
response with -32602 — installReceiverTaskResponseBypass swaps the wrapped
entry in the private _requestHandlers map for the task-augmented branch so
{ task } reaches the wire (fixes server-initiated sampling-via-task).
- Pagination: list verbs use raw client.request({method}, List*ResultSchema)
for single-page behavior (v2's client.listX() auto-aggregate all pages).
Web / auth / apps
- Rename local JsonSchemaType -> InspectorFormSchema + add toFormSchema()
structural narrowing at the SDK-schema -> form boundary.
- ext-apps still peers on SDK v1: one compat boundary with documented casts
(remove when ext-apps#702 lands a v2 peer).
- OAuth provider saveClientInformation reconciled with v2's
OAuthClientInformationContext; InvalidClientMetadataError re-homed as a
local class (server-legacy/auth is not browser-safe).
- vitest.shared.mts dedupes @modelcontextprotocol/client+core to one copy so
vi.mock intercepts the SUT's import.
Accepted v2 behavior changes (per #1624 / spec §9.2): unknown-tool call
rejects with -32602; cancellation is stream-abort (no guaranteed
notifications/cancelled frame); output-schema strictness restored via the
Inspector's own Ajv check on the default path.
npm run ci green: validate (web 3087 / cli 212 / tui 274 / launcher 5),
coverage (>=90 per-file, all clients), smoke (launcher/cli/web), storybook
(410).
Closes#1624
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(web): two-step URL elicitation completion (#1415)
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>
* feat(web): handle URL-elicitation-required (-32042) error path
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>
* feat(web): abort URL-elicitation error path on a repeated URL
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>
* fix(web): let Copy-URL reach the URL-elicitation completion step
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>
* refactor(core): address review of error-path URL elicitation
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>
* style(core): fix attemptToolCall body indentation
Run prettier over the extracted attemptToolCall method body, which was left
over-indented by 2 spaces after the bare-block removal in 0754b43d. 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>
* fix(core): settle pending elicitations on disconnect to avoid callTool 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>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>