2,355 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2439ef9c3c |
web: disable Mantine transitions in tests to kill post-teardown timer flake (#1760) (#1761)
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
* web: disable Mantine transitions in tests to kill post-teardown timer flake (#1760) A Mantine `Transition`/`Modal` open/close `setTimeout` can fire after happy-dom tears down `window` at the end of a run, throwing an uncaught `ReferenceError: window is not defined` that fails the whole `coverage` job even when every assertion passed (seen on PR #1759's CI, originating in ServerRemoveConfirmModal.test.tsx). Set `env="test"` on the shared `renderWithMantine` MantineProvider so Mantine renders transitions synchronously (no timer). This removes the leak class for every test that renders through the shared wrapper (Modals, Menus, Popovers, …). Also apply it to ViewHeader's local forced-dark provider. The handful of tests that assert mid-flight transition state (ViewHeader's `data-anim="out"` crossfade checks, #1450) genuinely need real transitions, so add an opt-in `renderWithMantineTransitions` wrapper (env="default") and route those three tests through it — each already drives the transition to completion (waitFor/findBy) so it doesn't reintroduce the leak. Tests that assert only the entered state stay on the default (leak-free) wrapper. Full `npm run ci` green; unit run reports zero unhandled errors. Closes #1760 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * docs: note the renderWithMantine env=test invariant in AGENTS.md (#1760) Per review feedback: document that tests must render through `renderWithMantine` (env="test", transition-free) rather than a hand-rolled `MantineProvider`, and reserve `renderWithMantineTransitions` for asserting mid-flight animation state (driving the transition to completion). Keeps future tests from quietly reintroducing the post-teardown timer leak class. Refs #1760 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
0679b9c5ec |
web: de-flake fetchTracking redaction test — exclude volatile id from leak checks (#1757) (#1759)
The secret-redaction tests assert the serialized tracked entry does not contain
a secret value (e.g. `JSON.stringify(tracked[0]).not.toContain("shh")`).
Redaction works, but the entry carries a random request id
(`${timestamp}-${Math.random().toString(36)…}`) whose base36 tail can
coincidentally contain a short secret substring (`…sw7kshhf0` contains "shh"),
producing spurious failures that pass on re-run.
Add a `serializedWithoutId(entry)` helper that serializes the entry with `id`
overwritten to `undefined` (JSON.stringify drops undefined-valued keys), and
route all nine whole-entry `.not.toContain(secret)` leak checks through it. The
id is generated independently of any secret and can never legitimately carry
one, so excluding it keeps every deterministic field under the leak check while
removing the only source of nondeterminism. No production change.
Closes #1757
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
99ef731746 |
Refactor / justify codebase-wide 'as unknown as' casts (#1690) (#1756)
* core: refactor serverList.ts object↔Record casts into typed helper (#1690) Remove the four pre-existing `as unknown as Record<string, unknown>` / `as unknown as MCPServerConfig` casts in core/mcp/serverList.ts: - stripInspectorFields: rewrite as clone + `delete` over INSPECTOR_FIELD_KEYS. Every Inspector-extension key is optional on StoredMCPServer, so the result is still (a subtype of) MCPServerConfig with no cast. - oauth-strip in extractSecretsFromStored: `oauth` is optional, so a plain `delete stripped.oauth` is legal — drop the cast. - mcpConfigToServerEntries: route the one genuinely-needed widening through a single documented `toRecord(value: object)` helper. Taking `object` makes the single `as Record<string, unknown>` legal (no TS2352), so the double cast disappears from the call site. Net: zero `as unknown as` in serverList.ts; the one remaining structural cast lives inside the audited toRecord helper. Behavior-neutral — existing coverage (79 serverList tests) unchanged; full `npm run ci` passes. Closes #1690 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * core+web+test-servers: eliminate/justify remaining `as unknown as` casts (#1690) Codebase-wide follow-on to the serverList refactor: audit every `as unknown as` double cast outside tests and either remove it or ensure it carries a justifying comment (the review bar is "no *unjustified* double cast"). Removed (widened via a shared helper or narrowed to a single cast): - Promote the serverList `toRecord(value: object)` widening helper to core/json/jsonUtils.ts and reuse it at core/mcp/node/config.ts (normalize loop), core/mcp/remote/node/server.ts (pino level dispatch), and clients/web/src/App.tsx (window global lookup). - inspectorClient.rawWireRequest: type the frame as `JSONRPCRequest` and narrow only `params` with a single structural cast (the SDK's `_meta`-typed params is what blocked direct assignment). - toolOutputValidation: SDK `outputSchema` → `JsonSchemaType` is a single cast. - test-server-fixtures: the optional `task` handle is reachable with a single cast (target field is optional). Justified in place (genuinely unavoidable — private SDK internals, partial interface stubs, third-party/nominal bridges): the inspectorClient private-map accesses, resourceContext + tokenAuthProvider partial `OAuthClientProvider` stubs, the ext-apps peer bridge, modern/legacy `_requestHandlers` bypasses, and the happy-dom `matchMedia` test mock. Behavior-neutral. Full `npm run ci` passes (validate, ≥90 coverage gate, smoke, Storybook). Refs #1690 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * address @claude review nits: consistent cast justifications + drop reflow noise (#1690) - test-servers/src/modern-tasks.ts: add an inline justification above the `_requestHandlers` double cast, mirroring the sibling in composable-test-server.ts (was leaning on the function JSDoc only). - Annotate the partial-`AppBridge` mock double casts in the story fixtures (AppsScreen ×2, AppRenderer, InspectorView) so no non-test-file double cast is unjustified. - inspectorClient.ts: revert unrelated Prettier reflow hunks (protocolVersion, task-content array, timeout-reject wrap, reconnect guard) so the diff is the single rawWireRequest cast change only. Behavior-neutral (comment/format-only). `npm run ci` green apart from a known flaky redaction test whose assertion collided with a random request id. Refs #1690 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * actually revert the reflow noise this time (#1690) The reflow reverts in cc187ea were silently undone by a `prettier --write` I ran on the same file right after, so the four Prettier-only hunks from 6a3afa4a were still in the diff (caught in re-review). Revert them for real now — and without re-running Prettier on the file: - core/mcp/inspectorClient.ts: restore the protocolVersion line, task-content array, timeout-reject call, and reconnect guard to their origin/v2/main form, so the file's diff is only the single rawWireRequest cast change. - test-servers/src/modern-tasks.ts: restore the ModernTaskStatus union to its one-line form (the leading-`|` reflow was also Prettier churn), leaving only the new `_requestHandlers` justification comment. Formatting-only; `npm run validate` green. Refs #1690 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
e788bcbaca |
web: pin Connection Info modal header while body scrolls (#1754) (#1755)
Same fix as #1698, applied to the Connection Info modal, which had the same pattern: a custom header Group (title + close) inside a Stack in the modal body, using the default `<Modal>` with no `scrollAreaComponent`. With a long server `instructions` section (e.g. the everything server) the details overflow the viewport and the whole modal scrolled, taking the header out of view. Switch to the compound `Modal.Root`/`Content`/`Header`/`Body` API, move the header into `Modal.Header` (sticky by design) and set `scrollAreaComponent={ScrollArea.Autosize}` so only the body scrolls. The fade-down transition is supplied app-wide by `ThemeModalRoot` (added in #1753), so no `transitionProps` literal is needed here. Closes #1754 Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
092165476e |
web: pin Server/Client Settings modal header while body scrolls (#1753)
* web: pin Server/Client Settings modal header while body scrolls (#1698) Expanding enough accordion sections grew the Server Settings modal past the viewport, so the whole modal scrolled and carried the header (title + close) out of view. The header was a custom Group inside the modal body, and the modal used no `scrollAreaComponent`, so overflow was not confined to the body. Switch both the Server Settings and Client Settings modals from the default `<Modal>` (which always wraps children in `Modal.Body`) to the compound `Modal.Root`/`Modal.Content`/`Modal.Header`/`Modal.Body` API, moving the toggle/title/close row into `Modal.Header` (sticky by design) and setting `scrollAreaComponent={ScrollArea.Autosize}` so only the body scrolls. `Modal.Root` keeps the close-on-escape / close-on-click-outside / focus-trap / scroll-lock / portal defaults; `transitionProps` restores the fade-down animation the `<Modal>` wrapper otherwise supplies. Client Settings does not yet have enough content to scroll but is kept consistent per the issue. Closes #1698 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * docs(pr-screenshots): Server Settings modal sticky-header proof (#1698) Two frames from the ServerSettingsModal story (all sections expanded, short viewport): header pinned at the top of the scroll and again scrolled down, showing it stays put while only the body scrolls. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * web: move Modal.Root fade-down transition into the theme (#1698 review) Addresses the review note on PR #1753: the `transitionProps={{ transition: "fade-down", duration: 200 }}` literal was duplicated in both settings modals to restore the animation `<Modal>` defaults to but `Modal.Root` doesn't inherit. Move it to a `ThemeModalRoot` (`Modal.Root.extend`) registered under the `ModalRoot` theme key, mirroring the existing `ThemeScrollAreaAutosize` precedent. Both call sites drop the literal; any future compound `Modal.Root` now animates consistently with every plain `<Modal>` from one source of truth. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c2b866110d |
tui: detect array-of-enum on items.enum alone (#1752)
* tui: detect array-of-enum on items.enum alone (#1751) The TUI schemaToForm array-of-enum branch was nested under an outer `if (property.enum)` guard, so a standard array-of-enums schema — `{ type: "array", items: { enum: [...] } }` with no top-level `enum` — never reached it and fell through to a plain string input. The web SchemaForm already guards on `items?.enum` alone; this aligns the TUI with it. Restructure the enum handling so the array-of-enums case keys on `property.type === "array" && property.items?.enum` independently of a top-level `enum`, falling back to the single-select `property.enum` branch otherwise. The enumNames labeling from #1691 applies unchanged. Tests: the array-of-enum cases now use the standard `items.enum`-only shape (dropping the redundant top-level `enum` the old nesting required), plus a case asserting a redundant top-level `enum` still works. Closes #1751 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * docs(pr-screenshots): TUI array-of-enum before/after proof shots (#1751) Before/after of the TUI tool form for an array-of-enums field with no top-level `enum`: before the fix it renders as a plain text field (raw "m"); with the fix it renders as a select resolving the enumNames title ("Medium"). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
8c41dcd383 |
web+tui: honor enumNames in enum forms (#1750)
* web+tui: honor enumNames in enum forms (#1691) Legacy titled enums (`enum` + non-standard `enumNames`) rendered their raw wire values instead of the human-readable titles in both the web SchemaForm and the TUI schemaToForm builder, so a user picking "pet-1" had no idea it meant "Cats". Web (SchemaForm.tsx): extract a `toEnumData` helper that zips `enum` with `enumNames` into `{ value, label }[]` when the lengths match, falling back to bare values otherwise. Use it in both the string-enum and array multi-select branches (the latter previously inlined the same zip). TUI (schemaToForm.ts): add `enumNames` / `items.enumNames` to the property interface and a `toSelectOptions` helper used by the single-select and array-of-enum branches, labeling by `enumNames[i]` when length-matched and falling back to `String(val)`. Both guard on the arrays being the same length: a wrong-length zip would mislabel options, which is worse than showing raw values. Raw values are always submitted; only the display label changes. Closes #1691 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * docs(pr-screenshots): TUI enumNames before/after proof shots (#1691) Before/after captures of the TUI tool-test form (schemaToForm -> ink-form) against a legacy titled enum: with the fix the field resolves pet-1 -> "Cats"; before, it showed the raw "pet-1". Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
402d7b204d |
Server Settings: disambiguate EMA OAuth credential fields (#1692) (#1749)
* Server Settings: disambiguate EMA OAuth credential fields (#1692) Under enterprise-managed authorization the OAuth Settings "Client ID" / "Client Secret" fields hold the *resource authorization server* credentials (EMA leg 3 — the Test Client from the xaa.dev resource registration), not the app/IdP pair configured in Client Settings. The unqualified labels sat under a toggle naming the enterprise IdP, pulling readers toward the wrong pair. - When enterpriseManaged is on, relabel to "Resource AS Client ID" / "Resource AS Client Secret" and add a description naming the resource authorization server and pointing the app pair back to Client Settings. - Document the Scopes delimiter: space-separated (RFC 6749), with a warning that commas produce one invalid token, plus a placeholder example. Adds tests for both label states and the scopes hint, plus an EnterpriseManagedOAuth story. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * pr-screenshots: OAuth EMA field disambiguation proof shots (#1692) Add before/after screenshots of the Server Settings → OAuth Settings fields (EMA off = plain Client ID/Secret; EMA on = Resource AS Client ID/Secret) plus the new space-separated Scopes hint, with a README section documenting them. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Address review: genericize EMA copy, reuse enterpriseManaged const (#1692) - Drop the hardcoded xaa.dev staging host from the resource-AS field description (it's a general EMA concept, not env-specific); phrase it as "the resource authorization server's registered client credential (EMA leg 3)". Also trims the copy's overlap with the checkbox description. - Reuse the existing `enterpriseManaged` const for the EMA Checkbox's `checked` prop instead of recomputing `settings.enterpriseManaged ?? false`. - Update the test assertion and pr-screenshots README/EMA-on screenshot to match the new copy. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Client Settings: mirror EMA disambiguation (IdP-qualified labels) (#1692) The Server Settings OAuth fields now label the leg-3 resource-AS pair explicitly; make Client Settings parallel so each modal names its pair and points at where the other lives. - Relabel the EMA credential fields to "IdP Client ID" / "IdP Client Secret" (parallel to Server Settings' "Resource AS Client ID/Secret"), so the label alone disambiguates without relying on the description. - Add the reciprocal cross-reference to each description: these are the enterprise IdP pair (EMA legs 1–2), not the per-server resource authorization server credentials, which go in Server Settings → OAuth Settings. - Update ClientSettingsForm/ClientSettingsModal tests for the new labels; add a pr-screenshots section + Client Settings screenshot. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c51d6aabd2 |
Extensions UI: own Advertised Extensions section + 'Client Advertised Extensions' heading (#1748)
* Settings: move Advertised Extensions to its own accordion section (#1747) The Advertised Extensions control (added in #1739) lived inside the Options accordion panel, wedged between "Fetch Lists One Page at a Time" and "Network Log Size". Promote it to its own top-level "Advertised Extensions" accordion section (after Options), so the distinct concept — which extensions the Inspector advertises to this server in its client capabilities — reads as its own collapsible section. - ServerSettingsForm: add "extensions" to ServerSettingsSection; move the checkbox group into a new Accordion.Item value="extensions". Its description moves up into the panel; no behavior change (still folds into settings.advertisedExtensions via onAdvertisedExtensionChange). - ServerSettingsModal: add "extensions" to allSectionsFor so expand/collapse-all covers it. - Tests: the toggle now lives under the new (collapsed-by-default) section, so the form tests render it expanded and the modal tests open it first. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Connection Info: rename "Advertised Extensions" heading to "Client Advertised Extensions" Disambiguates the client-side column from the adjacent "Server Extensions" column and from the (separate) Server Settings "Advertised Extensions" section. Label-only change; the value still renders clientCapabilities.extensions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * docs: refresh Connection Info screenshot for the renamed heading Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * docs: refresh #1747 proof screenshots (fresh capture) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Settings: add AdvertisedExtensions Storybook story (review) Address the #1748 review's optional note: the new Advertised Extensions accordion section had no story expanding it, so it wasn't documented in the visual catalog (the default story expands only ['options']). Add an AdvertisedExtensions story with expandedSections: ['extensions'] and a per-server override, showing the default-on and toggled-off checkbox states. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
e2c75da5ce |
Extensions: Connection Info both eras + io.modelcontextprotocol/ui advertisement (Phase 3 of #1633) (#1744)
* Extensions: Connection Info both eras + io.modelcontextprotocol/ui advertisement (Phase 3 of #1633) Completes the Extensions surfacing and advertises the MCP Apps UI extension. core/mcp/extensions.ts: - Add UI_EXTENSION_KEY ("io.modelcontextprotocol/ui") and MCP_APP_MIME_TYPE ("text/html;profile=mcp-app") — mirrors of ext-apps' EXTENSION_ID / RESOURCE_MIME_TYPE (hardcoded: the constants live on the package's /server subpath or an extensionless re-export that doesn't resolve under NodeNext). - Registry entries gain an optional `advertisement` value; the builder stamps it (default {}). Add the `ui` entry, default-advertised with `{ mimeTypes: ["text/html;profile=mcp-app"] }` — the Inspector always renders App tools. No ext-apps cast needed: the extensions value type is `object`. ConnectionInfoContent: - Source server extensions from the negotiated `capabilities.extensions` (getServerCapabilities, era-transparent) instead of the modern-only discoverResult, so a LEGACY server advertising extensions renders them too. - Add a two-column "Server Extensions" / "Advertised Extensions" section (the Inspector's own advertised map), mirroring the server/client capability columns. Drop the now-redundant Extensions row from the modern Discovery section (Supported versions stays). Tests: extensions.test.ts covers the ui entry + advertisement value and the per-extension enable/disable; ConnectionInfoContent.test.tsx covers legacy server extensions (no discovery), the advertised-extensions column, and the em-dash empty states. npm run ci green (the one InspectorView localStorage failure is a known flake, passes on re-run). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Extensions: address Phase 3 review (mime drift-guard, story, exact em-dashes) - extensions-mimetype.test.ts (integration/node): assert MCP_APP_MIME_TYPE equals ext-apps' real RESOURCE_MIME_TYPE (imported) and that the client advertises it — a genuine drift guard, replacing the tautological literal check in the unit test (which now just asserts the type). - ConnectionInfoContent Modern story: carry the tasks extension on initializeResult.capabilities too, so the Server Extensions row demonstrates it (the row now reads negotiated server capabilities, not discoverResult). - Tighten the em-dash assertions from ">= N" to exact counts (3/3/2) now that the two extension sections em-dash by default. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Extensions: drift-guard UI_EXTENSION_KEY against ext-apps EXTENSION_ID (review) Address the Phase 3 re-review nit: the extension id was the other half of the same drift risk (a server keys its Apps lookup on it). Import ext-apps' EXTENSION_ID from the /server subpath in the node integration test (fine there; the browser build is what avoids /server) and assert UI_EXTENSION_KEY === it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Extensions: clone the registry advertisement so builds never alias it (review) Address the Phase 3 re-review's optional defensive nit: buildClientExtensions returned the same object reference as the ADVERTISABLE_EXTENSIONS entry for the ui advertisement, so an in-place mutation of a stamped value (e.g. extensions[ui].mimeTypes) could corrupt the registry for later connections. structuredClone the advertisement on stamp; add a test asserting a mutated build doesn't leak into the next one. Nothing mutates it today — purely defensive. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Extensions: add #1740 Connection Info proof screenshots End-to-end smoke test in the web client against the legacy advertised-extensions-http.json server: the Connection Info modal shows the new era-transparent Server Extensions / Advertised Extensions section, with Advertised Extensions listing io.modelcontextprotocol/tasks + io.modelcontextprotocol/ui (the new ui advertisement), and only ui after toggling Tasks off and reconnecting. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
fe295396af |
Extensions: advertised-extensions toggle in Server Settings (Phase 2 of #1633) (#1743)
* Extensions: advertised-extensions toggle in Server Settings (Phase 2 of #1633) Adds the debugging knob from #1633: users choose which extensions the Inspector advertises to a server, and a server may register different tools in response. Builds on Phase 1's registry + advertisedExtensions option (#1738). Settings + persistence (core): - InspectorServerSettings.advertisedExtensions + StoredMCPServer field, wired through serverList's read/write converters and INSPECTOR_FIELD_KEY_MAP (omit-when-empty, byte-stable round-trip). - validateSettings (remote PUT route): isBooleanRecord guard + carry a non-empty map; unknown/malformed rejected. - App.tsx threads savedSettings.advertisedExtensions into the client options. UI: - ServerSettingsForm renders an "Advertised Extensions" group of checkboxes driven by ADVERTISABLE_EXTENSIONS; a per-server override wins over the registry default. Modal folds a toggle into settings.advertisedExtensions. Test server + proof: - composable-test-server extensionGatedTools: a tool registered disabled and enabled on `initialized` iff the client declared the mapped extension (getClientCapabilities). Legacy stateful leg. Threaded through load-config/resolve-config; advertised-extensions-http.json showcase + README. - Integration test connects legacy with/without the tasks extension advertised and asserts the gated tool appears/disappears in tools/list — the acceptance criterion, proven end to end, not asserted. Tests: serverList round-trip (non-empty/absent/empty), ServerSettingsForm toggle rows, ServerSettingsModal fold, validateSettings accept/reject, and the integration gating test. npm run ci green (per-file ≥90 gate held). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Extensions: add #1739 proof screenshots (advertised-extensions toggle) End-to-end smoke test in the web client against the legacy advertised-extensions-http.json test server (port 3220): connecting with the Tasks extension advertised (default) lists echo + get_weather, unchecking the Advertised Extensions → Tasks toggle and reconnecting lists only echo — the server's tool registration changing on the client-declared extension. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Extensions: reconverge advertisedExtensions to no-override on default (review) Address Phase 2 review observation #1: toggling an extension back to its registry default now deletes the override key (and drops the whole map to undefined when it empties), so the on-disk `advertisedExtensions` reconverges to the byte-stable "no override" state instead of persisting a redundant `{ key: <default> }`. Matches the omit-when-default policy the other settings use. Only a value that differs from the default is persisted. Adds a modal test asserting the reconvergence (re-checking the sole disabling override drops advertisedExtensions to undefined). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
2f80958d3f |
Extensions: first-class advertised-extensions map (Phase 1 of #1633) (#1742)
* Extensions: first-class advertised-extensions map (Phase 1 of #1633) Collapse the two ad-hoc `capabilities.extensions` spreads in the InspectorClient constructor (EMA, gated on enterpriseManaged; the modern Tasks extension, always-on) into a single builder that is the source of truth for what the client advertises. - core/mcp/extensions.ts: `ADVERTISABLE_EXTENSIONS` registry (id + label + default-on flag), the `EMA_EXTENSION_KEY` constant, and `buildClientExtensions()`. The registry is the shared list the Server Settings toggle UI (Phase 2, #1739) will drive; EMA stays a conditional built-in (auth-mode driven, not a free toggle). - InspectorClientOptions.advertisedExtensions: per-extension overrides (present key wins over the registry default) so a future UI can toggle advertised extensions as a debugging knob. - Behavior preserved: tasks defaults to advertised, so the map is non-empty and rides every modern request envelope; EMA advertised iff enterpriseManaged. The raw modern-task envelope re-stamp is left untouched (it fires only on the raw tasks channel, which requires the extension anyway). Tests: extensions.test.ts covers the registry and every builder branch (defaults, EMA on/off, override enable/disable, unknown key) at 100%. The tasks-era and EMA integration suites still assert the same wire advertisement. `advertisedExtensions` is plumbed but not yet wired to the form (that lands in Phase 2). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Extensions: address Phase 1 review (EMA-override test + envelope comment) - extensions.test.ts: add an EMA-specific assertion that an override for EMA_EXTENSION_KEY cannot advertise it (auth-mode only) — documents intent and guards against EMA being added to ADVERTISABLE_EXTENSIONS by mistake. - inspectorClient.ts: rewrite the withModernTaskEnvelope tasks-stamp comment so it stays accurate once Phase 2 lets a user disable general tasks advertisement — the force-stamp is load-bearing (the raw tasks/* channel requires the extension), not a redundant re-add. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * TUI: de-flake step-up OAuth frame assertions (raise poll ceiling) The step-up authorize tests intermittently timed out on the final "Step-up authorization succeeded" frame. Root cause is a test-infra timing budget, not product code: waitForFrame/waitUntil polled only 25× (25ms tick ≈ 625ms), and the step-up flow adds an extra async OAuth-runner hop plus React commits that can exceed that under CI load with v8 coverage. Hoist the poll count into a named POLL_TRIES = 100 (~2.5s ceiling). A poll exits the instant its predicate is true, so a higher ceiling never slows a passing assertion — it only widens the margin for the slow path. Verified 5× green (3 plain + 2 under --coverage), including the previously-flaky step-up cases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * TUI: prettier-format the wrapped waitForFrame signature The POLL_TRIES change pushed waitForFrame's signature past the print width; Prettier wraps it. (Root has no aggregate format script — format is per-client, so this was missed locally.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
e70a11360c |
Tools: x-mcp-header annotation tooling + -32602 unknown-tool rendering (#1632) (#1735)
* Tools: x-mcp-header annotation tooling + -32602 unknown-tool rendering (#1632) Card 9/10 of the SDK-v2 + New Spec stack. Adds SEP-2243 `x-mcp-header` tooling to the Tools tab and adjusts result rendering for SDK v2's unknown-tool behavior, and bumps the SDK to 2.0.0-beta.5. - **`core/json/xMcpHeader.ts`**: a faithful port of the SDK's (non-public) `scanXMcpHeaderDeclarations` — validates every SEP-2243 constraint (primitive types incl. `number`, RFC 9110 token, case-insensitive uniqueness, statically-reachable `properties`-only chain) and derives the mirrored `Mcp-Param-{Name}` params for a kept tool. - **Excluded tools**: the SDK silently drops tools with invalid annotations from `tools/list`. InspectorClient now re-lists the RAW (unfiltered) list and scans it to recompute the excluded set with reasons — gated to the modern non-stdio era (matching the SDK's gate), recomputed on every aggregate refresh, surfaced via a new `excludedToolsChange` event + `getExcludedTools()` and `useInspectorClient().excludedTools`. The Tools sidebar lists them struck-through under an "Excluded (SEP-2243)" divider with the reason on hover. - **Mirrored args**: ToolDetailPanel shows a "Mirrored request headers" section listing each arg → `Mcp-Param-{Name}` (with the browser-omits note). - **-32602 rendering**: SDK v2 rejects an unknown-tool call with `-32602` instead of an `isError` result, which previously rendered nothing. New ToolCallErrorPanel renders a thrown tool-call error, with a targeted "Unknown Tool" hint for `-32602`. Adds unit + Storybook + integration coverage (a real modern server with an invalid-header tool fixture) for every path. `npm run ci`-equivalent checks green. Closes #1632 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * test-servers: x-mcp-header modern showcase config + preset (#1632) Register the `invalid_header_tool` fixture as a preset and add `xmcpheader-modern-http.json` — a modern HTTP showcase serving echo, a valid-header `get_weather`, and the invalid-header tool — so the Tools-tab x-mcp-header tooling (mirrored headers, excluded-tools surface, -32602 rendering) can be exercised manually. Documented in the README alongside the other per-SEP showcase configs. Smoke-tested end to end in the web client. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * docs: add #1632 proof screenshots to pr-screenshots End-to-end web-client verification of the x-mcp-header Tools tooling against the modern showcase server: excluded-tools surface + reason, mirrored-headers section, and the -32602 unknown-tool error panel. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Respond to review: disambiguate -32602, doc/search nits (#1632) Addresses the review on PR #1735: 1. -32602 "Unknown Tool" mislabel: `-32602` is generic Invalid params, so a known tool called with bad arguments hit the same code and rendered under the definitive "Unknown Tool" heading. Replace `isUnknownToolError` with `classifyToolCallError(code, message)` → "unknown-tool" only when the message names it (not found / unknown tool / …), else "invalid-params" (new "Invalid Parameters" heading + schema hint), else "generic". 2. Double pagination walk: expand the `listAllTools` comment to document that the excluded-tools recompute is a deliberately un-cached second raw walk (doubles list round-trips on modern non-stdio, runs even on a cache-served aggregate) — accepted for a debugging tool, no-op on legacy/stdio. 3. Doc nit: `scanXMcpHeaderDeclarations` no longer claims to visit "$ref targets" — reworded to note `$ref` is never followed (refs resolve into the unreachable `$defs` body). 4. Search nit: the excluded-list filter now matches name AND title, matching the main tools filter. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Tighten unknown-tool matcher to be tool-scoped (#1632 re-review nit) The re-review noted the -32602 disambiguation was symmetric: broad markers ("does not exist", "unrecognized") could inversely mislabel a genuine invalid-params message (e.g. "property 'region' does not exist") as "Unknown Tool". Scope the not-found/does-not-exist family to require the word "tool" in the same clause (matching the SDK's "Tool <name> not found"); keep "unknown tool" / "no such tool" as standalone unambiguous markers. Adds a tool-less-"does not exist" test asserting it classifies as invalid-params. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Add trigger_invalid_params showcase tool + Invalid Parameters screenshot (#1632) Adds a `trigger_invalid_params` spec-error trigger to the modern showcase server: the injector answers its `tools/call` with a real `-32602 Invalid params` JSON-RPC error whose message does NOT name an unknown tool. This lets the "Invalid Parameters" branch of the tool-call error panel (added in the review response) be exercised live, distinct from the unknown-tool `-32602`. - `SPEC_ERROR_TRIGGERS` gains the `trigger_invalid_params` entry (HTTP 200, in-band -32602) + preset registration. - Enable `injectSpecErrors` on `xmcpheader-modern-http.json` and add the tool. - README + pr-screenshots README document it; adds the live "Invalid Parameters" proof screenshot. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
2259b1a3e1 |
Tasks: io.modelcontextprotocol/tasks extension on modern servers (era fork) (#1731)
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
* Tasks: modern io.modelcontextprotocol/tasks extension (era fork core) Core + test-server implementation of the SEP-2663 tasks extension for modern (2026-07-28) connections, forked from the legacy 2025-11-25 tasks path. SDK v2 removed all tasks support AND era-gates the tasks/* spec methods out of the modern era on both the client (outbound) and server (inbound) sides, and its codec rejects a resultType:"task" result outright. So the Inspector drives the extension itself: task creation rides tools/call (a modern method) with the resultType:"task" frame rewritten at the transport to a CallToolResult carrying the handle in _meta; tasks/get/update/cancel go over a raw-wire request channel that bypasses the era gate (full modern envelope, string ids, consumed by the transport). input_required tasks reuse the existing pending-request modal via fulfilInputRequests, answered with tasks/update. No tasks/list, no tasks/result. Tab + run-as-task gate on the negotiated extension; the managed task store's refresh re-polls known handles on modern (no server list). Test server serves the extension via an Express interceptor (SDK can't) + a shared poll-driven runtime. Full integration coverage of both eras. Refs #1631 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Tasks modern era: tests + coverage (raw-wire, schemas, era integration) Adds unit coverage for modernTaskSchemas and the raw-wire request channel (transport-null guard, send failure, timeout, error response, teardown), plus end-to-end era integration tests (create/poll/input-required/update/cancel, synchronous passthrough, unknown-id error, modern store re-poll). Fixes the App and constructor-capabilities tests for the always-on tasks extension. Refs #1631 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Tasks modern era: docs + share test servers per era to de-flake coverage Document the tasks era-fork test configs in README and the new core/test-server files in AGENTS.md. Share one modern + one legacy test server across the era integration block (started lazily, stopped in afterAll) — starting a fresh Express server per test made connection negotiation flaky under the heavy concurrent coverage run. Refs #1631 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Restore pr-screenshots deleted inadvertently The logging/subscriptions PR screenshots + README were staged-deleted in the working tree before this branch; restore them from v2/main so this PR doesn't remove other PRs' artifacts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Tasks modern era: offer Run-as-task for any tool + PR screenshots On modern (SEP-2663) task creation is server-directed — the client declares the extension once and any tool may return a task — so the Tools screen now offers 'Run as task' for every tool on a modern connection (threaded via a modernTasks prop through InspectorView → ToolsScreen → ToolDetailPanel), rather than gating on the legacy per-tool taskSupport. Verified end-to-end in the web UI (remote proxy): create → poll tasks/get → input_required elicitation → tasks/update → inline result, plus the unchanged legacy tasks/list/result flow. Adds proof screenshots. Refs #1631 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Tasks modern era: address two follow-ups (task-input note + ordinary-path auto-poll) 1. The pending-request note now distinguishes a modern task round from an MRTR round: a new 'task-input-required' PendingRequestOrigin threads through fulfilInputRequests → the pending-request modal → MrtrOriginNote, which shows 'submitted via a tasks/update request (SEP-2663), not a retry' instead of the MRTR retry copy. 2. The ordinary (non-run-as-task) modern callTool path now auto-polls an unsolicited task handle: attemptToolCall detects the rewritten handle in the result _meta and drives it to termination (get → input_required/update → inline result), dispatching task events so the Tasks tab + toasts track it and the tool result resolves to the task's final result. The poll loop is shared with the streaming path via extracted helpers (submitModernTaskInput / modernTaskTerminalOutcome / modernTaskPollInterval). Refs #1631 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Tasks modern era: address review (negotiated envelope version, input-round cap, clarifying comment) + fresh smoke screenshots Review of #1731: 1. withModernTaskEnvelope now stamps the NEGOTIATED protocol version (getProtocolVersion() ?? MODERN_PROTOCOL_VERSION) so the envelope matches the MCP-Protocol-Version header instead of a hardcoded constant. 2. A never-completing input_required task is now bounded by MRTR_MAX_ROUNDS in the shared submitModernTaskInput (threaded through both poll loops), so a non-conformant server can't re-prompt the user forever. New modern_loop_task test-server tool + integration test cover the cap. 3. ToolDetailPanel: comment clarifying that per-tool taskSupport:"forbidden" is deliberately ignored on modern (task creation is server-directed under SEP-2663). (The abort-during-poll observation is addressed in the PR reply: the cancel path uses tasks/cancel and disconnect rejects the in-flight raw request, so the poll stops; matches legacy.) Fresh web-UI smoke screenshots (modern connected, task completed, input_required with the corrected tasks/update note, input completed). Refs #1631 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Tasks modern era: fix uncancelable input-required loop + sticky cancel Two bug fixes reported during manual testing of the tasks era-fork (#1631): 1. A modern task paused at `input_required` (e.g. `modern_loop_task`, which never advances) could not be cancelled: the poll loop blocked on the pending-request modal, so `cancelRequestorTask` sent `tasks/cancel` but the modal kept re-prompting. Now each modern poll loop registers a per-task AbortController (`registerTaskInputAbort`), `cancelRequestorTask` aborts it to reject the pending elicitation and unblock the poll, and the embedded input requests are tagged with their owning task id (`RELATED_TASK_META_KEY`) so the pending-request UI's Cancel action cancels the TASK rather than sending a "cancel" answer the server would re-prompt on. 2. On the legacy server, cancelling a task briefly showed CANCELLED then flipped to COMPLETED when the in-flight tool call resolved. Cancellation is a deliberate terminal decision, so `ManagedRequestorTasksState` now remembers cancelled task ids and ignores any later non-cancelled status update. Also: move the compact task id onto its own line in the embedded monitoring sidebar so the status badge / Cancel Task control don't wrap; DRY the duplicated task poll-interval logic into a shared `taskPollInterval` helper. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Refresh tasks PR screenshots + capture both bug-fix scenarios Recapture the five tasks flow screenshots against the current build and add two demonstrating the post-review fixes: modern_loop_task cancelled from the pending-request modal (uncancelable-loop fix) and a legacy task staying CANCELLED after the in-flight call resolves (sticky-cancel fix). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Address re-review: sticky cancel on legacy refresh + App cancel-routing test Re-review of the cancelable-loop / sticky-cancel fix raised two non-blocking hardening items: 1. The legacy tasks/list refresh() rebuilt the list from the server, honoring only dismissedTaskIds — so a cooperative-cancel server that re-lists a user-cancelled task as completed could un-stick the cancel on a manual Refresh. The refresh path now pins a cancelledTaskIds task to "cancelled" (keeping it visible-as-cancelled, matching the live-merge handlers) rather than dropping it. Covered by a new state test. 2. Added an App.test.tsx case locking in the user-facing half of the fix: Cancel on a task-input-required elicitation calls cancelRequestorTask (not respond). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
320daf2f67 |
Resources: subscriptions/listen model on modern servers (era fork) (#1630) (#1730)
Fork resource subscriptions by protocol era: legacy keeps resources/subscribe; modern (2026-07-28) models subscriptions as a filter over one long-lived subscriptions/listen stream with reconnect-by-re-listen (capped backoff), an optimistic Connecting→Listening header badge, and a fix so the browser proxy no longer blocks listen() ~60s. Closes #1630. |
||
|
|
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. |
||
|
|
ec185f1053 |
Logging: per-request log level on modern servers (era fork) (#1629) (#1724)
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
* Logging: per-request log level on modern servers (era fork) (#1629) On modern (2026-07-28) servers `logging/setLevel` and the session-scoped level are gone: the client opts into logs per request via the `io.modelcontextprotocol/logLevel` `_meta` key, and the SDK does not attach it automatically, so server logs are silently absent until stamped. Core (`InspectorClient`): - `setModernLogLevel(level | undefined)` / `getModernLogLevel()` store the opt-in level; `mergeMeta` stamps it on every outgoing request, gated on the negotiated modern era so legacy connections are untouched. - Cleared on disconnect so it can't leak into the next connection. Web UI: - `LogControls` forks on `protocolEra`: legacy keeps the `logging/setLevel` selector + Set button; modern shows a per-request opt-in Select (Off + levels, applied immediately) with copy explaining logs arrive on the originating request's stream. - Threaded `protocolEra` / `modernLogLevel` / `onSetModernLogLevel` through LoggingScreen and InspectorView from App. Tests: unit coverage for the modern LogControls/LoggingScreen fork; integration coverage proving the `logLevel` `_meta` is stamped on a real modern server once opted in (and not on legacy). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * test-servers: logging era-fork showcase configs + proof screenshots (#1629) Add two manual-testing configs and README docs for the logging era fork: - logging-legacy-http.json: plain streamable-HTTP server with `logging: true` and a `send_notification` tool. The Logs tab shows the legacy session-scoped "Set Active Level" selector + Set button; calling the tool streams logs. - logging-modern-http.json: same, with `transport.modern: true`. Connect with Protocol Era = Modern and the Logs tab shows the per-request opt-in control; the client stamps `_meta["io.modelcontextprotocol/logLevel"]` on every request (visible in the Network tab request body). pr-screenshots/ documents the verified behavior end-to-end against both real servers: the legacy control with logs streaming, the modern per-request control, and the modern tools/call request body carrying the stamped logLevel _meta. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * test-servers: send_notification emits via per-request notifier so modern logs stream (#1629) The `send_notification` tool sent its `notifications/message` through the global `server.server.notification()`. On the modern (2026-07-28) stateless leg that isn't tied to the in-flight request, so the SDK answered the tool call with plain JSON and dropped the log — nothing reached the Inspector's Logs panel. Prefer the per-request `extra.sendNotification` (rebuilt from `ctx.mcpReq.notify`): scoped to the current request, it makes the SDK upgrade the response to SSE and stream the log back on the originating request's stream. Falls back to the global notifier (legacy path / no per-request context). Verified end-to-end: opting in via the Logs-tab "Log Level per Request" control and calling send_notification now streams the entry on the modern server; legacy behavior unchanged (server-notification integration tests green). README documents the modern logging showcase and the caveat that this test server streams whenever a tool emits — the SDK beta strips the per-request envelope from the handler context, so the tool can't gate on the client's logLevel opt-in (the client-side `_meta` stamp #1629 owns is still fully visible in the Network tab). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * test-servers: send_notification uses SDK request-scoped log() → spec-faithful modern gating (#1629) The previous fix routed `send_notification` through `extra.sendNotification` (`ctx.mcpReq.notify`), which streams on the modern leg but emits unconditionally — the log showed up even when the client hadn't opted in via the per-request `logLevel`. The SDK already provides the correct primitive: `ctx.mcpReq.log(level, data, logger)` (added in `McpServer.buildContext`). It applies the era-correct threshold gating itself — on the modern (2026-07-28) leg it reads the per-request `logLevel` opt-in from the request envelope and drops the message when the client didn't opt in or the level is below the requested severity; on legacy it honors the session level from `logging/setLevel` — and emits through `notify`, so the modern response still upgrades to SSE and the log rides the originating request's stream. Surface it as `extra.log` (`toHandlerExtra` maps `ctx.mcpReq.log`) and have `send_notification` prefer it, falling back to the global notifier for callers without per-request context. Verified end to end: opting in via the Logs-tab "Log Level per Request" control stamps `logLevel` in the request `_meta` and streams the log; setting it back to Off omits the key and the server gates the log (plain JSON response, no notification). Legacy server-notification integration tests still green. (No SDK change needed — the earlier apparent gap was our test server using the wrong low-level API.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * web+core: add "Log Level per Request" as a per-server setting, defaulting to on (#1629) Adds the modern per-request log level as a persisted Server Settings option (Options → "Log Level per Request", below Protocol Era), so a server can opt into logs by default instead of the Logs-tab control starting at Off. - types.ts: `ModernLogLevel` (`"off"` | the 8 levels), `DEFAULT_MODERN_LOG_LEVEL` = "debug" (default-on so a modern connection surfaces server logs out of the box), `MODERN_LOG_LEVELS`, and an `isModernLogLevel` guard. Field added to `InspectorServerSettings` and `StoredMCPServer`. - serverList.ts: round-trip the field (lift on read, omit-on-default on write, drop unknown literals) mirroring `protocolEra`; add to the persisted field allowlist. - remote/node/server.ts: validateSettings accepts/validates it and the normalize path drops a malformed literal. - inspectorClient.ts: the constructor seeds `modernLogLevel` from the setting (`"off"` → not opted in), so a modern connection stamps the level from the first request without UI interaction. mergeMeta still gates on the modern era. - ServerSettingsForm/Modal: a Select (Off + 8 levels) with a modern-era description. App seeds the live Logs-tab control from the setting on connect so the two stay in sync. Tests: form Select (default/onChange/off/reflect), modal handler, serverList round-trips, route validation (accept/reject/drop), and constructor seeding (seeded level stamped from the first request; "off" opts out). The prior modern-era "stamps once set" test is updated for the new default-on behavior. Coverage gate green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Fix CI + review: format ServerSettingsForm, drop stray network export, broaden ignore - Prettier-format ServerSettingsForm.tsx (the format:check failure that broke CI — my pre-push `npm run format` from the repo root was a silent no-op since there is no root format script; formatting is per-client). - Remove `inspector-network-logging-modern-2026-07-19T15-11-27.013Z.json`, a Network-tab export debugging artifact accidentally committed in ca2aa526 (flagged by review). - Broaden the .gitignore debugging-artifacts section with `/inspector-network-*.json` so future network exports are ignored (the existing `/inspector-history-*.json` didn't match the network export naming). Verified with the full `npm run ci` (validate → coverage → smoke → storybook), green across all clients. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * test: add modern-era log delivery + gating e2e tests (#1629) The modern-era suite covered the client-side `_meta` logLevel stamp but not that a log actually flows back — the legacy suite already has that (`should receive server-initiated notifications via SSE/streamable-http`), so the modern side was the gap. Add two live e2e tests against a modern (`createMcpHandler`) server serving the `send_notification` tool with `logging: true`: - opted-in (server setting = a level): calling the tool delivers a `notifications/message` over the request's SSE stream, with the right level/logger/data. - not opted in (setting "off"): the tool still returns a result, but the server gates the log — no `notifications/message` is received. This exercises the full path end to end: the client seeding + `_meta` opt-in, and the server's `send_notification` → `ctx.mcpReq.log` era-correct gating. Verified with the full `npm run ci` (green across all clients). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * web: hide the modern "Log Level per Request" setting on legacy connections (#1629) The per-request log level is meaningless on a legacy server (which uses logging/setLevel), so hide it in Server Settings when the era is legacy: - protocolEra pinned to "legacy" → hidden (no connection needed), or - protocolEra "auto" that negotiated legacy at connect time → hidden. It stays visible for a pinned "modern" server, and for an "auto" server that is either not yet connected (era unknown) or resolved to modern. Threads the negotiated era down: App passes `negotiatedEra` to ServerSettingsModal only when the settings target is the live-connected server (`connectionStatus === "connected" && settingsModalTargetId === activeServerId`); the modal forwards it to ServerSettingsForm, which gates the control on `configuredEra === "modern" || (configuredEra === "auto" && negotiatedEra !== "legacy")`. Tests: visibility matrix on the form (legacy hidden; auto+legacy hidden; auto+unconnected and auto+modern and modern shown) and a modal hide case; the existing modern-log tests now base on a modern-era settings object. Verified in the browser (legacy hides, switching to modern reveals it reactively) and with the full `npm run ci` (green across all clients). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
b3b04c6939 |
Thread cacheMode through managed list refresh via SDK cache-aware verbs (#1721) (#1723)
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
* Thread cacheMode through managed list refresh via SDK cache-aware verbs (#1721) Manual/auto refresh of tools/resources/prompts now forces a cache-bypassing round trip on modern servers, delivering the raw-request cacheMode control deferred from #1628. The InspectorClient list/read path uses the SDK's single-page `client.request` verbs (for pagination debugging), which never consult the SDK response cache — so `cacheMode` was inert there. This adds a separate aggregate path that routes through the SDK's cache-aware high-level verbs, where `cacheMode` is honored: - InspectorClient: new `listAllTools/Prompts/Resources/ResourceTemplates( { cacheMode, metadata })` that call `client.listTools()` etc. (all-page aggregate + SEP-2243 x-mcp-header exclusion + cache consult/write). A shared `getCacheableRequestOptions` / `aggregateListParams` back all four. - InspectorClientProtocol: the four `listAll*` methods added. - ManagedListState: delegates page walking to the SDK verb (config `fetchPage` loop → `fetchAll`); `refresh(metadata?, cacheMode?)` threads cacheMode. A `list_changed` auto-refresh bypasses the cache (`cacheMode: "refresh"`). - Managed hooks: user-initiated `refresh()` passes `cacheMode: "refresh"`, so the existing per-tab Refresh buttons become genuinely cache-bypassing — no App wiring change. Connect-time load stays default (`'use'`). The single-page `listTools(cursor)` primitive is unchanged; the paged hooks still surface per-page `nextCursor`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Single-page list pagination: sidebar toggle + server setting (#1721) Delivers the deferred single-page fetch UI: Tools/Resources/Prompts can be loaded one page at a time instead of auto-aggregating every page — a defensive option for servers with very large lists. - New `singlePageLists` server setting (persisted, server-wide), plumbed through ServerConfig/InspectorServerSettings, serverList convert/default, the node + remote settings paths, and the Server Settings form ("Fetch Lists One Page at a Time"). - New `ListPaginationControls` element (a "Single page" switch + "Load next page" button + "N pages loaded" status) rendered in each list sidebar; the toggle edits the setting live (optimistic) and persists it. - The paged state stores (PagedTools/Prompts/ResourcesState) now own loading: they auto-load page 1 on connect in single-page mode and track nextCursor + page count as observable state; the managed stores skip their all-page walk in single-page mode (so nothing is over-fetched) while still lighting the list-changed indicator. `usePaginatedList` selects the display source and pagination controls per mode — pure, no data-loading in React effects. - Refresh is mode-aware: reload page 1 in single-page mode, refetch the whole aggregate otherwise. Sidebars widened 340→360 so the controls don't crowd list entries. - Test server: `test-servers/configs/pagination-http.json` (12 tools/resources/ prompts, page size 4 → 3 pages each) exercises the flow end to end. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Add pr-screenshots proving single-page pagination (#1721) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Address @claude review: template load, double-click guard, auth recovery (#1721) - Fix: resource templates never loaded on connect in single-page mode. The connect-time all-page skip now applies only to lists with a paged counterpart (new `deferInSinglePage` flag on ManagedListConfig; false for resource templates), so templates still aggregate on connect regardless of the setting. - Guard `PagedTools/Prompts/ResourcesState.loadPage` against a concurrent load (fast double-click on "Load next page") so the same page can't be appended twice; the in-flight load owns the cursor and the second call is a no-op. - Thread ambient auth recovery through the single-page refresh, Load-next-page, and mode-switch loads (they previously bypassed it) — usePaginatedList's onRefresh/onLoadMore now return their promises so App can wrap them. - Drop the misleading "aggregated list" wording from the paged-store docs. Tests added for each; npm run coverage green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Review round 2: revert optimistic toggle when persisting the setting fails (#1721) Non-blocking review follow-up. If updateServerSettings rejects, the optimistic `singlePageListsOverride` is now cleared and the live client setting rolled back (the effect only clears the override when the persisted value changes, which doesn't happen on failure), so the UI and client reflect what's on disk rather than the failed edit. Added an App test for the revert path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Fix single-page Refresh not clearing list-changed indicator + UI polish (#1721) Round-3 review fix (blocking): in single-page mode a `list_changed` lights the list-changed indicator, but the single-page Refresh reloads page 1 of the paged store and bypassed `useManaged*.refresh()`, so the indicator was never cleared. Expose `clearListChanged` from useManagedTools/Prompts/Resources and call it in the single-page branch of onRefreshTools/Prompts/Resources. Tests added. UI polish (user feedback on the sidebar control): - Rename the switch label "Single page" → "Paginated". - Put "Load next page" on the same row as the switch, space-between (switch fully left, button fully right). - Move the "N pages loaded" status to its own line below the row, centered. - Hide the whole control once the list is known to be a single page (single-page mode, page 1 loaded, no nextCursor) — nothing to paginate. pr-screenshots refreshed to the new layout. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Rename the singlePageLists setting to paginatedLists for clarity (#1721) `singlePageLists` read backwards from the "Paginated" switch label (it sounds like paginated=false). Rename the server setting to `paginatedLists` with the same value meaning — true = paginated/one-page-at-a-time, false = aggregate-all — and follow through on the related identifiers for consistency: - Setting field `paginatedLists` across ServerConfig / InspectorServerSettings, serverList convert/default, node + remote settings paths, and the form/modal callbacks (onPaginatedListsChange). - `usePaginatedList` / `ListPaginationControls` prop `singlePage` → `paginated`, `onSinglePageChange` → `onPaginatedChange`. - `ManagedListConfig.deferInSinglePage` → `deferWhenPaginated`. - App state (paginatedListsOverride, onTogglePaginatedLists, …) and all tests/fixtures + mode-referring comments. "single-page" wording that denotes the SDK's one-page `client.request` verb (inspectorClient) is intentionally left as-is. Purely a rename — no behavior change; unit + integration + cli + tui + coverage all green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Format App.test.tsx (prettier) after the paginatedLists rename (#1721) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
d552564966 |
Network: modern Mcp-* headers, new error codes, cancellation-as-abort (#1628) (#1722)
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
* Network: modern Mcp-* headers, new error codes, cancellation-as-abort (#1628) Upgrade the Network tab for the modern Streamable HTTP transport (SEP-2243 / SEP-2575): - Recognise and highlight the mirrored headers (Mcp-Method, Mcp-Name, Mcp-Param-*, MCP-Protocol-Version), decode =?base64?…?= sentinel values inline, and cross-check each against the request body so a HeaderMismatch is visible before the server rejects it. - Show the new spec error codes distinctly: -32020 HeaderMismatch, -32021 MissingRequiredClientCapability, -32022 UnsupportedProtocolVersion (with the server-advertised `supported` versions), and modern -32601 MethodNotFound on HTTP 404 (distinguished from a legacy bare 404 by its JSON-RPC body). - Reflect cancellation as a connection abort ("Cancelled") rather than a hard error, per SEP §7.5. - Confirm and document browser-path header mirroring: the SDK builds the Mcp-* headers client-side and the /api/fetch proxy forwards them verbatim (new integration test). New pure util `mcpNetworkHeaders.ts` holds the recognition/decode/classify logic; new `McpErrorBadge` element renders the distinct error chips. The optional raw-request controls (cacheMode bypass/refresh, single-page fetch) are split to #1721 — they are cross-cutting core + feature-tab work beyond the Network tab. Also refreshes the AGENTS.md board Status option-ID table (the Building / MCP Apps Extension columns were removed). Closes #1628 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Network smoke-test showcase + fix redaction clobbering JSON-RPC error codes Adds a composable "modern-network" showcase server for exercising the #1628 Network-tab work end-to-end, and fixes a real bug the smoke test surfaced. Bug fix (core): the fetch-tracking body redaction masked ANY field named `code` (targeting the OAuth authorization code), including the numeric JSON-RPC `error.code`. That destroyed the field the Network tab classifies the modern spec errors on, so -32020/-32021/-32022/-32601 never rendered against real traffic. Redaction now only masks STRING values of a sensitive field (secrets are always strings; a numeric code is never one). Test-server showcase (test-servers): `modern-network-http.json` serves a `get_weather` tool with an `x-mcp-header: "City"` annotation plus four `trigger_*` tools. A new `transport.modern.injectSpecErrors` flag installs a middleware on the modern leg that answers the triggers with real HTTP status + JSON-RPC error bodies (400/-32020, 400/-32021, 400/-32022 w/ `supported`, 404/-32601), so the Inspector's error rendering can be driven live. Mirroring finding (verified live, comment corrected in createRemoteFetch.ts): the SDK builds the STANDARD mirrored headers (Mcp-Method / Mcp-Name / MCP-Protocol-Version) unconditionally — they reach the server through the web proxy — but gates `Mcp-Param-*` on a non-browser environment (`detectProbeEnvironment() !== "browser"`), so the web client never emits them. An x-mcp-header tool is therefore uncallable from the web client against a strict modern server (it answers -32020); it works from the Node CLI/TUI. The earlier "mirroring works through the proxy" note was corrected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Network: move the spec-error badge off the crowded compact top row In the narrow monitoring sidebar (embedded layout) the McpErrorBadge shared the top row with the timestamp/method/category/duration/status chips, which truncated the method and category ("PO…", "TRANSP…") and clipped the error name. Render the badge separately from `metaBadges` and place it on the second row, to the right of the URL, so the top row stays readable and the error name shows in full. The full-width layout keeps it in the top-right cluster (where there's room). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Network: keep the spec error name's casing in McpErrorBadge (no uppercase) The four spec/SDK error identifiers are PascalCase (`HeaderMismatch`, `MissingRequiredClientCapability`, `UnsupportedProtocolVersion`, `MethodNotFound`). Mantine's Badge uppercases label text by default, which flattened them to a hard-to-read run-together string ("UNSUPPORTEDPROTOCOLVERSION"). Set `tt="none"` so the badge shows the spec's own casing with word boundaries intact; short badges (POST, status) keep the default uppercase. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Network: in the full-width layout, move the spec-error badge to the second row The full-width entry kept the McpErrorBadge in the top-right cluster, where a long spec name (e.g. "MissingRequiredClientCapability") crowded the top row and truncated the method chips and URL. Move it to the start of the second row (the expand-toggle row) so the top row stays readable; the toggle stays at the end (`space-between` only when the badge is present). The compact sidebar already places it on its second row beside the URL. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Protocol: show Replay as the right-side icon in the wide layout too The compact (sidebar) ProtocolEntry already renders Replay as an icon next to the pin/expand icons; the wide layout used a left-aligned "Replay" text button instead. Make the wide layout match — Replay as the ReplayButton icon at the start of the right-side action cluster (Replay, Pin, Expand) — so the two layouts are consistent. Drops the now-unused SubtleButton constant. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Move the modern spec-error chip + alert from the Network tab to the Protocol tab The Network tab is focused on the HTTP transaction; the distinct protocol-error taxonomy (-32020/-32021/-32022/-32601) belongs with the protocol messages. So: - ProtocolEntry now renders the McpErrorBadge chip on the top line (right after the direction badge, both layouts) plus an explanatory alert in the expanded detail, classified by JSON-RPC error code via `classifyProtocolSpecError`. - NetworkEntry drops the chip + alert; it keeps the HTTP-level features (status badge, Mcp-* header highlight/sentinel-decode, header/body mismatch marker, cancellation-as-abort). - The SDK delivers -32020/21/22 (HTTP 400) as JSON-RPC error responses, so they reach the Protocol log directly. -32601 (HTTP 404) and other non-enveloped errors are *thrown* — the request stays pending. `enrichProtocolEntries` (App) correlates the Protocol log to the Network log by JSON-RPC id (parsed from the transport fetch's requestBody) and folds the thrown error onto its pending request, so it too appears in Protocol. This is a derived view; the underlying log is not mutated. Verified live against the modern-network showcase: both a -32020 error response and a thrown -32601 render distinctly in the Protocol tab. Follow-up: a "reveal in Network" link on the Protocol alert (cross-tab scroll-to + expand of the correlated HTTP entry). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Protocol: show the spec-error chip only in the wide layout, after the method Refine the placement of the spec-error chip added in the previous commit: - Wide layout: move it from after the direction badge to right after the method chip (reads as "tools/call -32020 HeaderMismatch"). - Compact sidebar row: drop the chip entirely — the two-line row stays uncluttered and relies on its ERROR status badge. The explanatory alert still appears in the expanded detail in both layouts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Protocol: "reveal in Network" link — jump from a spec error to its HTTP entry Adds the cross-tab navigation that was the follow-up to relocating the spec-error rendering. The expanded Protocol alert now shows a "View the HTTP request in the Network tab" link when the entry has a correlated Network request; clicking it: - correlates the Protocol message to its transport fetch by JSON-RPC id (`revealableMessageIds` gates the link; `correlateFetchEntry` finds the target on click), - clears whatever filter would hide the target (network filterText + category, and the embedded monitorSearch), - switches to the Network view in whichever mode is active — the pinned column (`pinMonitor`) when the sidebar is open, or the full-screen header tab (`onActiveTabChange`) when it's closed, - scrolls the matching NetworkEntry into view and force-expands it. The reveal is a one-shot: a new `revealId` threads NetworkScreen → NetworkStreamPanel → NetworkEntry, where the targeted entry scrolls itself into view (rAF, after `useScrollMemory`'s restore) + expands, then clears the signal via `onRevealComplete`. Verified live in both the pinned-sidebar and closed/full-screen layouts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Protocol: only show the "reveal in Network" link when it adds value Gate the link on a new `httpRelevant` flag: show it only when the error is either thrown by the SDK (its real HTTP response lives only in the Network log — the Protocol entry is a synthetic fold) OR tied to the HTTP request headers. A delivered, protocol-only error gets no link, since the raw HTTP entry adds nothing over the alert. - -32020 HeaderMismatch → link (the mirrored headers are the problem) - -32601 MethodNotFound → link (thrown; the 404 response is only in Network) - -32021 MissingRequiredClientCapability → no link (delivered, protocol-only) - -32022 UnsupportedProtocolVersion → no link (supported list is in the alert) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Network: cite SEP-2575 for cancellation-as-abort, not the internal doc's §7.5 The cancellation note referenced "(SEP §7.5)", but §7.5 is a section of the internal v2_new_spec_impact.md doc, not a SEP. The behavior — closing the Streamable HTTP request stream is the cancellation signal; notifications/cancelled is stdio-only — is defined in SEP-2575 (stateless-mcp). Cite SEP-2575 in the user-facing alert and the two related comments. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Network: fix reveal one-shot canceling its own scroll; review doc/comment nits Address @claude review of #1722: - Reveal one-shot: move onRevealComplete() inside the rAF (after scrollIntoView) so clearing revealId (which flips revealed->false and runs the effect cleanup) can no longer race/cancel the pending scroll. Add a flip-back test. - Comment the intentional double-mount of revealId (pinned monitor + full-screen ScreenStage both match; clear is idempotent). - Doc drift after the Network->Protocol relocation: McpErrorBadge + the mcpNetworkHeaders module header now say the spec-error chip lives in Protocol. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Network: clarify redaction array-of-scalars edge + revealableIds superset Address second @claude review of #1722 (both non-blocking polish notes): - fetchTracking redaction: note that masking assumes a sensitive value is a scalar or object; an array-of-scalars under a sensitive key would slip through (no OAuth/token payload has that shape). - revealableMessageIds: document it's a superset of the linked entries; the spec-error/httpRelevant narrowing is applied downstream at render. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Protocol: gate modern -32601 framing to a genuine 404, not in-band 200 Address third @claude review of #1722. classifyProtocolSpecError recognised -32601 from the code alone, so an ordinary in-band 'method not found' returned on HTTP 200 was dressed up as the modern MethodNotFound taxonomy: the spec badge, a 404-asserting alert, and a 'reveal in Network' link that jumped to a plain 200 (claiming diagnostic value that wasn't there). Gate the modern -32601 treatment on the correlated fetch actually being a 404 (mirroring classifyMcpSpecError). -32020/-32021/-32022 are SEP-reserved and unaffected. The correlated HTTP status is threaded from InspectorView (new correlatedFetchStatusById) through ProtocolScreen/ProtocolListPanel to ProtocolEntry; when unknown, -32601 falls back to code-only recognition (the thrown-404 path always carries a 404). Badge, alert copy, and link now resolve together, so a plain in-band -32601 falls through to ordinary error rendering. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Protocol: require a genuine 404 for modern -32601 (drop unknown-status fallback) Address fourth @claude review of #1722. The prior fix only rejected -32601 when the status was *known* and not 404; an unknown status fell back to code-only recognition. But the thrown-404 path always carries a 404 (folded in from a 404 fetch), so the unknown-status branch is only ever reached by an ordinary in-band -32601 with no correlated 404 — most notably a stdio connection (no HTTP at all) — which then wrongly got the modern MethodNotFound badge and 404-asserting copy. Drop the escape hatch: -32601 is the modern taxonomy only on httpStatus === 404. Nothing intended is lost (the genuine case always has its 404). Update the tests (code-only/undefined -32601 now expects null; add a stdio no-correlated-fetch ProtocolEntry case) and mark the MethodNotFound story with correlatedHttpStatus 404. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
9b2c188d11 |
MRTR: drive input_required manually to keep pending-request UX (#1704) (#1719)
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
* MRTR: drive input_required manually to keep pending-request UX (#1704) Follow-up to #1627/#1705. Instead of letting the SDK auto-fulfil the modern (2026-07-28) MRTR retry loop, InspectorClient now drives it itself so the Inspector keeps its pending-request UX, owns the loop, and shows era-accurate semantics. Core (core/mcp/inspectorClient.ts): - Construct the SDK Client with `inputRequired: { autoFulfill: false }` (unconditional; a no-op on legacy, which never returns input_required). - New `requestWithInputRequired()` driver: issues the request with `allowInputRequired` + `withInputRequired(schema)`, and while the result is `input_required`, fulfils each embedded request through the SAME pending sampling/elicitation plumbing, then retries the original call with `inputResponses` + the echoed `requestState` on a fresh id, bounded by MRTR_MAX_ROUNDS. - Route tools/call, prompts/get, and resources/read through the driver. - Embedded roots/list is auto-answered from configured roots (no pending UX); decline/cancel is echoed to the server (not treated as an abort); a tool-call abort mid-round settles the pending request and surfaces ToolCallCancelledError. - Extract shared `enqueuePendingSample`/`enqueuePendingElicitation` helpers used by both the legacy inbound handlers and the driver; add a per-request `origin` ("server-request" | "input-required") to the pending wrappers. Web UI: - New `MrtrOriginNote` element shows an "input_required — your answer returns as a retry" note on a modern MRTR round; legacy panels are unchanged. - Thread `origin` App -> PendingClientRequestModal -> the sampling/elicitation panels (and the inline panels). Test servers: new MRTR presets (multi-round, roots, sampling, loop, edge-case param shaping). Integration tests cover the manual round-trip (asserting the pending UI pauses and the retry carries inputResponses + requestState on a new id), multi-round, roots auto-answer, sampling, decline-echoed, maxRounds bound, abort mid-round, and requestState-only/inputRequests-only param shaping. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Review: clarify sampling-reject and between-rounds abort comments (#1704) Address review nits on PR #1719 (comment-only, no behavior change): - enqueuePendingSample docstring: sampling has no decline/cancel action; the panel either sends a result (resolve) or Rejects (reject → fails the call). The shared "declined/cancelled resolves" wording was accurate only for the elicitation case. - callToolWithRetries abort comment: note the between-rounds case — when the abort lands while an MRTR round awaits an embedded pending request there is no in-flight SDK request, so nothing is sent on the wire; the outcome (ToolCallCancelledError, no failed-call record) is identical. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * test-servers: add mrtr-showcase config exercising every MRTR preset (#1704) A single modern-era streamable-HTTP server (port 3111) bundling all six MRTR presets — mrtr_confirm, mrtr_two_step, mrtr_sample, mrtr_roots, mrtr_edge, mrtr_loop — for manual testing of the manual-driving pending-request UX across single-round, multi-round, sampling, roots, param-shaping, and maxRounds paths. Document it in the README alongside modern-mrtr-http.json. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
73d9b181fe |
History: MRTR conversation grouping + modern message vocabulary (#1705)
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
* History: MRTR conversation grouping + modern message vocabulary Teach the Protocol/History view the modern (2026-07-28) message vocabulary, correlating it off the frames the transport already captures — no change to how MRTR is driven (manual driving is deferred to #1704). - Correlation helpers in protocolUtils: extractResultType, extractRequestState, extractSubscriptionId, isModernFrame, and groupProtocolEntries — clustering the contiguous rounds of one logical operation (original call -> input_required -> retried call with a new id -> final result) that share a requestState token. - New MrtrConversation group renders that cluster as one expandable unit with a status derived from the final round, reusing ProtocolEntry for each round. - ProtocolEntry: resultType badge (input required / complete), a "modern" frame badge for server/discover, subscriptions/listen, and the acknowledged notification, and a copyable subscriptionId. - ProtocolListPanel folds each section through the grouping pass and shows an era badge in the header. Era is threaded from the negotiated protocolEra (App -> InspectorView -> ProtocolScreen -> panel) and never inferred from frames, respecting the transcript-classification caveat. - Shared EraBadge element (+ eraUtils); ConnectionInfoContent reuses it. - test-servers/configs/modern-mrtr-http.json serves an elicitation tool over the modern leg for eyeballing a real MRTR round-trip. - Pre-bundle @modelcontextprotocol/client in optimizeDeps.include so the Storybook vitest project doesn't re-optimize mid-run once protocolUtils pulls the client barrel into the browser graph. Closes #1627 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * test-servers: real MRTR tool + live-verified History grouping While driving the modern flow in the browser, the elicitation-based config proved unable to produce MRTR: the modern (2026-07-28) SDK server rejects `server.elicitInput` — server→client requests aren't allowed on that leg; a tool must instead return `inputRequired(...)`. So the shipped `modern-mrtr-http.json` (and its README claim) were wrong. - Add a genuine MRTR tool preset (`mrtr_confirm` / `createMrtrTool`): its handler returns `inputRequired({ inputRequests: { confirm: elicit(...) } })` on the first call and the final result once the client echoes the answer back on the retry. - Teach the composable test-server to support it: the tool wrapper forwards an `InputRequiredResult` verbatim (instead of coercing it to a CallToolResult), and `toHandlerExtra` surfaces `inputResponses`/`requestState` so a handler can branch between the original call and the retry. - Point `modern-mrtr-http.json` at the new preset; correct the README to describe the real mechanism. - Add an integration test that drives the full round-trip through InspectorClient (auto-fulfilling the embedded elicitation) and asserts the final result is only reachable after the input_required round + retry — regression cover that would have caught the original config bug. Verified live in the browser: the History view renders the round-trip as one grouped "MRTR conversation" (Round 1 input_required → Round 2 complete), keyed by the real requestState, under the Modern era badge. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Address review notes: dedupe status badge, unique MRTR token, import alias Non-blocking review feedback on #1705: - ProtocolEntry: suppress the redundant green "OK" status badge when a `resultType` badge already conveys the outcome (a modern success shows only `complete`/`input required`). Errors/pending have no `resultType`, so their status badge still shows. - createMrtrTool: mint a unique `requestState` per original call (closure counter) so two same-action invocations don't fold into one conversation during manual eyeballing. Real SDK tokens are already unique per operation. - ProtocolEntry: align the `MessageEntry` import to the `@inspector/core` alias used by its siblings (was a deep relative path). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Review round 2: green `complete` badge, lighter embedded MRTR container Non-blocking observations from the second review: - ProtocolEntry: `resultType` "complete" now renders green (was gray) so a modern success keeps the at-a-glance green success affordance a legacy "OK" had, now that the redundant OK badge is suppressed. `input_required` stays yellow. - MrtrConversation: in the narrow monitoring (embedded) column, drop the outer container border and tighten padding — the per-round entries are already bordered cards, so this avoids a busy border-in-border nesting. The full-width view keeps the bordered surface that sets a conversation apart. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Review round 3: module-scope MRTR counter; revert embedded border change - test-servers: hoist the MRTR `requestState` counter to module scope. The modern (2026-07-28) leg is stateless — `createMcpHandler` rebuilds the server (and every tool closure) per request — so the previous per-closure counter reset to 0 on every request and every token ended in `:1`, defeating its purpose. A module-level counter survives the per-request factory recreation and increments monotonically across the process, so same-action invocations get distinct tokens and don't fold into one conversation. (Review finding.) - MrtrConversation: revert the embedded borderless/tighter-padding change — per maintainer feedback from manual testing, dropping the border didn't improve the narrow-column visual, so the group keeps its border in all layouts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
e06c0a45f7 |
Add modern-era (2026-07-28) test-server preset (#1700) (#1703)
* Add modern-era (2026-07-28) test-server preset (#1700) The era-aware connection model (#1626, PR #1697) added per-server `protocolEra` and the era-aware Connection Info UI, but no bundled test server negotiated the modern era — the modern happy path had only unit/Storybook fixtures, no live coverage. Serve the modern (2026-07-28) protocol era from the HTTP test server via the SDK's `createMcpHandler`. A `modern` flag on `ServerConfig` (and a `transport.modern` field in the JSON config) mounts the handler with a dual-era `legacy: "stateless"` fallback by default, or `legacy: "reject"` for modern-only strict serving. The web-standard `fetch` handler reuses the existing `toWebRequest`/`writeWebResponse` conversion, so no new `@modelcontextprotocol/node` dependency is needed. An SDK v2 client negotiating `protocolEra: "auto" | "modern"` now reaches the modern leg end-to-end: `getProtocolEra() === "modern"`, `getNegotiatedProtocolVersion() === "2026-07-28"`, and a populated `getDiscoverResult()`. Verified live in the Inspector — Connection Info shows Era Modern / Session Sessionless / the Discovery section. - Modern serving path + `listen`/`startModernHttp` helpers in test-server-http.ts; modern-leg teardown in stop(). - `modern` config field threaded through load-config/resolve-config with a streamable-http-only validation guard; modern-http.json demo config. - Integration test covering auto/modern negotiation, the dual-era legacy fallback, a modern tools/call, and the strict `legacy: "reject"` rejection. Closes #1700 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Review nit: gate transport.modern guard on truthiness A falsy `transport.modern` (e.g. `false`) is a no-op that `resolveConfig` normalizes to `undefined`, so it shouldn't trip the streamable-http-only guard. Gate on truthiness instead of `!== undefined` so only *enabling* modern on a non-HTTP transport is rejected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
e026051b47 |
Era-aware connection model & capability gating (#1626) (#1697)
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
* Era-aware connection model & capability gating (#1626) Make protocol era (SEP §7.8) a first-class, per-server connection dimension, orthogonal to the transport type, and drive the SDK Client's versionNegotiation from it. Config - Add `ServerProtocolEra` ("legacy" | "auto" | "modern"), `DEFAULT_PROTOCOL_ERA` (legacy, per the SDK's don't-auto-probe guidance), `MODERN_PROTOCOL_VERSION`, and `eraToVersionNegotiation()` in core/mcp/types.ts. - Persist `protocolEra` on `StoredMCPServer` / `InspectorServerSettings`, round-tripping through serverList.ts (omitting the default on disk) and the `/api/servers` read + write validators. InspectorClient / state - Thread `versionNegotiation` through `InspectorClientOptions` (default legacy), replacing the hardcoded pin. After connect, read `getProtocolEra()` / `getDiscoverResult()` / `getNegotiatedProtocolVersion()`, expose them via getters, dispatch `protocolEraChange` / `discoverResultChange`, and clear on disconnect. `useInspectorClient` surfaces `protocolEra` / `discoverResult`. Web UI - ServerSettingsForm: a Protocol Era selector bound to the per-server setting. - ConnectionInfoContent: era-aware — Era badge, Session ("Sessionless" on modern), and a Discovery section (supported versions + extensions) from the `server/discover` result. CLI/TUI also map the mcp.json `protocolEra` into versionNegotiation so the field applies uniformly. Version negotiation stays fully client-side; modern connect works over the existing remote tunnel and disconnect is sessionless (the SDK skips DELETE when there is no session id). Mcp-* header fidelity is left to the History/Network card. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Address @claude review: era docstrings, era validation, stdio session label, AGENTS.md rule - Fix stale docstrings/comments in inspectorClient.ts, useInspectorClient.ts, inspectorClientEventTarget.ts, and ConnectionInfoContent.tsx: getProtocolEra() returns "legacy" (not undefined) on a plain legacy connect; it is undefined only when not connected. - Validate protocolEra at the storedFieldsToInspectorSettings read boundary (new shared isProtocolEra guard) so a hand-edited mcp.json read directly by the CLI/TUI drops an unknown era instead of passing it to eraToVersionNegotiation. Keeps the exhaustive switch intact. - ConnectionInfoContent: the Session row is HTTP-only — show "N/A (stdio)" for stdio instead of the misleading "Session-based". - AGENTS.md: add a rule against unjustified `as unknown as` double casts, and drop the one such cast introduced in the serverList test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Address re-review: dedupe isProtocolEra guard in remote server Replace the local isProtocolEra in remote/node/server.ts with the shared guard now exported from serverList.ts (already imported here), so the era literal set lives in one place and can't drift. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
7fecebed5e |
OAuth: re-key store to (server, issuer) + SEP-837/2350/2352 auth UX (#1694)
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
* OAuth: re-key store to (server, issuer) + SEP-837/2350/2352 auth UX (#1625) Re-key the persisted OAuth store to (server, issuer) and add the SEP-837/2350/2352 authorization-hardening UX. The SDK v2 auth() drives the SEP-2352 flow off the provider's ctx.issuer; the Inspector's job is to honor it and round-trip the issuer stamp. SEP-2352 — issuer-bound credentials - ServerOAuthState nests issuer-bound credentials under byIssuer[issuer] = { clientInformation, clientRegistrationKind, tokens }, with an activeIssuer pointer answering the transport's ctx-less per-request bearer read. - Fix a latent bug: OAuthStorageBase re-parsed tokens/client-info through OAuthTokensSchema/OAuthClientInformationSchema, which strip the non-RFC `issuer` field and silently defeat the SDK's discardIfIssuerMismatch. The store re-attaches the stamp from the byIssuer key. - BaseOAuthClientProvider threads ctx.issuer and implements discoveryState()/saveDiscoveryState() (activates the SDK's callback-leg AuthorizationServerMismatchError binding check) and invalidateCredentials(). - Lazy, migration-free upgrade: a legacy pre-1625 blob's top-level clientInformation/tokens are returned as an unkeyed fallback until the first issuer-stamped save promotes them into byIssuer. SEP-837 — application_type - DCR declares application_type: "native" (localhost Inspector). - ClientSettingsForm renders RegistrationRejectedError detail and reflects DCR's deprecated-in-favor-of-CIMD (SEP-991) status. SEP-2350 — step-up scope union - Per-server onInsufficientScope (reauthorize | throw) setting wired ServerSettingsForm -> StoredMCPServer.oauth -> StreamableHTTPClientTransport. - StepUpAuthModal visualizes the prior-union-challenged scope set, tagging each scope `new` vs `already granted`. mcpAuth simplification - Delete the hand-rolled authorizeWithoutRefresh (mishandled issuer stamping); forward forceReauthorization/skipIssuerMetadataValidation to native SDK auth() so both the initial and step-up paths get SEP-2352 stamping. Vocabulary + docs - OAuthStep gains cimd_fetch / issuer_comparison / scope_step_up (recorded on a step-up redirect); the Network tab labels auth requests by OAuth phase. - v2_auth_hardening.md gains an as-built status section. Deferred to the era-aware card: the SdkError(EraNegotiationFailed) -> UnauthorizedError unwrap and finishAuth(URLSearchParams) form (only fire under non-legacy negotiation). Closes #1625 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Address @claude review: issuer-stamp source gating + clear/activeIssuer + phase over-match - SEP-2352: stamp the issuer only when the credential actually came from the byIssuer slot (`fromSlot`), not merely when a slot exists. A partially-populated slot alongside a surviving legacy field would otherwise stamp a legacy (possibly cross-AS) credential with the current issuer, defeating discardIfIssuerMismatch. - Issuer-scoped clears no longer promote the cleared issuer to `activeIssuer` (updateIssuerSlot gains a `setActive` flag; save paths keep the promotion). - oauthNetworkPhase matches register/token/authorize as the final path segment (trailing slash tolerated) so `/token/refresh` or `/api/register/foo` no longer over-match. - Tests for all three. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
f915889fc4 |
Merge pull request #1688 from modelcontextprotocol/v2/sdk-v2-migration-1624
SDK v2 migration (behavior-neutral, versionNegotiation: 'legacy') (#1624) |
||
|
|
f4fb4b50c9 |
Forward RFC 9207 iss to the v2 SDK (fixes OAuth + EMA leg 1)
SDK v2 added RFC 9207 §2.4 authorization-response issuer validation
(`validateAuthorizationResponseIssuer` / `IssuerMismatchError`) as a mix-up
attack defense. It is strict: when AS metadata advertises
`authorization_response_iss_parameter_supported: true` and no `iss` reaches
`exchangeAuthorization`/`auth()`, the exchange throws.
v1.29.0 had no `iss` parameter and no RFC 9207 validation at all, so dropping
the callback `iss` was harmless. The migration swapped in the stricter SDK
without feeding it the new input, breaking the authorization-code exchange
against any RFC 9207 authorization server (Okta/xaa.dev advertise it):
Issuer mismatch in authorization response (RFC 9207):
expected "https://idp.xaa.dev", received undefined
Found by smoke-testing EMA against live xaa.dev staging; not caught by CI
because the mock IdP never advertised the parameter, so the SDK took its
lenient path (a missing `iss` is only fatal when advertised).
`iss` was dropped in three places, with the plumbing half-built —
`oauthManager`/`inspectorClient` already accepted `iss`, but nothing supplied
or forwarded it:
- `parseOAuthCallbackParams` never extracted `iss` from the callback query
- `mcpAuth` never forwarded `options.iss` to SDK `auth()` — its own comment
("forwarded on v2 upgrade; ignored by v1 SDK auth()") predicted this exact
gap, so standard OAuth was broken identically, not just EMA
- EMA leg 1 (`completeIdpOidcAuthorization`) never passed `iss` to
`exchangeAuthorization`
Thread `iss` end to end: callback query -> CallbackParams -> resumeAfterOAuth
-> completeOAuthFlow -> {mcpAuth | EMA leg 1} -> SDK. The Node callback server
and runner carry it for CLI/TUI.
Tests: `ema-mock-servers.ts` now advertises
`authorization_response_iss_parameter_supported: true` so the mock is at least
as strict as real IdPs — with it absent the SDK silently takes the lenient path
and a dropped `iss` goes unnoticed. Flipping it made three existing tests
reproduce the failure. Adds regression coverage for forwarding and for
rejecting a mismatched `iss` before any network call.
Verified end to end against live xaa.dev staging: EMA legs 1-3 (IdP SSO ->
ID-JAG mint -> resource token) now complete and the MCP session initializes.
web 3099, cli 212, tui 274 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VjMemXZohSR2EndBQYif2b
|
||
|
|
dbb8c40245 |
Address review: justify/refine SDK-gap casts (as unknown as)
Per human review on #1688: for each `as unknown as` in the PR, either refactor or justify with a comment naming the SDK gap (so it can inform SDK-side type improvements). - Sampling/elicit receiver-task returns: route through a typed `CreateTaskResult` so the object shape IS checked; the remaining cast to CreateMessageResult / ElicitResult is the honest SDK gap (the 2-arg setRequestHandler overload types the handler return as the spec result only, not the task-augmented variant). - `_requestHandlers` / `_progressHandlers` private-field casts: comments now name the exact missing public API (opt a handler out of result validation; keep a progress subscription alive across a resolved request). - test-server `_requestHandlers` cast: same SDK gap on the server side, noted. - serverList object->Record casts (pre-existing): documented as plain structural widening (no index signature -> TS requires the `unknown` step), not an SDK workaround. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
39362dd5ff |
Merge remote-tracking branch 'origin/v2/main' into v2/sdk-v2-migration-1624
# Conflicts: # clients/web/src/components/groups/ResourceLink/ResourceLink.tsx # clients/web/src/components/groups/ToolResultPanel/ToolResultPanel.stories.tsx # clients/web/src/components/screens/ToolsScreen/ToolsScreen.tsx |
||
|
|
8f70b60725 |
web: Group tool-result resource_links in a scrollable "Resource Links" box with Protocol colors (#1686)
* web: Group tool-result resource_links in a scrollable "Resource Links" box with Protocol colors Closes #1685 - ToolResultPanel: coalesce consecutive `resource_link` blocks into a single bordered, internally-scrollable "Resource Links" box (mirroring the "Messages" box in the Protocol monitoring sidebar). Non-link blocks and non-adjacent runs are preserved in order via `segmentContent`. - ResourceLink: render each link card on the recessed `inset` surface (`Card variant="inset"`), matching ProtocolEntry's colors in light and dark modes; nested read-result Code blocks read as raised on the grey card via the inset cascade variable. - Drop redundant metadata: the resource `description` line (ResourceLinkInfo, and thus ContentViewer/ResourceLink) and the green "Resource:" label above the expanded read result. - Update tests + stories for the removed props/label and the new grouping box; add multi-link and split-run coverage. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RwZGbocZ64yN9bgb2mj1qE * web: keep the resource_link description; only drop the "Resource:" label Reverts the description removal from the previous commit per review — the `description` line is retained on ResourceLinkInfo (and its ContentViewer / ResourceLink callers). Only the redundant green "Resource:" label above the expanded read result stays removed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RwZGbocZ64yN9bgb2mj1qE * web: drop the resource_link description from the link cards The `description` is generated by the server and can be misleading — e.g. a blob resource served with a `text/plain` mimeType gets described as a "plaintext resource" even though the payload is a blob. Rather than surface that nonsense text, drop the description line from ResourceLinkInfo (and its ContentViewer / ResourceLink callers) entirely. Reverts the description-retention from the prior commit; the grouped "Resource Links" box and the "Resource:" label removal are unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RwZGbocZ64yN9bgb2mj1qE * web: refine resource-link cards — URI-below-name + copy, ProtocolEntry toggle, full-height box Follow-up refinements to the Resource Links display: - ResourceLinkInfo: move the URI below the name and add a CopyButton beside it so the URI can be copied. The name + MIME badge (and the optional expand control) sit on the header row; the URI + copy sit on the row below. - ResourceLink: replace the bespoke chevron with the same tooltip'd ExpandToggle used by ProtocolEntry, and animate the read-result panel with Mantine's Collapse (same as ProtocolEntry). The toggle is a sibling of the URI copy button (no card-wide wrapping button) so the two real buttons never nest (axe `nested-interactive`). - ToolResultPanel: when a result contains a Resource Links box, fill the card's height so the box grows into the available space and scrolls internally (pinned "Resource Links" heading), instead of capping at a fixed content height. Plain text/image results keep the scroll-within-card body. - ToolsScreen: give the result card `flex: 1` so it fills the pane, providing the definite height the Resource Links box grows into. - Tests/stories updated for the new layout, the Expand/Collapse toggle labels, the mounted-through-collapse read result, and the full-height box (with a height-bounded decorator on the ResourceLinks story). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RwZGbocZ64yN9bgb2mj1qE * web: align resource-link card with ProtocolEntry — toggle row, badge, URI style Bring ResourceLinkInfo closer to the ProtocolEntry message card: - Move the expand/collapse toggle to the second (URI) row, opposite the copy button — mirroring ProtocolEntry, whose toggle sits on the row below the badges. The MIME badge stays on the header row's right, beside the name. - MIME badge: bump to `size="md"` so the lowercase MIME text matches the point size of ProtocolEntry's uppercase method/status badges, and make it a solid dark-blue fill with white text in dark mode (keeping the blue-light tint in light mode) instead of a washed-out translucent chip. - Render the URI like ProtocolEntry/NetworkEntry render URLs — `sm` / `fw: 500` in the default sans-serif face and text color — rather than a blue monospace "link". `monoBreak` is kept only for long-URI wrapping. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RwZGbocZ64yN9bgb2mj1qE * web: scope result-card fill to resource-link results (review follow-up) Code-review finding: ToolsScreen gave the result card `flex={1}` unconditionally, so plain text/image/error results also filled the full pane height — leaving a tall empty card and a height jump versus the input-form state. Fill is only needed so a "Resource Links" box can grow and scroll. Gate the fill on a new `resultHasResourceLinks(result)` helper (extracted to `toolResultUtils.ts` so the component module keeps exporting only components, per react-refresh/only-export-components). Non-link results keep their content-sized card; link results still fill and scroll internally. Tests: unit tests for the helper (link / text-only / empty / error) and a ToolsScreen test that a resource-link result renders the Resource Links box (covering the `flex={1}` branch). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RwZGbocZ64yN9bgb2mj1qE * web: prettier-format the resultHasResourceLinks error-case test fixture The error-case fixture in ToolResultPanel.test.tsx wasn't re-formatted after adding the required `name` field, failing CI's `format:check`. No logic change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RwZGbocZ64yN9bgb2mj1qE * web: cap a non-link block above the Resource Links box at 50% height Addresses the mixed-content case from the review: a long text block preceding the links would take its full natural height and push the Resource Links box almost entirely off-screen (only the first card peeking at the bottom). In the links-containing result, wrap each non-link block in a ScrollArea.Autosize capped at `mah: 50%` (flex "0 1 auto"), so it scrolls within at most half the card and the links box keeps the rest. A short block still takes only its content height; a result with no links renders non-link blocks uncapped in the outer scroll body as before. Adds a `ResourceLinksWithLongText` story (full-height decorator) exercising the cap. Verified visually: the text scrolls within ~50% and the links box fills the remainder. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RwZGbocZ64yN9bgb2mj1qE * web: address review — expand-toggle a11y, stale JSDoc, stronger fill test Review follow-ups on PR #1686: - ExpandToggle: add an optional `ariaLabel` (defaults to the tooltip verb, so ProtocolEntry/NetworkEntry/Task are unchanged) and expose `aria-expanded`. ResourceLink passes a per-resource label ("Expand/Collapse resource <uri>") so multiple link toggles are distinguishable to assistive tech and announce their state — restoring the a11y the card-wide button previously provided, while keeping the visible tooltip a plain "Expand"/"Collapse". - ResourceLinkInfo: fix the stale JSDoc that still described the URI as "monospace, link-styled" — it now renders sans-serif sm/fw:500 default color. - ToolsScreen test: assert the result card actually gets `flex: 1` for a links result and NOT for a plain text result, so the test verifies the fill branch it names rather than only that the heading renders. Tests: ExpandToggle gains ariaLabel + aria-expanded cases; ResourceLink / ToolResultPanel / stories updated for the per-resource toggle name. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RwZGbocZ64yN9bgb2mj1qE * web: restore cli/tui/launcher package-locks accidentally committed These lock files were touched by a local install in the worktree (dropping the `version` field and adding optional @emnapi platform deps); they are unrelated to this PR. Restore them to their v2/main state. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RwZGbocZ64yN9bgb2mj1qE --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
8a7a38f800 |
Fix MCP App load regression: translate ext-apps v1 setNotificationHandler API to v2
An MCP App (e.g. NightRider's drum-pad) failed to load with "'[object Object]' is not a spec notification method; pass schemas as the second argument to setNotificationHandler()". `@modelcontextprotocol/ext-apps` still peers on SDK v1 and, during the AppBridge connect handshake, subscribes to tools/resources/prompts list_changed with the v1 schema-first API `setNotificationHandler(NotificationSchema, handler)`. SDK v2 requires a method STRING and throws on a schema object — the migration's compat shim covered the types but not this runtime API difference. Fix in the app-renderer client Proxy (`getAppRendererClient`): translate a schema-first `setNotificationHandler` call to v2's method-string form by reading the method literal off the schema's `shape.method.value`; native string-first calls pass through, and an unrecognized first arg falls through to the SDK's clear error rather than being swallowed. Only `setNotificationHandler` needed this — AppBridge's `request(req, schema, …)` calls work as-is (the v1 SDK schemas expose `~standard`, so v2 accepts them). Remove when ext-apps#702 ships a v2 peer. Added an integration test covering the schema-first translation end-to-end (handler fires on a real tools/list_changed), plus the string-passthrough and unrecognized-schema branches. Coverage ≥90 holds; typecheck + App tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
f9f79fff54 |
Address @claude review: format core/, typed output-validation error, document progress heuristic
- 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> |
||
|
|
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>
|
||
|
|
8d33e76f97 |
web: server-screen, footer, monitoring-sidebar, and responsive-header polish (#1683)
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
Closes #1682 Server-screen, footer, monitoring-sidebar, and responsive-header polish: connected-card highlight/scroll + Failed status, footer row + MCP-docs tooltip, Servers box, card-surface refresh (gray-0 entry cards + white code), monitoring-sidebar layout, a 1280px responsive floor honored by the header (always-icon Disconnect, always-SegmentedControl tabs, 1500px status text, always-available sidebar), a horizontally-scrollable long resource URI in the compact Protocol entry, and copy buttons beside resource URIs / network URLs. 🤖 Generated with [Claude Code](https://claude.com/claude-code) |
||
|
|
590359abc5 |
web: Add Tasks to the monitoring sidebar (#1681)
* web: add Tasks to the monitoring sidebar (#1680) Make Tasks a pinnable panel in the monitoring sidebar alongside Logs / Protocol / Network / Console, so a live task list can be watched beside any screen. - InspectorView: add TASKS_TAB, extend the MonitorTab union / MONITOR_TABS / isMonitorTab, and add a monitorScreens[TASKS_TAB] entry built from a shared tasksScreenProps object (so the embedded and full-size instances can't drift), with the shared column search injected into its ui.search. The full-size Tasks screen now uses the same shared props. - TasksScreen / TaskListPanel: add an embedded prop mirroring LoggingScreen/LogStreamPanel — when embedded the screen fills its column height and drops the controls sidebar, and TaskListPanel scrolls via EmbeddableScrollArea (whose full-size cap matches the old viewport calc, so the standalone screen is unchanged). Tasks is capability-gated: it only appears in the monitor group when the server advertises the tasks capability (via availableTabs), so no change is needed to the failed-connect subset. Closes #1680 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * web: embedded task list ignores the status filter (review #1681) Mirror the sibling monitor panels: the embedded column exposes only the shared search box (its status filter lives in the full-size sidebar, which the embedded view drops), so a stale status filter must not silently hide tasks with no visible control to clear it. Thread an `ignoreStatus` flag into TaskListPanel.matchesFilters, set from the `embedded` prop — matching LogStreamPanel's `ignoreLevels`, NetworkStreamPanel's `ignoreCategories`, and ProtocolListPanel's `ignoreDirectionAndMethod` (#1616). Add tests for the ignored-status / still-applied-search embedded behavior, plus a direct assertion that the Tasks radio appears in the monitor column. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
3d75ef5f8e |
web: Accessibility — accessible palette + WCAG AA contrast, labels, names, roles (#1679)
* web: accessible light/dark color palette + dark-mode legibility fixes (#1678) Ports the design system's accessible palette. Colors are defined once as Mantine color tuples in theme.ts (shade 0 lightest → 9 darkest); every `--inspector-*` token in App.css references `--mantine-color-*`, so improving the tuples cascades through the whole UI. - theme.ts: full accessible tuple set (inspector/gray/dark/green/red/yellow/blue/ teal/violet), WCAG-AA at the primary shades. Tuples are the one place literal hex belongs — the token source. Primary stays inspector-7 (light) / -8 (dark). - theme/ActionIcon.ts: default `color: "gray"` so chrome icons (settings, theme toggle, sidebar, pin, sort, replay) render neutral grey instead of inheriting the blue primary. Icons that set their own color/variant still override. - App.css: fix light surfaces (`surface-body` gray-0, `surface-card` white — the body token pointed at near-black dark-9); add direction/method badge tokens; lift the semantic status/log/task/history colors 1–2 steps in the dark scheme (the shared `*-6` shade sat too dark on charcoal). Light `:root` keeps `*-6`. - Badges: MessageDirectionBadge + new MethodBadge element driven by `--inspector-badge-*` tokens (deep fill + light text in dark, tinted in light) via Mantine `bg`/`c` style props with `autoContrast={false}`; swaps `<Badge color="dark">{method}</Badge>` in ProtocolEntry/NetworkEntry (their Badge import stays — both still use it for status/category chips). Stories + test added for MethodBadge (≥90 coverage gate). Domain badge semantics live in the element components, not theme files. No raw hex outside the tuples; no inline styles. Supersedes the reverted #1677. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw * web: WCAG AA accessibility pass — contrast, labels, names, roles (#1678) Drives the axe-core ruleset (the engine behind Storybook's a11y addon) to zero violations across all stories in light and dark mode. Contrast (805 → 0 hits): - Darken Mantine `*-light-color` + `dimmed` in light mode (and lift dark `dimmed`) via `!important` App.css overrides — Mantine injects these at runtime at matching specificity, so specificity alone loses the tie. - `filledBadgeColor` pins amber (orange/yellow) filled badges to shade 5 so `autoContrast` (JS-computed from the theme shade, not CSS vars) picks black. - `accessibleTextColor` routes colored log/filter text through the scheme-aware `-light-color` token so it clears AA on light and dark surfaces. - `status` Badge variant: amber statuses use black-on-amber (orange text can't reach AA on light at any Mantine shade). - Resource URIs use `-light-color` (blue-4 was 4.38:1 in dark). Names / roles: - `aria-dialog-name`: custom-header modals use `Modal.Title` (wires the dialog's `aria-labelledby`) instead of a bare `Title` + a root `aria-label` (which also tripped `aria-prohibited-attr`). - `button-name`: a `Modal` theme default labels every built-in close button. - `label`: `aria-label` on the previously-unlabelled Textarea/Select inputs. - `scrollable-region-focusable`: keyboard-focusable content Code/ScrollArea viewports; long OAuth values wrap instead of scrolling. - `heading-order`: correct heading levels (keeping visual size via `size`) so outlines don't skip a level under the h2 modal/section titles. Scrollbars: `type="scroll"` ScrollArea theme default — scrollbars auto-hide and show only while scrolling (a deliberate product choice over the always-visible a11y default). `region` is disabled in the Storybook a11y config: isolated component/screen stories render outside the app's `AppShell` landmarks, so it's a page-level concern the components can't own. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * web: let the resource template form fill the main-area width (#1678) Drop the maw=40% cap on the template-form pane so the URI preview and variable inputs use the full width beside the sidebar, matching the resource preview pane. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * web: left-align + reverse the input-panel action rows (#1678) Move the footer action buttons to the left (justify flex-start) and put the primary action first on the Tool / Prompt / Resource-preview / Resource-template panels — Subscribe, Refresh, View Source on the preview panel; Execute before Cancel on the tool panel. Shortens the pointer travel from the sidebar controls / the form fields above to the actions, which matters most when the monitoring sidebar is hidden and the form is wide. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * web: match the dark-mode body background to the header tab bar (#1678) Set the dark `--inspector-surface-body` to `dark-8` (the Mantine SegmentedControl fill used by the header tab bar) instead of `dark-9`, mirroring light mode where the body and the tab bar share `gray-1`. Cards (`gray-9`) still sit a step above the body, and the contrast audit stays at zero AA violations on the slightly lighter background. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * web: enforce the a11y gate + fix modal-overlay heading-order (review #1679) Address the code review: - Flip the Storybook a11y addon from test: 'todo' to 'error' so axe violations fail the play-function tests in CI — the zero-violation state is now gated, not point-in-time. - Enforcing it surfaced a real heading-order skip: the Tools screen's h4 'Tools'/'Results' sit under an h2 Modal.Title when a sampling/ elicitation request modal opens over them. Lift both to h3 (size h4 preserves the look) so h2→h3 is valid in any DOM order. - Comment the intentional color+c pairing on the Remove/Disconnect danger buttons. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * web: brighter warning alerts + pin the elicitation form's actions (#1678) - Add a 'warning' Alert variant: a bright light-yellow fill (yellow-1) with crisp near-black text and a bright yellow-5 left accent, instead of the muddy brown a tinted yellow alert produced once yellow-light-color was darkened to yellow-9 for contrast. Dark mode keeps the light-on-dark-tint treatment. Applied to all five yellow warning/notice alerts. - Elicitation form: scroll only the form fields (capped ScrollArea), keeping the quoted message, warning, and Cancel/Decline/Submit buttons in view. The modal has no other close affordance, so the actions must never scroll out of reach. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * web: robustly pin the elicitation form's actions (#1678) The previous approach capped only the fields' ScrollArea with a fixed rem reserve for the pinned chrome, which underestimated it on some viewports — the whole modal then overflowed and the buttons clipped half-off-screen. Instead cap the whole panel below the modal height and lay it out as a flex column: the message and the warning + buttons are pinned (flex: 0 0 auto) and only the fields (flex: 1) shrink and scroll. Verified the buttons stay within the panel and the modal fits the viewport down to very short heights. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * web: fix elicitation form scrolling — fields scroll, actions pinned (#1678) The prior fix capped the panel with a bare max-height, but a flex:1 ScrollArea's viewport won't bound against a max-height (not fixed-height) parent, so a tall form's fields overflowed unscrollably (clipped) rather than scrolling. Adopt ToolDetailPanel's pattern instead: the modal gives its body a bounded flex column, the panel fills it, and the fields use a flex: "0 1 auto" ScrollArea — which sizes to content when the form is short (modal stays compact, nothing scrolls) and shrinks + scrolls once the panel is capped at the modal height, keeping the message, warning, and Cancel/Decline/Submit pinned in view. Add a ManyFields story (with a bounded-height decorator mirroring the modal) to cover the scroll case. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * web: fix elicitation scrolling in the real modal (cap fields, not flex) The flex-column approach (panel flex:1 in a flex-column modal body, fields flex:"0 1 auto") worked in a fixed-height Storybook decorator but NOT in the real modal: the modal body is bounded by max-height, not an explicit height, so the fields' ScrollArea viewport still couldn't bound — a tall form's fields overflowed and pushed the warning + Cancel/Decline/Submit off-screen. Drop the flex plumbing (and the modal-body styles) and cap the fields directly with ScrollArea.Autosize mah=calc(90dvh - 22rem): it sizes to content when the form is short (modal compact, nothing scrolls) and scrolls once the fields hit the cap. The 22rem reserve = the measured non-field chrome (modal header/padding + message + warning + buttons + gaps), so the whole modal stays within 90dvh and the actions are always in view. Verified in the real PendingClientRequestModal (new ElicitationFormTall story) for both short and tall forms. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * web: pin the sampling modal's actions too (#1678) Apply the elicitation-form treatment to SamplingRequestPanel: wrap the request/response content in a ScrollArea.Autosize capped at calc(90dvh - 10rem) so it scrolls while the Reject / Send Response buttons stay pinned in view (the modal has no other close affordance). The 10rem reserve is the pinned chrome (modal header/padding + the actions + gaps) — smaller than the elicitation form's 22rem since there's no warning banner. Add a SamplingTall story (8 messages + preferences) to cover the scroll case in the real PendingClientRequestModal. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
e2a97c1b8a |
web+cli+docs: Wave 4 — deep-link auto-connect + programmatic MCP App review (rollup of #1576–#1578, #1665, #1666) (#1676)
web+cli+docs: Wave 4 — deep-link auto-connect + programmatic MCP App review (rollup of #1576–#1578, #1665, #1666) |
||
|
|
7411fc886e |
web: address rollup review #1 — deep-link reconnect on restored activeServerId
Gate the deep-link connect phase on 'not already connected' rather than on activeServerId identity: if a prior session restored activeServerId to the 'deep-link' id while disconnected, the old guard marked the ref consumed and never connected, so reloading the same deep-link URL silently didn't auto- connect. onToggleConnection only disconnects when the id is active AND status is connected, so the new condition also avoids toggling a live connection off. Also documents (rollup review #3) that pre-selecting a deep-linked app deliberately bypasses AppsScreen.handleSelect's no-input auto-launch — a URL must not fire a tool call without the token-gated autoOpen. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw |
||
|
|
c6980f83ba | Merge branch 'v2/1578-docs' into v2/1579-wave4-rollup | ||
|
|
f2d4b6936c |
Merge branch 'v2/1665-refresh-token' into v2/1579-wave4-rollup
# Conflicts: # clients/cli/__tests__/stored-auth.test.ts |
||
|
|
43980fd68c | Merge branch 'v2/1666-print-handoff' into v2/1579-wave4-rollup | ||
|
|
864c6e7c7a | Merge branch 'v2/1577-deeplink-openapp' into v2/1579-wave4-rollup | ||
|
|
e6278aea0f |
docs: address #1674 review — fix broken link + list rendering
- Root README no longer links to docs/mcp-server-configuration.md (not yet ported to v2); keep only the mcp-app-review.md pointer (review #3). - Collapse the Local-fixture parenthetical onto one line so prettier no longer renders '- resource preset …' as a mid-sentence bullet (review #5). Findings #1/#2 (documents unmerged #1576/#1577/#1665 behavior) and #4 (the web-README #deep-link-auto-connect anchor) resolve in the Wave 4 rollup, which lands this doc together with those code PRs — the reviewer noted the same. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw |
||
|
|
b8cd4fd431 |
web: address #1671 review nit — simplify redundant autoOpen guard
DeepLink.autoOpen is a non-optional boolean, so the `?? false` was dead once openApp was truthy. Collapse to Boolean(deepLink?.openApp && deepLink.autoOpen) — still false when there's no deep link, still gated on openApp so a malformed autoOpen-without-openApp link can't fire. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw |
||
|
|
ac49000738 |
cli: address #1672 review nits — correct doc ref + hoist normalizeServerUrl
- The JSDoc referenced a non-existent `resolveServerConfig`; name the real `resolveServerConfigs` (core/mcp/node/config.ts) in plain text (it isn't imported here, so an @link wouldn't resolve). - Hoist the doubly-computed `normalizeServerUrl(serverUrl)` in buildHandoff to a single `normalizedUrl` reused for the query param and the serverUrl field. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw |
||
|
|
59f3bd92a4 |
cli: address #1673 review — refresh fallback, 0600 write, distinct code
- Refresh failure now falls back to injecting a still-present stored access token instead of hard-exiting 3, so a transient auth-server hiccup doesn't regress a previously-working invocation (review #1). Only a failure with no access token to fall back on surfaces as exit 3. - Persist rotated tokens via the shared writeStoreFile (0o600 + mkdir -p) instead of a raw writeFile, so the secrets file keeps owner-only perms identical to the web backend (review #2). - Missing clientInformation now throws the distinct no_client_information envelope code rather than reusing no_stored_token (review #5a). - Docs: --wait-for-auth no longer claims parity with --use-stored-auth (it injects the freshly-landed token without refreshing); noted the always-refresh rotation/concurrency caveat (review #3, #4). - Tests: discovery-returns-undefined metadata path, no_client_information code, and an e2e refresh-failure → access-token fallback. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw |
||
|
|
0beb6e65af |
web: address #1670 review — observable deep-link failures + correct stale-config connect
- The ensure-phase addServer catch now records non-409 failures (read-only catalog, backend 5xx) on data-error-message instead of swallowing every error and leaving the deep link permanently stuck at the guard with no signal (review finding #1). - Split the update and connect phases across renders (new deepLinkUpdateRef): connect only once servers reflects the updated config, so onToggleConnection (which reads serversRef, synced by an earlier passive effect) builds the client from the fresh transport rather than the pre-update config (finding #2 — the exact case deepLinkConfigEquals was added for). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw |
||
|
|
a30a576017 |
docs: MCP App review recipe + root README pointer (#1578)
Closes #1578 Adds docs/mcp-app-review.md — the CLI-first → one-shot-web recipe for automated MCP App review: `--app-info` probe → deep-link navigate → rendered widget, plus OAuth-gated servers (stored-auth handoff, now honoring refresh_token per #1665), SSH port-forward (both ports), and HTTP proxy support. Adapted from PR #1510's reference doc to only document what actually merged and to match the current implementation: - Correct driver contract: `data-testid="connection-status"` + `data-status` /`data-error-message`/`data-deeplink`, `data-testid="open-app"`, and the `data-app-status="ready"` app lifecycle (not the reference's stale selectors). - The zero-click `autoOpen` path (#1577) alongside the click-to-open variant. - `--use-stored-auth` now refreshes a stored refresh_token (#1665), so the "token is not refreshed" caveat is replaced. The per-client READMEs (web deep-link scheme + data-* contract, CLI handoff + refresh + proxy) were updated in their own Wave 4 PRs; this adds the root README pointer to docs/. Proxy/undici is already documented in the CLI README. Part of the #1579 decomposition (Wave 4, docs — lands last). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw |
||
|
|
cdf124106e |
cli: stored-auth injection honors refresh_token, not just access_token (#1665)
Closes #1665 `--use-stored-auth` previously injected the stored access token blindly, so an expired token failed even with a valid refresh_token on disk. Now, when a `refresh_token` is stored, the CLI runs the OAuth refresh grant and injects the fresh access token, persisting the rotation. - New exported `refreshStoredAuthToken(serverUrl, statePath, deps?)`: reuses the SDK's `refreshAuthorization` (not a hand-rolled token request) with the stored `clientInformation` + `serverMetadata` (discovered via core `getAuthorizationServerUrl` + SDK `discoverAuthorizationServerMetadata` when the metadata wasn't persisted). Rotated tokens are written back through `serializeOAuthPersistBlob` under the same `{servers,idpSessions}` key, so web and CLI stay consistent. Missing refresh_token/clientInformation or a failed grant throw `CliExitCodeError(AUTH_REQUIRED)` with a clear message + envelope code (`no_stored_token` / `refresh_failed`). - `--use-stored-auth` prefers refresh when a refresh_token is present (covers both absent and expired access tokens — the stored blob has no expiry, so the refresh token is the durable credential); falls back to injecting the stored access token otherwise (unchanged). - `deps` are injectable so the refresh/discovery are unit-tested without a live token endpoint; an e2e test drives the real SDK path against a mock token endpoint and asserts the refreshed token is injected + the rotation persisted. - CLI README updated (removed the "blind injection" caveat). CLI coverage gate green (cli.ts 100/92/100/100). Part of the #1579 decomposition (Wave 4, CLI lane). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw |
||
|
|
93f3fb64f2 |
cli: reconcile --print-handoff deep-link with #1576 canonical format (#1666)
Closes #1666 #1575 shipped `--print-handoff` with an interim deep-link shape (hardcoded `transport: "http"`, marked `TODO(#1576)`). Now that #1576 owns the canonical `?serverUrl&transport&autoConnect` format, `--print-handoff` emits exactly it: - New `deepLinkTransport(serverUrl, transport)` helper derives `http`|`sse` from the resolved server — explicit `--transport` wins, else auto-detect from the URL path (`/sse` → sse), else default `http`. Unlike the core resolver it defaults instead of throwing (the handoff is best-effort; the web parser defaults an unknown transport to http too). - `buildHandoff` takes the transport, emits the normalized serverUrl + derived transport in the query, and keeps `autoConnect=<token>` (which #1576 adopted as the canonical CSRF gate, so the interim guess was already correct). - Removed the `TODO(#1576)` marker; updated the doc comment + CLI README. Tests: handoff emits `transport=http` for `/mcp`, `transport=sse` for `/sse` and for explicit `--transport sse`; `deepLinkTransport` unit tests cover the explicit/auto/default/stdio/unparseable branches. CLI coverage gate green (cli.ts 100/92.8/100/100). Part of the #1579 decomposition (Wave 4). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw |
||
|
|
cbb2afe80c |
web: deep-link openApp/appArgs/autoOpen — pre-select & pre-fill an app from the URL (#1577)
Closes #1577 Extends the deep link (#1576) to land on a rendered MCP App: - `&openApp=<toolName>` — once connected and the tool appears in the app list, InspectorView switches to the Apps tab and pre-selects it. - `&appArgs=<base64url(JSON)>` — pre-fills the tool form; values are merged **over** `collectSchemaDefaults()` so required-with-default fields don't disable "Open App". - `&autoOpen=<token>` — token-gated auto-click of "Open App". Adapted to the current architecture (differs from PR #1510's reference): - Tab state is lifted to App.tsx (#1417), so the openApp effect lives in InspectorView and drives `onActiveTabChange("Apps")` + `onAppsUiChange` seed + `onSelectApp`, guarded by `deepLinkOpenAppRef` to fire once. - AppsScreen owns the `running`/iframe state, so `autoOpen` is threaded to it as a prop; a fire-once effect defers the open one microtask past the synchronous effect body (satisfying `react-hooks/set-state-in-effect`, matching App.tsx's connect effect). `resetAppChannels` memoized so the effect's deps don't churn. Tests: InspectorView (tab-switch + pre-select + appArgs-over-defaults merge + non-app openApp ignored), AppsScreen (autoOpen fires once with seeded values; no-selection stays idle). README documents the params. Coverage gate green. Part of the #1579 decomposition (Wave 4). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw |