Refactor ServerCard stack to use id-based callbacks
Distinguishes server `id` (the unique map key in MCPConfig.mcpServers)
from `name` (display label) so containers can dispatch by id without
relying on display strings. ServerCard, ConnectionToggle, ServerListScreen,
and supporting stories all updated.
- ServerCard: add `id` prop; all action callbacks now `(id: string) => void`
- ConnectionToggle: collapse `onConnect`/`onDisconnect` into single `onToggle`
- ServerListScreen: lift `activeServer` to a prop; combine `onSetActive` +
`onToggleConnection` into a single `onToggleConnection(id)` callback
- Stories: use UUIDs for server ids so callbacks visibly carry ids, not names
Removes the "Direct"/"Via Proxy" connection mode concept, which is no
longer planned. ServerSettingsForm drops the section, ServerCard drops
the badge, InspectorServerSettings drops the field.
Removes the "Test Client Features" dropdown from ServerCard along with
its four handlers; that surface will live elsewhere.
TransportBadge now collapses `sse` and `streamable-http` into a single
"HTTP" label; ServerCard adds a grey transport descriptor next to the
badge ("Standard I/O", "SSE (Server Sent Events) [deprecated]",
"Streamable HTTP"). The version badge moves from the header row to sit
beside the transport badge.
ServerCard header no longer wraps; long server names truncate with
ellipsis.
Specification docs (`v2_scope.md`, `v2_screenshots.md`,
`v2_ux_interfaces.md`, `v2_ux_storybook.md`) updated to match.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
29ce55290f
commit
1149288ea6
18 changed files with 196 additions and 326 deletions
|
|
@ -6,8 +6,7 @@ const meta: Meta<typeof ConnectionToggle> = {
|
|||
title: "Elements/ConnectionToggle",
|
||||
component: ConnectionToggle,
|
||||
args: {
|
||||
onConnect: fn(),
|
||||
onDisconnect: fn(),
|
||||
onToggle: fn(),
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -4,15 +4,13 @@ import type { ConnectionStatus } from "@inspector/core/mcp/types.js";
|
|||
export interface ConnectionToggleProps {
|
||||
status: ConnectionStatus;
|
||||
disabled?: boolean;
|
||||
onConnect: () => void;
|
||||
onDisconnect: () => void;
|
||||
onToggle: () => void;
|
||||
}
|
||||
|
||||
export function ConnectionToggle({
|
||||
status,
|
||||
disabled = false,
|
||||
onConnect,
|
||||
onDisconnect,
|
||||
onToggle,
|
||||
}: ConnectionToggleProps) {
|
||||
const isConnected = status === "connected";
|
||||
const isConnecting = status === "connecting";
|
||||
|
|
@ -22,13 +20,7 @@ export function ConnectionToggle({
|
|||
size="lg"
|
||||
checked={isConnected || isConnecting}
|
||||
disabled={disabled || isConnecting}
|
||||
onChange={(event) => {
|
||||
if (event.currentTarget.checked) {
|
||||
onConnect();
|
||||
} else {
|
||||
onDisconnect();
|
||||
}
|
||||
}}
|
||||
onChange={onToggle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export interface TransportBadgeProps {
|
|||
|
||||
const transportLabel: Record<ServerType, string> = {
|
||||
stdio: "STDIO",
|
||||
sse: "SSE",
|
||||
sse: "HTTP",
|
||||
"streamable-http": "HTTP",
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -33,16 +33,11 @@ const meta: Meta<typeof ServerCard> = {
|
|||
component: ServerCard,
|
||||
args: {
|
||||
onToggleConnection: fn(),
|
||||
onSetActiveServer: fn(),
|
||||
onServerInfo: fn(),
|
||||
onSettings: fn(),
|
||||
onEdit: fn(),
|
||||
onClone: fn(),
|
||||
onRemove: fn(),
|
||||
onTestSampling: fn(),
|
||||
onTestElicitationForm: fn(),
|
||||
onTestElicitationUrl: fn(),
|
||||
onConfigureRoots: fn(),
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -51,61 +46,47 @@ type Story = StoryObj<typeof ServerCard>;
|
|||
|
||||
export const Connected: Story = {
|
||||
args: {
|
||||
id: "7f3b2a8e-1c4d-4e5f-9a0b-3c2d1e4f5a6b",
|
||||
name: "My MCP Server",
|
||||
config: stdioConfig,
|
||||
info: { name: "My MCP Server", version: "1.2.0" },
|
||||
connection: connected,
|
||||
connectionMode: "Subprocess",
|
||||
canTestClientFeatures: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const Disconnected: Story = {
|
||||
args: {
|
||||
id: "7f3b2a8e-1c4d-4e5f-9a0b-3c2d1e4f5a6b",
|
||||
name: "My MCP Server",
|
||||
config: stdioConfig,
|
||||
info: { name: "My MCP Server", version: "1.2.0" },
|
||||
connection: disconnected,
|
||||
connectionMode: "Subprocess",
|
||||
canTestClientFeatures: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const Connecting: Story = {
|
||||
args: {
|
||||
id: "7f3b2a8e-1c4d-4e5f-9a0b-3c2d1e4f5a6b",
|
||||
name: "My MCP Server",
|
||||
config: stdioConfig,
|
||||
info: { name: "My MCP Server", version: "1.2.0" },
|
||||
connection: connecting,
|
||||
connectionMode: "Subprocess",
|
||||
canTestClientFeatures: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const Failed: Story = {
|
||||
args: {
|
||||
id: "7f3b2a8e-1c4d-4e5f-9a0b-3c2d1e4f5a6b",
|
||||
name: "My MCP Server",
|
||||
config: stdioConfig,
|
||||
info: { name: "My MCP Server", version: "1.2.0" },
|
||||
connection: failed,
|
||||
connectionMode: "Subprocess",
|
||||
canTestClientFeatures: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const WithClientFeatures: Story = {
|
||||
args: {
|
||||
name: "My MCP Server",
|
||||
config: stdioConfig,
|
||||
info: { name: "My MCP Server", version: "1.2.0" },
|
||||
connection: connected,
|
||||
connectionMode: "Subprocess",
|
||||
canTestClientFeatures: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const LongServerName: Story = {
|
||||
args: {
|
||||
id: "a1b2c3d4-e5f6-4789-9abc-def012345678",
|
||||
name: "my-organization-super-long-experimental-mcp-server-with-many-features-v2",
|
||||
config: {
|
||||
command:
|
||||
|
|
@ -116,42 +97,37 @@ export const LongServerName: Story = {
|
|||
version: "1.0.0-beta.42",
|
||||
},
|
||||
connection: connected,
|
||||
connectionMode: "Subprocess",
|
||||
canTestClientFeatures: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const HttpDirect: Story = {
|
||||
args: {
|
||||
id: "9e8d7c6b-5a4f-4321-bdc6-fedcba098765",
|
||||
name: "Remote API Server",
|
||||
config: httpConfig,
|
||||
info: { name: "Remote API Server", version: "2.0.0" },
|
||||
connection: connected,
|
||||
connectionMode: "Direct",
|
||||
canTestClientFeatures: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const CompactConnected: Story = {
|
||||
args: {
|
||||
id: "7f3b2a8e-1c4d-4e5f-9a0b-3c2d1e4f5a6b",
|
||||
name: "My MCP Server",
|
||||
config: stdioConfig,
|
||||
info: { name: "My MCP Server", version: "1.2.0" },
|
||||
connection: connected,
|
||||
connectionMode: "Subprocess",
|
||||
canTestClientFeatures: false,
|
||||
compact: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const CompactDisconnected: Story = {
|
||||
args: {
|
||||
id: "7f3b2a8e-1c4d-4e5f-9a0b-3c2d1e4f5a6b",
|
||||
name: "My MCP Server",
|
||||
config: stdioConfig,
|
||||
info: { name: "My MCP Server", version: "1.2.0" },
|
||||
connection: disconnected,
|
||||
connectionMode: "Subprocess",
|
||||
canTestClientFeatures: false,
|
||||
compact: true,
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Badge, Button, Card, Group, Menu, Stack, Text } from "@mantine/core";
|
||||
import { Badge, Button, Card, Group, Stack, Text } from "@mantine/core";
|
||||
import type { Implementation } from "@modelcontextprotocol/sdk/types.js";
|
||||
import type {
|
||||
ConnectionState,
|
||||
|
|
@ -12,33 +12,33 @@ import { ContentViewer } from "../../elements/ContentViewer/ContentViewer";
|
|||
import { InlineError } from "../../elements/InlineError/InlineError";
|
||||
|
||||
export interface ServerCardProps {
|
||||
/** Stable unique identifier — the MCPConfig.mcpServers map key. */
|
||||
id: string;
|
||||
/** Display label shown in the card header. May or may not equal id. */
|
||||
name: string;
|
||||
config: MCPServerConfig;
|
||||
info?: Implementation;
|
||||
connection: ConnectionState;
|
||||
connectionMode: string;
|
||||
canTestClientFeatures: boolean;
|
||||
activeServer?: string;
|
||||
onSetActiveServer?: (name: string | undefined) => void;
|
||||
onToggleConnection: (connect: boolean) => void;
|
||||
onServerInfo: () => void;
|
||||
onSettings: () => void;
|
||||
onEdit: () => void;
|
||||
onClone: () => void;
|
||||
onRemove: () => void;
|
||||
onTestSampling?: () => void;
|
||||
onTestElicitationForm?: () => void;
|
||||
onTestElicitationUrl?: () => void;
|
||||
onConfigureRoots?: () => void;
|
||||
onToggleConnection: (id: string) => void;
|
||||
onServerInfo: (id: string) => void;
|
||||
onSettings: (id: string) => void;
|
||||
onEdit: (id: string) => void;
|
||||
onClone: (id: string) => void;
|
||||
onRemove: (id: string) => void;
|
||||
compact?: boolean;
|
||||
}
|
||||
|
||||
const HeaderLeft = Group.withProps({
|
||||
gap: "sm",
|
||||
wrap: "nowrap",
|
||||
miw: 0,
|
||||
flex: 1,
|
||||
});
|
||||
|
||||
const HeaderRight = Group.withProps({
|
||||
gap: "sm",
|
||||
wrap: "nowrap",
|
||||
});
|
||||
|
||||
const ActionsRow = Group.withProps({
|
||||
|
|
@ -48,6 +48,9 @@ const ActionsRow = Group.withProps({
|
|||
const ServerName = Text.withProps({
|
||||
fw: 600,
|
||||
size: "lg",
|
||||
truncate: "end",
|
||||
miw: 0,
|
||||
flex: 1,
|
||||
});
|
||||
|
||||
const ModeText = Text.withProps({
|
||||
|
|
@ -70,6 +73,12 @@ function getTransport(config: MCPServerConfig): ServerType {
|
|||
return config.type ?? "stdio";
|
||||
}
|
||||
|
||||
const TRANSPORT_DESCRIPTION: Record<ServerType, string> = {
|
||||
stdio: "Standard I/O",
|
||||
sse: "SSE (Server Sent Events) [deprecated]",
|
||||
"streamable-http": "Streamable HTTP",
|
||||
};
|
||||
|
||||
function getCommandOrUrl(config: MCPServerConfig): string {
|
||||
if (config.type === "sse" || config.type === "streamable-http") {
|
||||
return config.url;
|
||||
|
|
@ -78,27 +87,21 @@ function getCommandOrUrl(config: MCPServerConfig): string {
|
|||
}
|
||||
|
||||
export function ServerCard({
|
||||
id,
|
||||
name,
|
||||
config,
|
||||
info,
|
||||
connection,
|
||||
connectionMode,
|
||||
canTestClientFeatures,
|
||||
activeServer,
|
||||
onSetActiveServer,
|
||||
onToggleConnection,
|
||||
onServerInfo,
|
||||
onSettings,
|
||||
onEdit,
|
||||
onClone,
|
||||
onRemove,
|
||||
onTestSampling,
|
||||
onTestElicitationForm,
|
||||
onTestElicitationUrl,
|
||||
onConfigureRoots,
|
||||
compact = false,
|
||||
}: ServerCardProps) {
|
||||
const isDimmed = activeServer !== undefined && activeServer !== name;
|
||||
const isDimmed = activeServer !== undefined && activeServer !== id;
|
||||
const transport = getTransport(config);
|
||||
const commandOrUrl = getCommandOrUrl(config);
|
||||
const version = info?.version;
|
||||
|
|
@ -111,10 +114,9 @@ export function ServerCard({
|
|||
{...(isDimmed ? { "aria-disabled": true, inert: true } : {})}
|
||||
>
|
||||
<Stack gap="sm">
|
||||
<Group justify="space-between" wrap="wrap">
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
<HeaderLeft>
|
||||
<ServerName>{name}</ServerName>
|
||||
{version && <Badge variant="outline">{version}</Badge>}
|
||||
</HeaderLeft>
|
||||
<HeaderRight>
|
||||
<ServerStatusIndicator
|
||||
|
|
@ -124,46 +126,17 @@ export function ServerCard({
|
|||
<ConnectionToggle
|
||||
status={connection.status}
|
||||
disabled={isDimmed}
|
||||
onConnect={() => {
|
||||
onSetActiveServer?.(name);
|
||||
onToggleConnection(true);
|
||||
}}
|
||||
onDisconnect={() => {
|
||||
onSetActiveServer?.(undefined);
|
||||
onToggleConnection(false);
|
||||
}}
|
||||
onToggle={() => onToggleConnection(id)}
|
||||
/>
|
||||
</HeaderRight>
|
||||
</Group>
|
||||
|
||||
{!compact && (
|
||||
<>
|
||||
<Group justify="space-between" mih={30}>
|
||||
<Group gap="sm">
|
||||
<TransportBadge transport={transport} />
|
||||
<ModeText>{connectionMode}</ModeText>
|
||||
</Group>
|
||||
{canTestClientFeatures && (
|
||||
<Menu>
|
||||
<Menu.Target>
|
||||
<SubtleButton>Test Client Features ▾</SubtleButton>
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown>
|
||||
<Menu.Item onClick={onTestSampling}>
|
||||
Simulate Sampling Request
|
||||
</Menu.Item>
|
||||
<Menu.Item onClick={onTestElicitationForm}>
|
||||
Simulate Elicitation (Form)
|
||||
</Menu.Item>
|
||||
<Menu.Item onClick={onTestElicitationUrl}>
|
||||
Simulate Elicitation (URL)
|
||||
</Menu.Item>
|
||||
<Menu.Item onClick={onConfigureRoots}>
|
||||
Configure Roots
|
||||
</Menu.Item>
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
)}
|
||||
<Group gap="sm" mih={30}>
|
||||
{version && <Badge variant="outline">{version}</Badge>}
|
||||
<TransportBadge transport={transport} />
|
||||
<ModeText>{TRANSPORT_DESCRIPTION[transport]}</ModeText>
|
||||
</Group>
|
||||
|
||||
<ContentViewer
|
||||
|
|
@ -183,13 +156,17 @@ export function ServerCard({
|
|||
|
||||
<Group justify="space-between">
|
||||
<ActionsRow>
|
||||
<SubtleButton onClick={onClone}>Clone</SubtleButton>
|
||||
<SubtleButton onClick={onEdit}>Edit</SubtleButton>
|
||||
<RemoveButton onClick={onRemove}>Remove</RemoveButton>
|
||||
<SubtleButton onClick={() => onClone(id)}>Clone</SubtleButton>
|
||||
<SubtleButton onClick={() => onEdit(id)}>Edit</SubtleButton>
|
||||
<RemoveButton onClick={() => onRemove(id)}>Remove</RemoveButton>
|
||||
</ActionsRow>
|
||||
<ActionsRow>
|
||||
<SubtleButton onClick={onServerInfo}>Server Info</SubtleButton>
|
||||
<SubtleButton onClick={onSettings}>Settings</SubtleButton>
|
||||
<SubtleButton onClick={() => onServerInfo(id)}>
|
||||
Server Info
|
||||
</SubtleButton>
|
||||
<SubtleButton onClick={() => onSettings(id)}>
|
||||
Settings
|
||||
</SubtleButton>
|
||||
</ActionsRow>
|
||||
</Group>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ export function ServerInfoContent({
|
|||
<ValueText>{serverInfo.name}</ValueText>
|
||||
|
||||
<Text size="sm">Version</Text>
|
||||
<ValueText>{serverInfo.version}</ValueText>
|
||||
<ValueText>{serverInfo.version ?? "—"}</ValueText>
|
||||
|
||||
<Text size="sm">Protocol</Text>
|
||||
<ValueText>{protocolVersion}</ValueText>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import {
|
|||
} from "./ServerSettingsForm";
|
||||
|
||||
const defaultSettings: InspectorServerSettings = {
|
||||
connectionMode: "proxy",
|
||||
headers: [],
|
||||
metadata: [],
|
||||
connectionTimeout: 30000,
|
||||
|
|
@ -25,12 +24,6 @@ function InteractiveRender(args: ServerSettingsFormProps) {
|
|||
args.onExpandedSectionsChange(sections);
|
||||
updateArgs({ expandedSections: sections });
|
||||
}}
|
||||
onConnectionModeChange={(mode) => {
|
||||
args.onConnectionModeChange(mode);
|
||||
updateArgs({
|
||||
settings: { ...args.settings, connectionMode: mode },
|
||||
});
|
||||
}}
|
||||
onAddHeader={() => {
|
||||
args.onAddHeader();
|
||||
updateArgs({
|
||||
|
|
@ -115,9 +108,8 @@ const meta: Meta<typeof ServerSettingsForm> = {
|
|||
component: ServerSettingsForm,
|
||||
render: InteractiveRender,
|
||||
args: {
|
||||
expandedSections: ["connectionMode"],
|
||||
expandedSections: ["headers"],
|
||||
onExpandedSectionsChange: fn(),
|
||||
onConnectionModeChange: fn(),
|
||||
onAddHeader: fn(),
|
||||
onRemoveHeader: fn(),
|
||||
onHeaderChange: fn(),
|
||||
|
|
@ -154,7 +146,6 @@ export const WithOAuth: Story = {
|
|||
args: {
|
||||
settings: {
|
||||
...defaultSettings,
|
||||
connectionMode: "direct",
|
||||
oauthClientId: "my-client-id",
|
||||
oauthClientSecret: "super-secret-value",
|
||||
oauthScopes: "read write admin",
|
||||
|
|
@ -165,7 +156,6 @@ export const WithOAuth: Story = {
|
|||
export const AllConfigured: Story = {
|
||||
args: {
|
||||
settings: {
|
||||
connectionMode: "proxy",
|
||||
headers: [
|
||||
{ key: "Authorization", value: "Bearer token-abc-123" },
|
||||
{ key: "X-Request-Id", value: "req-456" },
|
||||
|
|
|
|||
|
|
@ -4,15 +4,16 @@ import {
|
|||
Button,
|
||||
Group,
|
||||
NumberInput,
|
||||
Select,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
} from "@mantine/core";
|
||||
import type { InspectorServerSettings } from "@inspector/core/mcp/types.js";
|
||||
import type {
|
||||
InspectorServerSettings,
|
||||
OAuthSettings,
|
||||
} from "@inspector/core/mcp/types.js";
|
||||
|
||||
export type ServerSettingsSection =
|
||||
| "connectionMode"
|
||||
| "headers"
|
||||
| "metadata"
|
||||
| "timeouts"
|
||||
|
|
@ -22,28 +23,19 @@ export interface ServerSettingsFormProps {
|
|||
settings: InspectorServerSettings;
|
||||
expandedSections: ServerSettingsSection[];
|
||||
onExpandedSectionsChange: (sections: ServerSettingsSection[]) => void;
|
||||
onConnectionModeChange: (mode: "proxy" | "direct") => void;
|
||||
onAddHeader: () => void;
|
||||
onRemoveHeader: (index: number) => void;
|
||||
onHeaderChange: (index: number, key: string, value: string) => void;
|
||||
onAddMetadata: () => void;
|
||||
onRemoveMetadata: (index: number) => void;
|
||||
onMetadataChange: (index: number, key: string, value: string) => void;
|
||||
onTimeoutChange: (field: string, value: number) => void;
|
||||
onTimeoutChange: (
|
||||
field: "connectionTimeout" | "requestTimeout",
|
||||
value: number,
|
||||
) => void;
|
||||
onOAuthChange: (oauth: OAuthSettings) => void;
|
||||
}
|
||||
|
||||
export interface OAuthSettings {
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
scopes: string;
|
||||
}
|
||||
|
||||
const CONNECTION_MODE_OPTIONS = [
|
||||
{ value: "proxy", label: "Via Proxy" },
|
||||
{ value: "direct", label: "Direct" },
|
||||
];
|
||||
|
||||
const RemoveIcon = ActionIcon.withProps({
|
||||
color: "red",
|
||||
variant: "subtle",
|
||||
|
|
@ -103,7 +95,6 @@ export function ServerSettingsForm({
|
|||
settings,
|
||||
expandedSections,
|
||||
onExpandedSectionsChange,
|
||||
onConnectionModeChange,
|
||||
onAddHeader,
|
||||
onRemoveHeader,
|
||||
onHeaderChange,
|
||||
|
|
@ -113,11 +104,13 @@ export function ServerSettingsForm({
|
|||
onTimeoutChange,
|
||||
onOAuthChange,
|
||||
}: ServerSettingsFormProps) {
|
||||
const handleTimeoutChange = (field: string) => (value: number | string) => {
|
||||
const numValue =
|
||||
typeof value === "string" ? parseInt(value, 10) || 0 : value;
|
||||
onTimeoutChange(field, numValue);
|
||||
};
|
||||
const handleTimeoutChange =
|
||||
(field: "connectionTimeout" | "requestTimeout") =>
|
||||
(value: number | string) => {
|
||||
const numValue =
|
||||
typeof value === "string" ? parseInt(value, 10) || 0 : value;
|
||||
onTimeoutChange(field, numValue);
|
||||
};
|
||||
|
||||
function currentOAuth(): OAuthSettings {
|
||||
return {
|
||||
|
|
@ -136,24 +129,6 @@ export function ServerSettingsForm({
|
|||
}
|
||||
variant="separated"
|
||||
>
|
||||
<Accordion.Item value="connectionMode">
|
||||
<Accordion.Control>Connection Mode</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
<Select
|
||||
data={CONNECTION_MODE_OPTIONS}
|
||||
value={settings.connectionMode}
|
||||
onChange={(value) => {
|
||||
if (value) onConnectionModeChange(value as "proxy" | "direct");
|
||||
}}
|
||||
description={
|
||||
settings.connectionMode === "proxy"
|
||||
? "Route through inspector proxy (required for STDIO)"
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
|
||||
<Accordion.Item value="headers">
|
||||
<Accordion.Control>Custom Headers</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import {
|
|||
} from "./ServerSettingsModal";
|
||||
|
||||
const initialSettings: InspectorServerSettings = {
|
||||
connectionMode: "proxy",
|
||||
headers: [
|
||||
{ key: "Authorization", value: "Bearer token-abc-123" },
|
||||
{ key: "X-Request-Id", value: "req-456" },
|
||||
|
|
@ -64,7 +63,6 @@ export const FullyConfigured: Story = {
|
|||
export const EmptySettings: Story = {
|
||||
args: {
|
||||
settings: {
|
||||
connectionMode: "proxy",
|
||||
headers: [],
|
||||
metadata: [],
|
||||
connectionTimeout: 30000,
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
import { useState } from "react";
|
||||
import { CloseButton, Group, Modal, Title } from "@mantine/core";
|
||||
import type { InspectorServerSettings } from "@inspector/core/mcp/types.js";
|
||||
import { CloseButton, Group, Modal, Stack, Title } from "@mantine/core";
|
||||
import type {
|
||||
InspectorServerSettings,
|
||||
OAuthSettings,
|
||||
} from "@inspector/core/mcp/types.js";
|
||||
import { ListToggle } from "../../elements/ListToggle/ListToggle";
|
||||
import {
|
||||
ServerSettingsForm,
|
||||
type ServerSettingsSection,
|
||||
type OAuthSettings,
|
||||
} from "../ServerSettingsForm/ServerSettingsForm";
|
||||
|
||||
const ALL_SECTIONS: ServerSettingsSection[] = [
|
||||
"connectionMode",
|
||||
"headers",
|
||||
"metadata",
|
||||
"timeouts",
|
||||
|
|
@ -31,7 +32,7 @@ export function ServerSettingsModal({
|
|||
}: ServerSettingsModalProps) {
|
||||
const [expandedSections, setExpandedSections] = useState<
|
||||
ServerSettingsSection[]
|
||||
>(["connectionMode"]);
|
||||
>(["headers"]);
|
||||
|
||||
const allExpanded = expandedSections.length === ALL_SECTIONS.length;
|
||||
|
||||
|
|
@ -39,10 +40,6 @@ export function ServerSettingsModal({
|
|||
setExpandedSections(allExpanded ? [] : ALL_SECTIONS);
|
||||
}
|
||||
|
||||
function handleConnectionModeChange(mode: "proxy" | "direct") {
|
||||
onSettingsChange({ ...settings, connectionMode: mode });
|
||||
}
|
||||
|
||||
function handleAddHeader() {
|
||||
onSettingsChange({
|
||||
...settings,
|
||||
|
|
@ -85,7 +82,10 @@ export function ServerSettingsModal({
|
|||
onSettingsChange({ ...settings, metadata });
|
||||
}
|
||||
|
||||
function handleTimeoutChange(field: string, value: number) {
|
||||
function handleTimeoutChange(
|
||||
field: "connectionTimeout" | "requestTimeout",
|
||||
value: number,
|
||||
) {
|
||||
onSettingsChange({ ...settings, [field]: value });
|
||||
}
|
||||
|
||||
|
|
@ -103,37 +103,35 @@ export function ServerSettingsModal({
|
|||
opened={opened}
|
||||
onClose={onClose}
|
||||
withCloseButton={false}
|
||||
title={
|
||||
<Group justify="space-between" wrap="nowrap" w="100%">
|
||||
size="lg"
|
||||
centered
|
||||
>
|
||||
<Stack gap="md">
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
<ListToggle
|
||||
compact={allExpanded}
|
||||
variant="subtle"
|
||||
onToggle={handleToggleAll}
|
||||
/>
|
||||
<Title order={4} ta="center" style={{ flex: 1 }}>
|
||||
<Title order={4} ta="center" flex={1}>
|
||||
Server Settings
|
||||
</Title>
|
||||
<CloseButton onClick={onClose} />
|
||||
</Group>
|
||||
}
|
||||
size="lg"
|
||||
centered
|
||||
styles={{ title: { flex: 1 } }}
|
||||
>
|
||||
<ServerSettingsForm
|
||||
settings={settings}
|
||||
expandedSections={expandedSections}
|
||||
onExpandedSectionsChange={setExpandedSections}
|
||||
onConnectionModeChange={handleConnectionModeChange}
|
||||
onAddHeader={handleAddHeader}
|
||||
onRemoveHeader={handleRemoveHeader}
|
||||
onHeaderChange={handleHeaderChange}
|
||||
onAddMetadata={handleAddMetadata}
|
||||
onRemoveMetadata={handleRemoveMetadata}
|
||||
onMetadataChange={handleMetadataChange}
|
||||
onTimeoutChange={handleTimeoutChange}
|
||||
onOAuthChange={handleOAuthChange}
|
||||
/>
|
||||
<ServerSettingsForm
|
||||
settings={settings}
|
||||
expandedSections={expandedSections}
|
||||
onExpandedSectionsChange={setExpandedSections}
|
||||
onAddHeader={handleAddHeader}
|
||||
onRemoveHeader={handleRemoveHeader}
|
||||
onHeaderChange={handleHeaderChange}
|
||||
onAddMetadata={handleAddMetadata}
|
||||
onRemoveMetadata={handleRemoveMetadata}
|
||||
onMetadataChange={handleMetadataChange}
|
||||
onTimeoutChange={handleTimeoutChange}
|
||||
onOAuthChange={handleOAuthChange}
|
||||
/>
|
||||
</Stack>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { fn } from "storybook/test";
|
||||
import { ServerListScreen } from "./ServerListScreen";
|
||||
import type { ServerCardProps } from "../../groups/ServerCard/ServerCard";
|
||||
import { ServerListScreen, type ServerEntry } from "./ServerListScreen";
|
||||
|
||||
const meta: Meta<typeof ServerListScreen> = {
|
||||
title: "Screens/ServerListScreen",
|
||||
|
|
@ -11,64 +10,40 @@ const meta: Meta<typeof ServerListScreen> = {
|
|||
onAddManually: fn(),
|
||||
onImportConfig: fn(),
|
||||
onImportServerJson: fn(),
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof ServerListScreen>;
|
||||
|
||||
function makeServerCallbacks(): Pick<
|
||||
ServerCardProps,
|
||||
| "onToggleConnection"
|
||||
| "onServerInfo"
|
||||
| "onSettings"
|
||||
| "onEdit"
|
||||
| "onClone"
|
||||
| "onRemove"
|
||||
| "onTestSampling"
|
||||
| "onTestElicitationForm"
|
||||
| "onTestElicitationUrl"
|
||||
| "onConfigureRoots"
|
||||
> {
|
||||
return {
|
||||
onToggleConnection: fn(),
|
||||
onServerInfo: fn(),
|
||||
onSettings: fn(),
|
||||
onEdit: fn(),
|
||||
onClone: fn(),
|
||||
onRemove: fn(),
|
||||
onTestSampling: fn(),
|
||||
onTestElicitationForm: fn(),
|
||||
onTestElicitationUrl: fn(),
|
||||
onConfigureRoots: fn(),
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const connectedStdioServer: ServerCardProps = {
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof ServerListScreen>;
|
||||
|
||||
const connectedStdioServer: ServerEntry = {
|
||||
id: "5e8c3d1f-2a4b-4c6d-8e7f-1a2b3c4d5e6f",
|
||||
name: "Local Dev Server",
|
||||
config: {
|
||||
command: "npx @modelcontextprotocol/server-filesystem /home/user/projects",
|
||||
},
|
||||
info: { name: "Local Dev Server", version: "1.2.0" },
|
||||
connection: { status: "connected" },
|
||||
connectionMode: "Direct",
|
||||
canTestClientFeatures: true,
|
||||
...makeServerCallbacks(),
|
||||
};
|
||||
|
||||
const disconnectedStdioServer: ServerCardProps = {
|
||||
const disconnectedStdioServer: ServerEntry = {
|
||||
id: "b3a7c1d2-9f8e-4a5b-bc6d-7e8f9a0b1c2d",
|
||||
name: "Database Tools",
|
||||
config: {
|
||||
command: "python -m mcp_server_sqlite --db-path ./data.db",
|
||||
},
|
||||
info: { name: "Database Tools", version: "0.9.1" },
|
||||
connection: { status: "disconnected" },
|
||||
connectionMode: "Direct",
|
||||
canTestClientFeatures: false,
|
||||
...makeServerCallbacks(),
|
||||
};
|
||||
|
||||
const failedHttpServer: ServerCardProps = {
|
||||
const failedHttpServer: ServerEntry = {
|
||||
id: "c4d5e6f7-8a9b-4c0d-9e1f-2a3b4c5d6e7f",
|
||||
name: "Remote API Server",
|
||||
config: {
|
||||
type: "streamable-http",
|
||||
|
|
@ -83,21 +58,16 @@ const failedHttpServer: ServerCardProps = {
|
|||
details: "ECONNREFUSED 127.0.0.1:8080 - The server may not be running.",
|
||||
},
|
||||
},
|
||||
connectionMode: "Streamable HTTP",
|
||||
canTestClientFeatures: false,
|
||||
...makeServerCallbacks(),
|
||||
};
|
||||
|
||||
const connectingHttpServer: ServerCardProps = {
|
||||
const connectingHttpServer: ServerEntry = {
|
||||
id: "d6e7f8a9-0b1c-4d2e-bf3a-4b5c6d7e8f90",
|
||||
name: "Staging Server",
|
||||
config: {
|
||||
type: "streamable-http",
|
||||
url: "https://staging.example.com/mcp",
|
||||
},
|
||||
connection: { status: "connecting" },
|
||||
connectionMode: "Streamable HTTP",
|
||||
canTestClientFeatures: false,
|
||||
...makeServerCallbacks(),
|
||||
};
|
||||
|
||||
export const MultipleServers: Story = {
|
||||
|
|
|
|||
|
|
@ -4,11 +4,24 @@ import { ServerCard } from "../../groups/ServerCard/ServerCard";
|
|||
import { ServerListControls } from "../../groups/ServerListControls/ServerListControls";
|
||||
import type { ServerCardProps } from "../../groups/ServerCard/ServerCard";
|
||||
|
||||
export type ServerEntry = Pick<
|
||||
ServerCardProps,
|
||||
"id" | "name" | "config" | "info" | "connection"
|
||||
>;
|
||||
|
||||
export interface ServerListScreenProps {
|
||||
servers: ServerCardProps[];
|
||||
servers: ServerEntry[];
|
||||
/** Id of the server the wiring layer treats as active (drives card dimming). */
|
||||
activeServer?: string;
|
||||
onAddManually: () => void;
|
||||
onImportConfig: () => void;
|
||||
onImportServerJson: () => void;
|
||||
onToggleConnection: (id: string) => void;
|
||||
onServerInfo: (id: string) => void;
|
||||
onSettings: (id: string) => void;
|
||||
onEdit: (id: string) => void;
|
||||
onClone: (id: string) => void;
|
||||
onRemove: (id: string) => void;
|
||||
}
|
||||
|
||||
const PageContainer = Stack.withProps({
|
||||
|
|
@ -23,12 +36,18 @@ const EmptyState = Text.withProps({
|
|||
|
||||
export function ServerListScreen({
|
||||
servers,
|
||||
activeServer,
|
||||
onAddManually,
|
||||
onImportConfig,
|
||||
onImportServerJson,
|
||||
onToggleConnection,
|
||||
onServerInfo,
|
||||
onSettings,
|
||||
onEdit,
|
||||
onClone,
|
||||
onRemove,
|
||||
}: ServerListScreenProps) {
|
||||
const [compact, setCompact] = useState<boolean>(false);
|
||||
const [activeServer, setActiveServer] = useState<string | undefined>();
|
||||
|
||||
function handleToggleList() {
|
||||
setCompact((prev) => !prev);
|
||||
|
|
@ -58,10 +77,15 @@ export function ServerListScreen({
|
|||
>
|
||||
{servers.map((server) => (
|
||||
<ServerCard
|
||||
key={server.name}
|
||||
key={server.id}
|
||||
compact={compact}
|
||||
activeServer={activeServer}
|
||||
onSetActiveServer={setActiveServer}
|
||||
onToggleConnection={onToggleConnection}
|
||||
onServerInfo={onServerInfo}
|
||||
onSettings={onSettings}
|
||||
onEdit={onEdit}
|
||||
onClone={onClone}
|
||||
onRemove={onRemove}
|
||||
{...server}
|
||||
/>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,11 @@ import type { InspectorServerSettings } from "@inspector/core/mcp/types.js";
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { fn } from "storybook/test";
|
||||
import { UnconnectedView } from "./UnconnectedView.js";
|
||||
import { ServerListScreen } from "../../screens/ServerListScreen/ServerListScreen";
|
||||
import {
|
||||
ServerListScreen,
|
||||
type ServerEntry,
|
||||
} from "../../screens/ServerListScreen/ServerListScreen";
|
||||
import { ServerSettingsModal } from "../../groups/ServerSettingsModal/ServerSettingsModal";
|
||||
import type { ServerCardProps } from "../../groups/ServerCard/ServerCard";
|
||||
|
||||
const meta: Meta<typeof UnconnectedView> = {
|
||||
title: "Views/UnconnectedView",
|
||||
|
|
@ -19,38 +21,29 @@ const meta: Meta<typeof UnconnectedView> = {
|
|||
export default meta;
|
||||
type Story = StoryObj<typeof UnconnectedView>;
|
||||
|
||||
function makeServerCallbacks(): Pick<
|
||||
ServerCardProps,
|
||||
| "onToggleConnection"
|
||||
| "onServerInfo"
|
||||
| "onSettings"
|
||||
| "onEdit"
|
||||
| "onClone"
|
||||
| "onRemove"
|
||||
> {
|
||||
return {
|
||||
onToggleConnection: fn(),
|
||||
onServerInfo: fn(),
|
||||
onSettings: fn(),
|
||||
onEdit: fn(),
|
||||
onClone: fn(),
|
||||
onRemove: fn(),
|
||||
};
|
||||
}
|
||||
const sharedScreenCallbacks = {
|
||||
onAddManually: fn(),
|
||||
onImportConfig: fn(),
|
||||
onImportServerJson: fn(),
|
||||
onToggleConnection: fn(),
|
||||
onServerInfo: fn(),
|
||||
onSettings: fn(),
|
||||
onEdit: fn(),
|
||||
onClone: fn(),
|
||||
onRemove: fn(),
|
||||
};
|
||||
|
||||
function makeStdioServer(
|
||||
name: string,
|
||||
version: string,
|
||||
command: string,
|
||||
): ServerCardProps {
|
||||
): ServerEntry {
|
||||
return {
|
||||
id: crypto.randomUUID(),
|
||||
name,
|
||||
config: { command },
|
||||
info: { name, version },
|
||||
connection: { status: "disconnected" },
|
||||
connectionMode: "Via Proxy",
|
||||
canTestClientFeatures: false,
|
||||
...makeServerCallbacks(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -58,28 +51,19 @@ function makeHttpServer(
|
|||
name: string,
|
||||
version: string,
|
||||
url: string,
|
||||
): ServerCardProps {
|
||||
): ServerEntry {
|
||||
return {
|
||||
id: crypto.randomUUID(),
|
||||
name,
|
||||
config: { type: "streamable-http", url },
|
||||
info: { name, version },
|
||||
connection: { status: "disconnected" },
|
||||
connectionMode: "Direct",
|
||||
canTestClientFeatures: false,
|
||||
...makeServerCallbacks(),
|
||||
};
|
||||
}
|
||||
|
||||
export const Empty: Story = {
|
||||
args: {
|
||||
children: (
|
||||
<ServerListScreen
|
||||
servers={[]}
|
||||
onAddManually={fn()}
|
||||
onImportConfig={fn()}
|
||||
onImportServerJson={fn()}
|
||||
/>
|
||||
),
|
||||
children: <ServerListScreen servers={[]} {...sharedScreenCallbacks} />,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -104,9 +88,7 @@ export const WithServers: Story = {
|
|||
"https://api.example.com/mcp",
|
||||
),
|
||||
]}
|
||||
onAddManually={fn()}
|
||||
onImportConfig={fn()}
|
||||
onImportServerJson={fn()}
|
||||
{...sharedScreenCallbacks}
|
||||
/>
|
||||
),
|
||||
},
|
||||
|
|
@ -114,7 +96,6 @@ export const WithServers: Story = {
|
|||
|
||||
function SettingsModalStory() {
|
||||
const [settings, setSettings] = useState<InspectorServerSettings>({
|
||||
connectionMode: "proxy",
|
||||
headers: [
|
||||
{ key: "Authorization", value: "Bearer token-abc-123" },
|
||||
{ key: "X-Request-Id", value: "req-456" },
|
||||
|
|
@ -137,9 +118,7 @@ function SettingsModalStory() {
|
|||
"npx -y @modelcontextprotocol/server-everything",
|
||||
),
|
||||
]}
|
||||
onAddManually={fn()}
|
||||
onImportConfig={fn()}
|
||||
onImportServerJson={fn()}
|
||||
{...sharedScreenCallbacks}
|
||||
/>
|
||||
<ServerSettingsModal
|
||||
opened
|
||||
|
|
@ -221,9 +200,7 @@ export const ManyServers: Story = {
|
|||
"npx -y @modelcontextprotocol/server-sequential-thinking",
|
||||
),
|
||||
]}
|
||||
onAddManually={fn()}
|
||||
onImportConfig={fn()}
|
||||
onImportServerJson={fn()}
|
||||
{...sharedScreenCallbacks}
|
||||
/>
|
||||
),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -183,13 +183,23 @@ export interface InspectorResourceSubscription {
|
|||
lastUpdated?: Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* OAuth credentials surfaced by the settings form. The form callback
|
||||
* passes this whole object so callers don't have to thread per-field
|
||||
* dispatches through stringly-typed key arguments.
|
||||
*/
|
||||
export interface OAuthSettings {
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
scopes: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtime settings for a configured server. A subset of
|
||||
* InspectorClientOptions (v1.5) relevant to the settings form:
|
||||
* connection mode, headers, metadata, timeouts, and OAuth credentials.
|
||||
* headers, metadata, timeouts, and OAuth credentials.
|
||||
*/
|
||||
export interface InspectorServerSettings {
|
||||
connectionMode: "proxy" | "direct";
|
||||
headers: { key: string; value: string }[];
|
||||
metadata: { key: string; value: string }[];
|
||||
connectionTimeout: number;
|
||||
|
|
|
|||
|
|
@ -49,10 +49,6 @@
|
|||
* SSE
|
||||
* SHTTP
|
||||
|
||||
## Connection Type
|
||||
* Direct
|
||||
* Via Proxy
|
||||
|
||||
## Logging Level Control
|
||||
* Present and synchronized when connecting to server with logging capability
|
||||
|
||||
|
|
|
|||
|
|
@ -81,11 +81,6 @@ Request history with replay and pin functionality.
|
|||
|
||||
## Client Feature Handlers
|
||||
|
||||
### Test Client Features Dropdown
|
||||
Menu for testing client capabilities (Sampling, Elicitation, Roots).
|
||||
|
||||

|
||||
|
||||
### Sampling Panel
|
||||
Handler for `sampling/createMessage` requests with mock response input.
|
||||
|
||||
|
|
|
|||
|
|
@ -128,9 +128,9 @@ For each component, the plan records:
|
|||
- **Purpose** — Large on/off switch that connects or disconnects a configured server.
|
||||
- **Current props** — `checked: boolean`; `loading: boolean`; `disabled: boolean`; `onChange: (checked: boolean) => void`.
|
||||
- **MCP schema touch points** — None — purely presentational, but `checked`/`loading` are derived from `ConnectionStatus` application state (not MCP schema).
|
||||
- **Target props** — `status: ConnectionStatus` (core-owned enum: `"disconnected" | "connecting" | "connected" | "failed"`); `disabled?: boolean`; `onConnect: () => void`; `onDisconnect: () => void`.
|
||||
- **Callbacks → core hook** — `onConnect`, `onDisconnect` → `useConnection` (v1.5 `useConnection` exposes `connect`/`disconnect`).
|
||||
- **Internal refactors** — Derive `checked`/`loading` from `status` internally; split the single `onChange` into explicit `onConnect`/`onDisconnect` so the wiring layer can pass hook callbacks directly without a boolean adapter.
|
||||
- **Target props** — `status: ConnectionStatus` (core-owned enum: `"disconnected" | "connecting" | "connected" | "error"`); `disabled?: boolean`; `onToggle: () => void`.
|
||||
- **Callbacks → core hook** — `onToggle` → wiring layer reads the current `connection.status` for the owning server and dispatches `useConnection`'s `connect` or `disconnect` (v1.5 `useConnection` exposes both).
|
||||
- **Internal refactors** — Derive `checked`/`loading` from `status` internally; collapse `onChange(boolean)` to a single `onToggle()` — the wiring layer already has the connection state and decides connect vs disconnect, so a boolean from the toggle adds no information.
|
||||
|
||||
### ContentViewer
|
||||
|
||||
|
|
@ -238,9 +238,9 @@ For each component, the plan records:
|
|||
- **Purpose** — Toggle button to subscribe/unsubscribe from a single MCP resource.
|
||||
- **Current props** — `subscribed: boolean`; `onToggle: () => void`.
|
||||
- **MCP schema touch points** — `SubscribeRequest.params.uri` and `UnsubscribeRequest.params.uri` — but the URI is held by the parent row; the button itself only needs the current subscription boolean.
|
||||
- **Target props** — `subscribed: boolean`; `onSubscribe: () => void`; `onUnsubscribe: () => void` (split for parity with `ConnectionToggle`). Alternatively keep a single `onToggle` if the wiring layer prefers that.
|
||||
- **Callbacks → core hook** — `onSubscribe` / `onUnsubscribe` → `useInspectorClient` (wraps `client.subscribeResource` / `client.unsubscribeResource`); `subscribed` derived from a core-owned subscription set.
|
||||
- **Internal refactors** — Optional split of `onToggle` into the two explicit callbacks; otherwise none.
|
||||
- **Target props** — `subscribed: boolean`; `onToggle: () => void`. (`ConnectionToggle` settled on the same single-callback shape; the wiring layer reads `subscribed` and dispatches subscribe vs unsubscribe.)
|
||||
- **Callbacks → core hook** — `onToggle` → wiring layer dispatches `useInspectorClient`'s `client.subscribeResource` or `client.unsubscribeResource` based on the current `subscribed` value; `subscribed` derived from a core-owned subscription set.
|
||||
- **Internal refactors** — None.
|
||||
|
||||
### TaskStatusBadge
|
||||
|
||||
|
|
@ -528,10 +528,10 @@ For each component, the plan records:
|
|||
|
||||
- **Location**: `groups/ServerCard/`
|
||||
- **Purpose**: Card for a configured server with status, transport, connection toggle, actions, and test-client-features menu.
|
||||
- **Current props**: `name`, `version?`, `transport`, `connectionMode`, `command`, `status`, `retryCount?`, `error?`, `canTestClientFeatures`, `activeServer?`, many handlers.
|
||||
- **MCP schema touch points**: `Implementation` (server `name`, `version`) from `InitializeResult.serverInfo`. Transport/command/mode/status are Inspector-core-owned (connection lifecycle).
|
||||
- **Target props**: `config: InspectorServerConfig`, `info?: Implementation`, `connection: InspectorConnectionState` (`status`, `retryCount`, `error`), `canTestClientFeatures`, `activeServer?`, handlers unchanged.
|
||||
- **Callbacks → core hook**: `onToggleConnection`/`onSetActiveServer` from `useConnection`; `onEdit`/`onClone`/`onRemove` from `useServers`; test handlers from `useSampling`/`useElicitation`/`useRoots`.
|
||||
- **Current props**: `id`, `name`, `version?`, `transport`, `command`, `status`, `retryCount?`, `error?`, `activeServer?`, action handlers. (The grey transport descriptor next to the `TransportBadge` is derived from `config.type` — "Standard I/O", "SSE (Server Sent Events) [deprecated]", or "Streamable HTTP" — not a separate `connectionMode` prop. The "Test Client Features" dropdown was removed; sampling/elicitation/roots simulators live elsewhere.)
|
||||
- **MCP schema touch points**: `Implementation` (server `name`, `version`) from `InitializeResult.serverInfo`. Transport/command/status are Inspector-core-owned (connection lifecycle).
|
||||
- **Target props**: `id: string` (the `MCPConfig.mcpServers` map key), `name: string` (display label), `config: InspectorServerConfig`, `info?: Implementation`, `connection: InspectorConnectionState` (`status`, `retryCount`, `error`), `activeServer?` (read-only — set by the wiring layer), action handlers all keyed by `id`.
|
||||
- **Callbacks → core hook**: `onToggleConnection(id)` from `useConnection` — the wiring layer reads the current `connection.status` for that id and dispatches connect or disconnect (no separate `onSetActiveServer`; active state is owned by the wiring layer); `onEdit`/`onClone`/`onRemove` from `useServers`.
|
||||
- **Internal refactors**: Replace flat scalar props with grouped objects; **move the auto-connect `useEffect` out** — dumb components must not self-dispatch side effects.
|
||||
|
||||
### ServerInfoContent
|
||||
|
|
@ -557,8 +557,8 @@ For each component, the plan records:
|
|||
### ServerSettingsForm
|
||||
|
||||
- **Location**: `groups/ServerSettingsForm/`
|
||||
- **Purpose**: Per-server settings — connection mode, headers, metadata, timeouts, OAuth.
|
||||
- **Current props**: `connectionMode`, `headers`, `metadata`, `connectionTimeout`, `requestTimeout`, `oauthClient*`, many handlers.
|
||||
- **Purpose**: Per-server settings — headers, metadata, timeouts, OAuth. (The connection-mode "Via Proxy"/"Direct" toggle was removed; the inspector picks the right transport from `MCPServerConfig.type`.)
|
||||
- **Current props**: `headers`, `metadata`, `connectionTimeout`, `requestTimeout`, `oauthClient*`, many handlers.
|
||||
- **MCP schema touch points**: `Request._meta` passthrough is the only schema-level tie-in; the rest is Inspector-core-owned.
|
||||
- **Target props**: `settings: InspectorServerSettings`, `onSettingsChange(settings)` (or keep granular setters).
|
||||
- **Callbacks → core hook**: `useServers` (v2-only — no v1.5 analog).
|
||||
|
|
@ -761,13 +761,13 @@ themselves — every callback passed down must originate in a core hook.
|
|||
- **Current props**: `servers: ServerCardProps[]`, `onAddManually`, `onImportConfig`, `onImportServerJson`.
|
||||
- **MCP schema touch points**: `Implementation` (server `name`/`version`), `ServerCapabilities`, `InitializeResult` — identity and capability surface reported on initialize. Transport config (`command`, `args`, `env`, `url`, `headers`) is **not** MCP schema; it is Inspector core app state.
|
||||
- **Target props**:
|
||||
- `servers: ServerEntry[]` where `ServerEntry = { id: string; config: ServerTransportConfig; connection: { status: 'disconnected' | 'connecting' | 'connected' | 'error'; error?: string; initializeResult?: InitializeResult } }`.
|
||||
- `activeServerId?: string` — may be lifted to core if it drives routing; currently screen-local.
|
||||
- `onAddManually`, `onImportConfig`, `onImportServerJson`, `onConnect(id)`, `onDisconnect(id)`, `onSetActiveServer(id)`, `onEditServer(id)`, `onRemoveServer(id)`.
|
||||
- `servers: ServerEntry[]` where `ServerEntry = { id: string; name: string; config: ServerTransportConfig; connection: { status: 'disconnected' | 'connecting' | 'connected' | 'error'; error?: string; initializeResult?: InitializeResult } }`.
|
||||
- `activeServer?: string` — pass-through from the wiring layer; drives card dimming. **Not** local screen state.
|
||||
- `onAddManually`, `onImportConfig`, `onImportServerJson`, `onToggleConnection(id)`, `onEditServer(id)`, `onRemoveServer(id)` — the toggle is consolidated; the wiring layer reads current connection state for that id and decides whether to connect or disconnect.
|
||||
- **Callbacks → core hook**: `useServers` (v2-only — no v1.5 analog) owns the persisted config list; `useConnection` (v1.5, `core/react/useConnection.ts`) owns per-server transport lifecycle and exposes `connectionStatus`, `serverCapabilities`, `connect`, `disconnect`. `ServerListScreen` composes both via a parent view.
|
||||
- **Internal refactors**:
|
||||
- Stop flattening each server into a loose `ServerCardProps`; pass `ServerEntry` and let `ServerCard` read `config` / `connection` fields.
|
||||
- `activeServer` state may migrate to `useServers` once it gates which screen renders; until then, keep `useState` local.
|
||||
- `activeServer` is a prop, not local screen state — the wiring layer tracks which server is connected and passes the id down. `compact` toggle stays as local `useState`.
|
||||
|
||||
<!-- /AGENT:SCREENS -->
|
||||
|
||||
|
|
|
|||
|
|
@ -283,12 +283,10 @@ Server connection card with status, transport, command, and action buttons.
|
|||
| `name` | `string` | Server name |
|
||||
| `version` | `string?` | Server version |
|
||||
| `transport` | `'stdio' \| 'http'` | Transport type |
|
||||
| `connectionMode` | `string` | "Via Proxy" or "Direct" |
|
||||
| `command` | `string` | Command or URL |
|
||||
| `status` | `'connected' \| 'connecting' \| 'disconnected' \| 'failed'` | Connection state |
|
||||
| `retryCount` | `number?` | Failed retry count |
|
||||
| `error` | `{ message: string; details?: string }?` | Error info |
|
||||
| `canTestClientFeatures` | `boolean` | Show "Test Client Features" dropdown |
|
||||
| `onToggleConnection` | `(connect: boolean) => void` | Connect/disconnect |
|
||||
| `onCopyCommand` | `() => void` | Copy command |
|
||||
| `onServerInfo` | `() => void` | View server info |
|
||||
|
|
@ -296,14 +294,10 @@ Server connection card with status, transport, command, and action buttons.
|
|||
| `onEdit` | `() => void` | Edit server |
|
||||
| `onClone` | `() => void` | Clone server |
|
||||
| `onRemove` | `() => void` | Remove server |
|
||||
| `onTestSampling` | `() => void` | Test sampling |
|
||||
| `onTestElicitationForm` | `() => void` | Test elicitation (form) |
|
||||
| `onTestElicitationUrl` | `() => void` | Test elicitation (URL) |
|
||||
| `onConfigureRoots` | `() => void` | Configure roots |
|
||||
|
||||
**Mantine:** `Card` with `Card.Section` dividers. Uses StatusIndicator, TransportBadge, ConnectionToggle, CopyButton, InlineError elements. `Menu` for "Test Client Features" dropdown.
|
||||
**Mantine:** `Card` with `Card.Section` dividers. Uses StatusIndicator, TransportBadge, ConnectionToggle, CopyButton, InlineError elements.
|
||||
|
||||
**Stories:** Connected, Disconnected, Connecting, Failed, FailedWithError, WithClientFeatures, HttpDirect, LongCommand
|
||||
**Stories:** Connected, Disconnected, Connecting, Failed, FailedWithError, HttpDirect, LongCommand
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -679,11 +673,10 @@ Server metadata, capabilities, and instructions display (modal body content).
|
|||
|
||||
### 2.20 ServerSettingsForm
|
||||
|
||||
Server settings form with connection mode, headers, metadata, timeouts, and OAuth.
|
||||
Server settings form with headers, metadata, timeouts, and OAuth.
|
||||
|
||||
| Prop | Type | Description |
|
||||
|------|------|-------------|
|
||||
| `connectionMode` | `'proxy' \| 'direct'` | Connection mode |
|
||||
| `headers` | `Array<{ key: string; value: string }>` | Custom headers |
|
||||
| `metadata` | `Array<{ key: string; value: string }>` | Request metadata |
|
||||
| `connectionTimeout` | `number` | Connection timeout ms |
|
||||
|
|
|
|||
Loading…
Reference in a new issue