inspector/core/mcp
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Cliff Hall 01df9d81bb
fix(core): start OAuth on a modern/auto-era connect (#1805) (#1809)
* fix(core): start OAuth on a modern/auto-era connect (#1805)

The SDK's `server/discover` negotiation probe runs before anything else on
`protocolEra: "auto" | "modern"`, and its classifier reports whatever the
transport threw as `SdkError(ERA_NEGOTIATION_FAILED)` with the real error moved
to `data.cause`. That buried both connect-time auth signals: the remote path's
`AuthRecoveryRequiredError` (matched with `instanceof`, and carrying the
authorization URL) and a direct transport's `AuthChallengeError`. An
OAuth-protected server that authorized fine on the legacy era instead produced a
dead-end "Version negotiation probe failed" — no redirect, and the connection
status flipped to `error` despite `isConnectAuthRecoveryError` existing to
prevent exactly that.

- `findNestedAuthError()` walks `cause` / `data.cause` for either typed error.
  Not gated on the SDK's error code or message, so it survives rewording.
- `connect()` unwraps the rejection at the earliest point, ahead of
  `withDirectAuthRecovery` (whose `isAuthChallengeError` check is shallow), the
  outer status guard, and every client's connect error handling — so web, CLI,
  and TUI are all fixed without a client-side change.
- On the direct path, enable `interceptAuthChallenges` for the probing eras even
  with no stored tokens. Without an authProvider the 401 reaches the SDK as a raw
  `SdkHttpError`, and the classifier ignores the HTTP status (it only looks for a
  JSON-RPC error body) — so pin mode rethrew it with the 401 discarded entirely,
  no status and not even a cause for the walk to find.

Verified against a live dual-era server that answers 401 on both `initialize`
and `server/discover`: `auto` and `modern` now reject with
`AuthRecoveryRequiredError` carrying a real authorization URL. Each half of the
fix is load-bearing — reverting either fails the new integration tests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013SNjAqoEhUVVkr3UStR6om

* docs(core): mark the era-probe challenge interception as a removable workaround

Per review on #1806: the `|| this.probesProtocolEra()` clause compensates for
an upstream SDK gap (modelcontextprotocol/typescript-sdk#2561, tracked as
#1807). Make its lifespan explicit so whoever picks up #1807 knows this block
is the thing to delete, rather than having to re-derive it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016S7h83UKNQ7kvSiFDNksCt

* fix(core): resolve connect() when auth recovery is satisfied silently

Review findings on #1809:

- connect() built its handshake promise once, outside the runConnect closure
  withDirectAuthRecovery retries. On a "satisfied" outcome the recovery runs a
  complete nested connect() via reconnectAfterAuthRecovery(), so the retry leg
  re-awaited the already-rejected promise and rejected a connect() whose client
  was in fact connected. Short-circuit the retry when already connected, and
  skip the post-connect block the nested connect already ran (a duplicate
  `connect` event re-triggers every list-state manager's refresh).
- Document the accepted 403 side effect of enabling interceptAuthChallenges
  with no stored tokens, and why every `{ pin }` counts as probing.
- Replace the private-method double cast in the era-probe test with an
  observable assertion on the transport options, and split the non-auth
  failure case per era instead of a message alternation that passed either way.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016S7h83UKNQ7kvSiFDNksCt

* fix(core): key the connect retry leg off attempt, bound nested recoveries

Second-pass review findings on #1809:

- The satisfied-recovery short-circuit keyed off live connection status, so an
  onclose landing between the nested connect() and the retry leg fell through
  to the rejected handshake promise and reported the stale 401 as the reason
  the session died. withDirectAuthRecovery now passes `attempt` to its
  operation, so connect() identifies the retry leg structurally and reports the
  teardown when the recovered session is already gone.
- Bound nested silent recoveries. The `attempt >= 1` guard is per-call and a
  satisfied outcome recovers via a nested connect() with a fresh counter, so a
  server that keeps rejecting freshly refreshed tokens recursed without bound.
  Counted across the nesting boundary, capped at 3.
- Strengthen the regression test (server info populated by the nested connect,
  no duplicate statusChange) and add the two missing cases: a failed reconnect
  still rejects, and the recursion bound terminates.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016S7h83UKNQ7kvSiFDNksCt

* test(core): cover the recovery teardown branch; fix a duplicated case

Third-pass review findings on #1809, both about tests added in the previous
commit:

- The retry leg's teardown error had no coverage, so a regression to the old
  status fall-through would have stayed green. Covered deterministically: the
  nested connect dispatches `connect` after setting "connected", so closing the
  live transport from that listener flips the status before the retry leg runs.
  Asserts the error identity, which is the point of the change.
- "still rejects when the reconnect underneath the recovery fails" used a
  challenging transport for every connect, so it walked the same bounded chain
  as the recursion-bound test rather than covering a non-auth reconnect
  failure, and its comment claimed an "error" status the auth path never
  reaches. Now fails the reconnect with a plain error and asserts both.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016S7h83UKNQ7kvSiFDNksCt

---------

Co-authored-by: rinormaloku <rinormaloku37@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 15:06:01 -04:00
..
__tests__ Tools: x-mcp-header annotation tooling + -32602 unknown-tool rendering (#1632) (#1735) 2026-07-22 11:42:54 -04:00
import feat(web,core): persist & edit STDIO cwd/env in Server Settings (#1515) (#1518) 2026-06-24 19:15:43 -04:00
node chore: close quality-gate coverage gaps (#1689) (#1766) 2026-07-24 17:20:12 -04:00
remote test(cli,tui): typecheck the __tests__ dirs (#1791) (#1799) 2026-07-27 12:05:35 -04:00
state chore: close quality-gate coverage gaps (#1689) (#1766) 2026-07-24 17:20:12 -04:00
apps.ts SDK v2 migration (behavior-neutral, versionNegotiation: 'legacy') (#1624) 2026-07-15 10:44:24 -04:00
config.ts chore: close quality-gate coverage gaps (#1689) (#1766) 2026-07-24 17:20:12 -04:00
elicitationCreateMessage.ts fix(core): register peer-request handlers before connect (#1797) (#1798) 2026-07-27 11:32:23 -04:00
extensions.ts Extensions: Connection Info both eras + io.modelcontextprotocol/ui advertisement (Phase 3 of #1633) (#1744) 2026-07-23 10:11:36 -04:00
fetchTracking.ts Network: modern Mcp-* headers, new error codes, cancellation-as-abort (#1628) (#1722) 2026-07-18 17:32:05 -04:00
index.ts Guided-auth removal, OAuth smoke testing, and TUI/CLI support (#1531) 2026-06-29 15:35:51 -04:00
inspectorClient.ts fix(core): start OAuth on a modern/auto-era connect (#1805) (#1809) 2026-07-27 15:06:01 -04:00
inspectorClientEventTarget.ts Tools: x-mcp-header annotation tooling + -32602 unknown-tool rendering (#1632) (#1735) 2026-07-22 11:42:54 -04:00
inspectorClientProtocol.ts Tools: x-mcp-header annotation tooling + -32602 unknown-tool rendering (#1632) (#1735) 2026-07-22 11:42:54 -04:00
messageTrackingTransport.ts Tasks: io.modelcontextprotocol/tasks extension on modern servers (era fork) (#1731) 2026-07-21 19:17:04 -04:00
modernTaskSchemas.ts Tasks: io.modelcontextprotocol/tasks extension on modern servers (era fork) (#1731) 2026-07-21 19:17:04 -04:00
oauthManager.ts OAuth: re-key store to (server, issuer) + SEP-837/2350/2352 auth UX (#1694) 2026-07-16 16:52:53 -04:00
samplingCreateMessage.ts fix(core): register peer-request handlers before connect (#1797) (#1798) 2026-07-27 11:32:23 -04:00
serverList.ts fix(core): register peer-request handlers before connect (#1797) (#1798) 2026-07-27 11:32:23 -04:00
sessionStorage.ts feat(core): port v1.5 hook layer (#1243) (#1300) 2026-05-13 17:43:07 -04:00
taskNotificationSchemas.ts SDK v2 migration (behavior-neutral, versionNegotiation: 'legacy') (#1624) 2026-07-15 10:44:24 -04:00
toolCallCancelledError.ts fix(web): send a cancellation notification when cancelling an ordinary tool call (#1458) (#1463) 2026-06-13 22:23:02 -04:00
toolOutputValidation.ts Refactor / justify codebase-wide 'as unknown as' casts (#1690) (#1756) 2026-07-24 09:34:32 -04:00
typedEventTarget.ts Test/1396 harden managed state connect tests (#1508) 2026-06-24 10:46:09 -04:00
types.ts fix(core): register peer-request handlers before connect (#1797) (#1798) 2026-07-27 11:32:23 -04:00
urlElicitation.ts Address @claude review: format core/, typed output-validation error, document progress heuristic 2026-07-15 11:08:54 -04:00