Inspector V2 UX - Component Interfaces Implementation Plan
July 26, 2026 · View on GitHub
Brief | V1 Problems | V2 Scope | V2 Tech Stack | V2 UX | V2 Auth | V2 New Spec Impact
Overview | Features | Handlers | Screenshots | Components | Interfaces | Plan
Goal
Refactor every component under clients/web/src/components/ so its prop
interface aligns with the V2 UX Component Interfaces
spec. After this work, every dumb component consumes either:
- A raw MCP schema type imported from
@modelcontextprotocol/sdk/types.js(Tool,Prompt,Resource,ServerCapabilities,Implementation,LoggingMessageNotification,Task, etc.), or - An Inspector-owned wrapper type for a UI concept that has no MCP
equivalent (
MCPServerConfig,ConnectionStatus,MessageEntry,StderrLogEntry,FetchRequestEntry, …), or - A callback with a signature shaped by the MCP request/result types the wiring layer will eventually pass through, or
- 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 latest1.xline (currently^1.29.0). The SDK exports every type this plan references, includingTaskandLoggingLevel. -
Inspector-owned wrapper types — the
v1.5/mainbranch of this repo is the canonical reference. v1.5 already has a completecore/mcp/types.ts(416 lines) defining every wrapper type the v2 components need:core/mcp/types.ts—MCPServerConfigdiscriminated 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'snotifications/tasks/list_changedzod extension (the SDK exportsTaskandTaskStatusNotificationSchemanatively, but not the list-changed signal).core/react/useInspectorClient.ts—UseInspectorClientResultshape (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/CustomHeadersshape, used byServerSettingsForm.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
Datevsstringchoices). Do not invent new names. The interfaces doc'sInspector*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.tsxto the MCP transport. The currentApp.tsxis 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.
AnnotationsonResourceListItem). - 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. Nocore/, nocli/, notui/, no sharedclients/launcher/.clients/web/src/App.tsxis a 42-lineMantineProvidershell — no wiring.clients/web/package.jsonhas no@modelcontextprotocol/sdkdependency.- 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/sdktoclients/web/package.jsonso component files canimport 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,MCPConfigConnectionStatusStderrLogEntryMessageEntry(note:timestamp: Date, notstring)FetchRequestEntry,FetchRequestEntryBase,FetchRequestCategoryServerStateResourceReadInvocation,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,CreateTransportResultInspectorClientEnvironment,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 }InspectorTabenum ("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: the live tab-id type is InspectorTabId in clients/web/src/utils/inspectorTabs.ts, and the dead navigation.ts InspectorTab was removed — a pure UI domain type belongs in utils/, not src/types/, per the src/lib vs src/utils rule in AGENTS.md.)
Custom headers: clients/web/src/utils/customHeaders.ts is a verbatim
copy of the v1.5 module. It owns CustomHeader / CustomHeaders shape used by
ServerSettingsForm and the experimental panel.
JSON Schema typing for SchemaForm: copy v1.5's
clients/web/src/utils/jsonUtils.ts (defines JsonValue, JsonObject,
JsonSchemaType) and clients/web/src/utils/schemaUtils.ts (AJV-backed
validators). This resolves the JSON Schema typing risk and keeps
SchemaForm aligned with how v1.5 validates Tool.inputSchema and
Tool.outputSchema.
Elicitation URL request shape: copy the URL-mode elicitation
request/result types from v1.5 core/mcp/elicitationCreateMessage.ts into
the new core/mcp/types.ts (or a peer file). The interfaces doc's
InspectorUrlElicitRequest placeholder maps to whatever v1.5 calls them.
0.3 Vet every "likely" hedge in v2_ux_interfaces.md
v2_ux_interfaces.md was produced by parallel research subagents that I
instructed to prefix uncertain hook names with (likely) rather than fail
to fill in a section. The result: 52 instances of "likely" survived to
the doc, almost all of them attached to hook names or hook-name-to-callback
mappings that were never cross-checked against v1.5. Several are actively
wrong. Before any component is touched, walk through every likely and
either resolve it against v1.5 / the SDK or strike it.
Use these source-of-truth lookups, not guessing:
- v1.5 React hooks —
git show origin/v1.5/main:core/react/<hook>.ts. The complete v1.5 hook surface is:useInspectorClient.ts—status,capabilities,serverInfo,instructions,appRendererClient,connect,disconnect.useManagedTools.ts—{ tools: Tool[]; refresh }useManagedPrompts.ts—{ prompts: Prompt[]; refresh }useManagedResources.ts—{ resources: Resource[]; refresh }useManagedResourceTemplates.ts—{ resourceTemplates: ResourceTemplate[]; refresh }useManagedRequestorTasks.ts—{ tasks: Task[]; refresh }useMessageLog.ts— JSON-RPC message buffer (MessageEntry[])useStderrLog.ts— stdio stderr buffer (StderrLogEntry[])useFetchRequestLog.ts— auth/transport HTTP fetch buffer (FetchRequestEntry[])usePagedTools.ts,usePagedPrompts.ts,usePagedResources.ts,usePagedResourceTemplates.ts,usePagedRequestorTasks.ts— paged siblings of the managed hooks above.
- MCP SDK types —
import type { ... } from "@modelcontextprotocol/sdk/types.js". - v1.5 core types —
git show origin/v1.5/main:core/mcp/types.ts.
Confirmed hook-name corrections
v2_ux_interfaces.md (likely) claim | Actual v1.5 surface |
|---|---|
useTools | useManagedTools (core/react/useManagedTools.ts) |
usePrompts | useManagedPrompts |
useResources | useManagedResources |
useResourceTemplates (implied) | useManagedResourceTemplates |
useTasks | useManagedRequestorTasks |
useMcpClient | useInspectorClient |
useServerCapabilities | Not a separate hook — capabilities: ServerCapabilities is a field on useInspectorClient's result. Strike. |
useLogs | Three v1.5 hooks: useMessageLog, useStderrLog, useFetchRequestLog. The interfaces doc conflates them. Split per use site: log stream → useMessageLog; stdio stderr → useStderrLog; HTTP fetch (auth/transport) → useFetchRequestLog. |
useHistory | useMessageLog — v1.5's message log IS the history; MessageEntry is the wrapper type. Strike useHistory. |
useElicitation | No discrete hook in v1.5. Elicitation is handled inside InspectorClient (core/mcp/elicitationCreateMessage.ts) via SDK request handlers. UI state for pending elicitations lives wherever the consuming screen puts it. Strike useElicitation and replace with "handled inside InspectorClient request handlers; v2 will need a small UI-state hook for the pending queue but it doesn't exist yet". |
useSampling | No discrete hook in v1.5. Same shape as elicitation: core/mcp/samplingCreateMessage.ts plus SDK request handlers. Strike. |
useRoots | No discrete hook in v1.5. Roots are configured via InspectorClientOptions.roots: Root[] at construction time and answered by an SDK request handler inside InspectorClient. There is no React hook. Strike or mark as "v2 to introduce". |
useServers | Not in v1.5. v2-only — keep, but mark explicitly as "v2 to introduce, no v1.5 analog". |
useServerRegistry | Not in v1.5. v2-only — same treatment. |
useClientCapabilities | Not in v1.5. v2-only — same treatment. |
useInspectorNavigation | Not in v1.5. Pure speculation in the interfaces doc. v2 may not need a hook at all (tab state can be useState in the wiring layer). Strike or mark "may not exist". |
Confirmed wrapper / type corrections
These are also (likely)-flavored claims that slipped past the spec:
- The interfaces doc says "MCP 2025-11-25 base schema does not define a
first-class task primitive". This is wrong — the MCP TS SDK 1.x
exports
Taskfrom@modelcontextprotocol/sdk/types.js, and v1.5'suseManagedRequestorTasksalready returnsTask[]. Update every entry in the interfaces doc that proposes anInspectorTaskwrapper to useTaskdirectly. The list-changed signal is an Inspector extension (no SDK schema fornotifications/tasks/list_changed); v1.5 defines it incore/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 upstreammodelcontextprotocol/inspectorv1.x mainline, which an earlier subagent conflated with v1.5. v1.5 in this repo already has the fullcore/react/*hook split-out. Rewrite Appendix A to cite the actual v1.5 file paths undercore/react/and remove the "will be extracted cleanly" framing. - Appendix A's pointer to
client/src/lib/hooksin v1.5 is also wrong-source: that path is the upstream repo, not this repo. The path in this repo's v1.5 branch iscore/react/. Update the link. - Anywhere the doc references a
LogEntryshape that mixes in astringtimestamp: v1.5'sMessageEntryandStderrLogEntryuseDate, notstring. Reconcile.
Procedure
- Open
v2_ux_interfaces.mdand walk everylikelyinstance in document order (grep -n "likely" specification/v2_ux_interfaces.md). - For each, look up the claim against v1.5 (
git show origin/v1.5/main:...) or the SDK (grepinnode_modules/@modelcontextprotocol/sdk/dist/types.d.tsonce 0.1 lands). - Either replace
likely useFoowith the verified name or, if there is no v1.5 analog, replace it with an explicitv2 to introduce — no v1.5 analogannotation. Do not leave(likely)in the doc. - While doing this, fix Appendix A's structural errors above.
- Land the corrected
v2_ux_interfaces.mdas the second commit of Phase 0, immediately after thecore/mcp/types.tssetup commit. The commit message should call out the rename table from this section so reviewers can audit it. - After 0.3 ships,
grep "likely" specification/v2_ux_interfaces.mdmust 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(ingroups/SchemaForm/SchemaForm.tsx) →JsonSchemaTypefrom the newclients/web/src/utils/jsonUtils.ts(copied from v1.5).LogLevel(inelements/LogEntry/LogEntry.tsx) →LoggingLevelfrom the SDK.TaskStatus(ingroups/TaskCard/TaskCard.tsx) → status field on the SDKTasktype. Do not introduce a separateInspectorTaskStatus.TaskCardProps-as-data-shape (ingroups/TaskListPanel/TaskListPanel.tsx) → SDKTask[].PromptItem,SelectedPrompt→Prompt.ResourceItem,TemplateListItem,SubscriptionItem→Resource,ResourceTemplate,InspectorResourceSubscription.ToolListItemProps-as-data-shape →Tool.RootEntry→Root.KeyValuePair(inServerSettingsForm) → keep, but reconcile withCustomHeaderfrom v1.5; if the form is editing custom headers specifically, preferCustomHeader.- Inline
ConnectionStatusliteral unions in multiple components (e.g.ServerStatusIndicator,ServerCard,ConnectedView) → importConnectionStatusfromcore/mcp/types.ts. - Inline
"stdio" | "http"transport unions (e.g.TransportBadge) →ServerTypefromcore/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:
- Read
v2_ux_interfaces.mdSection 1's entry for the component. - Replace the current props interface with the Target props from the spec.
- Update the body to read fields off the new prop shape.
- Update the story
argsto construct realistic schema fixtures (use realLoggingLevelstrings, realAnnotationsobjects, etc.). - Run
npm run format && npm run lint && npm run build.
- Read
Order (rough dependency order)
- Pure presentational, no behavior change (warm-up):
CopyButton,ListToggle,ListChangedIndicator,SubscribeButton— spec says "unchanged" or trivial. - Schema-typed primitives that other elements depend on:
LogLevelBadge(introducesLoggingLevel),TransportBadge(introducesInspectorTransportType),TaskStatusBadge(introducesInspectorTaskStatus),ServerStatusIndicator(introducesConnectionStatus). - Components consuming MCP wrapper objects:
AnnotationBadge(consumesAnnotations/ToolAnnotations),CapabilityItem(consumeskeyof ServerCapabilities),LogEntry(consumesLoggingMessageNotification['params']+ receivedAt),MessageBubble(consumesSamplingMessage/PromptMessage),ProgressDisplay(consumesProgressNotification['params']). - Composite-input elements:
ContentViewer(consumes the fullContentBlockdiscriminated union),InlineError(consumesJSONRPCError['error']),ConnectionToggle(split singleonChangeintoonConnect/onDisconnect).
Validation per element
npm run format && npm run lint && npm run build(this is the project rule inAGENTS.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-connectuseEffect), the violation is fixed in the same commit that refactors the props — do not let it linger.
Cluster order (within Phase 2)
- Tools cluster —
ToolListItem,ToolControls,ToolDetailPanel,ToolResultPanel. All consumeTool/CallToolRequest/CallToolResult/ToolAnnotations. - Prompts cluster —
PromptListItem,PromptControls,PromptArgumentsForm,PromptMessagesDisplay. All consumePrompt/PromptArgument/GetPromptResult/PromptMessage. - Resources cluster —
ResourceListItem,ResourceControls,ResourcePreviewPanel,ResourceTemplatePanel,ResourceSubscribedItem. ConsumeResource/ResourceTemplate/ReadResourceResult/Text|BlobResourceContents/Annotations. - Logs cluster —
LogControls,LogStreamPanel. ConsumeLoggingLevel,LoggingMessageNotification['params']. - Tasks cluster —
TaskCard,TaskControls,TaskListPanel. IntroduceInspectorTaskwrapper. - History cluster —
HistoryControls,HistoryEntry,HistoryListPanel. IntroduceInspectorHistoryEntrywrapper. - Sampling / elicitation cluster —
SamplingRequestPanel,InlineSamplingRequest,ElicitationFormPanel,ElicitationUrlPanel,InlineElicitationRequest,PendingClientRequests. ConsumeCreateMessageRequest/Result,ElicitRequest/Result,InspectorUrlElicitRequest,InspectorPendingRequest. - Roots / Schema / Experimental cluster —
RootsTable(consumesRoot),SchemaForm(consumes shared JSON Schema type),ExperimentalFeaturesPanel(consumesServerCapabilities['experimental'],ClientCapabilities['experimental'],JSONRPCRequest/Response/Error). - Server / settings / view-shell cluster —
ServerCard(also lifts the auto-connectuseEffect),ServerInfoContent,ServerSettingsForm,ServerListControls,ServerAddMenu,ImportServerJsonPanel,ViewHeader. ConsumeImplementation,InitializeResult,ServerCapabilities,ClientCapabilities,InspectorServerConfig,InspectorServerSettings,InspectorOAuthDetails,InspectorServerJsonDraft,InspectorTransportType.
Validation per cluster
npm run format && npm run lint && npm run buildafter each cluster.- Spot-check the corresponding Storybook stories visually.
- After every cluster, run a
tsc --noEmitpass 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 throughas 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:
- 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.
- 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 clusterPromptsScreen← Prompts clusterResourcesScreen← Resources clusterLoggingScreen← Logs clusterTasksScreen← Tasks clusterHistoryScreen← History clusterServerListScreen← Server / settings / view-shell cluster
For each screen:
- Replace ad hoc list-of-
*Propsprops with<SchemaType>[]arrays. - 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 viatools.find(...). - Replace the separate
result/messagesprops with a unifiedcallState/getPromptState/readStatediscriminated union so the detail panel can render pending and error states uniformly. - Local UI state (search text, filter toggles, compact mode, selected name)
stays in
useStateinside the screen. - 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,availableTabswith:serverInfo: Implementation,capabilities: ServerCapabilities,connectionStatus: ConnectionStatus,latencyMs?: number,activeTab: InspectorTab,availableTabs: InspectorTab[]. - Compute
availableTabsagainstcapabilitiesrather than accepting an arbitrary string list. - Forward
serverInfoto the now-refactoredViewHeader.
UnconnectedView
- Rename internal
HomeLayoutPropsinterface toUnconnectedViewProps. - No prop changes needed.
- Optional: add
connectionStatusif 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— dropselectedToolNameandonSelectToolfrom the prop interface; track the selected tool name inuseStateinside the screen.ToolControlscontinues to receiveselectedNameandonSelectToolas props from the screen.callState(and any future result/progress payload) keeps coming in fromInspectorView.InspectorViewonly needs to know the selected tool name when it triggersonCallTool— 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 forselectedPromptName. FetchedGetPromptResultcontinues to arrive as a prop fromInspectorView.ResourcesScreen— same pattern for the selected resource URI and selected resource template URI.ReadResourceResultcontinues to arrive as a prop.
For each screen:
- Remove the
selectedXxxName/onSelectXxxprops from the screen's prop interface. - Add a
useStatefor the selection inside the screen and pass it down to the controls group. - 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. - Update each screen's stories to remove the now-deleted props. The
stories will start working without
useArgsselection wiring, because selection is now genuinely internal. - Audit the controls groups (
ToolControls,PromptControls,ResourceControls) — theirselectedName/onSelectXxxprops are correct (they ARE caller-owned, because the screen owns them) and should not change. - Update
InspectorViewand 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:
- Delete every local type listed in Phase 0.3 that should have disappeared.
- Run
npm run format && npm run lint && npm run buildone last time. git grepforJsonSchema,LogLevel,TaskStatus,RootEntry,ToolListItemProps,PromptItem,ResourceItem,SubscriptionItem,TemplateListItem,ServerCardProps as a data shape— confirm zero matches outside of intentional kept types.- Update
clients/web/README.mdif there's anything in it that refers to the now-deleted shapes. - Create a follow-up issue (or PR placeholder) for the v2
core/package migration: liftclients/web/src/types/inspector/intocore/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
Toolfrom the SDK in the story file and construct one — this catches type drift instantly).
After the whole effort:
- Read
specification/v2_ux_interfaces.mdend 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
JSON Schema typing.Resolved. Copy v1.5'sclients/web/src/utils/jsonUtils.ts(JsonValue,JsonObject,JsonSchemaType) andclients/web/src/utils/schemaUtils.ts(AJV-backedTool.outputSchemavalidation) verbatim.SchemaFormswitches from its localJsonSchematype toJsonSchemaType.PrimitiveSchemaDefinitionfor elicitation.ElicitRequest'srequestedSchemais constrained to primitives. The currentSchemaFormhandles 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 perInspectorClientOptions.elicitso the constraint is already represented.tools/toolChoiceonSamplingRequestPanel. These fields are not inCreateMessageRequestin 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'score/mcp/samplingCreateMessage.tsdoes or does not surface these fields before deciding.Taskis in the SDK 1.x — interfaces doc is partially stale. Thev2_ux_interfaces.mdtext 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 exportsTaskfrom@modelcontextprotocol/sdk/types.jsand v1.5 already uses it as a raw type viauseManagedRequestorTasks. Action: during Phase 0, post a follow-up commit that updates theTaskStatusBadge,TaskCard,TaskListPanel, andTasksScreenentries inv2_ux_interfaces.mdto referenceTaskdirectly instead of theInspectorTaskplaceholder. Do not silently diverge.InspectorClientOptionsboundary. v1.5'sInspectorClientOptionsis a construction-time options bag forInspectorClient, not a settings object the user edits at runtime. TheServerSettingsFormprops need a runtime subset (timeouts, OAuth client id/secret/scope, headers, metadata, custom instructions, feature flags). Create a smallInspectorServerSettingswrapper incore/mcp/types.tsthat picks exactly those fields offInspectorClientOptions— do not invent a parallel hierarchy.- Story bundle size. The existing
ConnectedView.stories.tsxis 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.tsfiles alongside the component, especially for tools / prompts / resources where the same object set is reused across multiple stories. Decide per cluster. - Wrapper type names. Use v1.5's names verbatim, not the
Inspector*placeholders fromv2_ux_interfaces.md. The "Name-mapping correction" table at the top of this plan is the authoritative rename list. Updatev2_ux_interfaces.mdafter Phase 0 so the spec and the implementation agree on names. - Date vs. string for timestamps. v1.5's
MessageEntry,StderrLogEntry,FetchRequestEntry, and the*Invocationtypes useDate, not ISO strings. The current Storybook stories pass ISO strings. When refactoring, convert fixtures tonew Date(...). Stories' visible output should not change becauseDate.toISOString()/toLocaleString()is invoked at render time anyway. - Visual regressions. The refactor is meant to be visually invisible,
but several elements get new visible fields (
ResourceListItemwill start renderingAnnotationsit currently ignores;LogEntrywill renderparams.datadirectly). Capture before/after Storybook screenshots for each cluster's PR description so reviewers can sign off. - v1.5 has more state machinery than this plan touches. v1.5 ships
core/mcp/state/managed{Tools,Prompts,Resources,ResourceTemplates,RequestorTasks}State.tspluspaged*State.tssiblings, pluscore/react/useManaged*.tshooks that subscribe to them viaEventTarget. 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. v2_ux_interfaces.mdwas 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 likelikely,probably,presumably,should benext 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.jsfor protocol schema types) before the doc ships, or mark them explicitly asnot 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, plususePaged*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.tsxto the v2 core hook layer. - CLI / TUI clients. v1.5 has both (
clients/cli/,clients/tui/); v2 will reuse the wrapper types fromcore/mcp/types.tsonce that file exists in v2. - Updating
v2_ux_interfaces.mdto 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 theInspector*and(likely)placeholders. Done as part of Phase 0.3 in this plan, not a follow-up.