# 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 `.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`. _(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/.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. ### 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.