| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
fe5b622fd1 |
fix: salvage a list result's valid entries when one is malformed (#1909)
The SDK validates a list result as a whole, so a single non-conforming entry
rejected the entire response and the panel showed "Couldn't load resources"
with zero items — the exact opposite of what a debugging tool should do with a
server that is 90% right. Reported against a PHP server whose empty
`annotations` object reaches the wire as `[]` rather than `{}`.
`listAll*` now falls back to a lenient re-walk when — and only when — the SDK
rejects a result it received (`InvalidResult` / `UnsupportedResultType`), keeps
the entries that validate, and reports the rest. A conforming server takes the
strict, cache-aware path exactly as before.
Salvaging is not accepting: the dropped entries are named with their reason in
a warning above the list, and the Protocol entry is still marked rejected, so
the wire truth survives. When every entry validates, the original error is
rethrown rather than swallowed — the rejection was about something the
per-item pass cannot explain.
`isClientDecodeRejection` moves to the new module and is now shared with
managedListState, which had the only copy.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDdo1rNRVQnVRRdsqSrvG3
Signed-off-by: cliffhall <cliff@futurescale.com>
|
||
|
|
f201032137 |
fix: surface list-fetch failures instead of swallowing them
A list load that failed — a transport error, or a result the SDK's era codec rejects as invalid — reported nothing. The connect-time refresh is fired with no caller to await it (`void this.refresh()`), so its rejection became an unhandled rejection: the header said "Connected", the panel rendered an empty list indistinguishable from a server that has none, and the failed exchange showed in the Protocol panel as a clean success. Two surfaces now tell the truth: - ManagedListState records the failure as observable state (`getError` + an `errorChange` event) and still re-throws, so App's auth-recovery wrapper keeps working. The four `useManaged*` hooks expose it via a shared `useManagedListError`, and the Tools/Prompts/Resources sidebars render a `ListLoadError` alert with the reason and a Retry. - A response the client refused is attributed back to its Protocol entry (`markResponseRejected` → `responseRejected` → `MessageEntry.clientError`), which now renders an Error status and a "Rejected by the Inspector" alert rather than the green resultType badge alone. The SDK gives no request id with a decode failure, so the id is recovered by correlation; see the comment on `markResponseRejected` for why that is exact. Also stops `listAllTools` from dropping a failing excluded-tools walk on the floor — it stays non-fatal but is logged. Closes #1953 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SW1p8E2uiyyLx4RKwrwSrt |
||
|
|
da37692485 |
refactor(web): sweep AGENTS.md TypeScript + Mantine rule violations (#1771)
Codebase-wide sweep bringing the web client into strict compliance with the AGENTS.md TypeScript and Mantine/React conventions: ~170 inline 2+-static-prop elements extracted to named .withProps() constants across ~77 files, the AGENTS.md rule wording tightened to match (static-prop boundary + Accordion/Transition/data-*/Box exemptions), a real ScreenStage Box->Flex layout regression fixed (direction: column) with a Storybook regression guard, and an App.tsx double-cast tightened. Closes #1762. |
||
|
|
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> |
||
|
|
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. |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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 |
||
|
|
0ddb79203a |
web: Wave 2 — spec-conformant MCP Apps host (rollup of #1565–#1572) (#1660)
* web: sandbox hardening — opaque-origin inner frame, srcdoc-only, frame-ancestors Harden the MCP Apps sandbox isolation model: - The inner (untrusted-widget) iframe is sandboxed WITHOUT `allow-same-origin` (default "allow-scripts allow-forms") and any server-supplied sandbox value has `allow-same-origin` stripped case-insensitively, so the widget runs under an opaque origin and cannot reach the proxy's DOM to bypass its own CSP via the parent realm. - Widget delivery is `srcdoc`-only (no document.write), which respects the opaque origin. - The relay accepts the inner frame's "null" origin with `event.source === inner.contentWindow` as the unforgeable identity check. - sandbox-controller.ts serves the proxy with `Content-Security-Policy: frame-ancestors http://127.0.0.1:* http://localhost:*`; fetch directives are deliberately omitted so a srcdoc iframe's inherited policy container does not override the per-app CSP. Updated the isolation-model comments and the sandbox-controller integration test asserts the frame-ancestors header (and the absence of fetch directives). Closes #1565 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw * web: enforce per-app _meta.ui.csp in the app bridge + surface resource-read failures - createAppBridgeFactory now builds each app's `_meta.ui.csp` host-side via the sandbox-csp library (approveCspSources → buildSandboxCspPolicy) and wraps the untrusted widget HTML with wrapSandboxedHtml so the CSP <meta> is always the first <head> child before the HTML reaches the sandboxed iframe. The approved (post-filter) csp + permissions are echoed back via hostCapabilities.sandbox so the view sees what was granted. The csp is no longer sent inline through sendSandboxResourceReady — it is enforced via the wrapped meta. - A per-app copy of HOST_CAPABILITIES prevents the sandbox echo from mutating the shared constant. - New optional `onResourceError` dep on AppBridgeFactoryDeps routes resource read/post failures to the caller (also always console.error'd) instead of leaving a silent blank frame. Closes #1566 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw * web: full hostContext delivery + live host-context-changed updates - createAppBridgeFactory now seeds the full hostContext via snapshotHostContext(iframe, HOST_AVAILABLE_DISPLAY_MODES): theme, styles (Mantine design tokens), displayMode ("inline"), availableDisplayModes, and containerDimensions — replacing the theme-only seed. The local currentTheme helper is dropped in favor of hostContext.ts. - AppRenderer gains `displayMode` and `containerRef` props and pushes live host-context changes via the SDK's sendHostContextChange (partial host-context-changed notifications), never a hand-maintained snapshot: - theme + styles via a MutationObserver on <html data-mantine-color-scheme>, - containerDimensions via a ResizeObserver on the host container (or the iframe fallback), gated on `initialized`, skipping 0x0 and value-equal repeats, plus a one-shot push on initialize once layout settles, - displayMode whenever the prop changes (Maximize/Restore), gated on init. Adds AppRenderer host-context tests (theme flip incl. resolved styles, displayMode push + init gate, container-dimensions on init and resize, observer teardown) and updates the factory + story mocks for the new hostContext shape and sendHostContextChange method. Closes #1567 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw * web: honor ui/notifications/size-changed and ui/request-display-mode AppRenderer: - New `onSizeChange` prop forwards the view's ui/notifications/size-changed reports (via the bridge `sizechange` event). - New `onRequestDisplayMode` prop backs an `onrequestdisplaymode` bridge handler that returns the mode the host actually applied; with no handler the request is declined by returning the current host-side mode (falling back to inline). AppsScreen: - Adds a host-controlled RendererContainer (measured for containerDimensions) with an inner box sized by the view-reported height (`appHeight`), so host→view container size and view→host size-changed don't couple. - `handleSizeChange` records the reported height (ignored while maximized); `displayMode` is derived from the maximize toggle and pushed to the view; `handleRequestDisplayMode` filters against HOST_AVAILABLE_DISPLAY_MODES (declining e.g. "pip") and maps fullscreen↔inline onto the maximize state. Adds AppRenderer tests (size forwarding, request-display-mode apply/decline/ fallback) and AppsScreen tests (frame sizing on report, no-height ignore, fullscreen/restore/decline via request-display-mode). Story mocks gain sendHostContextChange/addEventListener. Closes #1568 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw * web: honor ui/download-file with confirmation and http(s) allowlist createAppBridgeFactory now advertises the `downloadFile` host capability and attaches an `ondownloadfile` handler: - Requires a host-mediated confirmation (window.confirm) listing the requested file(s); a declined prompt or an empty payload returns isError without acting. - Inline EmbeddedResources (text or base64 blob) are written to disk via the shared downloadBlob object-URL anchor; base64 is decoded via base64ToBytes. - ResourceLinks are opened in a new tab, restricted to http(s) by the shared isHttpUrl allowlist (javascript:/data:/file:/malformed are rejected). - Labels shown in the confirmation are sanitized (control + format chars stripped, length clamped) so a server-supplied filename/URI can't inject newlines or reflow the prompt. - Partial-batch success is surfaced (isError only when nothing downloaded), with skipped items warned. Adds factory tests for the full download surface (text/blob/link, decline, empty, throw, non-http rejection, label sanitization/clamping, partial batch). Closes #1569 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw * web: surface ui/message and app logging in panels below the running app AppRenderer: - New `onMessage` prop backs an `onmessage` bridge handler that surfaces the view's ui/message content and returns the spec-required empty result; with no handler the submission is declined (isError). - New `onLog` prop forwards MCP `notifications/message` log entries via the bridge `loggingmessage` event, honoring the advertised `logging` capability. AppsScreen: - Adds `messages`/`appLogs` state (cleared with the reported height on select/open/close/back via resetAppChannels) and `handleMessage`/`handleLog`. - Renders a "Messages from app" panel (data-testid="apps-messages", reusing ContentViewer) and a default-expanded collapsible "App logs" panel (data-testid="apps-logs", reusing LogLevelBadge) with logger names and a Clear button, both as pinned panels below the running widget. Adds AppRenderer tests (log forward + no-handler no-throw, ui/message route/empty-result/decline) and AppsScreen tests (message log render + empty result + clear-on-close, log panel expand/collapse/clear). Closes #1570 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw * web: stage partial input — replay tool-input-partial before final tool input AppRenderer: - New `partialInputs` prop (ordered fragments), snapshotted into pendingPartials at bridge-build time (read via a ref so prop churn never rebuilds the iframe) and cleared on dispose. flushPending replays them via bridge.sendToolInputPartial BEFORE the complete tool-input, per spec. AppsScreen: - New `partialStages` state + `handleStagePartialInput` (snapshots current form values). resetAppChannels gains a `keepPartials` option so Open App preserves the staged fragments (they're consumed by the renderer) while select/close/ back clear them. - Adds a "Stage partial input" control (fielded apps only) with a staged count and "Clear staged", wired to AppRenderer's `partialInputs`. Adds AppRenderer tests (ordered replay before tool-input; none when omitted) and AppsScreen tests (stage/count/clear gated on fields; staged partials survive Open and are replayed). Closes #1571 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw * web: app lifecycle status + machine-readable data attributes on the Apps screen AppRenderer: - New AppRendererStatus ("loading" | "ready" | "error") + onAppStatusChange callback. Fires "loading" at the start of every (re)build, "ready" on the view's initialized signal, and "error" on a factory throw/rejection. AppsScreen: - appStatus/appError state; handleAppError captures the reason locally and forwards to the parent onError. resetAppChannels clears both. - Surfaces the contract as data-testid="apps-form" carrying data-app-status and data-app-error, renders an error panel (data-testid="apps-error") in place of the blank frame, and tags the stage button data-testid="apps-stage". - AppDetailPanel's Open App button gains data-testid="open-app". App.tsx: - Wires the bridge factory's onResourceError to a red toast so a malformed/404 UI resource is no longer console-only (the renderer separately drives data-app-status, so a driver times out on never-reaching-ready and reads it). Documents the data-testid/data-* automation contract in clients/web/README.md. Adds AppRenderer status-transition tests and AppsScreen status/error-panel tests. Closes #1572 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw * review: warn when server sandbox drops allow-scripts + revert incidental lockfile churn Addresses the claude[bot] review on PR #1650: - sandbox_proxy.html: log a console.warn when a server-supplied `sandbox` value lacks `allow-scripts` after the `allow-same-origin` strip, so a fail-closed (app-never-runs) sandbox is diagnosable instead of silent. - Restore clients/{cli,launcher,tui,web}/package-lock.json to their v2/main state — the earlier churn (@emnapi/@napi-rs optional deps, a dropped version field) was incidental `npm install` regeneration unrelated to the sandbox change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw * review: document CSP-vs-permissions filtering asymmetry (PR #1652) Address the claude[bot] review's finding #2: add a comment explaining why `csp` is injection-filtered (approveCspSources) while `permissions` is passed through verbatim — permissions is a structured boolean object consumed only by the sandbox proxy's buildAllowAttribute() (fixed key→token mapping), so it never reaches the iframe sandbox/allow attribute as raw text. No logic change. Also merges v2/1565-sandbox-hardening (review fixes) up the stack. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw * test: lock in per-app HOST_CAPABILITIES isolation (PR #1652 round 2) Add a test asserting the shared HOST_CAPABILITIES.sandbox stays undefined after a sandboxready run sets hostCapabilities.sandbox — guards against a silent regression if the per-app spread copy were ever dropped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw * review: gate theme observer on initialized + document seed/dep assumptions (PR #1654) Address the claude[bot] review: - Finding #1 (applied): gate the theme+styles MutationObserver on the view's `initialized` signal, matching the container/displayMode pushes — a theme flip in the construction→handshake window no longer races ui/initialize. The factory seeds the construction-time theme, and the first post-init flip carries the current value. Added tests: pre-init flip is dropped; the live flip test now completes the handshake first. - Finding #2 (applied, comment): note in snapshotHostContext that the `displayMode: "inline"` seed assumes inline-at-open (AppsScreen always opens inline; #1568's displayMode push carries transitions). - Finding #4 (applied, comment): explain why `containerRef` is a build-effect dep yet doesn't force a rebuild (sameInputs ignores it; read lazily). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw * review: guard 0-height size reports, distinct RendererFrame, shared payload type (PR #1656) Address the claude[bot] review: - #1 (applied): handleSizeChange ignores a non-positive height — a view's ResizeObserver can transiently fire 0 pre-layout/teardown, which would collapse the frame; mirrors AppRenderer's 0×0 skip. Added a test. - #2 (applied): reinstate a distinct `RendererFrame` constant for the inner content-sized box so its role reads clearly vs. the outer RendererContainer. - #3 (declined + comment): keep carrying `appHeight` across maximize→restore — clearing it would flash the frame to full-card height until the next size-changed; the carry-over restores at the prior size. Documented. - #4 (applied): type handleSizeChange's param as AppBridgeEventMap["sizechange"] so the payload shape has a single source of truth with AppRenderer's prop. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw * review: distinguish links, cap batch size, comment truncation (PR #1657) Address the claude[bot] review: - #1 (applied): the confirmation now reads "download or open N item(s)" and resource_links are prefixed "↗" in the summary, so a link that opens in a tab is distinguishable from an embedded file that saves to disk. - #2 (applied): cap a single ui/download-file batch at MAX_DOWNLOAD_ITEMS (20); an oversized batch is rejected (isError) before the prompt, with a warn. - #3 (declined): can't check window.open's return value to detect a blocked popup — we pass `noopener`, and per spec window.open returns null even on a successful noopener open, so null can't distinguish blocked from succeeded. - #4b (applied, comment): note the start-of-label truncation is intentional so a link's scheme+host stays visible for the trust decision. Adds tests: oversized batch rejected without confirming/acting; resource-link ↗ prefix in the summary. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw * review: skip embedded resource with neither text nor blob (PR #1657 round 2) Address the round-2 observation: an untrusted EmbeddedResource carrying neither `blob` nor a string `text` previously produced `new Blob([undefined])` — a file containing the literal text "undefined", counted as success. Now such an item is skipped (returns false, like a rejected link), so isError reflects it. Added a test asserting no object URL is created and the batch reports isError. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw * review: type-honest formatLogData, cap channels, log-panel a11y (PR #1658) Address the claude[bot] review: - #1 (applied): formatLogData coalesces JSON.stringify(undefined) → "" so the `: string` return type is honest for a data-less log. - #2 (applied): cap retained messages/logs at MAX_APP_CHANNEL_ENTRIES (500) via appendCapped — a chatty widget no longer grows the panels/DOM without bound between Clear/close; oldest entries drop. - #3 (declined): keep the Messages panel without a Clear/collapse — messages are sparser and already cleared on close/switch; keeping it simple. - #4 (applied): the App-logs toggle now has aria-controls pointing at the Collapse region (id="apps-logs-region") for assistive tech. Adds tests: no-data log renders (coalesce branch), 501 logs cap at 500 (oldest dropped), aria-controls assertion. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw * review: stable id key for Messages panel (PR #1658 round 2) Address the round-2 nit: give AppMessage a stable `id` (assigned via nextMessageIdRef in handleMessage) and key the Messages panel on it instead of the array index — so the appendCapped front-drop can't renumber keys once a run exceeds the cap, matching AppLogEntry's treatment. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw * review: note partialStages intentionally survives handleOpen (PR #1659) Address the claude[bot] review's observation #2: add a comment at the handleOpen call site clarifying that partialStages is intentionally NOT cleared there — AppRenderer snapshots the fragments into its own pendingPartialsRef at build time, and the staging UI only renders while not running, so the surviving state is invisible until the next reset drains it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw * review: align error-panel docs with behavior + gate data-app-error on running (PR #1660) Address the claude[bot] review: - #1 (applied, docs): the error panel renders BELOW the (blank) frame, not in place of it — the renderer stays mounted so a factory error still shows the reason alongside the iframe (gating the frame off on error would drop the auto-launched iframe an integration test relies on). Reworded the README row and the test comment to say "below the frame"; added a code comment. - #2 (applied): gate data-app-error on `running` (like data-app-status) so the two attributes can't desync if a future path sets an error without running. - #3 (no change): resource-read failures staying at data-app-status="loading" (toast only) is intentional and documented. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
6722438428 |
fix(web): make sidebar lists, monitor panels, and Tools placeholder fill height (#1663) (#1664)
Three related "fill the available height" layout fixes to the connected screens:
- Tools & Prompts sidebars: replace the fixed magic max-height on ToolControls/
PromptControls with the Resources pattern — sidebar Card `variant="sidebar"`
(full-height flex column) + `Stack flex={1} mih={0}` + a flex-filling
ScrollArea — so the list runs to the bottom of the card before scrolling.
- Logs/Console/Protocol/Network standalone screens: only override ScreenLayout
`h` when embedded. Passing `h={undefined}` clobbered the default
`h: calc(100dvh - header)` (withProps plain-spreads), collapsing an empty
screen to its controls' height. Also center the Network/Protocol empty states.
- Tools placeholder: render the "Select a tool" empty state in a full-height
`DetailCard flex={1}` (like Prompts/Resources) instead of the content-sized
preview card, so it fills the pane.
Claude-Session: https://claude.ai/code/session_015sMW1YZXkM7fDkdPtHpE95
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
dbda70e83b |
Monitoring sidebar toggle, Prompts-style Tools results, and icon-button UI polish (#1661) (#1662)
* feat(web): single header MonitoringToggle + Prompts-style Tools results (#1661) Consolidate the monitoring column's open/close affordances into one header toggle, and align the Tools/Prompts result panes and the sidebar log layout. - Add a single `MonitoringToggle` in the header, to the right of the theme icon. It opens/closes the monitoring column and only shows when a server is connected OR a connect attempt failed (wide viewport + a monitor tab available) — the same availability rule the column uses. - Remove the scattered pin/close icons: delete `PinColumnButton`; drop `onPin` from the Logs/Protocol/Network/Console screens and their stream panels; drop `onOpenMonitor` from the server list; remove the close button from `MonitoringControls`/`MonitoringScreen`. - Tools screen: remove the "Results will appear here" placeholder pane. Results now replace the input form in a single content pane (the Prompts pattern), with a top-left X (ToolResultPanel) that dismisses them back to the form; form values are preserved for a re-run. - Prompts screen: the argument form fills the content pane width (drop the 40% cap), matching the Tools input. - LogEntry: add a `compact` two-line layout (time/level/logger on line one, message wrapping below) used by the embedded monitoring-column Logs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015sMW1YZXkM7fDkdPtHpE95 * polish(web): tooltips on icon buttons + rename "monitoring column" → "monitoring sidebar" (#1661) - Add Mantine `Tooltip`s to the icon-only controls: Client settings, Theme (dynamic light/dark label), the monitoring-sidebar toggle, the list-level and per-entry Expand/Collapse toggles, the Pin/Unpin toggle, and the Replay button. Labels mirror each control's existing aria-label. - Rename the feature term "monitoring column" → "monitoring sidebar" across the user-facing labels (Open/Close/Resize), comments, and tests, so the UI calls it a sidebar consistently. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015sMW1YZXkM7fDkdPtHpE95 * review: consistent sidebar wording + reuse MonitoringToggleProps (#1661) Address the two optional notes from the PR #1662 review: - Make the MonitoringToggle JSDoc voice consistent ("sidebar" throughout, no lingering "column"). - Drop ViewHeader's duplicate `MonitorToggleProps` and reuse the element's exported `MonitoringToggleProps` as the single source of truth. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015sMW1YZXkM7fDkdPtHpE95 * feat(web): slide-in/out animation for the monitoring-sidebar toggle (#1661) The header MonitoringToggle now animates in/out via a Mantine Transition whose in/out styles drive width + margin-left (plus opacity/translateX): as its slot grows from 0 → the 36px icon, the flex-end header row reflows and pushes the theme + client-settings buttons one slot to the left; the exit reverses and they slide back over. A negative margin-left while collapsed cancels the row gap so the theme icon stays flush-right when the toggle is absent. Only appearance/ disappearance (connect / disconnect / narrow viewport) animates — opening or closing the sidebar leaves the toggle in place. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015sMW1YZXkM7fDkdPtHpE95 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
0c6fafbf60 |
feat(web): add an open-monitoring-column button to the server list
When a server is connected, has monitor screens to show, and the monitoring column isn't open, show an "open monitoring column" button to the right of the list expand/collapse toggle on the server list. It reuses PinColumnButton (the same right-sidebar affordance the monitor screens use, now with an optional label), so opening the column is reachable from the server list — not only from a monitor screen's pin button. InspectorView gates it on `canPin && monitorAvailable.length > 0 && !effectivePinned`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw |
||
|
|
9824055425 |
refactor(web): unify message-direction arrows and rename Pinned section (#1623)
- Direction labels now both point right so the arrow reads clearly at badge size: incoming "client ← server" becomes "server → client" (from → to), in MessageDirectionBadge and MessageDirectionFilter (shared by the Protocol and Network views). - The Protocol list's pinned section header now reads "Pinned Messages" (was "Pinned Requests"), matching the "Messages" panel title. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw |
||
|
|
ace4250d31 |
refactor(web): rename "History" tab/feature to "Protocol" (#1623)
Rename the web client's History tab and its supporting code to Protocol so the feature reads as the MCP "Protocol" view. - Tab renders as "Protocol"; INSPECTOR_TAB_IDS, InspectorView tab constants/MonitorTab, TAB_UI_REGISTRY key, and navigation.ts tab id all updated to Protocol. - Rename components + folders: HistoryScreen -> ProtocolScreen, HistoryListPanel -> ProtocolListPanel, HistoryEntry -> ProtocolEntry, HistoryControls -> ProtocolControls, historyUtils -> protocolUtils (plus their tests and stories, and exported types). - Rename App/InspectorView plumbing: historyUi, pinnedHistoryIds, on*History* handlers/props, replayHistoryRequest, isReplayableHistoryMethod, sort/compact scopes, etc. -> Protocol names. - Export filenames use the "protocol" slug (buildExportFilename + ExportKind). Preserved (per issue #1623 decisions): - The in-panel section discriminator keeps its "pinned" | "history" values, and the sub-sections stay labeled Pinned / History. - window.history and core message-log/replay concepts are untouched. npm run ci passes (format, lint, build, unit tests, >=90 per-file coverage gate, smoke, Storybook). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw |
||
|
|
87630f2356 |
refactor(web): address review — scope failure UX to connect attempts, dedupe timing (#1621)
Review feedback on #1622: - #1 (medium): `connectionStatus === "error"` also covers a mid-session crash of a previously-connected server, not just a failed connect attempt. Scope the auto-open + failure tab set to connect attempts only: the auto-open effect's error arm now requires the previous status to not be `connected` (connecting/disconnected → error), and `failed` (render) requires `erroredServerId`, which the parent sets only for connect- attempt failures. A mid-session crash now closes the column like any teardown instead of reopening a user-closed column / dropping the live Logs tab. This also aligns the column behavior with the red border. - #3 (minor): extract the column slide duration into a shared `monitorColumnAnimation.ts` (MONITOR_COLUMN_ANIM_MS + FAILED_CARD_SCROLL_DELAY_MS) so InspectorView's Transition and ServerCard's scroll delay can't drift. - #4 (nit): composite key for ConsoleScreen entry rows instead of the array index. Added a mid-session-crash test (connected → error with no erroredServerId closes the column). Verified live: a connect failure still opens the column to Console. Full `npm run ci` passes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw |
||
|
|
c6999dff96 |
feat(web): red border on a server card whose connection attempt failed (#1621)
Flags the server whose last connection attempt failed with a red card border, cleared when another server is connected or a new connection is attempted — a persistent, at-a-glance companion to the failure toast and the auto-opened monitoring column. - App tracks `failedServerId`: set in onToggleConnection's non-auth handshake catch, cleared at the start of any new connect attempt. It is deliberately kept out of resetSessionScopedUiState, since the failure's own `disconnect` event would otherwise clear it the same tick (that same event clears `activeServer`, which is why the flag is tracked separately). - New `errored` Card theme variant (red border via a new `--inspector-error-border` token), mirroring the `highlighted` green border. Precedence: disabled (dimmed) > errored > highlighted. - Plumbed `erroredServerId` through InspectorView → ServerListScreen → ServerCard (`errored` prop). Full `npm run ci` passes (validate + coverage + smoke + storybook). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw |
||
|
|
219298b24d |
feat(web): add Console (stderr) tab; show it on stdio connect failure (#1621)
A failed stdio launch had no useful diagnostic surface: the message log (History) is cleared on the error transition and there are no HTTP requests (Network), so the auto-opened monitoring column was empty. The process's stderr — which explains the failure (e.g. "No module named mcp_server_time") — was captured in StderrLogState but never displayed. - New ConsoleScreen renders the captured stderr stream (search / sort / clear / export; embeddable in the monitoring column), wired through App via the existing useStderrLog hook + StderrLogState. - Console is a pinnable monitor tab like Logs/History/Network, gated to stdio servers (HTTP has no child process). It lives in the header menu when the column isn't pinned, and moves into the column when it is. - The failure column now picks its tabs by which diagnostic actually captured content (stderr → Console, fetch requests → Network), not by the active server's transport: a connect failure fires the client `disconnect` event, which clears `activeServer`, so transport can't be read back there. The memo re-runs as stderr/fetch entries stream in, so the diagnostic tab appears as soon as the failing process prints. Verified live against a failing `python -m mcp_server_time` stdio server: the column auto-opens to Console showing the real ModuleNotFoundError. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw |
||
|
|
32a3610737 |
Merge remote-tracking branch 'origin/v2/main' into 1579-wave-1
# Conflicts: # clients/cli/package-lock.json # clients/cli/package.json # clients/web/src/App.tsx # clients/web/src/lib/environmentFactory.test.ts # clients/web/src/lib/environmentFactory.ts # clients/web/src/lib/remoteOAuthStorage.test.ts # clients/web/src/lib/remoteOAuthStorage.ts # clients/web/src/test/core/auth/connection-state.test.ts # clients/web/src/test/core/auth/providers.test.ts # clients/web/src/test/core/auth/storage-browser.test.ts # clients/web/src/test/core/auth/storage-remote.test.ts # clients/web/src/test/core/mcp/oauthManager.test.ts # clients/web/src/test/integration/auth/node/storage.test.ts # clients/web/src/test/integration/storage/adapters.test.ts # clients/web/src/utils/clearServerOAuthState.test.ts # clients/web/src/utils/clearServerOAuthState.ts # core/auth/connection-state.ts # core/auth/oauth-storage.ts # core/auth/providers.ts # core/auth/storage.ts # core/auth/store.ts # core/storage/adapters/remote-storage.ts |
||
|
|
c5e064ff71 |
feat(web): monitoring column search, close-to-fullscreen, and open/close animation (#1616)
- Add a search box to MonitoringControls (right of the tabs). It's a single shared search kept across column tab switches, filtering whichever screen is shown. Embedded panels apply this text but still ignore the sidebar-only filters (levels/categories/directions/method), which have no control there. - Close button now uses the "collapse sidebar" icon (TbLayoutSidebarRightCollapse), the mirror of the pin button's expand icon. - Closing the column carries its selection to the primary view: the screen the user was watching becomes the active header tab (rather than snapping back to the pre-pin selection). - Animate the column open/close: the handle + column slide in from the right and fade (reversing on close) via a Mantine Transition. - HistoryListPanel: drop the redundant "History (N)" header when there are no pinned requests (lone unpinned section renders headerless). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
9b76272ad1 |
fix(web): address review feedback on the monitoring column (#1616)
- ResizeHandle: clear the `resizing-col` body class on unmount so a drag
interrupted by the handle unmounting (narrow past the breakpoint / disconnect)
can't leave page-wide user-select:none / col-resize applied.
- Embedded monitor column now shows the full unfiltered stream instead of
silently mirroring the full-size screen's live filter (the column has no
filter control to explain an empty/partial list).
- MonitoringControls: give the SegmentedControl an aria-label ("Monitoring
screen") so the tab group has an accessible name.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
081fd789f1 |
feat(web): pin Logging/History/Network as a resizable column (#1616)
Add the ability to "pin" the Logging, History, and Network screens into a second column on the right of the InspectorView, so a primary screen and a monitor screen are visible at once (e.g. watch Logs while calling a Tool). - A "pin as column" button on the Logs/History/Network panel toolbars opens the column (shown only when wide + connected). - The column hosts a MonitoringControls tab row (available monitor tabs + close) over the selected embedded screen. Pinning removes the monitor group from the header tab bar; closing (x) returns it. - The divider between the primary area and the column is draggable (pointer + keyboard); width is clamped and persisted. - The split collapses to one column below ~1040px (the ServerListScreen single-card breakpoint) and while disconnected, without discarding the pin preference — it reopens when wide + connected again. State (pinned/tab/width) is view-local and persisted via the existing useLocalStorage pattern, so no new props thread through App and ViewHeader is unchanged (it just receives a filtered tab list). Screens/panels gain an `embedded` mode (fills the parent height, drops the 340px filter sidebar) via a shared EmbeddableScrollArea element. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
cfbdfdf2bd |
Merge remote-tracking branch 'origin/v2/main' into 1579-wave-1
# Conflicts: # AGENTS.md # clients/cli/README.md # clients/web/src/App.test.tsx # clients/web/src/App.tsx # clients/web/src/test/core/auth/utils.test.ts # core/mcp/node/transport.ts # test-servers/src/composable-test-server.ts |
||
|
|
562bdd00f1 | All of the mid-session and step-up auth work | ||
|
|
4bad110942 |
test(web): eliminate timeout flakiness in the web test suite (closes #1596)
Root-cause and deterministically fix the recurring 5s-timeout flakes that
surfaced under v8-instrumented, concurrent load. All fixes are test-only; no
product source changed. No test was skipped or disabled, and the per-file
coverage gate (>=90 on all four dims) still holds.
Common root cause across the modal/form/screen suites: userEvent.setup()
schedules a real setTimeout between keystrokes. Under CPU contention those
yields balloon, so multi-field interactions blew past the 5s per-test
timeout. Fix: userEvent.setup({ delay: null }) removes the per-keystroke
real-timer dependence (typing dispatches synchronously), making the suites
load-independent.
Per file:
- ServerConfigModal / ServerImportConfigModal / ServerImportJsonModal /
ResourcesScreen / InspectorView: userEvent.setup({ delay: null }).
- PromptArgumentsForm (completions): replaced wall-clock sleeps sized just
past the 300ms completion debounce (await setTimeout(400)) — which race
the debounce timer under load — with awaited conditions (findBy/waitFor)
on the real rendered outcome, plus delay:null. Negative assertions now
lean on the component's synchronous state/timer teardown instead of a
timed window, so they are deterministic regardless of machine speed.
- inspectorClient integration "tracks stderr logs": the child's stderr is
piped out-of-band from the tool's JSON-RPC response, so reading the log
synchronously after callTool raced the stderr chunk. Wrapped the assertion
in vi.waitFor so it polls until the line lands.
Verified: web validate + 6x test:coverage (4 sequential + 2 concurrent
under load) + standalone test:integration, all green (229 files / 3237
tests per coverage run; 799 integration tests), zero intermittent failures.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S3fTN8H3R8YV4yUGvZjYnX
|
||
|
|
0b0bd40e5d |
fix(web): remove mantine-border-animate, use green highlight border (#1536)
* fix(web): remove mantine-border-animate, use green highlight border (#1535) The @gfazioli/mantine-border-animate dependency emitted a persistent React "forwardRef render functions accept exactly two parameters" warning on every page load. Remove the dependency entirely and replace the freshly-added card's animated border with a green border. - Drop @gfazioli/mantine-border-animate from clients/web (package + lock) and remove its styles.css import from main.tsx. - Add a `highlighted` variant to the Card theme that draws a 2px green border, driven by a new --inspector-highlight-border token in App.css. - ServerCard now selects the variant (disabled > highlighted) instead of wrapping in BorderAnimate; the BorderAnimate flex-wrapper `w="100%"` workaround is no longer needed. - Update ServerCard/ServerListScreen tests to assert the highlighted variant instead of the removed BorderAnimate DOM. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(web): drop redundant Card props on ServerCard (#1535) ThemeCard.defaultProps already sets withBorder and padding="lg", so the explicit props on ServerCard's <Card> were redundant. Addresses review feedback on PR #1536. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(web): note highlighted Card variant relies on inherited withBorder (#1535) Addresses a maintainability flag from the PR #1536 re-review: the highlighted variant only sets borderColor/borderWidth and depends on the withBorder:true default, so make that coupling explicit in a comment. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c96db0402e |
test: raise per-file coverage gate to 90 across all clients (closes #1271) (#1532)
* test(web): audit branch-coverage outliers, raise per-file gate to 70 (#1271) The web per-file coverage gate had drifted red: the recently-landed EMA auth feature (core/auth/ema/*) and ClientSettings components shipped undertested, and the prior 50% branch floor let business-logic branches go untested. CI does not run the coverage gate, so this accumulated. Add tests to bring every failing file above the gate and push branch coverage well past 70 across the board, then raise the per-file branches threshold 50 -> 70 in clients/web/vite.config.ts. - EMA core (emaFlow, resourceContext, jwt, wire): 50-88% -> 86-100% branch - EMA OIDC/transport (idpOidc, tokenEndpoint, transportProvider): -> 97-100% - oauthManager / discovery: 73-83% -> 88-92% - ClientSettingsModal/Form, ConnectionInfoContent: 0-88% -> 96-100% - useClientSettingsDraft / useEmaIdpLoginState hooks: 20% -> 100% - TaskStatusBadge / SortableServerCard outliers: 50% -> 100% Coverable branches reached by mocking useComputedColorScheme (dark theme), dnd-kit useSortable (isDragging), and fetch/OIDC paths. Remaining sub-100 branches are genuinely-unreachable defensive guards or happy-dom-inherent (portal / useMediaQuery / typeof window) and documented in test comments. No source files changed. Global web branch coverage 88.73% -> 90.63%. AGENTS.md updated; cli/tui/launcher stay at 50 (out of scope). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test: raise per-file coverage gate to 90/90/90/90 across all clients Follows the #1271 web branch-coverage audit (50 -> 70) by raising the whole per-file gate to >=90 on all four dimensions — lines, statements, functions, branches — for web, cli, tui, and launcher (core/ is measured under the web suite). A codebase-wide audit added real tests for every outlier and brought global coverage up across the board. Config: all four vitest/vite coverage configs set to 90/90/90/90. Two pure-type modules (core/mcp/import/types.ts, clients/web/server/ types.ts) added to the web coverage exclude list, matching the existing type-only exclusions. Tests: ~50 test files extended/created across core (auth, ema, mcp, remote, state, storage, react), web components/screens/server, and the cli/tui clients. Highlights: full EMA OAuth flow + transport coverage, inspectorClient/remote server backfill, every Mantine control's handler branches, and the cli unsupported-method / argv-fallback paths. Genuinely-unreachable branches are annotated at the source with justified `/* v8 ignore -- <reason> */` comments rather than lowering the gate: happy-dom-inherent paths (Mantine portal, useMediaQuery, typeof window SSR guards), React StrictMode effect replay, and provably-dead defensive guards (?? fallbacks the types make impossible, Select.onChange values outside the allowed list). No runtime logic changed; the only structural edits are two behavior-preserving refactors (ResourceTemplatePanel hoists the field value to a local so the ignore applies in JSX; ToolsScreen swaps a ternary for an if) and prettier line-wrapping. Also fixed a flaky real-timer debounce assertion in PromptArgumentsForm (asserted an exact sibling-typing interleaving that raced the 300ms debounce; now asserts the load-bearing fact — a non-empty fire-time sibling capture). AGENTS.md updated to describe the uniform 90 gate and the v8-ignore policy. All four client coverage gates, root validate, storybook (370), and smoke pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(web): make connect-crash SSE reader stop on the terminal event `readSseEvents` looped until the server closed the stream (`done`). The server closes the events stream immediately after writing the terminal `transport_error`, but under the heavy parallel scheduler pressure of a full `coverage` run (v8 instrumentation across all workers) that close can lag far enough behind the event to trip the 30s test timeout — a hang, not a wrong result. This made the test intermittently fail under `npm run coverage` (it passed in isolation and under CI's lighter `test:integration`). Stop reading as soon as the terminal `transport_error` event arrives (cancelling the reader) instead of waiting only for `done`. That event is always the last one the session queue emits, so nothing is lost, and the test no longer depends on stream-close timing. The `done` break is kept for any stream that closes first. Verified green across two consecutive full `npm run test:coverage` runs (3094 tests) where it previously hung, plus isolation (~0.5s). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
d04a7fb9c8 |
fix(web): keep server cards a fixed height (#1528) (#1530)
* fix(web): keep server cards a fixed height (#1528) Two causes made server cards ragged-height: 1. The command/URL box wrapped long content onto multiple lines, growing the card. Add a `nowrap` Code variant (single line, ellipsis on overflow) and a `wrap` prop on ContentViewer (default true); ServerCard passes `wrap={false}`. The full value stays available via the copy button. 2. At ~1000-1500px the grid packed cards narrow enough that a connected card's action row (Clone/Edit/Remove + Connection Info/Settings) wrapped and stacked. Switch the SimpleGrid to container queries with thresholds that keep every card >= ~500px wide at each column count, so it drops to fewer/wider columns instead of wrapping. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): address review — correct threshold comment, single-line note, title tooltip - Correct the column-threshold comment in ServerListScreen: the values (1040/1560) use N gaps, not N−1; restate the formula accurately so the numbers aren't "fixed" to match a wrong formula later. - Note in the ContentViewer `wrap` JSDoc that `false` is for single-line values only (nowrap collapses embedded newlines). - Add a native `title` tooltip to the non-wrapping Code box so a clipped command/URL is readable on hover, not only via copy. Covered by tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
97b1a7fbff |
feat(web,core): import client configs and registry server.json (#1348) (#1511)
* feat(web,core): import client configs and registry server.json (#1348) Wire up the two server-import affordances that were previously todoNoop: - Import config: source picker (Claude Desktop / Cursor / Cline / VS Code) read on the backend via a new GET /api/import-source, plus a file-upload fallback. Per-server review with import/skip on new servers and overwrite/skip/rename on id conflicts, then a per-server outcome summary. - Import server.json: parse the MCP registry single-server format (npm/pypi/oci packages + remotes, env vars, package/runtime args, URL template vars) into a runnable config, with a file picker and debounced live validation; Add Server is disabled until validation passes. Core gains an isomorphic import strategy layer (core/mcp/import) with a strategy registry, client-config + server.json parsers, a strategy-agnostic merge, and a dependency-injected source resolver. useServers exposes importSource(). Modal headers are bold and the redundant server.json section header is folded into the modal title. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): collapse the server.json textarea into a File Contents disclosure Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): auto-collapse File Contents disclosure after content loads Once content is pasted or a file is loaded, collapse the File Contents disclosure after ~1s with Mantine's built-in accordion animation, signaling the input was accepted and surfacing the validation/env-var sections. The disclosure becomes controlled by the modal; clearing the textarea re-opens it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): flash File Contents hover highlight before auto-collapse Before the File Contents disclosure auto-collapses, paint it with its hover background for 250ms, then run the collapse over a 325ms animation, so the collapse reads as intentional. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): rename "Import server.json" to "Import from registry config" Rename the Add Servers menu entry and the modal header. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): rename "Import config" to "Import client config" Rename the Add Servers menu entry and the modal header. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(web,core): move import orchestration into core helpers + web hooks Make the import logic reusable by CLI/TUI and keep the modals display-only: - core/mcp/import: add parseClientConfig (mcpServers→servers fallback), resolveServerId, selectServerJsonOption, and buildServerConfigForSelection, so the full import (parse → select → build → merge) is callable from any environment. - core/storage: extract the pure validateStoreId into a Node-free store-id module (store-io re-exports it) so the isomorphic import layer can validate ids without pulling Node deps into the browser bundle. - clients/web/hooks: useServerJsonImport and useImportClientConfig own all the wizard/parse/debounce/apply state; ServerImportConfigModal and ServerImportJsonModal are now thin display shells over the dumb panel. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): rename "Import client config" to "Import from client config" Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): remove the Cancel button from the registry import modal The modal header X (and Escape) still dismiss it; drop the redundant Cancel button (and the panel onCancel prop). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): hide validation results + name override until content is present In the registry import modal, the Validation Results section and the Server Name override field stay hidden until something has been pasted/loaded into the textarea. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): make Validation Results a collapsible disclosure Wrap the registry import modal's validation results in a Mantine accordion disclosure (default open), matching the File Contents section. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): Server Name disclosure with read-only derived name + override Make Server Name a collapsible disclosure containing a read-only "From configuration" field (the id derived from the server.json name) and the editable Override field. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): scroll to and highlight a freshly-added server After a manual add/clone or an import (client config / registry), the new server's card scrolls into view and draws an animated border (Mantine Border Animate); clicking anywhere on the card dismisses it. - App tracks highlightedServerId; addServerHighlighted sets it (edits and conflict-overwrites don't). Threaded through InspectorView -> ServerListScreen -> ServerCard. - ServerCard scrolls into view on highlight and wraps in <BorderAnimate>; onClick clears the highlight. - Pin @gfazioli/mantine-border-animate@2.0.3 (Mantine 8 compatible; 2.0.4+ requires Mantine 9). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): highlighted card fills its cell and its controls work on first click - The BorderAnimate wrapper is display:flex, so the card was shrinking to content width while the border spanned the cell. Stretch the card with w=100% (and the wrapper). - Always render BorderAnimate and toggle show/animate instead of conditionally wrapping: swapping the element type on clear remounted the card, swallowing the click that triggered it (e.g. the connect toggle fired clear but not connect, needing a second click). Verified: one click now connects + clears. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web,core): address review — OCI env, untrack fixture, rename validation - Untrack the stray inspector-v1-multi.server.json (accidentally committed) and gitignore root *.server.json fixtures. - core/mcp/import/serverJson: forward declared env vars into OCI containers via '-e KEY' docker args (they previously only set the docker CLI process env); add a test. Clarify buildServerConfig (destructure env out instead of spread-then-delete) and document value-vs-default env handling. - useImportClientConfig: validate 'rename' targets inline (invalid charset / collision with existing, additions, or other renames) and gate the Import button on canImport; surface the error on the field. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): highlight all servers from a batch import, not just the last Track highlighted servers as a set of ids instead of one. addServerHighlighted accumulates each id (a multi-server client-config import highlights them all); opening an add/import modal starts a fresh batch. Clicking a card clears only that card's highlight; only the first highlighted card scrolls into view. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
91f02fd6db |
feat(web): catalog vs read-only session launch model + apply --header (#1481, #1483) (#1496)
* feat(web): catalog vs read-only session launch model + apply --header (#1481, #1483) Per specification/v2_catalog_launch_config.md, `mcp-inspector --web` now selects the backend's server-list source and a session-wide writability, instead of seeding an inert `initialMcpConfig` the UI never read: - `--catalog <path>` (and `MCP_CATALOG_PATH`) → active WRITABLE catalog (seed/CRUD/watch), like the default catalog. - `--config <path>` → READ-ONLY session file: served in the UI but never written, seeded, or plaintext-secret-migrated — safe to point at a foreign config (e.g. claude_desktop_config.json). - ad-hoc `--server-url`/command (+ `--header`) → one server held IN MEMORY (no file written); `--header` is lifted onto the entry so the seeded connection actually uses it (closes the #1483 warn-only no-op). - nothing → default catalog (writable), unchanged. Backend (`createRemoteApp`): new `writable` + `initialServers` options. GET /api/servers branches three ways (in-memory / read-only file with no seed+no migrate / writable catalog); the four mutating routes return 403 when read-only; `/api/config` carries `writable`; the file watcher is skipped for in-memory sessions. `--catalog`/`--config` are mutually exclusive and neither combines with an ad-hoc target or `--header`; `--header` requires an ad-hoc HTTP/SSE server (clear errors otherwise — no silent no-op). UI: `useServerListWritable` reads `writable` from /api/config; the Servers screen hides add/edit/clone/remove/reorder/settings and shows a read-only banner when false. All new props default to writable=true (back-compatible). Docs: spec gaps G2/G3/G4 updated; launcher README documents the flags. Verified end-to-end against the built launcher: --config (writable:false, file byte-stable, mutations 403), ad-hoc --header (in-memory, header lifted), and --catalog (writable:true, POST persists). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(web): address review — Mantine <Code> in read-only banner, clarify watcher comment - Replace raw <code> with Mantine <Code> in the read-only-session Alert (repo convention: prefer Mantine primitives over raw HTML). - Add a comment near `watchable` clarifying that a read-only `--config` file is still watched on purpose (read-only blocks UI writes, not external observation; `writable` does not gate watching). Deferred (per reviewer): consolidating useSandboxUrl + useServerListWritable into one useConfig hook to avoid the duplicate /api/config fetch — optional follow-up. The redundant ad-hoc header type-guard is kept as defensive. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
ef921ba052 |
fix(web): hide resource subscription UI when server lacks subscribe capability (#1479)
* fix(web): hide resource subscription UI when server lacks subscribe capability (#1478) Thread a `subscriptionsSupported` flag derived from `capabilities?.resources?.subscribe === true` from App through InspectorView → ResourcesScreen to the leaf components. When false: - ResourcePreviewPanel hides the Subscribe/Unsubscribe button (Refresh stays). - ResourceControls omits the Subscriptions accordion section entirely, dropping it from `allSections` so the expand/collapse-all toggle and open-section bookkeeping stay correct even if a stale "subscriptions" entry persists from a prior subscription-capable session. The flag defaults to true at the leaf components so existing callers, tests, and stories keep rendering the subscription controls unless a server is explicitly marked unsupported. Adds unit tests and Storybook stories covering the hidden and shown states across all three components. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): give the Servers screen scroll the same gutter/idle-hidden treatment (#1474) The server grid's ScrollArea.Autosize used the default type="hover" with no offsetScrollbars, so the scrollbar popped in on hover and overlaid the right edge of the server cards (occluding their action icons / status badges) — the same problem #1474 fixed for the History/Network/Logging list panels. Apply the same `type="scroll"` + `offsetScrollbars` treatment, and reuse the shared `expectScrollbarGutterIdleHidden` story assertion to regression-protect the gutter + idle-hidden behavior. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): stop the InspectorView shell from scrolling as a whole AppShell.Main carried Mantine's default `padding="md"` on top of the fixed-header offset, but every screen already sizes itself to `calc(100vh - header)` and supplies its own `xl` padding (with no allowance for Main's padding). That extra inset pushed screen content past the viewport, so the whole InspectorView gained a page-level scrollbar instead of confining overflow to each screen's inner ScrollArea. Set the shell `padding` to 0 (Main now contributes only the header offset, matching what the screens were built for) and clamp the Main slot to `height: 100dvh` with `overflow: hidden` in the theme, so the view can never scroll regardless of content — only the inner ScrollArea regions do. Adds an InspectorView story play assertion guarding the invariant (Main clips + is viewport-clamped) and a ManyServers fixture that confirms it holds under enough content to overflow, with the inner server-grid ScrollArea absorbing the overflow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): address review — align viewport units + drop stale subscriptions intent Responding to the @claude review on #1479: - Align all screen height calcs from `100vh` to `100dvh` so they match the Main slot's `100dvh` clamp. On a mobile viewport where `dvh < vh` a `100vh` child would have exceeded Main's `100dvh` content box and been clipped by `overflow: hidden`; the units are now in lockstep. - Restrict `intendedButEmptySections` in ResourceControls to `allSections` so a stale "subscriptions" entry persisted from a prior subscription-capable session is dropped from persisted open-sections instead of being perpetually re-appended once the section is no longer rendered. Adds a regression test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
bef8b84ee8 |
fix(web): style Resources accordion native scrollbars to match Mantine (#1466) (#1473)
* fix(web): style Resources accordion native scrollbars to match Mantine (#1466) The per-section accordion panels (from #1462) scroll with a native scrollbar, which looked inconsistent with the Mantine ScrollArea used elsewhere. Style it to match: thin, rounded, translucent thumb on a transparent track, via a new `--inspector-scrollbar-thumb` token (rgba(0,0,0,.4) light / rgba(255,255,255,.4) dark, mirroring Mantine's thumb), `scrollbar-width: thin` + `scrollbar-color` (Firefox) and `::-webkit-scrollbar` rules (Chromium/WebKit). A Mantine ScrollArea *component* can't be used here: its viewport has no intrinsic height, which would collapse the panel's content-based flex-basis that #1462's per-section sizing depends on (the sections would stop sizing to their content). Documented inline. Verified in a real browser that the per-section sizing/pinned-headers behavior is unchanged and the scrollbar is now thin + themed in both color schemes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(web): assert themed scrollbar-color on the accordion panel (#1473 review) Guard the per-section scrollbar's themed thumb against regressions, per review — `scrollbar-color` resolves to an rgba value (the --inspector-scrollbar-thumb token) vs a transparent track. WebKit pseudo-element styles aren't queryable, so this is the assertable proxy. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
d525fc9f54 |
fix(web): ResourceControls single-scroll, full-height panel, right/down chevrons (#1462) (#1464)
* fix(web): ResourceControls single-scroll + right/down disclosure chevrons (#1462) Two fixes to the Resources controls panel: 1. Sections scrolled prematurely. `panelMaxHeight` divided the available height equally across open sections (`… / n`), so a section with many items scrolled while sparse siblings left their equal share unused. Replace the per-section ScrollAreas with a single viewport-bounded ScrollArea.Autosize around the whole accordion (matching ToolControls / PromptControls). Sections size to content; nothing scrolls until the open sections' combined height exceeds the cap. 2. Accordion chevrons pointed down/up. Add a `disclosure` Accordion theme variant + `.disclosure-chevron[data-rotate]` rule that rotates a right-pointing RiArrowRightSLine 90° (not Mantine's default 180°), so closed sections point right and open sections point down — matching the ResourceLink chevron convention. Also keep empty (disabled) sections out of the accordion's open set so they render collapsed with a right chevron instead of an open-but-empty panel. Verified all states in a real browser via Playwright. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(web): cover empty-section-collapsed behavior in ResourceControls (#1462) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): make Resources sidebar fill panel height before scrolling (#1462) The first pass capped the accordion scroll region at a fixed sub-viewport height (`100vh - … - 160px`), so the sidebar scrolled before reaching the height of the detail panel to its right. Make the sidebar card full-height instead: add a `sidebar` Card variant (height:100%, flex column, overflow hidden), give it `flex: 1`, and let ResourceControls' root Stack (`flex: 1; min-height: 0`) and a plain flex-filling ScrollArea claim the space below the title/search. The card now matches the detail panel's height exactly and the single inner scroll region scrolls only once the open sections overflow the full panel. Adds a ResourcesScreen ManyResources story asserting the sidebar/detail height match and that overflow scrolls, plus a unit test for the empty-section-collapsed behavior. Verified heights + scroll in a real browser via Playwright. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): pin Resources section headers, scroll each section in place (#1462) The previous pass scrolled the whole accordion as one block (headers scrolled away). Restore pinned headers with per-section scrolling, without the original equal-division premature-scroll bug. The `disclosure` Accordion now fills its full-height card as a flex column (`disclosure-sections`): controls are pinned, and each open section's panel scrolls within its own share of the remaining height. Per-item `flex` weights (shrink ∝ item count, grow 0) hand space from long sections to short ones, so nothing scrolls until the combined content overflows the panel — a long URIs list scrolls in place while Templates stays fully visible below it. Drops the single outer ScrollArea (and useScrollMemory). Verified in a real browser: 6 URIs → no scroll, headers pinned, empty space at the bottom; 40 URIs → URIs panel scrolls in place, Templates intact, all three headers pinned, sidebar still matches the detail height. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): Resources sidebar grows with content, capped at screen (#1462) The `sidebar` Card variant forced `height: 100%`, so the panel was always full-height with empty space below short content. Switch to `max-height: 100%` (and drop `flex: 1`) so the card hugs its content and only caps at the screen height — matching the Tools panel. When content overflows the cap, the flex accordion still takes over per-section scrolling with pinned headers. Verified in a real browser: 6 URIs → card hugs (638px, shorter than the detail panel); 40 URIs → card caps at the screen (949px == detail), headers pinned, URIs scrolls in place. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): preserve empty-section intent + kill accordion collapse jump (#1464 review) Address review feedback on PR #1464: - Intent loss (review #1): the accordion's `value` is the filtered `visibleOpenSections`, so Mantine's toggle dropped open-but-empty sections from the persisted intent. Merge them back in the onChange handler so an open empty section stays open and reappears with items. Adds a unit test. - Collapse animation jump (review #3): Mantine's Collapse briefly set the panel to its full natural height (fighting the flex layout) on open/close, flashing siblings. Set `transitionDuration={0}` so flex controls the height; restore a smooth chevron rotation via CSS. Verified no jump in a real browser (panel 501→0, no spike). Since closed sections now render no content, drop the redundant (and incorrect) "Templates" toggle clicks from 4 ResourcesScreen tests (the section is open by default) and add a section-toggle test to keep the openSections handler covered. - Nits: scope the stories' chevron query to the accordion root; tighten the Collapsed assertion to the identity transform. Native-vs-styled scrollbar (review #4) split out as #1466. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(web): clarify chevron-timing and intent-merge invariants (#1464 review) Address the two optional doc nits from the re-review: - Note that the chevron's 200ms restates Mantine's default accordion duration (keep in sync if accordion timing is themed). - Note that the unconditional empty-section merge is safe because empty-section controls are disabled, so `next` never contains one. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
8d4363d0ea |
feat(web): expandable resource_link with read-on-demand (#1449) (#1456)
* feat(web): expandable resource_link with read-on-demand (#1449) Replace the placeholder `resource_link` rendering in ContentViewer with a dedicated, Mantine-native `ResourceLink` element that mirrors V1's behavior: - Shows uri (monospace, link-styled), optional name/description, and a mimeType badge. - Expand/collapse affordance (chevron + loading spinner) that is keyboard accessible (native button: role, aria-expanded, aria-label, Enter/Space). - On first expand, reads the linked resource on demand via an injected callback and renders the full read result inline as formatted JSON under a green "Resource:" label, capped to a max height with scroll for large results. The fetched result is cached across collapse/expand. The read handler is threaded as a "dumb component + callback as prop" through ToolResultPanel → ToolsScreen → InspectorView, with App supplying a handler that returns the `ReadResourceResult` from `inspectorClient.readResource`. Also extract the shared `resourceContentsToBlock` helper into `utils/resourceContents.ts` (previously private to ResourcePreviewPanel). Tests: unit coverage for ResourceLink (collapsed/expanded/error/cache) and the ContentViewer wiring, plus Storybook stories with play functions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): scroll large resource_link read results within the card A plain ScrollArea with only `mah` does not scroll when its parent (the content-sized card) has no definite height, so a large read result overflowed the card instead of scrolling. Switch to ScrollArea.Autosize, which grows with content up to the max height and then scrolls. Adds a LargeResult story covering the bounded-scroll behavior. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(web): make ResourceLink a group; extract ResourceLinkInfo element ResourceLink is stateful (expand/loading/cached result) and fetches data via a callback — group-like behavior, and the only `elements/` component using useState. Move it to `groups/` and split out the pure-display metadata as a new `elements/ResourceLinkInfo` element (uri/name/description/mime + an optional action slot). This keeps the layer dependencies clean (no element→group import, which moving ResourceLink alone would have introduced): - ContentViewer (element) renders ResourceLinkInfo (element) statically for `resource_link` blocks and no longer takes an onReadResource prop. - ToolResultPanel (group) renders the interactive ResourceLink (group) for `resource_link` blocks, ContentViewer for the rest. - ResourceLink (group) composes ResourceLinkInfo + ContentViewer. Also reverts the now-single-consumer resourceContents.ts extraction, restoring ResourcePreviewPanel's local toContentBlock. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): make ResourceLink read retryable and guard double-fetch Address PR review: a failed read is no longer cached as terminal — only a successful result is cached, so collapsing and re-expanding retries the read after a transient failure. Add a loading guard so toggling while a read is in flight does not fire a redundant second read. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * style(web): prettier formatting for ResourceLink test Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: remove accidentally-committed build artifacts; gitignore clients/*/build A stray 'git add -A' swept 8 compiled build files into d25c9382. Remove them from tracking and add clients/*/build to .gitignore so client build output is never committed again. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
0890c7509a |
feat(web): add Mantine clear buttons to text inputs app-wide (#1333) (#1448)
* feat(web): add Mantine clear buttons to text inputs app-wide (#1333) Add a clear (×) affordance to every user-editable text input across the web client so a populated field can be reset in one click. - Autocomplete: enable `clearable` by default via the theme (`theme/Autocomplete.ts`) — covers the prompt-argument and resource-template variable autocomplete branches. - TextInput / Textarea: Mantine v8 does not expose a public `clearable` prop on these primitives (only the combobox-based components do), so add the clear button manually via `rightSection` + `CloseButton` (aria-label="Clear") shown only when the field has a value, wired to reset the field's controlled value to "". Applied to sidebar search inputs (prompts, tools, apps, tasks, history, resources, network, logs), prompt-argument and resource-template variable inputs, the sampling response/model fields, server config & settings forms, roots table, schema form, experimental features panel, and the server.json import panel. Read-only/display Textareas (OutputValidationModal, UrlElicitationErrorModal) are intentionally left untouched. Tests cover the clear behavior on representative inputs from each category (sidebar search, prompt argument, resource-template variable, sampling response, plus the config/settings forms). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): disable ServerConfigModal clear buttons while submitting Address review #2 on #1448: the clear CloseButton sat in `rightSection` with `rightSectionPointerEvents="auto"`, so it stayed clickable even while the field was `disabled={submitting}`, letting a user wipe a field mid-submit. Forward `disabled={submitting}` to each clear button so it locks together with its input. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(web): remove redundant Auto-respond from SamplingRequestPanel The sampling modal pre-loads its draft with the default stub result, and "Send Response" sends that draft — so on an untouched form "Auto-respond" did exactly the same thing as "Send Response", just with a fresh copy of the same defaults. Drop the redundant button and its `onAutoRespond` prop; "Send Response" now covers both the unedited (stub) and edited cases. The InlineSamplingRequest variant keeps its Auto-respond button — there it is the quick path versus "Edit & Send" (which opens this editable modal), not a duplicate of an adjacent send action. Updated PendingClientRequestModal wiring, stories, and tests accordingly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
d5f12b2a82 |
feat(web): reorderable ServerCards — drag-and-drop + keyboard (#1369) (#1447)
* feat(web): reorderable ServerCards — drag-and-drop + keyboard (#1369) Let users control the order of servers on the Servers screen instead of hand-editing mcp.json. Each ServerCard gets a grip handle (in the header, left of the name) that drives an @dnd-kit sortable; pointer and keyboard sensors both work, with live-region announcements for screen readers. - Backend: PUT /api/servers/order rewrites mcp.json in the supplied order inside the write lock, reusing the atomic-write + watcher-notify path. Registered before /:id so "order" isn't captured as a server id. Rejects with 409 when the id set no longer matches disk and 400 on duplicate / malformed bodies, so a reorder racing an external edit can't drop or duplicate an entry. - Hook: useServers.reorderServers(orderedIds) — optimistic local reorder, PUT, refresh-to-disk-truth on failure. - UI: SortableServerCard wrapper owns the dnd-kit concerns; ServerListScreen wraps the grid in DndContext + SortableContext (rect strategy) with pointer + keyboard sensors and announcements. Reorder math and a11y copy extracted to reorderIds.ts / serverReorder.ts for direct unit testing. Renders plain cards (no grip) when no onReorder is wired. - Wired onServerReorder through InspectorView -> App.tsx. Tests: pure-helper units, hook (persist / optimistic / 409 revert), 9 backend integration tests (incl. id-set-mismatch rejection and the route-registration guard), ServerCard/ServerListScreen units, and a real-browser Storybook keyboard-reorder play (the dnd-kit keyboard sensor needs layout rects that happy-dom doesn't provide). Adds @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): notify on reorder failure; skip reorder glue when not reorderable Address PR #1447 review: - App.tsx: catch reorderServers rejection (409 from a racing external edit, or a network error) and show a red toast, matching the error-handling pattern used by every other mutation — previously the rejection was discarded with `void` and the drag silently bounced back. - ServerListScreen: only build handleDragEnd / announcements when `reorderable`, so the four-closure announcements object isn't allocated every render when no onReorder is wired. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
b0a024269e |
feat(web): list-changed handling — indicator, pull-on-demand default, and per-server auto-refresh opt-in (#1402) (#1443)
* feat(web): wire the listChanged indicator across all screens (#1402) The per-screen list-changed indicator was hardcoded false in InspectorView for Tools, Apps, Prompts, and Resources, so it could never light up. Source it from the managed-state layer instead. Each ManagedXState now tracks a listChanged flag: set true when its */list_changed notification arrives (the existing auto-refresh leaves it set), exposed via getListChanged()/a listChangedChange event, and cleared by clearListChanged() or on disconnect. The useManagedX hooks surface the flag and clear it on a user-initiated refresh (the indicator's Refresh button = acknowledge). App threads the three flags to InspectorView, which maps them to the screens — Apps shares the tools flag since apps are a filtered view of tools. Tests cover set/clear/disconnect at the state-manager layer, the hook subscription + refresh-clears behavior, and the InspectorView routing (including Apps sharing the tools flag and no cross-wiring between screens). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(core): close clear-after-await race in managed-list refresh (#1402 review) The useManagedX refresh() callbacks cleared the listChanged flag AFTER awaiting the fetch. If a */list_changed arrived mid-fetch, the state re-set the flag (and auto-refreshed), then the trailing clear wiped that genuinely-new signal. Clear BEFORE the await instead: the user's refresh acknowledges only the change in hand, and a mid-fetch change re-sets the flag and survives. Adds a race regression test on the tools hook. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): drop duplicate Refresh on the Apps screen (#1402) AppControls uniquely had a standalone toolbar Refresh button AND the ListChangedIndicator on a separate row, so once the indicator could actually appear (this PR), the Apps screen showed two Refresh buttons. Match the other three controls (Tools/Prompts/Resources): the indicator is the single header-right refresh affordance. Updates AppControls and AppsScreen tests to assert exactly one Refresh (and none when the list hasn't changed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(core): stop auto-invoking */list on list_changed; user pulls via Refresh (#1402) With auto-refresh on, the list was already current by the time the list-changed indicator appeared, making its Refresh button moot. Now the managed Tools/Prompts/Resources state managers only flag the change on */list_changed (they no longer auto-refresh); the list stays as-is until the user clicks Refresh, which fetches the new list and clears the indicator. The connect-time initial load is unchanged. Resource templates (no own indicator, part of the Resources screen) also stop auto-refreshing; onRefreshResources now refreshes both resources and templates, since a single resources/list_changed covers both. Tasks are left auto-refreshing — they have no list-changed indicator and need live status updates. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(core): correct ManagedResourceTemplatesState header comments (#1402 review) The class no longer subscribes to resourceTemplatesListChanged; update the stale file/class docstrings that still claimed it syncs on that notification to reflect connect + statusChange only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): add per-server 'Auto Refresh on List Changed Notifications' option (#1402) New Options section in ServerSettingsForm with a single checkbox. When checked, the managed Tools/Prompts/Resources (and resource templates) state auto-refreshes on */list_changed notifications; when unchecked (default) the notification only lights the list-changed indicator and the user pulls via Refresh. Plumbing: new optional autoRefreshOnListChanged on InspectorServerSettings and StoredMCPServer, round-tripped through serverList (lifted with a false default, persisted only when true, added to the exhaustive field-key map); surfaced from InspectorClient via a new getServerSettings() on the protocol; the state managers read it live at notification time to decide auto-refresh vs flag-only. Resource templates re-subscribe to their list_changed but only auto-refresh when the option is on (they have no indicator of their own). Tests cover the round-trip (true persists, false omitted), the form section (render/checked/toggle), and each state manager's on/off branch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(server): persist autoRefreshOnListChanged through validateSettings (#1402) The PUT /api/servers write path runs settings through validateSettings, which rebuilds the value from explicitly-named fields and silently drops unknown keys — so autoRefreshOnListChanged never reached disk (the serverList round-trip test passed because it calls inspectorSettingsToStoredFields directly, bypassing this path). Validate it as an optional boolean and include it in the built value; false is omitted on disk by inspectorSettingsToStoredFields as before. Adds integration tests over the real PUT path: true persists, false is omitted, and a non-boolean is rejected (400). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): default the Server Settings modal to the Options section (#1402) Open 'Options' instead of 'Custom Headers' when the Server Settings modal first opens, so the new Auto Refresh toggle is the first thing surfaced. Updates the modal default-expanded section, the modal test's initial-state assertions, the three header tests (now expand 'Custom Headers' first since it's no longer open by default), and the story defaults. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: drop accidentally-committed Inspector history dump; gitignore the class 83076441 swept an exported Inspector message-history JSON (manual-testing artifact at the repo root) into the tree via git add -A. Untrack it and extend the existing repo-root debug-artifact .gitignore block (alongside /*.png) to cover /inspector-history-*.json so these can't be re-added. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
121f875a0e |
feat(web): History — Pin & Replay, plus two history-status fixes (#1438) (#1441)
* fix(web): don't show a permanent PENDING badge on history notifications (#1439) `extractStatus` derived the history status badge purely from whether a `response` was attached, so a fire-and-forget notification (no id, no response ever) — and any unmatched standalone response — was labelled "Pending" forever. The pending → OK/Error lifecycle only applies to request entries (messageLogState attaches the response by JSON-RPC id), so scope the status to `direction === "request"` and render no request-style badge for notifications/responses. The method badge already labels them. Part of the History-screen work on #1438. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(core): track outgoing responses so server→client requests resolve in history (#1440) MessageTrackingTransport.send only tracked outgoing requests, so the client's response to a server→client request (roots/list, sampling, elicitation) was never recorded. The inbound request entry never got its response folded in, leaving it stuck "Pending" with no response body. Make send symmetric with onmessage: track outgoing responses (by id + result/error) as well as outgoing requests. The response now correlates to its request entry, which resolves to OK/Error with the body. Outgoing notifications remain untracked (separate gap). Part of the History-screen work on #1438. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): wire up History Pin and Replay (#1438) Both HistoryEntry actions were rendered but stubbed to todoNoop in App. - Pin: App now owns a session-scoped Set<string> of pinned entry ids, toggled by onTogglePinHistory and passed down as pinnedHistoryIds. HistoryListPanel already sorts pinned entries to the top. The set resets with the rest of the per-screen state on disconnect/server-switch. - Replay: onReplayHistory re-issues the entry's original request by method (tools/call → callTool, prompts/get → getPrompt, resources/read → readResource). The call flows through InspectorClient → tracked transport → message log, so the fresh request+response surface as a new History entry (history-local) without touching the Tools/Prompts/ Resources panels. Unsupported methods / a removed tool surface a toast. Tests: pin toggle propagation, replay dispatch for each supported method, unsupported-method and missing-tool toasts. Closes #1438. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): make history Replay support list/discovery requests Replaying a tools/list entry reported "Replay isn't supported". Extend replayHistoryRequest beyond the call-style requests to the read/discovery methods, re-issuing each via its InspectorClient method (preserving the pagination cursor): - tools/list → listTools, prompts/list → listPrompts, resources/list → listResources, resources/templates/list → listResourceTemplates, ping → ping. Tests: tools/list replay (cursor preserved) and an unsupported-method toast (logging/setLevel). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): replay tasks/list; reorder + conditionally show History buttons - Add tasks/list to the replayable set (re-issued via listRequestorTasks), completing the client-issued */list methods. - Reverse the HistoryEntry actions to Pin then Replay, and hide Replay for entries whose method can't be replayed. - Introduce a single source of truth — REPLAYABLE_HISTORY_METHODS / isReplayableHistoryMethod in historyUtils — used by HistoryEntry to show/hide the button and by App's replayHistoryRequest to gate dispatch, so the two can't drift. Tests: tasks/list replay, Replay hidden for a non-replayable method, and Pin-before-Replay ordering. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): add client↔server direction badge to History and Network (#1437) A dual-state badge at the top-left of each entry shows which way it traveled: "client → server" (green) for inspector-originated, "client ← server" (yellow) for server-originated. - New shared element MessageDirectionBadge (Elements). - History: MessageEntry gains an `origin` ("client" | "server") set at tracking time — MessageTrackingTransport tags `send` as client and `onmessage` as server, threaded through the track callbacks. HistoryEntry maps origin → badge. (direction stays the request/response/notification message-type used for response correlation; origin is the flow.) - Network: fetches are always inspector-originated, so NetworkEntry renders the badge as outgoing. Tests + stories for the badge; HistoryEntry direction tests; transport test asserts the origin argument. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * style(web): use purple (not yellow) for the client ← server direction badge Yellow + green read as caution/ok status; purple vs green reads as direction. Incoming (server → client) is now violet; outgoing stays green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): make History Pinned/History sections collapsible toggles The "Pinned Requests (N)" and "History (N)" section headers are now clickable toggle buttons that expand/collapse their entries — styled like the LogControls level toggles (UnstyledButton listItem variant with the active background) and wrapping the entries in a Collapse. Both default open and collapse independently. Tests assert the per-section aria-expanded toggling and independence. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): replace Expand/Collapse text button with an icon toggle History, Network, and Task cards now use a shared ExpandToggle ActionIcon instead of the "Expand"/"Collapse" text button: LuArrowDownToLine when collapsed (expand), LuArrowUpFromLine when expanded (collapse). The aria-label stays "Expand"/"Collapse" so the control reads the same. - New shared element ExpandToggle (+ test + stories). - HistoryEntry: actions split into a left group (Pin/Replay) with the toggle pushed right via space-between. - NetworkEntry / TaskCard: drop their now-unused SubtleButton const. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): pin icon toggle on HistoryEntry, moved to the right Replace the Pin/Unpin text button with a shared PinToggle ActionIcon (TiPinOutline when unpinned, TiPin when pinned; aria-label stays Pin/Unpin). Move it to the right action group, just left of the expand/collapse toggle — Replay stays on the left. - New shared element PinToggle (+ test + stories). - HistoryEntry action row: Replay left; Pin + expand toggle right. - Drop the now-unused formatPinLabel helper. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): per-section Clear/Export on the Pinned and History headers Each section header now shows Clear and Export links on the right, but only when both sections are present (a single section is already covered by the panel-level Clear/Export). They act on just that section's entries: - Clear: removes that section's entries by pin membership (clearing the Pinned section also drops the now-stale pinned id set). - Export: downloads just that section's entries (inspector-history-pinned/-unpinned-...json). Threaded onClearSection/onExportSection App → InspectorView → HistoryScreen → HistoryListPanel; App computes each section from messages + pinned ids. Extracted a CollapsibleSection subcomponent and a SectionActions links group. Extended ExportKind with history-pinned/history-unpinned. Tests cover both-sections visibility gating and per-section dispatch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): Filter by Message Direction on the History controls Add a "Filter by Message Direction" section to HistoryControls, modeled on LogControls' "Filter by Level": a Select/Deselect All control plus two listItem toggles — "client → server" and "client ← server" — that show or hide entries by origin. - HistoryUiState gains visibleDirections (Record<MessageOrigin, boolean>), defaulting to both visible; persists with the other per-screen state. - HistoryScreen wires the toggles (single + all) through onUiChange and passes the filter to HistoryListPanel, where matchesFilters drops entries whose direction is off (entries without a recorded origin are never filtered out). Tests cover the controls (render, single toggle, select/deselect all), the HistoryScreen onUiChange wiring, and the list-level direction filtering. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * style(web): section Clear/Export as subtle buttons, not link anchors Match the Select/Deselect All control in HistoryControls: the per-section Clear/Export on the History/Pinned headers now use a subtle Button (variant subtle, size xs) instead of an Anchor link. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * style(web): color the direction filter labels to match the badge The "client → server" / "client ← server" toggles in HistoryControls now color their labels green / violet, matching the MessageDirectionBadge. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): track outgoing client notifications in history too Review follow-up (#1441): MessageTrackingTransport.send now also tracks the client's own notifications (initialized, progress, roots/list_changed) as origin "client", making send fully symmetric with onmessage. Previously only id-bearing outgoing messages (requests, responses) were tracked, so client notifications never reached History while incoming server notifications did. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): Filter by Message Direction on the Network screen Add the same "Filter by Message Direction" section to NetworkControls as on HistoryControls. Extracted the shared section into a MessageDirectionFilter group component (used by both HistoryControls and NetworkControls) so they stay identical — a Select/Deselect All control plus green/violet client → server / client ← server toggles. - NetworkUiState gains visibleDirections (default both visible), persisted with the rest of the per-screen state; NetworkScreen wires the toggles and NetworkStreamPanel filters on them. - Network fetches are always inspector-originated, so all entries are client → server: the client toggle gates them all and the server toggle matches nothing (documented in NetworkStreamPanel). The section mirrors History for parity. Tests cover the shared filter, both controls' direction sections, the Network list filtering, and the NetworkScreen wiring; ambiguous "Deselect All" selectors (now two sections) disambiguated by order. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * revert(web): drop message direction from the Network tab Network traffic is always client → server — even SSE is a client-initiated stream tagged outgoing — so a direction badge/filter there is meaningless. Remove the MessageDirectionBadge from NetworkEntry and the Filter by Message Direction section (and its NetworkUiState.visibleDirections wiring) from the Network screen. History keeps its direction badge + filter; the shared MessageDirectionBadge and MessageDirectionFilter components stay (still used by History). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): a lone History section isn't a collapsible accordion When only one of Pinned / History is present, its header is now a plain title and its entries always show — instead of a `listItem` accordion toggle that could hide them based on a stale collapsed state from when both sections were on screen. The accordion (and per-section Clear/Export) only applies when both sections are present. Example fixed: collapse History while pins exist, then unpin everything — History becomes the sole section and shows its entries under a plain header rather than staying collapsed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * style(web): put section Clear/Export on the header pleat itself The per-section Clear/Export sat to the right of the collapsible header bar, on the panel background. Make the whole header row the "pleat": a new Group `sectionHeader` theme variant (rounded + hover, active bg passed per instance) wraps a flex-1 toggle button (the title) on the left and the Clear/Export buttons on the right — so the actions live on the pleat. The toggle is a separate button since the action buttons can't nest inside it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * style(web): entry expand toggle uses the same icons as ListToggle Swap ExpandToggle (History / Network / Task cards) from the LuArrow icons to RiExpandVerticalLine / RiCollapseVerticalLine — the same expand/collapse icons the list-level ListToggle uses. Same ActionIcon size, so it matches the other small buttons on the cards; aria-labels unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): panel Clear keeps pinned entries (history-only) Review follow-up (#1441): the panel Clear button is gated on the unpinned count (implying history-only), but onClearHistory wiped everything (pinned included) and left pinnedHistoryIds stale. Make Clear history-only — clearMessages((m) => !pinnedHistoryIds.has(m.id)) — so pinning protects an entry, matching the gating and the per-section model; the pin set stays valid since its entries survive. Also drop the stale "and Network" mention from the MessageDirectionFilter docstring (Network direction filter was reverted). Test: panel Clear's predicate keeps pinned ids and removes unpinned. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
ed6eda7492 |
feat(web): wire up MCP task support — Tasks screen, Run-as-task, status toasts (#1422) (#1424)
Wires the ported Tasks screen + core task subsystem through App.tsx: sticky Clear Completed, cancel/refresh error toasts, core-side per-task progress correlation, Run-as-task on the Tools screen (gated on server support), per-server Task TTL, and live task-status toasts. |
||
|
|
8923a9bbf9 |
feat(web): tool progress toasts + persist all per-screen state across navigation (#1414, #1417) (#1420)
* feat(web): tool progress toasts + persist tool selection/result across navigation (#1414) Surface incoming notifications/progress as toasts so the user can watch a long-running tool's progress without leaving the tool view — the v2 replacement for v1's always-visible "Server Notifications" shelf. Toasts are keyed by progress stream and updated (not stacked) per tick so a chatty server updates one toast rather than flooding the corner. Progress notifications still land in the History tab via the existing transport-level tracking. Companion change: lift the selected tool and its form values into App.tsx alongside the already-lifted toolCallState, make ToolsScreen controlled, and drop the clear-on-unmount effect. Leaving the Tools tab and returning now preserves selection, inputs, and result. All three reset together on disconnect via resetSessionScopedUiState, preserving the #1368/#1394 behavior. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): persist per-screen selection/search/filter across navigation (#1417) Lift every screen's selection, form, search, and filter state out of the screen components into session-scoped state owned by App, passed down as controlled props. The screens unmount on tab switch, so this is what keeps the user's context in place when they navigate away and back within a live session. State is cleared only on disconnect (resetSessionScopedUiState) or an explicit user action — never on plain navigation. Lifted per screen: - Tools: sidebar search (selection/form/result were lifted in #1414) - Prompts: selected prompt, argument values, submitted marker, search - Resources: selected resource/template URI, originating-template marker, search, accordion open-sections - Apps: selected app, form values, search (running/maximized stay local — they're tied to the live iframe/bridge that is torn down on unmount) - Tasks: search + status filter - Logs: filter text + visible-level set - History: search + method filter - Network: filter text + visible-category set The four sidebar *Controls (Tool/Prompt/App/Resource) become controlled for search; ResourceControls also controls accordion open-sections. Level/category default maps move to logLevels.ts / fetchCategories.ts so the screen files keep a single component export. Pagination already persists (it lives in the App-level Paged*State managers), so no work was needed there. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): persist list scroll position across navigation + reset tests (#1417) Add useScrollMemory: a module-keyed hook that remembers a Mantine ScrollArea's offset across unmount/remount (the screens unmount on tab switch). Scroll lives in a module-scope map rather than threaded App state — it's ephemeral DOM state, and threading ~16 more props would balloon the surface for no benefit. App calls clearScrollMemory() in resetSessionScopedUiState so offsets clear on disconnect alongside the lifted selection/filter state. Wired into the four main list panels (Logs, History, Network, Tasks) and the Tools/Prompts/Apps sidebar lists. Pagination already persists via the App-level Paged*State managers. Tests: useScrollMemory unit test (save/restore, key isolation, clear), and the App disconnect test now also exercises representative lifted UI state (tool search, prompt selection, log filter) clearing on disconnect. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(web): collapse per-screen lifted state into one {screen}Ui object Replace the ~46 individual selection/search/filter props threaded App → InspectorView → screen with one `{screen}Ui` object + one `on{Screen}UiChange` setter per screen (8 pairs). Each screen reads its fields from `ui` and updates via `onUiChange({ ...ui, field })`; the in-flight result panels (toolCallState / getPromptState / readResourceState) stay separate since they're written by App's async action handlers, not the screens. The `*UiState` interfaces live with their screens; the empty-state defaults are gathered in screenUiState.ts so the screen files keep a single component export (react-refresh), mirroring logLevels.ts / fetchCategories.ts. Trims InspectorView's prop surface substantially and keeps the persistence behavior identical. Tests/stories collapse their stateful wrappers to a single ui object; added Tools/Tasks/History interaction tests to keep per-file coverage above the gate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): dismiss visible progress toasts on client teardown (#1414 review) The progressNotification effect cleanup cleared its id-tracking set but left on-screen "Tool progress" toasts to auto-close up to 5s later — they could linger into the next session, and the lingering toast's onClose could delete an id from the new session's set and trigger a duplicate-id re-show. Hide each tracked toast in cleanup so both gaps close. Addresses @claude review note #2. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): clear the tool result when a different tool is selected The Tools result panel renders `toolCallState` regardless of which tool is selected, so selecting a new tool used to leave the previous tool's result showing beneath it. Route tools UI changes through an App handler that drops `toolCallState` whenever `selectedToolName` changes; search/form edits keep the selection (and the result) intact. Tests cover both the clear-on-switch and keep-on-search paths. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
8f6a2e0f78 |
feat(web): render pending sampling/elicitation requests (#1407) (#1411)
* feat(web): render pending sampling/elicitation requests (#1407) Server-initiated sampling and elicitation requests never reached the v2 web UI: the InspectorClient queued them and returned a Promise that only resolved on a user response, but nothing subscribed to the pending-change events or rendered the (already-built) request components. A tool that triggered one spun the Execute button forever. Wire the queues to the UI: - Add usePendingClientRequests() (core/react) subscribing to pendingSamplesChange / pendingElicitationsChange and returning the live arrays. Expose getPendingSamples() / getPendingElicitations() on InspectorClientProtocol (and the test fake) so hooks can read them. - Add PendingClientRequestModal, an App-level modal that surfaces one queued request at a time (sampling-first) via the existing SamplingRequestPanel / ElicitationFormPanel / ElicitationUrlPanel. It is non-dismissable so the blocked call only resolves via an explicit action. - Wire it in App.tsx: responding/rejecting resolves the client's handler Promise, which unblocks the originating call and clears the spinner. Also fold in two issues surfaced while verifying: - SchemaForm rendered an `array` + `items.enum` (multi-select enum) as a JSON textarea because only `items.anyOf` had a branch; add a MultiSelect branch with enumNames label support. - ToolsScreen's result lives in App and outlived the screen's local selection (reset on tab-switch unmount), leaving a result with no selected tool. Clear the result on unmount for a clean slate on return. Prompts/Resources gate their preview on selection, so they are unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(elicitation): support explicit decline; honest queue label; review polish Address @claude review on #1411: - Add an explicit "Decline" action to ElicitationFormPanel so the spec's decline (refusal) is distinguishable from cancel (dismissal); the JSDoc claimed accept/decline/cancel but only accept/cancel were wired. Wire the modal body to emit { action: "decline" }. - Replace the misleading "1 of N" queue label (the leading 1 never changed since the modal always shows the head) with an "N pending" hint, hidden when nothing else is queued. - Make the dead useMemo honest: derive the head request inside the memo from the source arrays so it actually caches (the inline activeElicitation had a fresh identity every render). - Document the URL elicitation accept-on-open semantic with a comment pointing at the follow-up (#1415). - Add an App-level round-trip test driving pendingSamplesChange end-to-end (modal opens, respond() fires, empty event closes it). Follow-ups filed: #1415 (URL two-step completion flow), #1416 (form required- field submit gate + double-submit guard). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(elicitation): include untouched schema defaults in submitted content The schema form displays a field's default via resolveValue(), but onChange only writes a field into `values` once the user edits it. So an elicitation submitted with default-only fields left untouched dropped them entirely — v2 sent just the manually-edited fields while v1 sent defaults too (reported with the everything server: firstLine/integer/number/enum defaults missing). Add collectSchemaDefaults() to jsonUtils (recurses nested objects, omits fields with no default) and seed ElicitationFormModalBody's values state with it via a lazy initializer. The modal body is keyed by request id, so it re-seeds per request; user edits still override seeded defaults via the existing onChange merge. Tests: collectSchemaDefaults unit tests (field types, nested, empty) in jsonUtils.test; a modal test asserting an untouched-default form still submits its defaults in content. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(forms): seed schema defaults for tool-call and app-launch forms too Extend the elicitation defaults fix (collectSchemaDefaults) to the other two SchemaForm consumers that own form state and submit it: ToolsScreen (tool execute args) and AppsScreen (app launch args). Both reset formValues to {} on selection, so default-only fields the user never edited were dropped from the submitted args — same root cause as the elicitation modal. Seed formValues from the selected tool/app inputSchema defaults on select. User edits still override via the existing onFormChange merge. (InlineElicitationRequest has no consumers wired up, so it's left as-is.) Tests: ToolsScreen sends a tool's default on Execute without editing; AppsScreen sends an app's default on Open App without editing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(elicitation): gate form submit on required fields; guard double-submit (#1416) Fold the #1416 hardening into this PR: - Gate the elicitation form's Submit button on required fields. Promote the hasMissingRequiredFields helper out of AppDetailPanel into jsonUtils (shared) and use it in ElicitationFormPanel so Submit is disabled until every required field has a value. - Guard against double-submit. Add an optional `busy` prop to the sampling, elicitation-form, and elicitation-url panels that disables their responding actions. PendingClientRequestModal's bodies use a new ref-guarded useRespondOnce() hook: the first action dispatches and locks the panel; later clicks no-op. This stops a fast double-click from calling respond()/reject() twice (which throws and produced an unhandled rejection). Copy URL stays enabled — it doesn't resolve the request. Tests: hasMissingRequiredFields unit tests; ElicitationFormPanel submit-gate and busy-lock tests; a modal double-click-resolves-once test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(tools): cap detail/result panels at viewport and scroll internally The Tools screen's detail and result columns sized to their content and bled past the bottom of the viewport on long output (e.g. the env dump or a long elicitation result). Cap each column at the screen's available height and let its panel scroll internally instead. - ToolsScreen: drop `align: flex-start` and wrap each column in a stretched `ContentPane` (Flex column, capped by `mah` = viewport − header − xl*2). The pane's definite height is what lets the card's inner ScrollArea know how far to shrink — a bare `mah` on the card does not (the card sizes to content, so the scroll region renders full-height and overflow:hidden just clips it, unreachable). Mirrors the proven Prompts/Resources preview panes. - ToolResultPanel: pinned header + inner ScrollArea (flex 0 1 auto, mih 0). - ToolDetailPanel: pinned title/annotations header, scrolling body (description + form + progress), pinned Execute footer. Audited the other screens: Prompts/Resources already use this pattern, ServerList and the four list panels (Logging/Network/History/Tasks) already cap via ScrollArea.Autosize. Only AppsScreen's app-input form had the same gap — fixed it the same way: AppDetailPanel now scrolls internally and the card uses variant="preview" so the running app's iframe and a long form both stay within the viewport. Verified in-browser against the everything server: the get-env result now stops at the viewport bottom with margin and scrolls within the card (clientHeight 502 vs scrollHeight 1931), header pinned. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(remote): use sync pino destinations in logging tests to fix teardown race CI flaked with an uncaught `ENOENT: ... /tmp/inspector-log-unknown-level-*/app.log` that failed an unrelated later test. The remote-logging integration tests create `pino.destination(logFile)` loggers (async sonic-boom streams) and rmSync the temp dir in finally/afterEach without closing the stream — a buffered background flush then fires after the dir is gone and throws an uncaught error, which Vitest counts as a run failure even though all tests pass. Make those destinations `sync: true` so the write lands during the request instead of on a later flush, removing the race. Test-only; unrelated to this PR's feature work but it was blocking the PR's CI. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe5b622fd1 | ||
|
|
f201032137 | ||
|
|
da37692485 |
||
|
|
99ef731746 |
||
|
|
e70a11360c |
||
|
|
2259b1a3e1 |
||
|
|
320daf2f67 |
||
|
|
ec185f1053 |
||
|
|
b3b04c6939 |
||
|
|
d552564966 |
||
|
|
73d9b181fe |
||
|
|
39362dd5ff | ||
|
|
8f70b60725 |
||
|
|
19a6df7ad3 | ||
|
|
8d33e76f97 |
||
|
|
590359abc5 |
||
|
|
3d75ef5f8e |
||
|
|
cbb2afe80c | ||
|
|
0ddb79203a |
||
|
|
6722438428 |
||
|
|
dbda70e83b |
||
|
|
0c6fafbf60 | ||
|
|
9824055425 | ||
|
|
ace4250d31 | ||
|
|
87630f2356 | ||
|
|
c6999dff96 | ||
|
|
219298b24d | ||
|
|
32a3610737 | ||
|
|
c5e064ff71 | ||
|
|
9b76272ad1 | ||
|
|
081fd789f1 | ||
|
|
cfbdfdf2bd | ||
|
|
562bdd00f1 | ||
|
|
4bad110942 | ||
|
|
0b0bd40e5d |
||
|
|
c96db0402e |
||
|
|
d04a7fb9c8 |
||
|
|
97b1a7fbff |
||
|
|
91f02fd6db |
||
|
|
ef921ba052 |
||
|
|
bef8b84ee8 |
||
|
|
d525fc9f54 |
||
|
|
8d4363d0ea |
||
|
|
0890c7509a |
||
|
|
d5f12b2a82 |
||
|
|
b0a024269e |
||
|
|
121f875a0e |
||
|
|
ed6eda7492 |
||
|
|
8923a9bbf9 |
||
|
|
8f6a2e0f78 |