Commit graph inspector/clients/web/src/lib
Author SHA1 Message Date
Cliff Hall
420da2e415
feat(servers): export server list as mcp.json download (#1346) (#1351)
* feat(servers): export server list as mcp.json download (#1346)

Adds an Export button to the Servers screen, next to "Add Servers".
Clicking it downloads the current server list as a canonical mcp.json
file (2-space indent, same shape serializeStore writes), so the user
can hand it off, sync it to another machine, or symlink into Claude
Desktop / Cursor / Cline.

Wiring:
- ServerListControls: new Export Button (variant=default), disabled
  when serverCount === 0 (nothing to download).
- ServerListScreen / InspectorView: thread `onExport` / `onServerExport`
  through.
- App.tsx: onServerExport runs serverEntriesToMcpConfig on the in-memory
  servers list, JSON.stringify with 2-space indent, and triggers a Blob
  download via a temporary anchor (appended to body for Firefox
  compatibility, then removed; object URL revoked).

Tests:
- ServerListControls unit tests cover button-count (toggle hidden vs
  shown, Export always present), disabled-on-empty, click fires onExport.
- ServerListControls.stories grows play functions for both stories —
  WithServers asserts Export is enabled + click fires the spy in real
  Chromium, WithoutServers asserts Export is disabled.
- Existing screen + view tests / stories updated for the new prop.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(servers): address PR #1351 review (extract helpers + exception safety + guard)

Substantive items from the review:

1. Serialization format parity (review #1): added
   `serializeMcpConfig(entries)` to core/mcp/serverList.ts — a
   browser-safe helper that runs `serverEntriesToMcpConfig` then
   stringifies with 2-space indent. Single source of truth for export
   formatting; can't drift from serializeStore's on-disk shape.

2. Exception safety on the anchor cleanup (review #2) + a direct test
   for the App.tsx download wiring (review #6): extracted
   `downloadJsonFile(filename, json)` to clients/web/src/lib/
   downloadFile.ts. removeChild + revokeObjectURL run in a `finally`
   so a thrown click() doesn't leak the DOM node or the object URL.
   Unit-tested under happy-dom with URL.createObjectURL mocked
   (happy-dom doesn't ship it).

3. Empty-list guard (review #5): App.tsx onServerExport bails when
   `servers.length === 0`. The button's disabled prop already prevents
   the UI path, but the handler is now locally correct against any
   future programmatic caller (storybook, keyboard shortcut, etc.).

Acknowledged but not changed:
- Tooltip on the disabled Export button (review #3) — Mantine's
  disabled-button + Tooltip wart wants a span wrapper + custom events
  config. Optional per the reviewer; deferred.
- Repeated exports → `mcp (1).json` browser suffix (review #4) — a
  deliberate trade-off since `mcp.json` is the conventional name a user
  would expect to symlink into Claude Desktop / Cursor / etc.

Tests: 1647 unit (+6) + 410 integration + 306 storybook = 2363 green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 15:15:56 -04:00
Cliff Hall
4d74b94bed
Wire App.tsx to v2 core/ hook layer; remove demo stub from InspectorView (#1322)
* feat(web): wire App.tsx to v2 core/ hook layer; remove demo stub from InspectorView

App.tsx mounts InspectorView with live data from the v2 hook layer instead
of the title-page placeholder. InspectorView becomes pure prop-driven; the
demo handshake stub and connection state machinery are removed.

Connection lifecycle:
- App.tsx lazily instantiates `InspectorClient` on the connect edge,
  rebuilding it (and its state managers) when the user picks a different
  server. State managers (Managed{Tools,Prompts,Resources,ResourceTemplates,
  RequestorTasks}State + MessageLog/FetchRequestLog/StderrLog) are
  destroyed on switch.
- `useInspectorClient` + the per-primitive `useManaged*` hooks drive the
  view's data. `latencyMs` is captured at the connecting → connected edge
  via a ref so the intermediate rerenders don't lose the start timestamp.
- `errorMessage` is captured from `client.connect()` rejection; status-
  machine-driven errors that don't surface through the promise are a known
  follow-up (no `error` event in the v2 InspectorClientEventMap yet).

Action handlers:
- `onCallTool` / `onGetPrompt` / `onReadResource` await the corresponding
  InspectorClient method and reflect pending → ok/error in `ToolCallState`
  / `GetPromptState` / `ReadResourceState` panel props. Manually verified
  end-to-end against `npx @modelcontextprotocol/server-filesystem` —
  the Tools result panel renders the real "Allowed directories: /private/tmp"
  output rather than a stub.
- `onSetLogLevel` calls `client.setLoggingLevel(level)` and optimistically
  bumps `currentLogLevel` locally (the request has no echo notification).
- `onSubscribeResource` / `onUnsubscribeResource` / `onCancelTask` route to
  their respective client methods. Server CRUD (`onServerAdd`/`onServerEdit`
  /…) and history pinning are intentionally `todoNoop` for now; the seed
  server list is hardcoded per the agreed scope (a `useServers` v2-only
  hook is a separate effort).
- `notifications/message` notifications flowing through `MessageLogState`
  are filtered into `LogEntryData[]` and passed to the Logs screen, so
  `logging/setLevel` and the resulting log stream round-trip.

InspectorView (`InspectorView.tsx`):
- Removed: `STUB_*` constants, `handshakeTimer` ref, `handleToggleConnection`
  / `disconnect` functions, stub `bridgeFactory`/`sandboxPath`, internal
  connection state, `logLevel` state.
- Added 38 props covering connection state, panel states, log level, and
  action callbacks. The view is pure presentational.
- `availableTabs` is derived from `connectionStatus`; `activeTab` is
  clamped to whatever's currently available (avoiding the `set-state-in-effect`
  lint and keeping the previously-selected tab on reconnect).

Stories + tests:
- `InspectorView.stories.tsx` adds spy callbacks for every action prop and
  new `Connected` / `ConnectionError` stories for the connected/error
  narratives.
- `InspectorView.test.tsx` is rewritten around the prop-driven contract
  (10 tests covering the empty list, server cards, toggle dispatch,
  connected header, error banner, tab snap-back on disconnect, app
  filtering, log-level dispatch, autoScroll local toggle).

Dev backend port wiring (`vite.config.ts`):
- Vite's dev server is now pinned to `CLIENT_PORT` (default 6274) with
  `strictPort: true`, matching the dev backend's `allowedOrigins`. Without
  this, Vite would fall back to 5173 while the Hono origin check still
  required 6274 — breaking every browser `/api/*` fetch out of the box.
- Top-level `resolve.alias` now spreads the same bare-module aliases the
  vitest projects use. Rolldown can't reach `pino/browser.js` /
  `zustand/middleware` from `core/`'s parent (no node_modules there); the
  wired App pulls those modules into the browser dep graph for the first
  time, so the aliases need to be visible at build time too.

createWebEnvironment helper (`src/lib/environmentFactory.ts`):
- Mirrors v1.5's `lib/adapters/environmentFactory.ts`: builds
  `InspectorClientEnvironment` from `createRemoteTransport` +
  `createRemoteFetch` + `createRemoteLogger` + `BrowserOAuthStorage` +
  `BrowserNavigation`. Returns the assembled environment plus the logger
  for direct app-level use.

Closes #1244.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(web): wire dev-backend auth token into createWebEnvironment + show full stdio command in ServerCard

Two issues surfaced after the #1244 wiring landed locally:

1. **401 on `/api/mcp/connect`** when running `npm run dev` with the
   default auth (no `DANGEROUSLY_OMIT_AUTH`). The dev backend generates a
   random token and prints `http://localhost:6274?MCP_INSPECTOR_API_TOKEN=…`
   in the banner, but App.tsx was calling `createWebEnvironment(undefined, …)`
   so the browser never sent `x-mcp-remote-auth`. The Hono middleware
   correctly rejected with "Authentication required. Use the
   x-mcp-remote-auth header with Bearer token."

   Adds a small `getAuthToken()` helper in App.tsx that reads
   `MCP_INSPECTOR_API_TOKEN` off `window.location.search`, falls back to
   `sessionStorage`, and persists fresh values into sessionStorage so
   client-side navigation / OAuth round-trips don't drop the token. The
   resolved value is passed to `createWebEnvironment` whenever a new
   InspectorClient is built.

2. **Misleading ServerCard display** — the seed server's `config` is
   `{ command: "npx", args: ["-y", "@modelcontextprotocol/server-filesystem",
   "/tmp"] }` but the card only rendered `"npx"`, so it looked like the
   command was incomplete. `getCommandOrUrl` now joins `command + args`
   for stdio configs, so the card shows the same string that gets spawned.
   SSE / streamable-http paths unchanged.

Manual end-to-end re-verified against `npm run dev` (no DANGEROUSLY_OMIT_AUTH):
- Banner prints the auth-token URL.
- Browser navigates to that URL → `getAuthToken()` picks the token off the
  query string, persists to sessionStorage, threads it through to every
  `/api/*` request.
- Server card shows `npx -y @modelcontextprotocol/server-filesystem /tmp`.
- Toggle connect → connection succeeds; ViewHeader shows
  `Connected (~1.3s)`; Tools tab populates with the 14 filesystem tools.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(web): use dark-9 instead of blue-9 for dark-mode page background

`main.tsx` overrode `--mantine-color-body` to `--mantine-color-blue-9` in
dark mode, while `.storybook/preview.tsx` used `--mantine-color-dark-9`.
The dev app then rendered with a blue background while every story (and
the design intent visible in storybook) used dark grey.

Switch main.tsx to `--mantine-color-dark-9` so the dev app matches the
storybook reference.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(web): seed the Servers screen with the "everything" reference server

Adds `@modelcontextprotocol/server-everything` to the hardcoded seed list
alongside the filesystem server. The everything server exposes tools,
prompts, resources, sampling, and completion — covering most of the
surface a developer might want to exercise against the wired-up
InspectorView without configuring anything.

Comment updated to explain the two-seed shape.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(web): address #1244 PR review feedback (JsonValue cast, client lifecycle, dead code)

- **#1 — Misleading `Record<string, never>` cast**: Imported `JsonValue`
  from `@inspector/core/mcp/index.js` and changed `args as Record<string,
  never>` to `args as Record<string, JsonValue>` in `onCallTool`. The new
  cast honestly narrows from the screen-level `Record<string, unknown>`
  to what `InspectorClient.callTool` actually requires. Added a comment
  explaining the boundary.

- **#2 + #3 — Previous InspectorClient not disconnected on server switch
  or unmount**: Added a `useEffect` keyed on `inspectorClient` whose
  cleanup calls `inspectorClient.disconnect()`. One effect covers both
  the swap-on-switch leak (prior session's transport stayed open until
  GC) and the unmount-during-connected leak (HMR / tests).
  `setupClientForServer`'s existing state-manager `destroy()` calls
  handle the listener side; the new effect handles the transport side.

- **#4 — Dead `void` block at bottom of file**: Deleted the trailing
  `void FetchRequestLogState; void StderrLogState;` block. The state
  managers are already constructed via `new FetchRequestLogState(client)`
  / `new StderrLogState(client)`, so the imports are referenced by the
  linter's reckoning. The `void` pair was dead code with a comment that
  no longer matched reality.

- **#5 / #7 / #8 — Follow-up TODOs**: Added TODO refs to the new
  follow-up issues:
  - TODO(#1323): `error` event in InspectorClientEventMap so mid-session
    transport failures surface (connect-only catch is incomplete).
  - TODO(#1324): negotiated `protocolVersion` through `useInspectorClient`
    (currently hard-coded `"2025-06-18"`).
  - TODO(#1325): `useResourceSubscriptions` hook so subscribe/unsubscribe
    buttons reflect their server-side effect.

Items #6 (useCallback dep churn), #9 (helper unit tests), and #10
(shared no-op bridge factory fixture) are acknowledged in the PR reply
as deferred — no functional impact and out of scope for a "wire the
hook layer" PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 15:41:55 -04:00
cliffhall
8edff4ea77 Phase 0 foundations for component interfaces refactor
Stands up the type-only prerequisites for the v2 UX component
interfaces refactor per specification/v2_ux_interfaces_plan.md.

Phase 0.1 — MCP SDK dependency:
- Add @modelcontextprotocol/sdk ^1.29.0 and zod ^4.3.6 to both the
  root package.json and clients/web/package.json so component files
  can import Tool / Prompt / Resource / Task / LoggingLevel / etc.
  directly from the SDK.

Phase 0.2 — Inspector wrapper-types module:
- Create core/mcp/types.ts mirroring the type-only subset of v1.5's
  core/mcp/types.ts: MCPServerConfig (discriminated union), ServerType,
  MCPConfig, ConnectionStatus, StderrLogEntry, MessageEntry (Date
  timestamps, not ISO strings), FetchRequestEntry(+Base), ServerState,
  and the *Invocation types. Adds v2-only wrappers
  InspectorResourceSubscription and InspectorServerJsonDraft.
- Add core/mcp/taskNotificationSchemas.ts for the Inspector-owned
  notifications/tasks/list_changed signal (not in the SDK).
- Add core/mcp/elicitationCreateMessage.ts re-exporting ElicitRequest
  and ElicitResult plus an InspectorPendingElicitation interface for
  typing the pending-queue.
- Add core/json/jsonUtils.ts for the shared JsonValue / JsonObject
  types used inside core/mcp/types.ts.
- Copy v1.5's clients/web/src/utils/jsonUtils.ts (JsonSchemaType) and
  schemaUtils.ts (AJV-backed validators) verbatim so SchemaForm can
  switch off its local JsonSchema type.
- Copy v1.5's clients/web/src/lib/types/customHeaders.ts verbatim for
  ServerSettingsForm and the experimental panel.
- Add clients/web/src/types/navigation.ts with InspectorTab (UI-routing
  concept, lives with the web client rather than in core/).
- Wire tsconfig.app.json + vite.config.ts with the @inspector/core/*
  path alias so component files can import from core/ without
  relative-path hell.

Phase 0.3 — Vet (likely) hedges in v2_ux_interfaces.md:
- Follow-up sweep on commit 2ef257da. Replaces the remaining
  useMcpClient references with useInspectorClient, ties pending
  elicitation / sampling callbacks to the in-InspectorClient queue
  rather than a non-existent useElicitation / useSampling hook,
  corrects Task references to the SDK type (not an InspectorTask
  wrapper), corrects log hook wiring to useMessageLog, and strikes
  all remaining v2-only hooks with explicit "no v1.5 analog"
  annotations. grep "likely" specification/v2_ux_interfaces.md now
  returns zero results.

Phase 0.4 — Local-redeclaration inventory:
- specification/v2_ux_interfaces_phase_0_4_inventory.md enumerates
  every ad hoc local type (JsonSchema, LogLevel, TaskStatus,
  PromptItem, ResourceItem, ToolListItemProps, RootEntry, KeyValuePair,
  inline ConnectionStatus / transport unions) that will be replaced
  in Phases 1–4, including the file it lives in, every site that
  re-imports it, and the SDK / wrapper type that replaces it. Intended
  for the Phase 0 PR description and as a running checklist during
  the refactor.

Build state: npm run build / lint / format:check all pass in
clients/web.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 16:06:23 -04:00