Merge pull request #1236 from modelcontextprotocol/v2/phase-5-spec-audit
Phase 5 audit: annotate spec, close HistoryControls partial
This commit is contained in:
commit
63e112113e
7 changed files with 99 additions and 25 deletions
|
|
@ -1,12 +1,25 @@
|
||||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||||
import { fn } from "storybook/test";
|
import { fn } from "storybook/test";
|
||||||
|
import type { MessageMethod } from "@inspector/core/mcp/types.js";
|
||||||
import { HistoryControls } from "./HistoryControls";
|
import { HistoryControls } from "./HistoryControls";
|
||||||
|
|
||||||
|
const SAMPLE_METHODS: MessageMethod[] = [
|
||||||
|
"tools/call",
|
||||||
|
"tools/list",
|
||||||
|
"resources/read",
|
||||||
|
"resources/list",
|
||||||
|
"prompts/get",
|
||||||
|
"prompts/list",
|
||||||
|
"sampling/createMessage",
|
||||||
|
"elicitation/create",
|
||||||
|
];
|
||||||
|
|
||||||
const meta: Meta<typeof HistoryControls> = {
|
const meta: Meta<typeof HistoryControls> = {
|
||||||
title: "Groups/HistoryControls",
|
title: "Groups/HistoryControls",
|
||||||
component: HistoryControls,
|
component: HistoryControls,
|
||||||
args: {
|
args: {
|
||||||
searchText: "",
|
searchText: "",
|
||||||
|
availableMethods: SAMPLE_METHODS,
|
||||||
onSearchChange: fn(),
|
onSearchChange: fn(),
|
||||||
onMethodFilterChange: fn(),
|
onMethodFilterChange: fn(),
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,18 @@
|
||||||
import { Select, Stack, TextInput, Title } from "@mantine/core";
|
import { Select, Stack, TextInput, Title } from "@mantine/core";
|
||||||
|
import type { MessageMethod } from "@inspector/core/mcp/types.js";
|
||||||
const METHOD_OPTIONS = [
|
|
||||||
"tools/call",
|
|
||||||
"tools/list",
|
|
||||||
"resources/read",
|
|
||||||
"resources/list",
|
|
||||||
"prompts/get",
|
|
||||||
"prompts/list",
|
|
||||||
"sampling/createMessage",
|
|
||||||
"elicitation/create",
|
|
||||||
];
|
|
||||||
|
|
||||||
export interface HistoryControlsProps {
|
export interface HistoryControlsProps {
|
||||||
searchText: string;
|
searchText: string;
|
||||||
methodFilter?: string;
|
methodFilter?: MessageMethod;
|
||||||
|
availableMethods: MessageMethod[];
|
||||||
onSearchChange: (text: string) => void;
|
onSearchChange: (text: string) => void;
|
||||||
onMethodFilterChange: (method: string | undefined) => void;
|
onMethodFilterChange: (method: MessageMethod | undefined) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function HistoryControls({
|
export function HistoryControls({
|
||||||
searchText,
|
searchText,
|
||||||
methodFilter,
|
methodFilter,
|
||||||
|
availableMethods,
|
||||||
onSearchChange,
|
onSearchChange,
|
||||||
onMethodFilterChange,
|
onMethodFilterChange,
|
||||||
}: HistoryControlsProps) {
|
}: HistoryControlsProps) {
|
||||||
|
|
@ -36,9 +28,11 @@ export function HistoryControls({
|
||||||
<Title order={6}>Filter by Method</Title>
|
<Title order={6}>Filter by Method</Title>
|
||||||
<Select
|
<Select
|
||||||
placeholder="All methods"
|
placeholder="All methods"
|
||||||
data={METHOD_OPTIONS}
|
data={availableMethods}
|
||||||
value={methodFilter ?? null}
|
value={methodFilter ?? null}
|
||||||
onChange={(value) => onMethodFilterChange(value ?? undefined)}
|
onChange={(value) =>
|
||||||
|
onMethodFilterChange((value as MessageMethod | null) ?? undefined)
|
||||||
|
}
|
||||||
clearable
|
clearable
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import {
|
||||||
Text,
|
Text,
|
||||||
Title,
|
Title,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import type { MessageEntry } from "../../../../../../core/mcp/types.js";
|
import type { MessageEntry, MessageMethod } from "@inspector/core/mcp/types.js";
|
||||||
import { HistoryEntry } from "../HistoryEntry/HistoryEntry";
|
import { HistoryEntry } from "../HistoryEntry/HistoryEntry";
|
||||||
import { ListToggle } from "../../elements/ListToggle/ListToggle";
|
import { ListToggle } from "../../elements/ListToggle/ListToggle";
|
||||||
import { extractMethod } from "../historyUtils.js";
|
import { extractMethod } from "../historyUtils.js";
|
||||||
|
|
@ -17,7 +17,7 @@ export interface HistoryListPanelProps {
|
||||||
entries: MessageEntry[];
|
entries: MessageEntry[];
|
||||||
pinnedIds: Set<string>;
|
pinnedIds: Set<string>;
|
||||||
searchText: string;
|
searchText: string;
|
||||||
methodFilter?: string;
|
methodFilter?: MessageMethod;
|
||||||
onClearAll: () => void;
|
onClearAll: () => void;
|
||||||
onExport: () => void;
|
onExport: () => void;
|
||||||
onReplay: (id: string) => void;
|
onReplay: (id: string) => void;
|
||||||
|
|
@ -53,7 +53,7 @@ function formatHistoryTitle(count: number): string {
|
||||||
function matchesFilters(
|
function matchesFilters(
|
||||||
entry: MessageEntry,
|
entry: MessageEntry,
|
||||||
searchText: string,
|
searchText: string,
|
||||||
methodFilter?: string,
|
methodFilter?: MessageMethod,
|
||||||
): boolean {
|
): boolean {
|
||||||
const method = extractMethod(entry);
|
const method = extractMethod(entry);
|
||||||
if (methodFilter && method !== methodFilter) return false;
|
if (methodFilter && method !== methodFilter) return false;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
import type { MessageEntry } from "../../../../../core/mcp/types.js";
|
import type { MessageEntry, MessageMethod } from "@inspector/core/mcp/types.js";
|
||||||
|
|
||||||
export function extractMethod(entry: MessageEntry): string {
|
export function extractMethod(entry: MessageEntry): MessageMethod {
|
||||||
if ("method" in entry.message) {
|
if ("method" in entry.message) {
|
||||||
return entry.message.method;
|
// Cast: SDK types message.method as `string`, but every entry in this
|
||||||
|
// app's MessageEntry log originates from MCP SDK schemas.
|
||||||
|
return entry.message.method as MessageMethod;
|
||||||
}
|
}
|
||||||
return "response";
|
return "response";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import { useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { Card, Flex, Stack } from "@mantine/core";
|
import { Card, Flex, Stack } from "@mantine/core";
|
||||||
import type { MessageEntry } from "../../../../../../core/mcp/types.js";
|
import type { MessageEntry, MessageMethod } from "@inspector/core/mcp/types.js";
|
||||||
import { HistoryControls } from "../../groups/HistoryControls/HistoryControls";
|
import { HistoryControls } from "../../groups/HistoryControls/HistoryControls";
|
||||||
import { HistoryListPanel } from "../../groups/HistoryListPanel/HistoryListPanel.js";
|
import { HistoryListPanel } from "../../groups/HistoryListPanel/HistoryListPanel.js";
|
||||||
|
import { extractMethod } from "../../groups/historyUtils.js";
|
||||||
|
|
||||||
export interface HistoryScreenProps {
|
export interface HistoryScreenProps {
|
||||||
entries: MessageEntry[];
|
entries: MessageEntry[];
|
||||||
|
|
@ -39,7 +40,12 @@ export function HistoryScreen({
|
||||||
onTogglePin,
|
onTogglePin,
|
||||||
}: HistoryScreenProps) {
|
}: HistoryScreenProps) {
|
||||||
const [searchText, setSearchText] = useState("");
|
const [searchText, setSearchText] = useState("");
|
||||||
const [methodFilter, setMethodFilter] = useState<string | undefined>();
|
const [methodFilter, setMethodFilter] = useState<MessageMethod | undefined>();
|
||||||
|
|
||||||
|
const availableMethods = useMemo(
|
||||||
|
() => Array.from(new Set(entries.map(extractMethod))).sort(),
|
||||||
|
[entries],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScreenLayout>
|
<ScreenLayout>
|
||||||
|
|
@ -48,6 +54,7 @@ export function HistoryScreen({
|
||||||
<HistoryControls
|
<HistoryControls
|
||||||
searchText={searchText}
|
searchText={searchText}
|
||||||
methodFilter={methodFilter}
|
methodFilter={methodFilter}
|
||||||
|
availableMethods={availableMethods}
|
||||||
onSearchChange={setSearchText}
|
onSearchChange={setSearchText}
|
||||||
onMethodFilterChange={setMethodFilter}
|
onMethodFilterChange={setMethodFilter}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
import type {
|
import type {
|
||||||
CallToolResult,
|
CallToolResult,
|
||||||
|
ClientNotification,
|
||||||
|
ClientRequest,
|
||||||
GetPromptResult,
|
GetPromptResult,
|
||||||
Implementation,
|
Implementation,
|
||||||
JSONRPCErrorResponse,
|
JSONRPCErrorResponse,
|
||||||
|
|
@ -10,6 +12,8 @@ import type {
|
||||||
ReadResourceResult,
|
ReadResourceResult,
|
||||||
Resource,
|
Resource,
|
||||||
ServerCapabilities,
|
ServerCapabilities,
|
||||||
|
ServerNotification,
|
||||||
|
ServerRequest,
|
||||||
Tool,
|
Tool,
|
||||||
} from "@modelcontextprotocol/sdk/types.js";
|
} from "@modelcontextprotocol/sdk/types.js";
|
||||||
import type { JsonValue } from "../json/jsonUtils.js";
|
import type { JsonValue } from "../json/jsonUtils.js";
|
||||||
|
|
@ -98,6 +102,14 @@ export interface MessageEntry {
|
||||||
duration?: number; // Time between request and response in ms
|
duration?: number; // Time between request and response in ms
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Method name for any MessageEntry traffic, plus synthetic "response" for result/error entries. */
|
||||||
|
export type MessageMethod =
|
||||||
|
| ClientRequest["method"]
|
||||||
|
| ClientNotification["method"]
|
||||||
|
| ServerRequest["method"]
|
||||||
|
| ServerNotification["method"]
|
||||||
|
| "response";
|
||||||
|
|
||||||
export type FetchRequestCategory = "auth" | "transport";
|
export type FetchRequestCategory = "auth" | "transport";
|
||||||
|
|
||||||
export interface FetchRequestEntry {
|
export interface FetchRequestEntry {
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,52 @@ For each component, the plan records:
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Audit results (Phase 5 close-out, 2026-04-26)
|
||||||
|
|
||||||
|
After Phases 0–5 of [`v2_ux_interfaces_plan.md`](v2_ux_interfaces_plan.md), the
|
||||||
|
component layer was audited against this spec by reading every component file
|
||||||
|
under `clients/web/src/components/` and comparing its exported props interface
|
||||||
|
and internal rendering against the corresponding entry's **Target props** and
|
||||||
|
**Internal refactors** here. Spec entries that reference `Inspector*`
|
||||||
|
placeholder names map to v1.5 actual names per the plan's
|
||||||
|
"Name-mapping correction" table — components using those v1.5 names
|
||||||
|
(`MCPServerConfig`, `ServerType`, `Task`, `MessageEntry`, etc.) are treated
|
||||||
|
as satisfied.
|
||||||
|
|
||||||
|
**Result: 57 of 62 satisfied, 5 partial, 0 unstarted.**
|
||||||
|
|
||||||
|
### Partials still open
|
||||||
|
|
||||||
|
- **ElicitationUrlPanel** — accepts scalar `message` / `url` / `requestId`
|
||||||
|
props. Spec target wraps these in `InspectorUrlElicitRequest`. **Blocked
|
||||||
|
on the v2 core hook layer effort** — the wrapper type was deferred from
|
||||||
|
Phase 0.2 and was not added to `core/mcp/types.ts` because URL-mode
|
||||||
|
elicitation has no dedicated handler in this repo yet.
|
||||||
|
- **InlineElicitationRequest** — accepts only `ElicitRequest['params']`
|
||||||
|
(form variant). Spec target is a discriminated union over
|
||||||
|
`ElicitRequest | InspectorUrlElicitRequest`. **Same blocker as above.**
|
||||||
|
- **InlineSamplingRequest** — exposes a flat `responseText: string` prop.
|
||||||
|
Spec target is `draftResult?: CreateMessageResult`. In-scope to fix
|
||||||
|
without the hook layer; deferred to a follow-up because the inline card's
|
||||||
|
draft-editing UX is co-evolving with `SamplingRequestPanel`.
|
||||||
|
- **ExperimentalFeaturesPanel** — accepts a local
|
||||||
|
`clientToggles: ClientExperimentalToggle[]` prop. Spec target is the
|
||||||
|
freeform `clientExperimental: ClientCapabilities['experimental']` record
|
||||||
|
iterated in-component. The local-toggle shape was retained because the
|
||||||
|
panel needs per-toggle metadata (label, description) that the bare
|
||||||
|
capabilities record doesn't carry; reconciling the two shapes is a
|
||||||
|
follow-up.
|
||||||
|
- **ResourceListItem** — props match the target shape, but the entry's
|
||||||
|
internal-refactor bullet ("Actually render the annotations currently
|
||||||
|
received but unused") is unimplemented; the component still ignores
|
||||||
|
`resource.annotations`.
|
||||||
|
|
||||||
|
The component contracts produced by this refactor are the input spec for
|
||||||
|
the upcoming v2 `core/` hook layer effort. The two blocked partials above
|
||||||
|
will close out as part of that work.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Section 1 — Elements (`clients/web/src/components/elements/`)
|
## Section 1 — Elements (`clients/web/src/components/elements/`)
|
||||||
|
|
||||||
> Small, focused presentational primitives. Each renders a single MCP concept
|
> Small, focused presentational primitives. Each renders a single MCP concept
|
||||||
|
|
@ -310,7 +356,7 @@ For each component, the plan records:
|
||||||
- **Purpose**: Search + method filter sidebar for the history screen.
|
- **Purpose**: Search + method filter sidebar for the history screen.
|
||||||
- **Current props**: `searchText`, `methodFilter`, `onSearchChange`, `onMethodFilterChange`.
|
- **Current props**: `searchText`, `methodFilter`, `onSearchChange`, `onMethodFilterChange`.
|
||||||
- **MCP schema touch points**: Method filter values are MCP `Request.method` literals.
|
- **MCP schema touch points**: Method filter values are MCP `Request.method` literals.
|
||||||
- **Target props**: `searchText`, `methodFilter?: RequestMethod`, `availableMethods: RequestMethod[]`, `onSearchChange`, `onMethodFilterChange`.
|
- **Target props**: `searchText`, `methodFilter?: MessageMethod`, `availableMethods: MessageMethod[]`, `onSearchChange`, `onMethodFilterChange`.
|
||||||
- **Callbacks → core hook**: `useMessageLog`.
|
- **Callbacks → core hook**: `useMessageLog`.
|
||||||
- **Internal refactors**: Type the method filter as a union of MCP method strings.
|
- **Internal refactors**: Type the method filter as a union of MCP method strings.
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue