Add component interfaces refactor implementation plan
Adds specification/v2_ux_interfaces_plan.md, the implementation plan for the component interface refactor scoped in v2_ux_interfaces.md. Uses the v1.5/main branch of this repo as the source of truth for Inspector-owned wrapper types (MCPServerConfig, ConnectionStatus, MessageEntry, FetchRequestEntry, StderrLogEntry, ServerState, the *Invocation cache types) and pins @modelcontextprotocol/sdk at the latest 1.x line (^1.29.0). Includes a name-mapping table that translates the Inspector* placeholders in v2_ux_interfaces.md to v1.5's actual type names. Plan organizes the work into phases: foundations (SDK + wrapper types), elements, group+screen feature clusters (folded together to keep the build green at every commit), views, and a cleanup sweep. Calls out the discovered ServerCard auto-connect useEffect violation to fix in-place during the server cluster, and flags that Task is already exported by the SDK 1.x so the InspectorTask wrapper framing in v2_ux_interfaces.md is stale and needs a follow-up correction. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e9d4e928a9
commit
d4feca2e31
1 changed files with 552 additions and 0 deletions
552
specification/v2_ux_interfaces_plan.md
Normal file
552
specification/v2_ux_interfaces_plan.md
Normal file
|
|
@ -0,0 +1,552 @@
|
||||||
|
# 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 `<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`.
|
||||||
|
|
||||||
|
**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 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.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
## 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.) instead of the
|
||||||
|
`Inspector*` placeholders. Should be a small follow-up commit on this
|
||||||
|
branch immediately after Phase 0 lands so the spec and the code agree.
|
||||||
Loading…
Reference in a new issue