* refactor(web): update OAuth E2E tests to use new authentication method - Replaced `runGuidedAuth` with `authenticate` in the InspectorClient OAuth end-to-end tests to support quick authentication with CIMD pre-registration. - Updated comments to reflect the change in authentication method and its support for HTTP test metadata URLs. - Ensured that the tests continue to validate the expected authorization URL after the change. Additionally, imported `ensureCimdClientRegistration` in the OAuthManager to streamline client registration during the authentication process. * Guided-auth removal, OAuth smoke testing, and TUI/CLI support * Added CLI OAuth runner flags tests * Added clarifying comment on port 0 binding * README notes on TUI oauth callback port, prettier fixes for CLI tests * CIMD validation uses inline like EMA IdP * Fixed TUI test failures from merge * test: bring 5 OAuth files over the 90 per-file coverage gate The guided-auth-removal work added/touched several files that fell below the repo's >=90 per-file coverage gate (npm run coverage). CI doesn't run the gate (it's local-only), so validate + test:integration passed while these gaps slipped through. Bring them all back over the line: - core/auth/node/runner-oauth-callback.ts: test bad-URL/non-http-scheme throws, no-port->80 default, and IPv6 hostname bracketing. Annotate the provably-dead guards (empty host, out-of-range port, String(err) fallback, pathname "/" fallback) -- all rejected by new URL() upstream -- with justified v8 ignore comments instead of lowering the gate. - core/auth/cimd.ts: test the AS-doesn't-support-CIMD early return. - core/react/useClientSettingsDraft.ts: test the functional-updater path, the stale-onChange-after-close prev===null guard, and the timer-fires-after-close value===null guard. - ClientSettingsForm.tsx: test the CIMD checkbox toggle and CIMD URL clear. - clients/tui/src/utils/oauthDisplay.ts: test static/dcr registration kinds, the expired IdP session, and the configured-scope/empty-scope branches of formatScopes. npm run coverage now passes (exit 0) across web, cli, tui, launcher. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: cliffhall <cliff@futurescale.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
14 KiB
Inspector V2 — Authorization hardening (MCP 2026-07-28)
Brief | V2 Scope | Mid-session auth | EMA / XAA | Smoke testing
Align Inspector v2 with the authorization hardening in the upcoming MCP 2026-07-28 specification release — six SEPs that tighten OAuth/OIDC client behavior for MCP's single-client, many-server deployment pattern.
Summary
The 2026-07-28 release candidate adds six authorization SEPs. All are OAuth client requirements. Inspector implements OAuth through a mix of SDK delegation (connect-time standard OAuth via auth()) and local code (EMA wire, storage, web callback, mid-session recovery).
Strategy: Upgrade to the v2 TypeScript SDK (@modelcontextprotocol/client) when its auth-hardening PRs land. Do not reimplement SDK OAuth logic in Inspector — delegate connect-time standard OAuth to the SDK and wire the gaps (callback parameters, storage shape, client type). Mid-session authorization is a separate, near-term track (Mid-session auth); this doc covers connect-time and storage hardening that mid-session builds on.
Normative references
- MCP authorization (draft — 2026-07-28 RC target)
- MCP authorization (2025-11-25 — current stable)
- 2026-07-28 RC announcement — Authorization Hardening
- RFC 9207 — OAuth 2.0 Authorization Server Issuer Identification (
issparameter) - RFC 6750 §3.1 — Bearer token
insufficient_scope - TypeScript SDK tracking: modelcontextprotocol/typescript-sdk issues
#2197–#2201,#2198,#2256; PRs#2265,#2271,#2272
Relationship to mid-session auth
Mid-session auth ships in the near term. Overlap:
| Concern | Mid-session spec | This doc |
|---|---|---|
| SEP-2350 scope union on 403 step-up | handleAuthChallenge() owns union + UX for runtime challenges |
Connect-time step-up inherits SDK union after v2 upgrade; mid-session still uses saveScope(authorizationScopes) in the challenge handler |
SEP-2207 refresh / offline_access |
EMA legs 2–3 re-mint; standard OAuth silent refresh | Connect-time scope selection and DCR metadata |
SEP-2468 iss validation |
Applies to any interactive re-auth redirect (401 mid-session) | Connect-time callback wiring is the first place to land iss passthrough |
| SEP-2352 issuer-bound credentials | Re-register after AS migration may surface as mid-session failure | Storage and invalidateCredentials must be correct before mid-session recovery can succeed |
Implement mid-session auth now. Land auth hardening on the v2 SDK upgrade path without blocking mid-session work — use compatible storage and callback shapes so both tracks merge cleanly.
The six SEPs
| SEP | Requirement | Primary owner |
|---|---|---|
| SEP-2468 | Validate iss on authorization responses per RFC 9207; reject mix-up across authorization servers |
Client (+ SDK reference impl) |
| SEP-837 | Declare OIDC application_type during Dynamic Client Registration (native vs web) |
Client (+ SDK DCR body) |
| SEP-2352 | Bind persisted DCR credentials to the AS issuer; re-register when resource migrates between ASes |
Client storage (+ SDK AS-change detection) |
| SEP-2207 | Request refresh tokens from OIDC-style ASes (offline_access in scope when appropriate); do not assume refresh tokens are issued |
Client (+ SDK determineScope) |
| SEP-2350 | On 403 step-up, re-authorize with union of previously requested scopes and challenge scopes; on 401 re-login, replace scope | Client (+ SDK transport retry for direct transport) |
| SEP-2351 | Stable RFC 8414 .well-known discovery suffix for authorization and resource metadata |
Spec / discovery (SDK helpers) |
Servers emit per-operation scopes in 403 challenges (SEP-2350 server posture). Inspector consumes them; server-side scope-challenge middleware is out of scope unless needed for tests.
SDK vs Inspector responsibilities
Inspector today uses @modelcontextprotocol/sdk ^1.29.0 (v1 monolith). Auth hardening lands on v2 main (@modelcontextprotocol/client). Upgrade is part of this work.
Connect-time standard OAuth EMA Web remote backend
─────────────────────────── ─── ──────────────────
SDK auth() + BaseOAuthClientProvider Local wire (ema/) Frozen token stub
OAuthStorage keyed by serverUrl IdP OIDC in idpOidc.ts No interactive OAuth
completeOAuthFlow(code) only No SDK CrossAppAccess Mid-session → browser
What the v2 SDK provides (after upgrade)
| SEP | SDK status (Jun 2026) | Inspector gets |
|---|---|---|
| SEP-2207 | Implemented on v2 main (#2199) |
offline_access augmentation in authorize scope for standard OAuth via auth() |
| SEP-2350 | Open PR #2265 | unionScopes() + 403 retry uses union on streamable HTTP direct transport |
| SEP-2352 | Open PR #2271 | AS migration detection → invalidateCredentials → re-DCR |
| SEP-2468 | Open PR #2272 | iss validation in code-exchange path when host passes iss |
| SEP-837 | Open #2198 | application_type in DCR request body |
| SEP-2351 | Tracking #2256 | Discovery URL suffix in SDK metadata helpers |
What Inspector must still implement
| SEP | Inspector work | Why not SDK-only |
|---|---|---|
| SEP-2468 | Parse iss from OAuth callback URL; extend completeOAuthFlow(code, iss?); pass through to SDK auth() |
Host owns the redirect handler — SDK cannot read the callback URL |
| SEP-837 | Ensure web vs TUI/CLI set correct client type (web / native) in provider metadata or environment |
SDK may infer from redirect URI; Inspector must not mis-declare cross-environment |
| SEP-2352 | Key OAuthStorage credentials by AS issuer (not only serverUrl); implement invalidateCredentials on BaseOAuthClientProvider; migrate shared oauth.json |
Storage is Inspector-owned; SDK calls provider hooks |
| SEP-2207 | EMA leg 1 already requests openid offline_access (IDP_OIDC_SCOPES); verify standard OAuth path after upgrade |
EMA bypasses SDK authorize for leg 1 |
| SEP-2350 | Mid-session union in handleAuthChallenge() + saveScope(authorizationScopes); web/EMA paths bypass SDK transport retry |
See Mid-session auth |
| SEP-2351 | Verify discovery after upgrade; no duplicate discovery logic in Inspector | Uses SDK discoverOAuthProtectedResourceMetadata today |
Do not duplicate SDK auth.ts logic (RFC 9207 decision table, AS migration edge cases, scope union helpers). Upgrade and wire.
Implementation strategy
Principles
- Delegate connect-time standard OAuth to the v2 SDK
auth()flow. - Wire callback parameters, storage, and client-type metadata in Inspector.
- Extend EMA and mid-session paths locally where the SDK has no EMA/XAA API.
- Upgrade from
@modelcontextprotocol/sdkv1 to v2 packages as a gated step — not a silent dependency bump.
Order of work
| Step | When | Work |
|---|---|---|
| 1 | Now (parallel with mid-session auth) | Compatible prep: callback type includes optional iss; storage schema reserves authorizationServerIssuer; mid-session saveScope(union) per Mid-session auth |
| 2 | When v2 SDK auth PRs merge | Upgrade to @modelcontextprotocol/client; run existing OAuth integration tests |
| 3 | Immediately after upgrade | SEP-2468 callback passthrough; SEP-2352 storage + invalidateCredentials; SEP-837 client type per environment |
| 4 | Verify | Smoke scenarios in v2_auth_smoke_testing.md; add hardening-specific cases |
Land order in the SDK (maintainer plan): SEP-2350 → SEP-2352 → SEP-2468, with SEP-837 in the same auth-release series.
Per-SEP Inspector mapping
SEP-2468 — iss validation (RFC 9207)
- Web:
App.tsxOAuth callback — readissfrom query string alongsidecode; pass toInspectorClient.completeOAuthFlow(code, iss?). - TUI / CLI: Node callback server — same passthrough.
- Core:
OAuthManager.completeOAuthFlowforwardsissto SDKauth(). - EMA: Leg 1 OIDC callback is separate (
completeEmaIdpAuthorizationAndMint); evaluate whether IdP responses requireissvalidation on the OIDC path (may reuse SDK OIDC helpers after upgrade).
SEP-837 — application_type in DCR
- Web (
BrowserOAuthClientProvider):application_type: "web"(https redirect). - TUI / CLI:
application_type: "native"(localhost / custom scheme redirect). - Pre-registered clients: N/A — no DCR.
- CIMD: Portable client IDs — no
application_typeon DCR.
SEP-2352 — credentials bound to AS issuer
- Extend
ServerOAuthState/oauth.jsonwithauthorizationServerIssuer(or key storage by issuer + resource). - Implement
OAuthClientProvider.invalidateCredentials(scope)onBaseOAuthClientProvider— clear client info and/or tokens per SDK request. - On AS migration (PRM
authorization_serverschange), SDK triggers invalidation; Inspector storage must honor it and allow re-DCR. - CIMD carve-out: HTTPS
client_idURLs remain portable across AS changes; tokens still invalidate per SDK behavior.
SEP-2207 — OIDC refresh token guidance
- Standard OAuth: Inherited from v2 SDK
determineScope()after upgrade. - EMA leg 1: Already requests
openid offline_access; IdP refresh inrefreshIdpOidcSession. - Mid-session: Silent refresh and EMA re-mint per Mid-session auth; do not assume refresh tokens exist.
SEP-2350 — step-up scope accumulation
- Connect-time (direct transport): v2 SDK transport 403 retry after #2265.
- Mid-session:
handleAuthChallenge()computesauthorizationScopes = union(previous, required);saveScopeafter success. Standard OAuth step-up = interactive; EMA step-up = silent legs 2–3 when IdP session valid. - 401 re-login: Replace scope set (not union) — mid-session and connect-time.
SEP-2351 — discovery suffix
- Rely on v2 SDK discovery helpers used by
auth(), CIMD (core/auth/cimd.ts), and EMA (core/auth/ema/resourceContext.ts). - No Inspector-specific discovery URL construction unless regression found in smoke tests.
Non-goals
- Reimplementing MCP authorization-server or resource-server wire formats.
- v1 / v1.5 backport.
- Client credentials grant (#1225) — separate track.
- Full MCP 2026-07-28 stateless transport migration (separate from auth hardening).
- Server-side scope-challenge middleware in Inspector test servers beyond what Mid-session auth requires for 403 fixtures.
Testing
| SEP | Test |
|---|---|
| SEP-2468 | Callback with matching iss succeeds; mismatched iss rejected; AS advertising support without iss rejected |
| SEP-837 | DCR succeeds for web (https redirect) and TUI/CLI (localhost redirect) against OIDC AS requiring application_type |
| SEP-2352 | Simulated AS migration in PRM → credentials invalidated → re-DCR → connect succeeds |
| SEP-2207 | Authorize request includes offline_access when AS advertises it; connect succeeds without refresh token when AS omits one |
| SEP-2350 | Connect-time and mid-session 403 step-up use union scopes; 401 re-login replaces scopes |
| SEP-2351 | Discovery resolves against draft suffix paths |
Document manual scenarios in v2_auth_smoke_testing.md after implementation.
File touch list (expected)
| Area | Files |
|---|---|
| Upgrade | Root and client package.json; import paths v1 → v2 |
| Callback | clients/web/src/App.tsx, core/auth/node/oauth-callback-server.ts, core/mcp/oauthManager.ts, core/mcp/inspectorClient.ts |
| Provider | core/auth/providers.ts, core/auth/browser/providers.ts |
| Storage | core/auth/store.ts, core/auth/oauth-storage.ts, core/mcp/remote/node/remoteOAuthStorage.ts (if landed) |
| Mid-session overlap | core/auth/challenge.ts, core/mcp/oauthManager.ts (handleAuthChallenge, saveScope) — see Mid-session auth |
| Tests | clients/web/src/test/core/auth/, clients/web/src/test/integration/mcp/inspectorClient-oauth*.test.ts |
| Smoke doc | specification/v2_auth_smoke_testing.md |
Related specs
| Doc | Relationship |
|---|---|
| v2_auth_mid_session.md | Mid-session auth (near term); owns runtime SEP-2350 behavior and most recovery UX |
| v2_auth_ema.md | EMA legs 1–3; SEP-2207 leg 1 scopes; future v2 SDK Layer-2 helpers |
| v2_auth_smoke_testing.md | Manual verification ladder |
| v2_scope.md | OAuth handling scope item |
| v2_storage.md | Shared oauth.json — issuer-keyed credentials affect storage schema |