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.
* 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>
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>
* 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>
* 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>