diff --git a/specification/v2_ux_interfaces_plan.md b/specification/v2_ux_interfaces_plan.md new file mode 100644 index 000000000..26e07a318 --- /dev/null +++ b/specification/v2_ux_interfaces_plan.md @@ -0,0 +1,670 @@ +# 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 +#### [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/lib/types/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. Place under `clients/web/src/types/` or alongside `ViewHeader`. | 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 `.tsx` + `.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; 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`. + +**Custom headers**: copy `clients/web/src/lib/types/customHeaders.ts` from +v1.5 verbatim. 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/.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 `.tsx`, updates + `.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 `[]` 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. + +## 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.