inspector/specification/v2_ux_interfaces_plan.md
Cliff Hall 4defa32ac6
chore: close a batch of quality-gate & tidy gaps (#1785 #1787 #1789 #1790 #1767) (#1792)
* chore: close a batch of quality-gate & tidy gaps (#1785 #1787 #1789 #1790 #1767)

Bundles five small, related tidy/gate items into one PR:

- #1785: delete the dead src/types/navigation.ts InspectorTab (0 importers;
  duplicated/drifted from utils/inspectorTabs InspectorTabId). src/types/
  now holds only ambient .d.ts stubs.
- #1787: add src/hooks/** to the web coverage `include` (it was silently
  outside the ≥90 gate) and add tests for the two untested hooks,
  useServerJsonImport and useImportClientConfig, bringing every hook to
  ≥90 on lines/statements/functions/branches.
- #1789: widen clients/{cli,tui,launcher} format/format:check to their
  top-level configs; add root format:shared / format:check:shared covering
  test-servers/src/**, vitest.shared.mts, and the repo-root eslint.config.js.
- #1790: pin prettier to an exact version (3.8.4, dropping the ^3.8.1
  caret) in all five package.json files so the format gate's verdict can't
  shift with an in-range patch bump; lockfiles synced.
- #1767: its root-file format+lint gap is closed here (format:shared +
  a new lint:shared via a second files block in the root eslint.config.js;
  web/server/ was already gated in #1788). Its larger cli/tui __tests__
  typecheck piece (76 type errors) is split out to #1791.

Prettier-normalized the newly-covered files (pure formatting). No runtime
behavior change. `npm run ci` green.

Closes #1785
Closes #1787
Closes #1789
Closes #1790
Closes #1767

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5

* chore: address PR #1792 review findings

- #1789 gap: widen clients/web format to include .storybook and clients/
  launcher format to include scripts/; reformat the 3 .storybook files
  (were non-conforming under the pinned prettier 3.8.4). Audited all 885
  tracked source files — now fully prettier-clean.
- Export VALIDATE_DEBOUNCE_MS / COLLAPSE_DELAY_MS / HIGHLIGHT_DURATION_MS
  from useServerJsonImport and use them in the test instead of hardcoded
  300/1000/250 (so a tuned constant can't silently desync the test).
- Assert the symmetric renameErrors["alpha"] alongside ["beta"] so a
  one-directional collision-check regression fails.
- Comment the blank-rename test as exercising a UI-unreachable defensive
  branch (canImport gates submit).
- Un-stale the two specification/v2_ux_interfaces_plan.md references to the
  now-removed navigation.ts InspectorTab.

npm run ci green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5

* docs: sync AGENTS.md format-surface description with the widened globs (#1792 review r2)

1ce3b64 added .storybook to clients/web's format glob and scripts/ to
clients/launcher's, but AGENTS.md:222 still described the pre-fix scope.
Bring it in line and note the non-recursive top-level glob is why nested
dirs are named explicitly. Docs only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5

* chore: handle PR #1792 round-3 follow-ups in-place (format-coverage guard, uniform globs, theme gate)

Rather than defer these to tickets, fold them into this PR:

- Obs 2: widen every client + root format glob to a uniform
  *.{ts,tsx,mts,cts,js,jsx,mjs,cjs} so a future .mts/.cjs/.tsx top-level
  file can't silently slip the gate.
- Obs 1: add scripts/verify-format-coverage.mjs (wired into `validate`) —
  it parses the format:check globs out of every package.json, enumerates
  all tracked source files, and fails listing any not covered by a glob.
  The durable, standing version of #1789's one-shot audit; negative-tested.
- Obs 3: bring src/theme/** under the coverage gate (21/23 files were
  already ≥90 transitively; add Paper.test.tsx for the one laggard,
  Paper.ts). src/App.tsx (~4.5k lines, ~42% branches) is converted from a
  silent omission into an explicit, documented exclusion (comment on the
  coverage `include` + AGENTS.md) — gating it is a dedicated effort, not a
  whitelist tweak.

npm run ci green (the new guard reports 885/885 tracked source files gated).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5

* chore: address PR #1792 round-4 findings (guard hardening, docs, exclusion list)

- Harden verify-format-coverage: only harvest format:check globs from
  scripts transitively reachable from `validate` (via npm run refs), so an
  unwired/renamed format:check no longer counts as coverage. Negative-tested
  — renaming clients/web format:check to an unreferenced name now fails the
  guard (627 files flagged) instead of passing.
- Name src/main.tsx and src/index.ts (browser + bin bootstraps) alongside
  src/App.tsx in the coverage-include comment and AGENTS.md, so the
  documented exclusion list is complete rather than claiming App.tsx is the
  only one.
- Doc sync: document verify:format-coverage (AGENTS.md + README script table
  + scripts/ layout line), fix the `validate` ordering (verify:format-
  coverage now runs before validate:core) in AGENTS.md and README, and
  update the quoted globs to the uniform *.{ts,tsx,mts,cts,js,jsx,mjs,cjs} /
  test-servers *.{ts,tsx,mts,cts}.

npm run ci green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5

* chore: address PR #1792 round-5 findings (airtight guard + doc/config sync)

- verify-format-coverage: assert the root `validate` chain invokes each
  client's `validate` (cd <dir> && npm run validate), closing the last
  reachability edge — a client dropped from the root chain now fails the
  guard even though its own globs still exist. Negative-tested.
- eslint.config.js: widen the shared `files` block to
  test-servers/src/**/*.{ts,tsx,mts,cts} to match the widened lint:shared
  glob, so a future .mts/.cts there is lint-gated, not just prettier-gated.
- AGENTS.md: scope the "uniform extension set" claim to client globs
  (core stays {ts,tsx}, shared {ts,tsx,mts,cts}), with verify:format-coverage
  as the backstop — removing the self-contradiction with the line below.

npm run ci green.

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>
2026-07-26 10:43:11 -04:00

721 lines
41 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Inspector V2 UX - Component Interfaces Implementation Plan
### [Brief](README.md) | [V1 Problems](v1_problems.md) | [V2 Scope](v2_scope.md) | [V2 Tech Stack](v2_web_client.md) | V2 UX | [V2 Auth](v2_auth.md) | [V2 New Spec Impact](v2_new_spec_impact.md)
#### [Overview](v2_ux.md) | [Features](v2_ux_features.md) | [Handlers](v2_ux_handlers.md) | [Screenshots](v2_screenshots.md) | [Components](v2_ux_components.md) | [Interfaces](v2_ux_interfaces.md) | Plan
## Goal
Refactor every component under `clients/web/src/components/` so its prop
interface aligns with the [V2 UX Component Interfaces](v2_ux_interfaces.md)
spec. After this work, every dumb component consumes either:
1. A **raw MCP schema type** imported from `@modelcontextprotocol/sdk/types.js`
(`Tool`, `Prompt`, `Resource`, `ServerCapabilities`, `Implementation`,
`LoggingMessageNotification`, `Task`, etc.), or
2. An **Inspector-owned wrapper type** for a UI concept that has no MCP
equivalent (`MCPServerConfig`, `ConnectionStatus`, `MessageEntry`,
`StderrLogEntry`, `FetchRequestEntry`, …), or
3. A **callback** with a signature shaped by the MCP request/result types
the wiring layer will eventually pass through, or
4. **Local UI state** (search text, compact toggle, selected name, etc.)
that is screen-owned and not derived from any MCP object.
### Sources of truth
- **MCP schema types** — `@modelcontextprotocol/sdk` (TypeScript SDK), pinned
at the latest `1.x` line (currently `^1.29.0`). The SDK exports every type
this plan references, including `Task` and `LoggingLevel`.
- **Inspector-owned wrapper types** — the `v1.5/main` branch of *this* repo
is the canonical reference. v1.5 already has a complete `core/mcp/types.ts`
(416 lines) defining every wrapper type the v2 components need:
- `core/mcp/types.ts` — `MCPServerConfig` discriminated union
(`StdioServerConfig`/`SseServerConfig`/`StreamableHttpServerConfig`),
`ServerType`, `MCPConfig`, `ConnectionStatus`, `StderrLogEntry`,
`MessageEntry`, `FetchRequestEntry`, `FetchRequestEntryBase`,
`ServerState`, `ResourceReadInvocation`,
`ResourceTemplateReadInvocation`, `PromptGetInvocation`,
`ToolCallInvocation`, `InspectorClientEnvironment`, `InspectorClientOptions`,
`CreateTransport`, `AppRendererClient`.
- `core/mcp/taskNotificationSchemas.ts` — Inspector's
`notifications/tasks/list_changed` zod extension (the SDK exports
`Task` and `TaskStatusNotificationSchema` natively, but not the
list-changed signal).
- `core/react/useInspectorClient.ts` — `UseInspectorClientResult` shape
(`status`, `capabilities`, `serverInfo`, `instructions`,
`appRendererClient`, `connect`, `disconnect`).
- `core/react/useManaged{Tools,Prompts,Resources,ResourceTemplates,RequestorTasks}.ts`
— confirmed return shapes the screen layer will eventually consume:
`Tool[]`, `Prompt[]`, `Resource[]`, `ResourceTemplate[]`, `Task[]`.
- `core/react/{useMessageLog,useStderrLog,useFetchRequestLog}.ts` — log
wrapper consumers.
- `clients/web/src/utils/customHeaders.ts` — `CustomHeader` /
`CustomHeaders` shape, used by `ServerSettingsForm`.
- `clients/web/src/utils/schemaUtils.ts` + `clients/web/src/utils/jsonUtils.ts`
— JSON Schema typing (`JsonValue`, `JsonSchemaType`, `JsonObject`) and
AJV-backed validation for tool input/output schemas.
**Rule**: when a wrapper type is needed in v2, copy the v1.5 definition
verbatim (including field names, optionality, and `Date` vs `string`
choices). Do not invent new names. The interfaces doc's `Inspector*`
prefixes (e.g. `InspectorHistoryEntry`, `InspectorTask`,
`InspectorServerConfig`, `InspectorTaskStatus`) are placeholders — they
resolve to v1.5's actual names below.
### Name-mapping correction (interfaces doc → v1.5 source of truth)
Several names invented in `v2_ux_interfaces.md` are already defined in v1.5
and should be replaced verbatim during the refactor:
| `v2_ux_interfaces.md` placeholder | v1.5 actual | Source |
| --- | --- | --- |
| `InspectorServerConfig` | `MCPServerConfig` (discriminated union) | `core/mcp/types.ts` |
| `InspectorTransportType` | `ServerType` (`"stdio" \| "sse" \| "streamable-http"`) | `core/mcp/types.ts` |
| `ConnectionStatus` | `ConnectionStatus` (already named that — `"disconnected" \| "connecting" \| "connected" \| "error"`) | `core/mcp/types.ts` |
| `InspectorHistoryEntry` | `MessageEntry` (note `timestamp: Date`, not `string`) | `core/mcp/types.ts` |
| `InspectorTask` | `Task` (raw SDK type — exported by MCP SDK 1.x at `@modelcontextprotocol/sdk/types.js`) | SDK |
| `InspectorTaskStatus` | Status field on the SDK `Task` type — no separate wrapper needed | SDK |
| `InspectorLogEntry` | (consume `LoggingMessageNotification['params']` directly, plus a v2-side `{ id, receivedAt }` lift) | SDK + light wrapper |
| `InspectorPendingRequest` | No first-class wrapper in v1.5 — pending sampling/elicitation flows are coordinated as raw SDK request handlers inside `InspectorClient` with UI state living locally. v2 may need a small `{ id, request }` wrapper if the dumb component needs an id key — keep it minimal. | n/a |
| `InspectorResourceSubscription` | No dedicated wrapper in v1.5; v2 should add `{ resource: Resource; lastUpdated?: Date }` per the interfaces spec, placed in `core/mcp/types.ts` alongside the others. | new (v2-only) |
| `InspectorOAuthDetails` | `InspectorClientOptions['oauth']` shape from `core/mcp/types.ts` (`clientId`, `clientSecret`, `clientMetadataUrl`, `scope`) | `core/mcp/types.ts` |
| `InspectorServerJsonDraft` | No v1.5 equivalent — v2-only. Keep the wrapper but place it in `core/mcp/types.ts` next to `MCPConfig`. | new (v2-only) |
| `InspectorServerSettings` | Closest v1.5 analog is `InspectorClientOptions` (timeouts, OAuth, sampling/elicit/roots flags). Settings form should accept the relevant subset. | `core/mcp/types.ts` |
| `InspectorRequestHistoryItem` | Subset of `MessageEntry` filtered to outbound requests. No new type needed. | `core/mcp/types.ts` |
| `InspectorUrlElicitRequest` | URL-mode elicitation IS supported by v1.5 via `InspectorClientOptions.elicit: { url: true }`; the request/response shape lives in `core/mcp/elicitationCreateMessage.ts`. v2 should mirror that file's types rather than invent. | `core/mcp/elicitationCreateMessage.ts` |
| `InspectorTab` | No v1.5 analog — v2-only enum. Superseded by `InspectorTabId` in `clients/web/src/utils/inspectorTabs.ts` (#1785). | new (v2-only) |
## Non-goals
- **Building the v2 `core/` hook layer.** This plan only refactors component
prop interfaces and their Storybook fixtures. The hooks (`useTools`,
`usePrompts`, `useConnection`, etc.) that will eventually populate those
props are out of scope here. They'll arrive in a separate effort, and the
contracts produced by this work will be the input spec for that effort.
- **Wiring `App.tsx` to the MCP transport.** The current `App.tsx` is a
42-line theme-toggle shell. It stays that way until the core hook layer
exists.
- **Updating CLI / TUI clients.** Those don't exist yet in this repo.
- **Changing the visual design** of any component. Refactors are interface-only;
Storybook output should look identical (same screenshots) unless a new field
becomes visible because it was previously dropped (e.g. `Annotations` on
`ResourceListItem`).
- **Adding tests** beyond what Storybook gives us. Vitest unit tests for the
refactored components are a follow-up — the AGENTS.md 90% coverage rule will
be addressed in its own pass once the interface dust settles.
## Current state (snapshot taken on this branch)
- `clients/web/` is the only client. No `core/`, no `cli/`, no `tui/`, no
shared `clients/launcher/`.
- `clients/web/src/App.tsx` is a 42-line `MantineProvider` shell — no wiring.
- `clients/web/package.json` has **no** `@modelcontextprotocol/sdk` dependency.
- Components live under `clients/web/src/components/{elements,groups,screens,views}`,
each in its own folder with `<Name>.tsx` + `<Name>.stories.tsx`.
- Component count: **16 elements**, **37 groups**, **7 screens**, **2 views**
= 62 components.
- Components use ad hoc local types and flat scalar props (`name: string`,
`version: string`, `transport: "stdio" | "http"`, `level: LogLevel`, …)
invented during the visual-design phase. Several local re-declarations
exist for what should be schema types: `JsonSchema`, `LogLevel`,
`PromptItem`, `ResourceItem`, `ToolListItemProps`, `TaskStatus`,
`RootEntry`, etc.
## Phase 0 — Foundations
Before any component is touched, decide and stand up the things every later
phase depends on.
### 0.1 Add the MCP SDK as a dependency
- Add `@modelcontextprotocol/sdk` to `clients/web/package.json` so component
files can `import type { Tool, Prompt, Resource, Task, LoggingLevel, ... }
from "@modelcontextprotocol/sdk/types.js";`.
- Pin to **`^1.29.0`** (latest 1.x line as of writing). v1.5 is on `^1.25.2`,
but the 1.29 schema bundle is a strict superset and includes the latest
task / progress refinements.
- Verify TypeScript path resolution by importing one type into a throwaway
test file and running `npm run build`.
- If 1.29 introduces a breaking change vs 1.25 that affects a wrapper type
copied from v1.5, prefer adapting the v2 wrapper to 1.29 — do not roll the
SDK back.
### 0.2 Stand up the Inspector wrapper-types module
The interfaces doc invents `Inspector*` wrapper names; the v1.5 branch of
this repo has the canonical definitions under different (cleaner) names. Use
v1.5 as the source of truth — see the **Sources of truth** and
**Name-mapping correction** sections above.
**Decision: create `core/mcp/types.ts` in v2** at the same path v1.5 uses,
copying the types-only subset of v1.5's `core/mcp/types.ts` verbatim. This
sets up the directory structure for the eventual v2 core hook layer (which
is out of scope for this plan but will arrive in a follow-up effort) and
avoids two consecutive renames.
**Subset to copy from v1.5 `core/mcp/types.ts`** (types only — no runtime
helpers, no `InspectorClient` constructor types):
- `StdioServerConfig`, `SseServerConfig`, `StreamableHttpServerConfig`,
`MCPServerConfig`, `ServerType`, `MCPConfig`
- `ConnectionStatus`
- `StderrLogEntry`
- `MessageEntry` (note: `timestamp: Date`, not `string`)
- `FetchRequestEntry`, `FetchRequestEntryBase`, `FetchRequestCategory`
- `ServerState`
- `ResourceReadInvocation`, `ResourceTemplateReadInvocation`,
`PromptGetInvocation`, `ToolCallInvocation`
**Skip from v1.5** (transport / OAuth / lifecycle types — these belong in
the eventual v2 core hook layer, not this plan):
- `CreateTransport`, `CreateTransportOptions`, `CreateTransportResult`
- `InspectorClientEnvironment`, `InspectorClientOptions`, `AppRendererClient`
**Add new in v2** (v2-only wrappers needed by the interfaces refactor that
have no v1.5 equivalent):
- `InspectorResourceSubscription` = `{ resource: Resource; lastUpdated?: Date }`
- `InspectorServerJsonDraft` = `{ rawText: string; parsed?: RegistryServerJson; selectedPackageIndex?: number; envOverrides: Record<string, string>; nameOverride?: string }`
- `InspectorTab` enum (`"tools" | "prompts" | "resources" | "logs" | "tasks" | "history"`)
**Tab enum placement**: `InspectorTab` is a UI-routing concept, not an MCP
or transport concept. Place it under `clients/web/src/types/navigation.ts`
rather than in `core/mcp/types.ts`. Everything else lives in `core/mcp/types.ts`.
_(Superseded by [#1785](https://github.com/modelcontextprotocol/inspector/issues/1785): the live tab-id type is `InspectorTabId` in `clients/web/src/utils/inspectorTabs.ts`, and the dead `navigation.ts` `InspectorTab` was removed — a pure UI domain type belongs in `utils/`, not `src/types/`, per the `src/lib` vs `src/utils` rule in AGENTS.md.)_
**Custom headers**: `clients/web/src/utils/customHeaders.ts` is a verbatim
copy of the v1.5 module. It owns `CustomHeader` / `CustomHeaders` shape used by
`ServerSettingsForm` and the experimental panel.
**JSON Schema typing for `SchemaForm`**: copy v1.5's
`clients/web/src/utils/jsonUtils.ts` (defines `JsonValue`, `JsonObject`,
`JsonSchemaType`) and `clients/web/src/utils/schemaUtils.ts` (AJV-backed
validators). This resolves the JSON Schema typing risk and keeps
`SchemaForm` aligned with how v1.5 validates `Tool.inputSchema` and
`Tool.outputSchema`.
**Elicitation URL request shape**: copy the URL-mode elicitation
request/result types from v1.5 `core/mcp/elicitationCreateMessage.ts` into
the new `core/mcp/types.ts` (or a peer file). The interfaces doc's
`InspectorUrlElicitRequest` placeholder maps to whatever v1.5 calls them.
### 0.3 Vet every "likely" hedge in `v2_ux_interfaces.md`
`v2_ux_interfaces.md` was produced by parallel research subagents that I
instructed to prefix uncertain hook names with `(likely)` rather than fail
to fill in a section. The result: **52 instances of "likely"** survived to
the doc, almost all of them attached to hook names or hook-name-to-callback
mappings that were never cross-checked against v1.5. Several are actively
wrong. Before any component is touched, walk through every `likely` and
either resolve it against v1.5 / the SDK or strike it.
Use these source-of-truth lookups, not guessing:
- **v1.5 React hooks** — `git show origin/v1.5/main:core/react/<hook>.ts`.
The complete v1.5 hook surface is:
- `useInspectorClient.ts` — `status`, `capabilities`, `serverInfo`,
`instructions`, `appRendererClient`, `connect`, `disconnect`.
- `useManagedTools.ts` — `{ tools: Tool[]; refresh }`
- `useManagedPrompts.ts` — `{ prompts: Prompt[]; refresh }`
- `useManagedResources.ts` — `{ resources: Resource[]; refresh }`
- `useManagedResourceTemplates.ts` — `{ resourceTemplates: ResourceTemplate[]; refresh }`
- `useManagedRequestorTasks.ts` — `{ tasks: Task[]; refresh }`
- `useMessageLog.ts` — JSON-RPC message buffer (`MessageEntry[]`)
- `useStderrLog.ts` — stdio stderr buffer (`StderrLogEntry[]`)
- `useFetchRequestLog.ts` — auth/transport HTTP fetch buffer (`FetchRequestEntry[]`)
- `usePagedTools.ts`, `usePagedPrompts.ts`, `usePagedResources.ts`,
`usePagedResourceTemplates.ts`, `usePagedRequestorTasks.ts` — paged
siblings of the managed hooks above.
- **MCP SDK types** — `import type { ... } from "@modelcontextprotocol/sdk/types.js"`.
- **v1.5 core types** — `git show origin/v1.5/main:core/mcp/types.ts`.
#### Confirmed hook-name corrections
| `v2_ux_interfaces.md` `(likely)` claim | Actual v1.5 surface |
| --- | --- |
| `useTools` | **`useManagedTools`** (`core/react/useManagedTools.ts`) |
| `usePrompts` | **`useManagedPrompts`** |
| `useResources` | **`useManagedResources`** |
| `useResourceTemplates` (implied) | **`useManagedResourceTemplates`** |
| `useTasks` | **`useManagedRequestorTasks`** |
| `useMcpClient` | **`useInspectorClient`** |
| `useServerCapabilities` | Not a separate hook — `capabilities: ServerCapabilities` is a field on `useInspectorClient`'s result. Strike. |
| `useLogs` | **Three** v1.5 hooks: `useMessageLog`, `useStderrLog`, `useFetchRequestLog`. The interfaces doc conflates them. Split per use site: log stream → `useMessageLog`; stdio stderr → `useStderrLog`; HTTP fetch (auth/transport) → `useFetchRequestLog`. |
| `useHistory` | **`useMessageLog`** — v1.5's message log IS the history; `MessageEntry` is the wrapper type. Strike `useHistory`. |
| `useElicitation` | **No discrete hook in v1.5.** Elicitation is handled inside `InspectorClient` (`core/mcp/elicitationCreateMessage.ts`) via SDK request handlers. UI state for pending elicitations lives wherever the consuming screen puts it. Strike `useElicitation` and replace with "handled inside `InspectorClient` request handlers; v2 will need a small UI-state hook for the pending queue but it doesn't exist yet". |
| `useSampling` | **No discrete hook in v1.5.** Same shape as elicitation: `core/mcp/samplingCreateMessage.ts` plus SDK request handlers. Strike. |
| `useRoots` | **No discrete hook in v1.5.** Roots are configured via `InspectorClientOptions.roots: Root[]` at construction time and answered by an SDK request handler inside `InspectorClient`. There is no React hook. Strike or mark as "v2 to introduce". |
| `useServers` | **Not in v1.5.** v2-only — keep, but mark explicitly as "v2 to introduce, no v1.5 analog". |
| `useServerRegistry` | **Not in v1.5.** v2-only — same treatment. |
| `useClientCapabilities` | **Not in v1.5.** v2-only — same treatment. |
| `useInspectorNavigation` | **Not in v1.5.** Pure speculation in the interfaces doc. v2 may not need a hook at all (tab state can be `useState` in the wiring layer). Strike or mark "may not exist". |
#### Confirmed wrapper / type corrections
These are also `(likely)`-flavored claims that slipped past the spec:
- The interfaces doc says **"MCP 2025-11-25 base schema does not define a
first-class task primitive"**. This is **wrong** — the MCP TS SDK 1.x
exports `Task` from `@modelcontextprotocol/sdk/types.js`, and v1.5's
`useManagedRequestorTasks` already returns `Task[]`. Update every entry
in the interfaces doc that proposes an `InspectorTask` wrapper to use
`Task` directly. The list-changed signal **is** an Inspector extension
(no SDK schema for `notifications/tasks/list_changed`); v1.5 defines it
in `core/mcp/taskNotificationSchemas.ts`. Cite that file rather than
inventing.
- **Appendix A line 854** asserts: "v1.5 monorepo bundles most
primitive-specific logic inside `client/src/App.tsx`; hooks marked
'likely' will be extracted cleanly in v2 core". This is **wrong about
v1.5/main of THIS repo**. It's true of the upstream
`modelcontextprotocol/inspector` v1.x mainline, which an earlier subagent
conflated with v1.5. v1.5 in this repo already has the full
`core/react/*` hook split-out. Rewrite Appendix A to cite the actual
v1.5 file paths under `core/react/` and remove the "will be extracted
cleanly" framing.
- **Appendix A's pointer to `client/src/lib/hooks`** in v1.5 is also
wrong-source: that path is the upstream repo, not this repo. The path in
this repo's v1.5 branch is `core/react/`. Update the link.
- Anywhere the doc references a `LogEntry` shape that mixes in a `string`
timestamp: v1.5's `MessageEntry` and `StderrLogEntry` use `Date`, not
`string`. Reconcile.
#### Procedure
1. Open `v2_ux_interfaces.md` and walk every `likely` instance in document
order (`grep -n "likely" specification/v2_ux_interfaces.md`).
2. For each, look up the claim against v1.5 (`git show origin/v1.5/main:...`)
or the SDK (`grep` in `node_modules/@modelcontextprotocol/sdk/dist/types.d.ts`
once 0.1 lands).
3. Either replace `likely useFoo` with the verified name or, if there is
no v1.5 analog, replace it with an explicit `v2 to introduce — no v1.5
analog` annotation. **Do not leave `(likely)` in the doc.**
4. While doing this, fix Appendix A's structural errors above.
5. Land the corrected `v2_ux_interfaces.md` as the **second commit** of
Phase 0, immediately after the `core/mcp/types.ts` setup commit. The
commit message should call out the rename table from this section so
reviewers can audit it.
6. After 0.3 ships, **`grep "likely" specification/v2_ux_interfaces.md`
must return zero results**. This is the definition-of-done for Phase 0.3.
(See Risks #11 below for the underlying root cause and the
"vet weasel words" rule that should now apply to every planning doc in
this repo.)
### 0.4 Inventory current local re-declarations to delete
Run a single pass to find every local type that will be replaced by an MCP
schema type or a wrapper. Add a checklist to the PR description so reviewers
can confirm all of them are gone by the end:
- `JsonSchema` (in `groups/SchemaForm/SchemaForm.tsx`) → `JsonSchemaType`
from the new `clients/web/src/utils/jsonUtils.ts` (copied from v1.5).
- `LogLevel` (in `elements/LogEntry/LogEntry.tsx`) → `LoggingLevel` from the
SDK.
- `TaskStatus` (in `groups/TaskCard/TaskCard.tsx`) → status field on the SDK
`Task` type. **Do not introduce a separate `InspectorTaskStatus`.**
- `TaskCardProps`-as-data-shape (in `groups/TaskListPanel/TaskListPanel.tsx`)
→ SDK `Task[]`.
- `PromptItem`, `SelectedPrompt` → `Prompt`.
- `ResourceItem`, `TemplateListItem`, `SubscriptionItem` → `Resource`,
`ResourceTemplate`, `InspectorResourceSubscription`.
- `ToolListItemProps`-as-data-shape → `Tool`.
- `RootEntry` → `Root`.
- `KeyValuePair` (in `ServerSettingsForm`) → keep, but reconcile with
`CustomHeader` from v1.5; if the form is editing custom headers
specifically, prefer `CustomHeader`.
- Inline `ConnectionStatus` literal unions in multiple components
(e.g. `ServerStatusIndicator`, `ServerCard`, `ConnectedView`) → import
`ConnectionStatus` from `core/mcp/types.ts`.
- Inline `"stdio" | "http"` transport unions (e.g. `TransportBadge`) →
`ServerType` from `core/mcp/types.ts`.
Output of 0.3 lives in the PR description, not in this file.
## Phase 1 — Elements (16 components)
> Smallest blast radius, fewest dependencies, and the type changes here
> propagate up. Do this first so groups can immediately consume the new
> element shapes.
### Strategy
- One commit per element (or one commit per logical batch of 2–3 closely
related elements). Each commit refactors `<Name>.tsx`, updates
`<Name>.stories.tsx`, and runs format/lint/build.
- For each element, the steps are mechanical:
1. Read `v2_ux_interfaces.md` Section 1's entry for the component.
2. Replace the current props interface with the **Target props** from the spec.
3. Update the body to read fields off the new prop shape.
4. Update the story `args` to construct realistic schema fixtures (use real
`LoggingLevel` strings, real `Annotations` objects, etc.).
5. Run `npm run format && npm run lint && npm run build`.
### Order (rough dependency order)
1. **Pure presentational, no behavior change** (warm-up):
`CopyButton`, `ListToggle`, `ListChangedIndicator`, `SubscribeButton` —
spec says "unchanged" or trivial.
2. **Schema-typed primitives** that other elements depend on:
`LogLevelBadge` (introduces `LoggingLevel`), `TransportBadge` (introduces
`InspectorTransportType`), `TaskStatusBadge` (introduces
`InspectorTaskStatus`), `ServerStatusIndicator` (introduces
`ConnectionStatus`).
3. **Components consuming MCP wrapper objects**:
`AnnotationBadge` (consumes `Annotations`/`ToolAnnotations`),
`CapabilityItem` (consumes `keyof ServerCapabilities`),
`LogEntry` (consumes `LoggingMessageNotification['params']` + receivedAt),
`MessageBubble` (consumes `SamplingMessage`/`PromptMessage`),
`ProgressDisplay` (consumes `ProgressNotification['params']`).
4. **Composite-input elements**:
`ContentViewer` (consumes the full `ContentBlock` discriminated union),
`InlineError` (consumes `JSONRPCError['error']`),
`ConnectionToggle` (split single `onChange` into `onConnect`/`onDisconnect`).
### Validation per element
- `npm run format && npm run lint && npm run build` (this is the project rule
in `AGENTS.md`).
- Open the element's story in Storybook locally; visual output should be
unchanged unless the spec calls out a new visible field.
### Phase 1 checkpoint
- Single commit on the branch summarizing the elements pass, OR a stack of
smaller commits — preference: small commits.
- Before moving to Phase 2, scan `clients/web/src/components/elements/` for
any remaining local-union literal types that should have been replaced.
Delete the dead local types.
## Phase 2 — Groups (37 components)
> The bulk of the schema surface flows through this layer. Most groups embed
> 1–3 elements that already changed in Phase 1, so doing groups *after* Phase
> 1 means we only touch each file once.
### Strategy
- **One PR per logical group cluster** (not one PR per component) — clusters
below. Each PR refactors all components in the cluster, updates their
stories, and runs format/lint/build.
- For groups whose props are mostly a flat copy of an MCP schema object
(`ToolListItem`, `ResourceListItem`, `PromptListItem`, `ToolControls`,
`PromptControls`, `ResourceControls`, `RootsTable`, `LogStreamPanel`,
`LogControls`), the refactor is mechanical: import the schema type, swap
the local prop name, update story fixtures.
- For groups that wrap a single full panel of state (`ToolDetailPanel`,
`ResourcePreviewPanel`, `ResourceTemplatePanel`, `SamplingRequestPanel`,
`ElicitationFormPanel`, `ElicitationUrlPanel`, `PromptArgumentsForm`),
the refactor moves multiple flat scalar props into a single schema or
wrapper object and updates internal destructuring.
- For groups that flag a **dumb-component principle violation** (notably
`ServerCard`'s auto-connect `useEffect`), the violation is fixed in the
same commit that refactors the props — do not let it linger.
### Cluster order (within Phase 2)
1. **Tools cluster** — `ToolListItem`, `ToolControls`, `ToolDetailPanel`,
`ToolResultPanel`. All consume `Tool` / `CallToolRequest` /
`CallToolResult` / `ToolAnnotations`.
2. **Prompts cluster** — `PromptListItem`, `PromptControls`,
`PromptArgumentsForm`, `PromptMessagesDisplay`. All consume `Prompt` /
`PromptArgument` / `GetPromptResult` / `PromptMessage`.
3. **Resources cluster** — `ResourceListItem`, `ResourceControls`,
`ResourcePreviewPanel`, `ResourceTemplatePanel`, `ResourceSubscribedItem`.
Consume `Resource` / `ResourceTemplate` / `ReadResourceResult` /
`Text|BlobResourceContents` / `Annotations`.
4. **Logs cluster** — `LogControls`, `LogStreamPanel`. Consume `LoggingLevel`,
`LoggingMessageNotification['params']`.
5. **Tasks cluster** — `TaskCard`, `TaskControls`, `TaskListPanel`. Introduce
`InspectorTask` wrapper.
6. **History cluster** — `HistoryControls`, `HistoryEntry`, `HistoryListPanel`.
Introduce `InspectorHistoryEntry` wrapper.
7. **Sampling / elicitation cluster** — `SamplingRequestPanel`,
`InlineSamplingRequest`, `ElicitationFormPanel`, `ElicitationUrlPanel`,
`InlineElicitationRequest`, `PendingClientRequests`. Consume
`CreateMessageRequest`/`Result`, `ElicitRequest`/`Result`,
`InspectorUrlElicitRequest`, `InspectorPendingRequest`.
8. **Roots / Schema / Experimental cluster** — `RootsTable` (consumes `Root`),
`SchemaForm` (consumes shared JSON Schema type),
`ExperimentalFeaturesPanel` (consumes
`ServerCapabilities['experimental']`, `ClientCapabilities['experimental']`,
`JSONRPCRequest`/`Response`/`Error`).
9. **Server / settings / view-shell cluster** — `ServerCard` (**also** lifts
the auto-connect `useEffect`), `ServerInfoContent`, `ServerSettingsForm`,
`ServerListControls`, `ServerAddMenu`, `ImportServerJsonPanel`,
`ViewHeader`. Consume `Implementation`, `InitializeResult`,
`ServerCapabilities`, `ClientCapabilities`, `InspectorServerConfig`,
`InspectorServerSettings`, `InspectorOAuthDetails`,
`InspectorServerJsonDraft`, `InspectorTransportType`.
### Validation per cluster
- `npm run format && npm run lint && npm run build` after each cluster.
- Spot-check the corresponding Storybook stories visually.
- After every cluster, run a `tsc --noEmit` pass over the whole web client to
catch consumers in screens/views that are still passing the old shape.
Those will be temporarily broken — that's expected; screens/views are
Phase 3 / Phase 4. To keep the build green during the transition, screens
pass props through `as unknown as ...` adapter casts that get deleted in
Phase 3. Alternative: do Phase 3 alongside each cluster — see below.
### Inter-phase coupling decision
There are two ways to keep the build green while groups are mid-refactor:
1. **Adapter shims** in the consuming screen for the duration of the cluster,
deleted in Phase 3. Pro: clean per-cluster commits. Con: noisy diffs in
screens that get rewritten anyway.
2. **Refactor each group cluster's parent screen in the same PR.** Pro: no
shims, no temporarily-broken builds. Con: bigger PRs, but those PRs are
the natural unit of work anyway because each cluster maps almost 1:1 to
one screen.
**Proposed: option 2.** When the Tools group cluster is refactored, the
`ToolsScreen` is refactored in the same PR. This collapses Phase 2 and Phase
3 into a single per-feature pass and keeps `npm run build` green at every
commit.
## Phase 3 — Screens (7 components, folded into Phase 2)
Per the decision above, each screen is refactored in the same PR as its
group cluster:
- `ToolsScreen` ← Tools cluster
- `PromptsScreen` ← Prompts cluster
- `ResourcesScreen` ← Resources cluster
- `LoggingScreen` ← Logs cluster
- `TasksScreen` ← Tasks cluster
- `HistoryScreen` ← History cluster
- `ServerListScreen` ← Server / settings / view-shell cluster
For each screen:
1. Replace ad hoc list-of-`*Props` props with `<SchemaType>[]` arrays.
2. Move selection from a pre-built selected-detail prop (`selectedTool:
ToolDetailPanelProps`) to a selected name/uri/id (`selectedToolName:
string`) and derive the detail object internally via `tools.find(...)`.
3. Replace the separate `result` / `messages` props with a unified
`callState` / `getPromptState` / `readState` discriminated union so the
detail panel can render pending and error states uniformly.
4. Local UI state (search text, filter toggles, compact mode, selected name)
stays in `useState` inside the screen.
5. Update the screen's stories — the existing stories carry the burden of
exercising every prop combination. They will need new fixtures because
they currently pass a flattened shape that no longer compiles.
### Sampling / elicitation cluster note
Sampling and elicitation flows do not have a dedicated screen — they appear
inline (`InlineSamplingRequest`, `InlineElicitationRequest`) or as modal
panels invoked from other screens (`SamplingRequestPanel`,
`ElicitationFormPanel`, `ElicitationUrlPanel`). Their cluster ends with
updates to the *stories* that mount them in modals (`UnconnectedView ⇒
WithSettingsModal`-style and `ConnectedView ⇒ ToolsWithElicitation*Modal`-style
existing stories) rather than a screen refactor.
## Phase 4 — Views (2 components)
> Trivial after groups + screens. Finishes the refactor.
### `ConnectedView`
- Replace flat scalars `serverName`, `status`, `latencyMs`, `activeTab`,
`availableTabs` with: `serverInfo: Implementation`, `capabilities:
ServerCapabilities`, `connectionStatus: ConnectionStatus`, `latencyMs?:
number`, `activeTab: InspectorTab`, `availableTabs: InspectorTab[]`.
- Compute `availableTabs` against `capabilities` rather than accepting an
arbitrary string list.
- Forward `serverInfo` to the now-refactored `ViewHeader`.
### `UnconnectedView`
- Rename internal `HomeLayoutProps` interface to `UnconnectedViewProps`.
- No prop changes needed.
- Optional: add `connectionStatus` if the embedded header needs it.
### 4.3 Move selection state into the screens
The Phase 2 cluster work threaded `selectedXxxName` / `onSelectXxx` props
through every list-and-detail screen, leaving selection as caller-owned
state. That choice contradicts Phase 3's "selected name stays in `useState`
inside the screen" rule and creates a real wiring gap: nothing above the
screen has any reason to know which tool/prompt/resource the user clicked
on, so under `InspectorView` clicks would have to be round-tripped through
`App.tsx` purely to get the detail panel to render.
Selection is screen-local UI state. MCP-derived data (`callState` / fetched
prompt messages / resource read results) keeps flowing in from
`InspectorView` as props. Refactor each screen accordingly.
Screens to fix:
- **`ToolsScreen`** — drop `selectedToolName` and `onSelectTool` from the
prop interface; track the selected tool name in `useState` inside the
screen. `ToolControls` continues to receive `selectedName` and
`onSelectTool` as props from the screen. `callState` (and any future
result/progress payload) keeps coming in from `InspectorView`.
`InspectorView` only needs to know the selected tool name when it
triggers `onCallTool` — which the screen already owns, so it can pass
the name up at call time rather than maintaining it in the parent.
- **`PromptsScreen`** — same pattern for `selectedPromptName`. Fetched
`GetPromptResult` continues to arrive as a prop from `InspectorView`.
- **`ResourcesScreen`** — same pattern for the selected resource URI and
selected resource template URI. `ReadResourceResult` continues to
arrive as a prop.
For each screen:
1. Remove the `selectedXxxName` / `onSelectXxx` props from the screen's
prop interface.
2. Add a `useState` for the selection inside the screen and pass it down
to the controls group.
3. When the user triggers a request (`onCallTool` / `onGetPrompt` /
`onReadResource`), pass the currently selected name/uri up alongside
any other arguments — the parent does not need to track selection
between calls.
4. Update each screen's stories to remove the now-deleted props. The
stories will start working without `useArgs` selection wiring, because
selection is now genuinely internal.
5. Audit the controls groups (`ToolControls`, `PromptControls`,
`ResourceControls`) — their `selectedName` / `onSelectXxx` props are
correct (they ARE caller-owned, because the screen owns them) and
should not change.
6. Update `InspectorView` and any wiring fixtures to drop the now-removed
selection props they were passing to each screen.
## Phase 5 — Cleanup pass
After all four phases, sweep:
1. Delete every local type listed in Phase 0.3 that should have disappeared.
2. Run `npm run format && npm run lint && npm run build` one last time.
3. `git grep` for `JsonSchema`, `LogLevel`, `TaskStatus`, `RootEntry`,
`ToolListItemProps`, `PromptItem`, `ResourceItem`, `SubscriptionItem`,
`TemplateListItem`, `ServerCardProps as a data shape` — confirm zero
matches outside of intentional kept types.
4. Update `clients/web/README.md` if there's anything in it that refers to
the now-deleted shapes.
5. Create a follow-up issue (or PR placeholder) for the v2 `core/` package
migration: lift `clients/web/src/types/inspector/` into `core/types/`,
update all import paths.
## Validation strategy (whole-project)
After each phase:
- `cd clients/web && npm run format && npm run lint && npm run build`
- Visually open Storybook (`npm run storybook`) and click through the
refactored components.
- For groups/screens that introduced or substantially changed a wrapper
type, write at least one Storybook story that uses a fixture clearly
derived from a real MCP SDK type (e.g. import `Tool` from the SDK in the
story file and construct one — this catches type drift instantly).
After the whole effort:
- Read `specification/v2_ux_interfaces.md` end to end and confirm every
component's "Internal refactors" bullet is satisfied. Mark done.
- The follow-up "build the v2 core hook layer" effort can begin with this
branch's component contracts as its input spec.
## Risks and open questions
1. ~~**JSON Schema typing.**~~ **Resolved.** Copy v1.5's
`clients/web/src/utils/jsonUtils.ts` (`JsonValue`, `JsonObject`,
`JsonSchemaType`) and `clients/web/src/utils/schemaUtils.ts` (AJV-backed
`Tool.outputSchema` validation) verbatim. `SchemaForm` switches from its
local `JsonSchema` type to `JsonSchemaType`.
2. **`PrimitiveSchemaDefinition` for elicitation.** `ElicitRequest`'s
`requestedSchema` is constrained to primitives. The current `SchemaForm`
handles arbitrary schemas. The fix is either two form components or one
form component with a primitive-only mode flag — decide during the
sampling/elicitation cluster. Check what v1.5 does first; v1.5 already
supports both form-mode and URL-mode elicitation per
`InspectorClientOptions.elicit` so the constraint is already represented.
3. **`tools` / `toolChoice` on `SamplingRequestPanel`.** These fields are not
in `CreateMessageRequest` in MCP 2025-11-25. Per the spec, the choice is
"Inspector extension or drop". Default proposal: **drop** them; revisit if
a future MCP revision adds them. Verify v1.5's
`core/mcp/samplingCreateMessage.ts` does or does not surface these fields
before deciding.
4. **`Task` is in the SDK 1.x — interfaces doc is partially stale.** The
`v2_ux_interfaces.md` text says "MCP 2025-11-25 base schema doesn't have
a first-class task primitive". This is no longer correct: MCP TS SDK 1.x
exports `Task` from `@modelcontextprotocol/sdk/types.js` and v1.5 already
uses it as a raw type via `useManagedRequestorTasks`. **Action**: during
Phase 0, post a follow-up commit that updates the `TaskStatusBadge`,
`TaskCard`, `TaskListPanel`, and `TasksScreen` entries in
`v2_ux_interfaces.md` to reference `Task` directly instead of the
`InspectorTask` placeholder. Do not silently diverge.
5. **`InspectorClientOptions` boundary.** v1.5's `InspectorClientOptions` is a
construction-time options bag for `InspectorClient`, not a settings object
the user edits at runtime. The `ServerSettingsForm` props need a runtime
*subset* (timeouts, OAuth client id/secret/scope, headers, metadata,
custom instructions, feature flags). Create a small `InspectorServerSettings`
wrapper in `core/mcp/types.ts` that picks exactly those fields off
`InspectorClientOptions` — do not invent a parallel hierarchy.
6. **Story bundle size.** The existing `ConnectedView.stories.tsx` is already
long because each story inlines a full screen of fixture data. Refactoring
each story to use real schema types will make them longer. Consider
factoring fixtures into shared `*.fixtures.ts` files alongside the
component, especially for tools / prompts / resources where the same
object set is reused across multiple stories. Decide per cluster.
7. **Wrapper type names.** Use **v1.5's names verbatim**, not the
`Inspector*` placeholders from `v2_ux_interfaces.md`. The
"Name-mapping correction" table at the top of this plan is the
authoritative rename list. Update `v2_ux_interfaces.md` after Phase 0 so
the spec and the implementation agree on names.
8. **Date vs. string for timestamps.** v1.5's `MessageEntry`,
`StderrLogEntry`, `FetchRequestEntry`, and the `*Invocation` types use
`Date`, not ISO strings. The current Storybook stories pass ISO strings.
When refactoring, convert fixtures to `new Date(...)`. Stories' visible
output should not change because `Date.toISOString()` / `toLocaleString()`
is invoked at render time anyway.
9. **Visual regressions.** The refactor is meant to be visually invisible,
but several elements get *new* visible fields (`ResourceListItem` will
start rendering `Annotations` it currently ignores; `LogEntry` will
render `params.data` directly). Capture before/after Storybook
screenshots for each cluster's PR description so reviewers can sign off.
10. **v1.5 has more state machinery than this plan touches.** v1.5 ships
`core/mcp/state/managed{Tools,Prompts,Resources,ResourceTemplates,RequestorTasks}State.ts`
plus `paged*State.ts` siblings, plus `core/react/useManaged*.ts` hooks
that subscribe to them via `EventTarget`. None of that is in scope for
this plan — only the *types* are. The hook layer build-out is the
follow-up effort that will consume the contracts produced here.
11. **`v2_ux_interfaces.md` was produced with unverified hedges.** The doc
contains 52 instances of `(likely)` because the parallel research
subagents that filled in each section were instructed to prefix
uncertain hook names with the hedge rather than fail to fill in a
section. Several of those hedges turned out to be wrong (see Phase 0.3
rename table). The **rule going forward** for any planning / spec doc
in this repo: hedging words like `likely`, `probably`, `presumably`,
`should be` next to a type name, hook name, or file path are an
unverified claim. Either resolve them against the source of truth
(v1.5 branch of this repo for Inspector-owned types/hooks/wrappers;
`@modelcontextprotocol/sdk/types.js` for protocol schema types) before
the doc ships, or mark them explicitly as `not present in v1.5 — needs
design`. Do not leave a hedge in a planning doc and treat it as
research. The Phase 0.3 step is the immediate cleanup; the rule is the
durable preference.
## Out of this plan, but adjacent
- **Vitest unit tests** for each refactored component (90% per AGENTS.md).
- **Building the v2 `core/` hook layer.** v1.5 already has the full hook
surface (`useInspectorClient`, `useManagedTools`, `useManagedPrompts`,
`useManagedResources`, `useManagedResourceTemplates`,
`useManagedRequestorTasks`, `useMessageLog`, `useStderrLog`,
`useFetchRequestLog`, plus `usePaged*` siblings) and the state machinery
it subscribes to (`core/mcp/state/managed*State.ts`,
`core/mcp/state/paged*State.ts`). Porting that layer wholesale to v2 is a
separate effort that will consume the component contracts produced here as
its input spec.
- **Wiring `App.tsx`** to the v2 core hook layer.
- **CLI / TUI clients.** v1.5 has both (`clients/cli/`, `clients/tui/`); v2
will reuse the wrapper types from `core/mcp/types.ts` once that file exists
in v2.
- **Updating `v2_ux_interfaces.md`** to use v1.5's actual wrapper-type names
(`MCPServerConfig`, `MessageEntry`, `Task`, etc.) and v1.5's actual hook
names (`useManagedTools`, `useInspectorClient`, `useMessageLog`, etc.)
instead of the `Inspector*` and `(likely)` placeholders. **Done as part
of Phase 0.3 in this plan**, not a follow-up.