| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2eeab05d00 |
Merge remote-tracking branch 'origin/v2/main' into v2/fix/2172-oidc-discovery-compat
Signed-off-by: cliffhall <cliff@futurescale.com> # Conflicts: # README.md # core/mcp/inspectorClient.ts # test-servers/src/test-server-oauth.ts |
||
|
|
4429c0c0bf |
fix(auth): keep the failure path total, and align the docs with the new order (#2144)
Copilot review round 14. Two code fixes and a documentation sweep. - `buildRevocationRequest` ran outside `revokeToken`'s best-effort try. `encodeURIComponent` throws on a lone UTF-16 surrogate — valid JSON, so it can reach there from a persisted client id or secret — and every caller has already cleared its local state by then, so a rejection instead of a `failed` outcome would break the documented guarantee. Construction is inside the try now, and the catch reports `params.endpoint` rather than a `url` that may not exist on that path. - The fixture's Basic decode turned a malformed percent escape into an Express 500, reporting a bad credential as a server fault rather than as the `invalid_client` 401 the endpoint means. Decoding failure is failed authentication. - Ten places still said revocation happens *before* the clear, which the reorder in the previous commit made false: the settings-form description, the `--no-revoke` help text, both READMEs, `docs/mcp-server-configuration.md`, three JSDoc blocks, and the PR body. Swept, and the ordering is stated where it is load-bearing rather than asserted in passing. Also merged the two adjacent JSDoc blocks on `InspectorClient.clearOAuthTokens`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0177mPHAdECD18nLLCTwR5rh Signed-off-by: cliffhall <cliff@futurescale.com> |
||
|
|
56ac83051b |
fix(auth): RFC-encode the Basic credential; make the TUI clear await its work (#2144)
Copilot review round 6. - The Basic credential is now form-urlencoded per RFC 6749 §2.3.1. Round 1 matched the SDK's raw `id:secret` on the theory that a credential should be presented the same way here as at the token endpoint; that does not hold — a client authenticated with `client_secret_post` never exercised the SDK's Basic path, so there is no precedent to match, and the raw form is ambiguous for an id containing `:` and makes `btoa` throw on a non-Latin-1 secret. The fixture decodes each half. - The ctx-less grant read is suppressed on the store's issuer *stamp* rather than on the token's value. The active slot can hold client information without a token, in which case the read falls back to the legacy grant — and a value collision with any other issuer would have dropped that grant unrevoked and unreported. - The TUI announced "OAuth state cleared" on the keypress, so with a bounded revocation request it said so while the work was still in flight and then removed it when the work finished. `onClearOAuth` returns a promise; `AuthTab` shows "Clearing OAuth state…" until it settles, ignores repeats while one is in flight, and skips exactly the revision bump its own clear caused so the confirmation is not immediately reset by it. - `handleClearOAuth` now claims an attempt and retires a superseded one, the way `handleDisconnect` does: the await can keep it suspended for seconds, long enough for the user to switch servers and have server A's outcome — including its disconnect — land on server B. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0177mPHAdECD18nLLCTwR5rh Signed-off-by: cliffhall <cliff@futurescale.com> |
||
|
|
781aa69d19 |
fix(auth): revoke every issuer-bound grant, not just the active one (#2144)
Copilot review round 3. `clear(serverUrl)` deletes EVERY `byIssuer` slot, but revocation read only the context-free (active-issuer) token. A server that had authorized against issuers A and B therefore revoked B, then destroyed the local record of A while A's grant stayed live at its authorization server — the same leak this feature exists to close, one level down. - `OAuthStorage.listIssuers(serverUrl)` is the new seam (one implementation: every backend extends `OAuthStorageBase`). `collectGrants` walks it plus the ctx-less read, deduping by token, so every grant the clear will delete is covered. - Metadata is cached once per server, not per issuer, so a grant bound to an issuer the cached document does not describe cannot be revoked: sending its token to that endpoint would hand a credential to a server that never minted it. That grant is reported as failed — dropped unrevoked — rather than silently discarded or misdirected. - `aggregateOutcomes` gives a failure precedence over another grant's success, so one live grant cannot hide behind another's 200. Exported for its own test, since `computeOutcome` returns early on the empty case. Also from that round: - The fixture's /oauth/revoke now verifies token ownership (RFC 7009 §2.1). Access tokens record their owning `client_id`; a token belonging to another client is left alone and still answered 200 (§2.2 — the response must not tell one client whether another's token exists). New e2e case proves a second registered client cannot revoke the first's grant. - The TUI rendered the revocation-failure note in cyan, the informational tone. It is a partial success — the local clear really happened — so it is not an `error` status, but the grant may still be live and cyan understated that. `AuthTab` takes an `oauthMessageTone`; the message says so explicitly. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0177mPHAdECD18nLLCTwR5rh Signed-off-by: cliffhall <cliff@futurescale.com> |
||
|
|
7f27d7e9c7 |
fix(auth): address Copilot review round 1 on the RFC 7009 revocation (#2144)
- RFC 8414 §2 defaults an omitted `revocation_endpoint_auth_methods_supported` to `client_secret_basic`; it does not inherit the token endpoint's list. Inheriting made metadata advertising only `client_secret_post` there send POST credentials to an endpoint that never advertised the method. - The 5s timeout was inert on the web paths: `createRemoteFetch` re-issues the call as a POST to `/api/fetch` and drops `init.signal`, and the backend's outbound fetch gets none either, so a wedged AS could hold the teardown open indefinitely. A wall-clock race now enforces the deadline; the signal is kept because it genuinely cancels the direct-fetch paths. - The settings-modal Clear read `settingsModalTarget`, which comes from the persisted `servers` list, while edits live in `settingsDraft` until the save debounce. Toggling the checkbox and clearing immediately used the previous value. It now merges the draft. - `--relogin` clears both key spellings, so it now revokes from both rather than stopping at the first populated one — a stale entry under the other spelling is a live grant. The normalised key goes first, matching `findStoredServerState`, and a second key holding the same token is skipped. - The fixture's /oauth/revoke now ENFORCES RFC 7009 §2.1 client authentication (both RFC 6749 §2.3.1 forms), so the e2e's claim that the Inspector authenticates correctly is actually tested; two negative cases assert a wrong secret and an unidentified client are refused. - Dropped the `as unknown as` casts from the tests: a typed `InspectorLogger` double, and a spy on the real storage instead of a spread-and-cast. - Added CLI-level tests through `runCli` for `--no-revoke`, the per-server `oauth.revokeOnClear`, and the stderr warning, and TUI tests for the forwarded opt-out and the rendered failure. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0177mPHAdECD18nLLCTwR5rh Signed-off-by: cliffhall <cliff@futurescale.com> |
||
|
|
65fd2b9e35 |
feat(auth): revoke OAuth tokens at the authorization server on clear (#2144)
Clearing the Inspector's OAuth state deleted the local copy of the tokens and stopped there. The access token — and the refresh token, which is long-lived by design — stayed valid at the authorization server until they expired on their own, so a day of connect/disconnect iteration left it holding grants for sessions that ended hours ago. RFC 7009 §1 describes exactly that case, and nothing in the repo constructed a revocation request, so a server author implementing `revocation_endpoint` had no way to exercise it with the tool that exists to exercise this. core/auth/revocation.ts builds and sends the request; the three clear paths call it, so the behavior is written once — web's "Clear OAuth state and disconnect" (both the active and stored-only paths), the TUI's Auth tab, and the CLI's --relogin. It runs BEFORE the local clear, because the token, the client credentials and the discovered `revocation_endpoint` all live in the store the clear is about to empty. The request names the refresh token when there is one: §2.1 asks the AS to also invalidate the access tokens issued under the same grant, so one request covers both halves. Best-effort by construction. Every path returns a TokenRevocationOutcome rather than throwing: no advertised endpoint means nothing is sent and that authorization server behaves exactly as before, and a network error, a non-2xx, a 5s timeout or an unreadable store is reported and nothing more. Forgetting the tokens is what the user asked for. `lost_authorization_state` recovery deliberately skips it — it clears a half-finished flow to retry it, and an authorization that never completed has no grant to revoke. Client authentication resolves the preregistered slot first, mirroring BaseOAuthClientProvider.clientInformation; reading only the dynamic slot would send no authentication for a server configured with `oauth.clientId`. The Basic credential is byte-identical to the SDK's applyBasicAuth so this request and the token request cannot present the same secret differently. Opt out per server with `oauth.revokeOnClear` (default on, only `false` written to disk) or per CLI run with --no-revoke. Read at clear time rather than connect time, so toggling needs no reconnect. Turning it off is a testing affordance: a client that walks away still holding live tokens is a case a server author may want to reproduce. test-servers now advertises `revocation_endpoint` and serves POST /oauth/revoke, faithful on §2.2 (an unknown token is a success) and §2.1 (revoking a refresh token invalidates the access tokens under the same grant), with oauth-revocation-http.json / oauth-no-revocation-http.json as showcase configs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0177mPHAdECD18nLLCTwR5rh Signed-off-by: cliffhall <cliff@futurescale.com> |
||
|
|
7ca753bc4c |
fix(auth): accept RFC 8414 metadata served at the OIDC well-known path
The SDK's `discoverAuthorizationServerMetadata` picks its validation schema from the well-known filename that resolved rather than from the document that came back, so anything found at `/.well-known/openid-configuration` is validated as an OpenID provider document — requiring `jwks_uri`, `subject_types_supported` and `id_token_signing_alg_values_supported`, three fields RFC 8414 does not define. RFC 8414 §5 permits that filename for general OAuth metadata, so a conforming plain OAuth 2.0 authorization server is rejected; and because the parse throws instead of continuing the candidate loop, discovery aborts and the connection fails outright. Filed upstream as modelcontextprotocol/typescript-sdk#2733. `core/auth/oidcDiscoveryCompat.ts` works around it without fabricating a field: on a failed RFC 8414 candidate it probes the OIDC candidates the SDK would try next and, when one returns a document that satisfies `OAuthMetadataSchema` but fails the OIDC schema, serves that body as the RFC 8414 response so the SDK picks the schema that describes it. A genuine OpenID provider document is left to the SDK's own OIDC leg. Issuer validation runs unchanged, since the substituted document is the one the server published. It wraps `effectiveAuthFetch` — above the fetch tracker, the opposite of `withOAuthEndpointOverrides` — so the Network tab still records the real 404 and the real probe rather than the substitution. The candidate derivation mirrors the SDK's `buildDiscoveryUrls`, which a fetch wrapper cannot call, and a test pins it against that export so the two cannot drift. Adds `oauth.asMetadataPath` to the composable test server (mirroring `oauth.resourceMetadataPath`) and the `oauth-rfc8414-at-oidc-path-http.json` showcase config that reproduces the failure. Closes #2172 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UL9retfvAXRgvi6EWk4SY4 Signed-off-by: cliffhall <cliff@futurescale.com> |
||
|
|
8beba25b58 |
fix(auth): address review — reject a query or fragment in the metadata path
`isOriginRelativePath` compared the resolved href against the literal, which `URL` preserves a query and fragment in — so `/doc?version=1` and `/doc#section` passed. Express matches on the path alone (and treats `?` as a pattern character), and a fragment never reaches the server, so the advertised URL could not resolve to the registered route. Reject both, with cases added to the table-driven test. Signed-off-by: cliffhall <cliff@futurescale.com> |
||
|
|
2a0290df26 |
fix(auth): address review — proxy CIMD discovery fetch, validate metadata path
Two findings from review: - `ensureCimdClientRegistration` ran protected-resource discovery on the global fetch while its AS-metadata leg used the configured one. On web that is `createRemoteFetch`, which proxies through the backend to sidestep CORS — so the discovery this PR routes to a challenge-advertised URL would fail in the browser, be swallowed by the catch, and leave CIMD probing the wrong authorization server. Forward `params.fetchFn`, and assert in the test that nothing reaches the global. - `oauth.resourceMetadataPath` accepted `//other-host/doc`. Resolved against the request base that re-points the origin in the advertised `resource_metadata` while Express still registers the route locally, so the server would advertise a document it does not serve. `/\\host` does the same, since the URL parser folds a backslash into a slash for special schemes — a leading-slash check misses both. Validate origin-relativity with a shared predicate, used by the JSON-config validator and again at server setup for a programmatically built ServerConfig. Signed-off-by: cliffhall <cliff@futurescale.com> |
||
|
|
f8a46726b7 |
fix(auth): pass the challenge's resource_metadata URL to SDK auth()
An RFC 9728 `WWW-Authenticate: Bearer resource_metadata="…"` challenge was parsed and then dropped by the normalized `AuthChallenge`, so every challenge-driven `mcpAuth()` call ran discovery without it and probed locations derived from the MCP server URL instead. OAuth failed outright against any server whose metadata document lives off the well-known path. The SDK handles this correctly inside its own transports, but the Inspector never reaches that path — it intercepts the 401 and drives `auth()` itself to capture the authorization URL and run the step-up scope union. Carry the advertised URL on `AuthChallenge` as a string (it crosses the web client's remote-backend boundary as JSON), convert it at the OAuth boundary, and pass it to the standard challenge leg, the forced step-up leg, and CIMD pre-registration — which runs before `auth()` and did its own discovery. A malformed value is ignored rather than surfaced, matching the SDK's parser. The callback leg needs nothing: SDK `auth()` persists the URL in its discovery state. Adds an `oauth.resourceMetadataPath` test-server mode that moves the document off the well-known path entirely, so the integration test proves discovery genuinely fails without the fix. Closes #2071 Signed-off-by: cliffhall <cliff@futurescale.com> |
||
|
|
834fd2d77d |
chore(deps): migrate to eslint 10 to clear the brace-expansion DoS advisory (#1900)
* chore(deps): migrate to eslint 10 to clear the brace-expansion DoS advisory (#1838) eslint 9 pulled `minimatch@3` → `brace-expansion@1.1.16`, which carries GHSA-mh99-v99m-4gvg (high, DoS via unbounded expansion length). It appeared in all five lockfiles — the root plus each of the four clients, none of which is an npm workspace, so all five had to move together. Deferred from the pre-2.0.0 sweep because it is dev-scope, not attacker- controlled, and a major bump across five packages was the wrong thing to attempt immediately before an irreversible publish. This is that work. ## What moved - `eslint` `^9.39.4/5` → `^10.8.0` and `@eslint/js` → `^10.0.1`, in all five manifests. - `eslint-plugin-react-hooks` `^7.0.1` → `^7.1.1` in web and tui. Required, not cosmetic: 7.0.1's peer range stops at `^9.0.0`, so installing it alongside eslint 10 fails `ERESOLVE`. - Everything else already declared eslint 10 support and stayed put: `typescript-eslint@^8.56.1` (`^8.57 || ^9 || ^10`), `eslint-plugin-react-refresh@^0.5.2` (`^9 || ^10`), `eslint-plugin-storybook@^10.2.19` (`>=8`). ## Config migration None was needed. All five configs were already flat (`eslint.config.js` with `defineConfig`/`globalIgnores` from `eslint/config`), which is the format v10 keeps and the only one it still supports. The root config's `core/` and shared- surface blocks — including the `_`-prefix `argsIgnorePattern` / `varsIgnorePattern` / `caughtErrorsIgnorePattern` — are unchanged and still gate `lint:core` and `lint:shared`. No rule was disabled, downgraded, or scoped away to make the new eslint pass. v10 surfaced 21 real violations across three rules; all 21 are fixed in the code. ### `preserve-caught-error` (new in `eslint:recommended`) — 15 sites A `throw new Error(msg)` inside a `catch` silently drops the original error. Each now passes `{ cause }`, so the underlying failure survives into the chain. Messages are unchanged, so nothing that asserts on them moves. 12 in `core/`, 2 in `clients/cli`, 1 in `test-servers/src`. ### `no-useless-assignment` (new in `eslint:recommended`) — 2 sites `clients/cli/src/cli.ts`'s `optionArgs` and `test-servers/src/test-server-oauth` 's `valid` were initialized and then unconditionally overwritten on every path. Both are now declared without the dead initializer; TypeScript's definite- assignment analysis covers the remaining paths. ### `react-hooks/set-state-in-effect` — 8 sites (web) These were false negatives the plugin fixed in 7.1.0, not a rule we newly opted into: `set-state-in-effect` has been `error` in `flat/recommended` since 7.0.1, and the web config has always extended it. Every one was the same anti-pattern — resetting or re-syncing local state from a prop inside a `useEffect`, which paints the stale value for one frame and then renders again to correct it. All eight now use a new `useValueChange(value, onChange)` hook (`clients/web/src/hooks/useValueChange.ts`), which is React's documented "adjusting state during render": compare against the previous render's value with `Object.is` and call back *during* render, so React discards the in-progress output before it reaches the DOM. - `MrtrConversation`, `ProtocolEntry`, `TaskCard` — mirror `isListExpanded`. - `NetworkEntry` — same mirror, plus the "Reveal in Network" force-open. The reveal's rAF scroll stays an effect (real external-system work); only the `setIsExpanded(true)` moved. `isExpanded` is now seeded from `isListExpanded || revealed`, since a render-time sync fires on change and so cannot cover an entry that mounts already revealed. - `PromptArgumentsForm`, `ResourceTemplatePanel` — reset on template/prompt switch. - `ServerConfigModal` — reset on open. Keyed on `opened ? initial : undefined`, which collapses "opened flipped" and "initial changed while open" into one value; `initial` is already memoized, so it cannot thrash. `useValueChange` has its own test file, and `ServerConfigModal` gains an open→edit→close→reopen test (the close leg was previously untested, which is what left the new guard uncovered). ## Advisory delta `npm audit`, per package, before → after: | Package | before | after | | ------------------- | -------------------------- | -------------------------- | | root | 9 (1 low, 6 mod, 2 high) | 8 (1 low, 6 mod, 1 high) | | `clients/web` | 19 (3 low, 6 mod, 6 high, 4 crit) | 17 (3 low, 6 mod, 4 high, 4 crit) | | `clients/cli` | 6 (2 low, 4 high) | 4 (2 low, 2 high) | | `clients/tui` | 4 (1 low, 3 high) | 2 (1 low, 1 high) | | `clients/launcher` | 3 (3 high) | 1 (1 high) | `brace-expansion` is gone from all five — `eslint@10` resolves `minimatch@10` → `brace-expansion@5.0.9`. Dropping `@eslint/eslintrc` (removed in v10) also took `js-yaml` with it, clearing GHSA-52cp-r559-cp3m (high) from all four clients as a bonus. The remainder are unrelated and tracked separately. `npm run ci` passes end to end — validate, the per-file ≥90 coverage gate, the build gate, all five smokes (including `smoke:tui`, run locally on a real TTY), and 462 Storybook tests. Also documents the effect rule in AGENTS.md and mirrors it into `.github/copilot-instructions.md`, since it is now something a reviewer cites. Claude-Session: https://claude.ai/code/session_01YAt8rqxysNbhYWLhoRm3fU Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: state useValueChange's referential-stability requirement (#1838) Copilot review on #1900: the hook compares with `Object.is`, so an object or array literal rebuilt in the component body compares unequal every render. Because the callback is what updates state, that is not merely a wasted render — it is an infinite loop. Every current caller passes a primitive (`boolean`, `string`) or an already- memoized value, so nothing changes behaviorally. But the hazard was implicit, and the next caller is the one it would bite. Documented on the hook itself, and one clause added to the AGENTS.md rule with its mirror in `.github/copilot-instructions.md`. No runtime guard: the requirement is the same one a `useEffect` dependency array carries, and here the failure is loud and immediate rather than silent. `npm run ci` passes. Claude-Session: https://claude.ai/code/session_01YAt8rqxysNbhYWLhoRm3fU Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(cli): stop asserting the --tool-args-json catch binding is an Error (#1838) Copilot review round 2 on #1900: the `--tool-args-json` parse handler formatted its message with `(e as Error).message`. A `catch` binding is `unknown`, so that cast is an assertion rather than a check — a thrown non-Error would render `--tool-args-json is not valid JSON: undefined`, which points the user at nothing. `JSON.parse` does only throw `SyntaxError` today, so this is not a live bug. But the cast is the kind AGENTS.md asks us not to leave unjustified, the same handler was already being touched here to add `{ cause: e }`, and every other site in this PR uses the guarded form. Now it does too. `npm run ci` passes. Claude-Session: https://claude.ai/code/session_01YAt8rqxysNbhYWLhoRm3fU Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: require useValueChange's onChange to be render-pure (#1838) Copilot review round 3 on #1900: `onChange` is called during render, so it is bound by render-phase purity — and the web client mounts under `StrictMode`, which deliberately double-renders in development. A callback doing anything external would run an unpredictable number of times, and concurrent React can abandon an in-progress render entirely. Every current caller already passes a callback that only calls `setState`, so nothing changes. But "runs during render" is the whole point of the hook and the constraint that follows from it was left implicit. Stated on the hook, with the same clause in the AGENTS.md rule and its `.github/copilot-instructions.md` mirror. All three now point at `NetworkEntry` as the worked example of the split: the reveal's force-open is a state update and lives in `useValueChange`, while its `requestAnimationFrame` scroll stays a `useEffect`. `npm run ci` passes. Claude-Session: https://claude.ai/code/session_01YAt8rqxysNbhYWLhoRm3fU Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a71e6d2012 |
Auth hardening CI gaps + SDK consolidation: EMA leg 2 / scopes (#1527) (#1725)
Some checks failed
Run install, format, lint, build, and test on every push / build (push) Has been cancelled
Run install, format, lint, build, and test on every push / publish (push) Has been cancelled
Run install, format, lint, build, and test on every push / publish-github-container-registry (push) Has been cancelled
Close #1527 acceptance gaps with automated tests (SEP-2468 iss, SEP-2207 offline_access, SEP-2351 discovery shapes, SEP-2352 AS-migration, EraNegotiationFailed→401 unwrap); lean EMA leg 2 and scope helpers on the v2 SDK; drop deprecated TS baseUrl. Reviewed LGTM. |
||
|
|
19a6df7ad3 |
SDK v2 migration (behavior-neutral, versionNegotiation: 'legacy') (#1624)
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>
|
||
|
|
a066873e37 | Fixes for remaining issues | ||
|
|
562bdd00f1 | All of the mid-session and step-up auth work | ||
|
|
13dafe457b |
Guided-auth removal, OAuth smoke testing, and TUI/CLI support (#1531)
* 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> |
||
|
|
5de4e911e3 |
feat(web,core): enterprise-managed authorization (EMA) (#1509) (#1512)
Adds enterprise-managed authorization (EMA): browser-side IdP login + RFC 8693 token-exchange and JWT-bearer resource-token mint, per-server EMA opt-in in Server Settings, IdP config in Client Settings, and IdP clientSecret keychain storage. Includes the v2/main merge and review follow-ups (400 on malformed client config; normalizeIdpIssuer import consolidation). |
||
|
|
0ead1deea6 |
feat(core): port v1.5 InspectorClient + transports + auth subsystem (#1302) (#1306)
* wip(core): bulk-copy v1.5 InspectorClient runtime files (#1302) Verbatim ports from origin/v1.5/main into v2/main core/: - logging/ (2 files) - storage/ + storage/adapters/ (4 files) - auth/ + auth/browser/ + auth/node/ + auth/remote/ (16 files) - mcp/config.ts + mcp/node/ (4 files, stdio transport) - mcp/remote/ + mcp/remote/node/ (12 files, HTTP/SSE transport) - mcp/inspectorClient.ts (~2169 LOC), messageTrackingTransport, fetchTracking, oauthManager, index, state/index Total: ~7,800 LOC of source files, copied unmodified via `git show origin/v1.5/main:<path> > <dest>`. Also: - Add deps: pino, hono, @hono/node-server, atomically, zustand, @types/node - tsconfig.app.json: extend paths to resolve new node-only deps from core/, add "node" to types, disable erasableSyntaxOnly (v1.5 code uses parameter properties — TODO to re-enable in follow-up) Compiles down to 47 type errors, all in three categories: - 24 TS2305: v2 dropped v1.5 type exports (InspectorClientOptions, CreateTransport, InspectorClientEnvironment, AppRendererClient, SamplingCreateMessage class, ElicitationCreateMessage class, jsonUtils helpers, OAuth events). Per user direction, will re-add these to v2's diverged files (types.ts, samplingCreateMessage.ts, etc.) so the ported code stays verbatim. - 15 TS7006: implicit-any params in v1.5 code (v1.5 tsconfig was looser than v2's noImplicitAny). Need explicit annotations. - 8 other TS errors: ../package.json import, Transport|null narrowing, hono Context generic mismatch, one unused parameter. Tests not yet ported (19 files, ~11,170 LOC). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * wip(core): integrate ported v1.5 InspectorClient + port tests (#1302) Source integration (typecheck + build now pass): - Re-add v1.5 types/classes dropped during v2 evolution: - types.ts: CreateTransport*, AppRendererClient, InspectorClient{Environment,Options} - samplingCreateMessage.ts / elicitationCreateMessage.ts: classes + keep v2's InspectorPending{Sampling,Elicitation} interface - inspectorClientEventTarget.ts: oauth* event keys - json/jsonUtils.ts: convertParameterValue / convertToolParameters / convertPromptArguments + ParameterSchema - inspectorClient.ts: replace `../package.json` import (v2 has no core/package.json) with a hardcoded identity constant - remote/node/server.ts: cast hono Context for streamSSE (hono's generic typing tightened since v1.5) - oauth-callback-server.ts: drop unused hostname field - tsconfig: enable @types/node, add paths for new bare-module deps (pino, zustand, hono, atomically) in app + test configs - vite.config.ts: mirror those module aliases for the unit project (which runs from repoRoot) Tests ported (19 files, ~11K LOC) under clients/web/src/test/core/: - All core/__tests__/auth/* (7 files) - inspectorClient.test.ts + 4 OAuth variants - mcp/oauthManager.test.ts, jsonUtils.test.ts - transport, remote-transport, remote-server-config, storage-adapters - helpers/oauth-client-fixtures.ts Bulk-rewrote `from "../mcp/…"` → `from "@inspector/core/mcp/…"` etc. Current state: - typecheck: 0 errors - build: passes (vite v8, 788 modules) - unit tests: 1014 / 1034 pass (937 pre-existing + 77 new core tests) - 20 failures in 13 files; failure modes: - 13 file-load failures needing the v1.5 `test-servers/` package (@modelcontextprotocol/inspector-test-server) which isn't in v2 yet or real fs/network not available in happy-dom - 7 assertion failures in discovery.test.ts + state-machine.test.ts (SDK 1.29 vs 1.25 API drift — mocks don't match new shape) Coverage gate not yet checked against new files. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * wip(core): port test-servers, more types, partial test integration (#1302) - Port v1.5 test-servers/ package (12 files, ~5K LOC) under repo-root/test-servers/, wire @modelcontextprotocol/inspector-test-server alias in both vite.config.ts (regex form) and tsconfig.test.json. - Add deps: express ^5, yaml ^2, @types/express ^5. - Re-export InspectorClientOptions/Environment/Transport* from inspectorClient.ts so v1.5 tests that import them from that module path keep resolving. - Switch InspectorClientEventMap to use SamplingCreateMessage / ElicitationCreateMessage classes (matches v1.5 event payload contracts) while keeping the InspectorPending{Sampling,Elicitation} interfaces in the *.ts modules for v2 consumers that still use them. - Fix verbatim-module-syntax issue in test-servers and drop unused stdio config field. - Adjust inspectorClient.test.ts task types to TaskWithOptionalCreatedAt (SDK 1.29 tightened Task shape vs v1.5's 1.25). - Drop spurious maxMessages/maxFetchRequests/maxStderrLogEvents from remote-transport.test.ts (never on InspectorClientOptions). State: - typecheck: 0 errors - build: passes - unit tests: 1099 / 1270 pass - 171 failures in 10 test files, almost all integration tests that need real HTTP/fs/network (happy-dom can't host them). Next: add a node-env vitest project to run these properly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(core): port v1.5 InspectorClient + transports + auth subsystem (#1302) Bulk-port of the v1.5 core runtime (~7,800 LOC source + ~16K LOC tests + ~5K LOC test-servers/) into v2. Files copied verbatim from origin/v1.5/main via `git show >`; only minimal adapters added on the v2 side. Subsystems landed ----------------- - core/logging/, core/storage/, core/auth/ (browser + node + remote) - core/mcp/ runtime: inspectorClient.ts (2169 LOC), oauthManager, messageTrackingTransport, fetchTracking, config, index - core/mcp/node/ (stdio transport) - core/mcp/remote/ + remote/node/ (HTTP/SSE transport + Hono server) - test-servers/ package (composable MCP test servers, fixtures, harness) v2 adapter layer ---------------- - types.ts: append CreateTransport*, AppRendererClient, InspectorClient{Environment,Options} (v2 had pruned these — v1.5 needs them) - samplingCreateMessage / elicitationCreateMessage: keep v1.5 classes AND v2's Inspector Pending* interfaces in the same files - inspectorClientEventTarget: add oauth* event keys; switch pending sampling/elicitation payloads to the classes (matches v1.5 contracts) - json/jsonUtils: append convertParameterValue/convertToolParameters/ convertPromptArguments + ParameterSchema - inspectorClient.ts: re-export Options/Environment/Transport* so tests that import these from `inspectorClient.js` keep resolving; replace `../package.json` import with a hardcoded identity (v2 has no core/package.json) - remote/node/server.ts: cast hono Context for streamSSE (hono generic tightened since v1.5) - oauth-callback-server: drop unused hostname field - tsconfig.app.json / tsconfig.test.json: add @types/node + paths for pino/zustand/hono/@hono/node-server/atomically/express/yaml, disable erasableSyntaxOnly (parameter-property syntax from v1.5) - vite.config.ts: regex-form module aliases (so the package's exports field handles subpaths like `hono/streaming`, `zustand/middleware`), alias for @modelcontextprotocol/inspector-test-server → test-servers/src Tests ----- - 19 v1.5 test files copied under clients/web/src/test/core/, imports rewired to @inspector/core/* and @modelcontextprotocol/inspector-test-server - 1004 unit tests pass; 298 storybook tests pass; coverage gate passes Known follow-ups (out-of-scope here, tracked in TODOs) ------------------------------------------------------ - 13 integration test files are excluded from the unit project (spawn real HTTP/stdio servers; run real OAuth flows; mock SDK auth → happy-dom CORS). Needs a node-env vitest project. Files listed under `test.projects[0].test.exclude`. - Source files exercised only by those integration tests are excluded from the coverage gate pending the node-env project. Listed under `test.coverage.exclude` with comment. - Manual smoke tests against real stdio / HTTP / OAuth server per the AC on #1302 still need verification locally. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs/fix: address #1306 review (AGENTS tree, #1307 links, @types/node, .substr) - AGENTS.md: expand the project-structure tree to include all v1.5-ported subsystems (core/auth/{browser,node,remote}, core/storage, core/logging, core/mcp/{node,remote/{,node}}, core/mcp/state) plus the new top-level test-servers/ directory (with a note about its @modelcontextprotocol/ inspector-test-server alias). - vite.config.ts: reference #1307 from both the test exclude block and the coverage exclude block so the next person editing either sees the marching order for shrinking the lists. - package.json: restore @types/node to ^24 (an earlier `npm install --save-dev @types/node@^22` accidentally pinned it lower than the repo was using; nothing here required ^22). - core/mcp/fetchTracking.ts: replace deprecated .substr(2, 9) with .slice(2, 11) to match the convention in core/mcp/inspectorClient.ts. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2eeab05d00 | ||
|
|
4429c0c0bf | ||
|
|
56ac83051b | ||
|
|
781aa69d19 | ||
|
|
7f27d7e9c7 | ||
|
|
65fd2b9e35 | ||
|
|
7ca753bc4c | ||
|
|
8beba25b58 | ||
|
|
2a0290df26 | ||
|
|
f8a46726b7 | ||
|
|
834fd2d77d |
||
|
|
a71e6d2012 |
||
|
|
19a6df7ad3 | ||
|
|
a066873e37 | ||
|
|
562bdd00f1 | ||
|
|
13dafe457b |
||
|
|
5de4e911e3 |
||
|
|
0ead1deea6 |