Phase 2: Refactor Server/Settings/ViewShell cluster to MCP SDK types
ServerCard: Replace flat scalar props (name, version, transport, status, error) with grouped objects (config: MCPServerConfig, info?: Implementation, connection: ConnectionState). Remove auto-connect useEffect — dumb components must not self-dispatch side effects. Derive transport and command/url from config. ServerInfoContent: Replace flat props with initializeResult: InitializeResult and clientCapabilities: ClientCapabilities from MCP SDK. Derive capability lists by iterating the ServerCapabilities record. ServerSettingsForm: Collapse flat setting props (connectionMode, headers, metadata, timeouts, oauth*) into settings: InspectorServerSettings. Add InspectorServerSettings type to core/mcp/types.ts. ImportServerJsonPanel: Replace scattered scalar props (jsonContent, selectedPackageIndex, serverName) with draft: InspectorServerJsonDraft. ViewHeader: Replace serverName: string with serverInfo: Implementation. Use ConnectionStatus from core types. ConnectedView: Update to pass serverInfo: Implementation instead of serverName. Use ConnectionStatus type. Update all stories for ServerCard, ServerInfoContent, ServerSettingsForm, ImportServerJsonPanel, ViewHeader, ServerListScreen, ConnectedView, and UnconnectedView. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c25a092ed1
commit
771a67ee20
16 changed files with 562 additions and 539 deletions
|
|
@ -1,7 +1,13 @@
|
||||||
|
import type { InspectorServerJsonDraft } from "@inspector/core/mcp/types.js";
|
||||||
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 { ImportServerJsonPanel } from "./ImportServerJsonPanel";
|
import { ImportServerJsonPanel } from "./ImportServerJsonPanel";
|
||||||
|
|
||||||
|
const emptyDraft: InspectorServerJsonDraft = {
|
||||||
|
rawText: "",
|
||||||
|
envOverrides: {},
|
||||||
|
};
|
||||||
|
|
||||||
const meta: Meta<typeof ImportServerJsonPanel> = {
|
const meta: Meta<typeof ImportServerJsonPanel> = {
|
||||||
title: "Groups/ImportServerJsonPanel",
|
title: "Groups/ImportServerJsonPanel",
|
||||||
component: ImportServerJsonPanel,
|
component: ImportServerJsonPanel,
|
||||||
|
|
@ -13,10 +19,8 @@ const meta: Meta<typeof ImportServerJsonPanel> = {
|
||||||
onServerNameChange: fn(),
|
onServerNameChange: fn(),
|
||||||
onAddServer: fn(),
|
onAddServer: fn(),
|
||||||
onCancel: fn(),
|
onCancel: fn(),
|
||||||
selectedPackageIndex: 0,
|
|
||||||
envVars: [],
|
envVars: [],
|
||||||
serverName: "",
|
validation: [],
|
||||||
validationResults: [],
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -25,28 +29,31 @@ type Story = StoryObj<typeof ImportServerJsonPanel>;
|
||||||
|
|
||||||
export const Empty: Story = {
|
export const Empty: Story = {
|
||||||
args: {
|
args: {
|
||||||
jsonContent: "",
|
draft: emptyDraft,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ValidJson: Story = {
|
export const ValidJson: Story = {
|
||||||
args: {
|
args: {
|
||||||
jsonContent: JSON.stringify(
|
draft: {
|
||||||
{
|
rawText: JSON.stringify(
|
||||||
name: "my-mcp-server",
|
{
|
||||||
version: "1.0.0",
|
name: "my-mcp-server",
|
||||||
packages: [
|
version: "1.0.0",
|
||||||
{
|
packages: [
|
||||||
registryType: "npm",
|
{
|
||||||
identifier: "my-mcp-server",
|
registryType: "npm",
|
||||||
runtimeHint: "node",
|
identifier: "my-mcp-server",
|
||||||
},
|
runtimeHint: "node",
|
||||||
],
|
},
|
||||||
},
|
],
|
||||||
null,
|
},
|
||||||
2,
|
null,
|
||||||
),
|
2,
|
||||||
validationResults: [
|
),
|
||||||
|
envOverrides: {},
|
||||||
|
},
|
||||||
|
validation: [
|
||||||
{ type: "success", message: "Valid server.json format detected" },
|
{ type: "success", message: "Valid server.json format detected" },
|
||||||
{ type: "success", message: "Package configuration is valid" },
|
{ type: "success", message: "Package configuration is valid" },
|
||||||
{ type: "info", message: "1 package found" },
|
{ type: "info", message: "1 package found" },
|
||||||
|
|
@ -56,31 +63,38 @@ export const ValidJson: Story = {
|
||||||
|
|
||||||
export const MultiplePackages: Story = {
|
export const MultiplePackages: Story = {
|
||||||
args: {
|
args: {
|
||||||
jsonContent: JSON.stringify(
|
draft: {
|
||||||
{
|
rawText: JSON.stringify(
|
||||||
name: "multi-server",
|
{
|
||||||
packages: [
|
name: "multi-server",
|
||||||
{
|
packages: [
|
||||||
registryType: "npm",
|
{
|
||||||
identifier: "@scope/server",
|
registryType: "npm",
|
||||||
runtimeHint: "node",
|
identifier: "@scope/server",
|
||||||
},
|
runtimeHint: "node",
|
||||||
{
|
},
|
||||||
registryType: "pip",
|
{
|
||||||
identifier: "server-python",
|
registryType: "pip",
|
||||||
runtimeHint: "python3",
|
identifier: "server-python",
|
||||||
},
|
runtimeHint: "python3",
|
||||||
],
|
},
|
||||||
},
|
],
|
||||||
null,
|
},
|
||||||
2,
|
null,
|
||||||
),
|
2,
|
||||||
validationResults: [
|
),
|
||||||
|
envOverrides: {},
|
||||||
|
},
|
||||||
|
validation: [
|
||||||
{ type: "success", message: "Valid server.json format detected" },
|
{ type: "success", message: "Valid server.json format detected" },
|
||||||
{ type: "info", message: "2 packages found - select one to install" },
|
{ type: "info", message: "2 packages found - select one to install" },
|
||||||
],
|
],
|
||||||
packages: [
|
packages: [
|
||||||
{ registryType: "npm", identifier: "@scope/server", runtimeHint: "node" },
|
{
|
||||||
|
registryType: "npm",
|
||||||
|
identifier: "@scope/server",
|
||||||
|
runtimeHint: "node",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
registryType: "pip",
|
registryType: "pip",
|
||||||
identifier: "server-python",
|
identifier: "server-python",
|
||||||
|
|
@ -92,25 +106,28 @@ export const MultiplePackages: Story = {
|
||||||
|
|
||||||
export const WithEnvVars: Story = {
|
export const WithEnvVars: Story = {
|
||||||
args: {
|
args: {
|
||||||
jsonContent: JSON.stringify(
|
draft: {
|
||||||
{
|
rawText: JSON.stringify(
|
||||||
name: "api-server",
|
{
|
||||||
packages: [
|
name: "api-server",
|
||||||
{
|
packages: [
|
||||||
registryType: "npm",
|
{
|
||||||
identifier: "api-server",
|
registryType: "npm",
|
||||||
runtimeHint: "node",
|
identifier: "api-server",
|
||||||
},
|
runtimeHint: "node",
|
||||||
],
|
},
|
||||||
envVars: [
|
],
|
||||||
{ name: "API_KEY", required: true },
|
envVars: [
|
||||||
{ name: "API_URL", required: false },
|
{ name: "API_KEY", required: true },
|
||||||
],
|
{ name: "API_URL", required: false },
|
||||||
},
|
],
|
||||||
null,
|
},
|
||||||
2,
|
null,
|
||||||
),
|
2,
|
||||||
validationResults: [
|
),
|
||||||
|
envOverrides: {},
|
||||||
|
},
|
||||||
|
validation: [
|
||||||
{ type: "success", message: "Valid server.json format detected" },
|
{ type: "success", message: "Valid server.json format detected" },
|
||||||
{
|
{
|
||||||
type: "warning",
|
type: "warning",
|
||||||
|
|
@ -136,31 +153,36 @@ export const WithEnvVars: Story = {
|
||||||
|
|
||||||
export const FullyConfigured: Story = {
|
export const FullyConfigured: Story = {
|
||||||
args: {
|
args: {
|
||||||
jsonContent: JSON.stringify(
|
draft: {
|
||||||
{
|
rawText: JSON.stringify(
|
||||||
name: "full-server",
|
{
|
||||||
version: "2.1.0",
|
name: "full-server",
|
||||||
packages: [
|
version: "2.1.0",
|
||||||
{
|
packages: [
|
||||||
registryType: "npm",
|
{
|
||||||
identifier: "@org/full-server",
|
registryType: "npm",
|
||||||
runtimeHint: "node",
|
identifier: "@org/full-server",
|
||||||
},
|
runtimeHint: "node",
|
||||||
{
|
},
|
||||||
registryType: "docker",
|
{
|
||||||
identifier: "org/full-server:latest",
|
registryType: "docker",
|
||||||
runtimeHint: "docker",
|
identifier: "org/full-server:latest",
|
||||||
},
|
runtimeHint: "docker",
|
||||||
],
|
},
|
||||||
envVars: [
|
],
|
||||||
{ name: "TOKEN", required: true },
|
envVars: [
|
||||||
{ name: "DEBUG", required: false },
|
{ name: "TOKEN", required: true },
|
||||||
],
|
{ name: "DEBUG", required: false },
|
||||||
},
|
],
|
||||||
null,
|
},
|
||||||
2,
|
null,
|
||||||
),
|
2,
|
||||||
validationResults: [
|
),
|
||||||
|
selectedPackageIndex: 1,
|
||||||
|
envOverrides: { TOKEN: "sk-abc123", DEBUG: "true" },
|
||||||
|
nameOverride: "My Custom Server",
|
||||||
|
},
|
||||||
|
validation: [
|
||||||
{ type: "success", message: "Valid server.json format detected" },
|
{ type: "success", message: "Valid server.json format detected" },
|
||||||
{ type: "success", message: "All required fields present" },
|
{ type: "success", message: "All required fields present" },
|
||||||
{ type: "info", message: "2 packages found" },
|
{ type: "info", message: "2 packages found" },
|
||||||
|
|
@ -181,7 +203,6 @@ export const FullyConfigured: Story = {
|
||||||
runtimeHint: "docker",
|
runtimeHint: "docker",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
selectedPackageIndex: 1,
|
|
||||||
envVars: [
|
envVars: [
|
||||||
{
|
{
|
||||||
name: "TOKEN",
|
name: "TOKEN",
|
||||||
|
|
@ -196,6 +217,5 @@ export const FullyConfigured: Story = {
|
||||||
value: "true",
|
value: "true",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
serverName: "My Custom Server",
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import {
|
||||||
Textarea,
|
Textarea,
|
||||||
Title,
|
Title,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
|
import type { InspectorServerJsonDraft } from "@inspector/core/mcp/types.js";
|
||||||
|
|
||||||
export interface ValidationResult {
|
export interface ValidationResult {
|
||||||
type: "success" | "warning" | "info" | "error";
|
type: "success" | "warning" | "info" | "error";
|
||||||
|
|
@ -29,12 +30,10 @@ export interface EnvVarInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ImportServerJsonPanelProps {
|
export interface ImportServerJsonPanelProps {
|
||||||
jsonContent: string;
|
draft: InspectorServerJsonDraft;
|
||||||
validationResults: ValidationResult[];
|
validation: ValidationResult[];
|
||||||
packages?: PackageInfo[];
|
packages?: PackageInfo[];
|
||||||
selectedPackageIndex: number;
|
|
||||||
envVars: EnvVarInfo[];
|
envVars: EnvVarInfo[];
|
||||||
serverName: string;
|
|
||||||
onJsonChange: (content: string) => void;
|
onJsonChange: (content: string) => void;
|
||||||
onValidate: () => void;
|
onValidate: () => void;
|
||||||
onSelectPackage: (index: number) => void;
|
onSelectPackage: (index: number) => void;
|
||||||
|
|
@ -64,12 +63,10 @@ function formatPackageLabel(pkg: PackageInfo): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ImportServerJsonPanel({
|
export function ImportServerJsonPanel({
|
||||||
jsonContent,
|
draft,
|
||||||
validationResults,
|
validation,
|
||||||
packages,
|
packages,
|
||||||
selectedPackageIndex,
|
|
||||||
envVars,
|
envVars,
|
||||||
serverName,
|
|
||||||
onJsonChange,
|
onJsonChange,
|
||||||
onValidate,
|
onValidate,
|
||||||
onSelectPackage,
|
onSelectPackage,
|
||||||
|
|
@ -85,7 +82,7 @@ export function ImportServerJsonPanel({
|
||||||
<HintText>Paste server.json content or drag and drop a file:</HintText>
|
<HintText>Paste server.json content or drag and drop a file:</HintText>
|
||||||
|
|
||||||
<Textarea
|
<Textarea
|
||||||
value={jsonContent}
|
value={draft.rawText}
|
||||||
onChange={(e) => onJsonChange(e.currentTarget.value)}
|
onChange={(e) => onJsonChange(e.currentTarget.value)}
|
||||||
ff="monospace"
|
ff="monospace"
|
||||||
autosize
|
autosize
|
||||||
|
|
@ -97,7 +94,7 @@ export function ImportServerJsonPanel({
|
||||||
|
|
||||||
<Title order={5}>Validation Results:</Title>
|
<Title order={5}>Validation Results:</Title>
|
||||||
|
|
||||||
{validationResults.map((result, index) => {
|
{validation.map((result, index) => {
|
||||||
const { icon, color } = validationIcons[result.type];
|
const { icon, color } = validationIcons[result.type];
|
||||||
return (
|
return (
|
||||||
<Group key={index} gap="xs">
|
<Group key={index} gap="xs">
|
||||||
|
|
@ -112,7 +109,7 @@ export function ImportServerJsonPanel({
|
||||||
<Divider />
|
<Divider />
|
||||||
<Title order={5}>Package Selection:</Title>
|
<Title order={5}>Package Selection:</Title>
|
||||||
<Radio.Group
|
<Radio.Group
|
||||||
value={String(selectedPackageIndex)}
|
value={String(draft.selectedPackageIndex ?? 0)}
|
||||||
onChange={(value) => onSelectPackage(Number(value))}
|
onChange={(value) => onSelectPackage(Number(value))}
|
||||||
>
|
>
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
|
|
@ -151,7 +148,7 @@ export function ImportServerJsonPanel({
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
label="Server Name (optional override)"
|
label="Server Name (optional override)"
|
||||||
value={serverName}
|
value={draft.nameOverride ?? ""}
|
||||||
onChange={(e) => onServerNameChange(e.currentTarget.value)}
|
onChange={(e) => onServerNameChange(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,31 @@
|
||||||
|
import type { MCPServerConfig } from "@inspector/core/mcp/types.js";
|
||||||
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 { ConnectionState } from "./ServerCard";
|
||||||
import { ServerCard } from "./ServerCard";
|
import { ServerCard } from "./ServerCard";
|
||||||
|
|
||||||
|
const stdioConfig: MCPServerConfig = {
|
||||||
|
command: "npx -y @modelcontextprotocol/server-everything",
|
||||||
|
};
|
||||||
|
|
||||||
|
const httpConfig: MCPServerConfig = {
|
||||||
|
type: "streamable-http",
|
||||||
|
url: "https://api.example.com/mcp",
|
||||||
|
};
|
||||||
|
|
||||||
|
const connected: ConnectionState = { status: "connected" };
|
||||||
|
const disconnected: ConnectionState = { status: "disconnected" };
|
||||||
|
const connecting: ConnectionState = { status: "connecting" };
|
||||||
|
const failed: ConnectionState = {
|
||||||
|
status: "error",
|
||||||
|
retryCount: 3,
|
||||||
|
error: {
|
||||||
|
message: "Connection refused",
|
||||||
|
details:
|
||||||
|
"ECONNREFUSED 127.0.0.1:3000 - The server process exited unexpectedly.",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const meta: Meta<typeof ServerCard> = {
|
const meta: Meta<typeof ServerCard> = {
|
||||||
title: "Groups/ServerCard",
|
title: "Groups/ServerCard",
|
||||||
component: ServerCard,
|
component: ServerCard,
|
||||||
|
|
@ -26,11 +50,10 @@ type Story = StoryObj<typeof ServerCard>;
|
||||||
export const Connected: Story = {
|
export const Connected: Story = {
|
||||||
args: {
|
args: {
|
||||||
name: "My MCP Server",
|
name: "My MCP Server",
|
||||||
version: "1.2.0",
|
config: stdioConfig,
|
||||||
transport: "stdio",
|
info: { name: "My MCP Server", version: "1.2.0" },
|
||||||
|
connection: connected,
|
||||||
connectionMode: "Subprocess",
|
connectionMode: "Subprocess",
|
||||||
command: "npx -y @modelcontextprotocol/server-everything",
|
|
||||||
status: "connected",
|
|
||||||
canTestClientFeatures: false,
|
canTestClientFeatures: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -38,11 +61,10 @@ export const Connected: Story = {
|
||||||
export const Disconnected: Story = {
|
export const Disconnected: Story = {
|
||||||
args: {
|
args: {
|
||||||
name: "My MCP Server",
|
name: "My MCP Server",
|
||||||
version: "1.2.0",
|
config: stdioConfig,
|
||||||
transport: "stdio",
|
info: { name: "My MCP Server", version: "1.2.0" },
|
||||||
|
connection: disconnected,
|
||||||
connectionMode: "Subprocess",
|
connectionMode: "Subprocess",
|
||||||
command: "npx -y @modelcontextprotocol/server-everything",
|
|
||||||
status: "disconnected",
|
|
||||||
canTestClientFeatures: false,
|
canTestClientFeatures: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -50,11 +72,10 @@ export const Disconnected: Story = {
|
||||||
export const Connecting: Story = {
|
export const Connecting: Story = {
|
||||||
args: {
|
args: {
|
||||||
name: "My MCP Server",
|
name: "My MCP Server",
|
||||||
version: "1.2.0",
|
config: stdioConfig,
|
||||||
transport: "stdio",
|
info: { name: "My MCP Server", version: "1.2.0" },
|
||||||
|
connection: connecting,
|
||||||
connectionMode: "Subprocess",
|
connectionMode: "Subprocess",
|
||||||
command: "npx -y @modelcontextprotocol/server-everything",
|
|
||||||
status: "connecting",
|
|
||||||
canTestClientFeatures: false,
|
canTestClientFeatures: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -62,17 +83,10 @@ export const Connecting: Story = {
|
||||||
export const Failed: Story = {
|
export const Failed: Story = {
|
||||||
args: {
|
args: {
|
||||||
name: "My MCP Server",
|
name: "My MCP Server",
|
||||||
version: "1.2.0",
|
config: stdioConfig,
|
||||||
transport: "stdio",
|
info: { name: "My MCP Server", version: "1.2.0" },
|
||||||
|
connection: failed,
|
||||||
connectionMode: "Subprocess",
|
connectionMode: "Subprocess",
|
||||||
command: "npx -y @modelcontextprotocol/server-everything",
|
|
||||||
status: "error",
|
|
||||||
retryCount: 3,
|
|
||||||
error: {
|
|
||||||
message: "Connection refused",
|
|
||||||
details:
|
|
||||||
"ECONNREFUSED 127.0.0.1:3000 - The server process exited unexpectedly.",
|
|
||||||
},
|
|
||||||
canTestClientFeatures: false,
|
canTestClientFeatures: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -80,11 +94,10 @@ export const Failed: Story = {
|
||||||
export const WithClientFeatures: Story = {
|
export const WithClientFeatures: Story = {
|
||||||
args: {
|
args: {
|
||||||
name: "My MCP Server",
|
name: "My MCP Server",
|
||||||
version: "1.2.0",
|
config: stdioConfig,
|
||||||
transport: "stdio",
|
info: { name: "My MCP Server", version: "1.2.0" },
|
||||||
|
connection: connected,
|
||||||
connectionMode: "Subprocess",
|
connectionMode: "Subprocess",
|
||||||
command: "npx -y @modelcontextprotocol/server-everything",
|
|
||||||
status: "connected",
|
|
||||||
canTestClientFeatures: true,
|
canTestClientFeatures: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -92,12 +105,16 @@ export const WithClientFeatures: Story = {
|
||||||
export const LongServerName: Story = {
|
export const LongServerName: Story = {
|
||||||
args: {
|
args: {
|
||||||
name: "my-organization-super-long-experimental-mcp-server-with-many-features-v2",
|
name: "my-organization-super-long-experimental-mcp-server-with-many-features-v2",
|
||||||
version: "1.0.0-beta.42",
|
config: {
|
||||||
transport: "stdio",
|
command:
|
||||||
|
"npx -y @my-organization/super-long-experimental-mcp-server-with-many-features-v2",
|
||||||
|
},
|
||||||
|
info: {
|
||||||
|
name: "my-organization-super-long-experimental-mcp-server-with-many-features-v2",
|
||||||
|
version: "1.0.0-beta.42",
|
||||||
|
},
|
||||||
|
connection: connected,
|
||||||
connectionMode: "Subprocess",
|
connectionMode: "Subprocess",
|
||||||
command:
|
|
||||||
"npx -y @my-organization/super-long-experimental-mcp-server-with-many-features-v2",
|
|
||||||
status: "connected",
|
|
||||||
canTestClientFeatures: true,
|
canTestClientFeatures: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -105,11 +122,10 @@ export const LongServerName: Story = {
|
||||||
export const HttpDirect: Story = {
|
export const HttpDirect: Story = {
|
||||||
args: {
|
args: {
|
||||||
name: "Remote API Server",
|
name: "Remote API Server",
|
||||||
version: "2.0.0",
|
config: httpConfig,
|
||||||
transport: "streamable-http",
|
info: { name: "Remote API Server", version: "2.0.0" },
|
||||||
|
connection: connected,
|
||||||
connectionMode: "Direct",
|
connectionMode: "Direct",
|
||||||
command: "https://api.example.com/mcp",
|
|
||||||
status: "connected",
|
|
||||||
canTestClientFeatures: false,
|
canTestClientFeatures: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -117,11 +133,10 @@ export const HttpDirect: Story = {
|
||||||
export const CompactConnected: Story = {
|
export const CompactConnected: Story = {
|
||||||
args: {
|
args: {
|
||||||
name: "My MCP Server",
|
name: "My MCP Server",
|
||||||
version: "1.2.0",
|
config: stdioConfig,
|
||||||
transport: "stdio",
|
info: { name: "My MCP Server", version: "1.2.0" },
|
||||||
|
connection: connected,
|
||||||
connectionMode: "Subprocess",
|
connectionMode: "Subprocess",
|
||||||
command: "npx -y @modelcontextprotocol/server-everything",
|
|
||||||
status: "connected",
|
|
||||||
canTestClientFeatures: false,
|
canTestClientFeatures: false,
|
||||||
compact: true,
|
compact: true,
|
||||||
},
|
},
|
||||||
|
|
@ -130,11 +145,10 @@ export const CompactConnected: Story = {
|
||||||
export const CompactDisconnected: Story = {
|
export const CompactDisconnected: Story = {
|
||||||
args: {
|
args: {
|
||||||
name: "My MCP Server",
|
name: "My MCP Server",
|
||||||
version: "1.2.0",
|
config: stdioConfig,
|
||||||
transport: "stdio",
|
info: { name: "My MCP Server", version: "1.2.0" },
|
||||||
|
connection: disconnected,
|
||||||
connectionMode: "Subprocess",
|
connectionMode: "Subprocess",
|
||||||
command: "npx -y @modelcontextprotocol/server-everything",
|
|
||||||
status: "disconnected",
|
|
||||||
canTestClientFeatures: false,
|
canTestClientFeatures: false,
|
||||||
compact: true,
|
compact: true,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import { useEffect } from "react";
|
|
||||||
import { Badge, Button, Card, Group, Menu, Stack, Text } from "@mantine/core";
|
import { Badge, Button, Card, Group, Menu, Stack, Text } from "@mantine/core";
|
||||||
|
import type { Implementation } from "@modelcontextprotocol/sdk/types.js";
|
||||||
import type {
|
import type {
|
||||||
ConnectionStatus,
|
ConnectionStatus,
|
||||||
|
MCPServerConfig,
|
||||||
ServerType,
|
ServerType,
|
||||||
} from "@inspector/core/mcp/types.js";
|
} from "@inspector/core/mcp/types.js";
|
||||||
import { ServerStatusIndicator } from "../../elements/ServerStatusIndicator/ServerStatusIndicator";
|
import { ServerStatusIndicator } from "../../elements/ServerStatusIndicator/ServerStatusIndicator";
|
||||||
|
|
@ -10,15 +11,18 @@ import { ConnectionToggle } from "../../elements/ConnectionToggle/ConnectionTogg
|
||||||
import { ContentViewer } from "../../elements/ContentViewer/ContentViewer";
|
import { ContentViewer } from "../../elements/ContentViewer/ContentViewer";
|
||||||
import { InlineError } from "../../elements/InlineError/InlineError";
|
import { InlineError } from "../../elements/InlineError/InlineError";
|
||||||
|
|
||||||
export interface ServerCardProps {
|
export interface ConnectionState {
|
||||||
name: string;
|
|
||||||
version?: string;
|
|
||||||
transport: ServerType;
|
|
||||||
connectionMode: string;
|
|
||||||
command: string;
|
|
||||||
status: ConnectionStatus;
|
status: ConnectionStatus;
|
||||||
retryCount?: number;
|
retryCount?: number;
|
||||||
error?: { message: string; details?: string };
|
error?: { message: string; details?: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ServerCardProps {
|
||||||
|
name: string;
|
||||||
|
config: MCPServerConfig;
|
||||||
|
info?: Implementation;
|
||||||
|
connection: ConnectionState;
|
||||||
|
connectionMode: string;
|
||||||
canTestClientFeatures: boolean;
|
canTestClientFeatures: boolean;
|
||||||
activeServer?: string;
|
activeServer?: string;
|
||||||
onSetActiveServer?: (name: string | undefined) => void;
|
onSetActiveServer?: (name: string | undefined) => void;
|
||||||
|
|
@ -68,15 +72,23 @@ const RemoveButton = Button.withProps({
|
||||||
color: "red.6",
|
color: "red.6",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function getTransport(config: MCPServerConfig): ServerType {
|
||||||
|
return config.type ?? "stdio";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCommandOrUrl(config: MCPServerConfig): string {
|
||||||
|
if (config.type === "sse" || config.type === "streamable-http") {
|
||||||
|
return config.url;
|
||||||
|
}
|
||||||
|
return config.command;
|
||||||
|
}
|
||||||
|
|
||||||
export function ServerCard({
|
export function ServerCard({
|
||||||
name,
|
name,
|
||||||
version,
|
config,
|
||||||
transport,
|
info,
|
||||||
|
connection,
|
||||||
connectionMode,
|
connectionMode,
|
||||||
command,
|
|
||||||
status,
|
|
||||||
retryCount,
|
|
||||||
error,
|
|
||||||
canTestClientFeatures,
|
canTestClientFeatures,
|
||||||
activeServer,
|
activeServer,
|
||||||
onSetActiveServer,
|
onSetActiveServer,
|
||||||
|
|
@ -92,24 +104,10 @@ export function ServerCard({
|
||||||
onConfigureRoots,
|
onConfigureRoots,
|
||||||
compact = false,
|
compact = false,
|
||||||
}: ServerCardProps) {
|
}: ServerCardProps) {
|
||||||
const isThisConnecting = activeServer === name;
|
|
||||||
const isDimmed = activeServer !== undefined && activeServer !== name;
|
const isDimmed = activeServer !== undefined && activeServer !== name;
|
||||||
const displayStatus = isThisConnecting ? "connecting" : status;
|
const transport = getTransport(config);
|
||||||
|
const commandOrUrl = getCommandOrUrl(config);
|
||||||
useEffect(() => {
|
const version = info?.version;
|
||||||
if (isThisConnecting) {
|
|
||||||
onToggleConnection(true);
|
|
||||||
}
|
|
||||||
}, [isThisConnecting, onToggleConnection]);
|
|
||||||
|
|
||||||
function handleToggle(connect: boolean) {
|
|
||||||
if (connect && !activeServer) {
|
|
||||||
onSetActiveServer?.(name);
|
|
||||||
} else if (!connect && activeServer && activeServer === name) {
|
|
||||||
onSetActiveServer?.(undefined);
|
|
||||||
onToggleConnection(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
|
|
@ -126,14 +124,20 @@ export function ServerCard({
|
||||||
</HeaderLeft>
|
</HeaderLeft>
|
||||||
<HeaderRight>
|
<HeaderRight>
|
||||||
<ServerStatusIndicator
|
<ServerStatusIndicator
|
||||||
status={displayStatus}
|
status={connection.status}
|
||||||
retryCount={retryCount}
|
retryCount={connection.retryCount}
|
||||||
/>
|
/>
|
||||||
<ConnectionToggle
|
<ConnectionToggle
|
||||||
status={displayStatus}
|
status={connection.status}
|
||||||
disabled={isDimmed}
|
disabled={isDimmed}
|
||||||
onConnect={() => handleToggle(true)}
|
onConnect={() => {
|
||||||
onDisconnect={() => handleToggle(false)}
|
onSetActiveServer?.(name);
|
||||||
|
onToggleConnection(true);
|
||||||
|
}}
|
||||||
|
onDisconnect={() => {
|
||||||
|
onSetActiveServer?.(undefined);
|
||||||
|
onToggleConnection(false);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</HeaderRight>
|
</HeaderRight>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
@ -168,15 +172,18 @@ export function ServerCard({
|
||||||
)}
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<ContentViewer block={{ type: "text", text: command }} copyable />
|
<ContentViewer
|
||||||
|
block={{ type: "text", text: commandOrUrl }}
|
||||||
|
copyable
|
||||||
|
/>
|
||||||
|
|
||||||
{error && (
|
{connection.error && (
|
||||||
<InlineError
|
<InlineError
|
||||||
error={{
|
error={{
|
||||||
message: error.message,
|
message: connection.error.message,
|
||||||
data: error.details,
|
data: connection.error.details,
|
||||||
}}
|
}}
|
||||||
retryCount={retryCount}
|
retryCount={connection.retryCount}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,31 @@
|
||||||
|
import type {
|
||||||
|
ClientCapabilities,
|
||||||
|
InitializeResult,
|
||||||
|
} from "@modelcontextprotocol/sdk/types.js";
|
||||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||||
import { ServerInfoContent } from "./ServerInfoContent";
|
import { ServerInfoContent } from "./ServerInfoContent";
|
||||||
|
|
||||||
|
const fullResult: InitializeResult = {
|
||||||
|
protocolVersion: "2025-03-26",
|
||||||
|
serverInfo: { name: "Everything Server", version: "2.1.0" },
|
||||||
|
capabilities: {
|
||||||
|
tools: { listChanged: true },
|
||||||
|
resources: { subscribe: true, listChanged: true },
|
||||||
|
prompts: { listChanged: true },
|
||||||
|
logging: {},
|
||||||
|
completions: {},
|
||||||
|
},
|
||||||
|
instructions:
|
||||||
|
"This server provides access to the project management system. Use the list_projects tool first to discover available projects before querying tasks. Rate limiting applies: max 60 requests per minute.",
|
||||||
|
};
|
||||||
|
|
||||||
|
const fullClientCaps: ClientCapabilities = {
|
||||||
|
roots: { listChanged: true },
|
||||||
|
sampling: {},
|
||||||
|
elicitation: {},
|
||||||
|
experimental: {},
|
||||||
|
};
|
||||||
|
|
||||||
const meta: Meta<typeof ServerInfoContent> = {
|
const meta: Meta<typeof ServerInfoContent> = {
|
||||||
title: "Groups/ServerInfoContent",
|
title: "Groups/ServerInfoContent",
|
||||||
component: ServerInfoContent,
|
component: ServerInfoContent,
|
||||||
|
|
@ -11,80 +36,54 @@ type Story = StoryObj<typeof ServerInfoContent>;
|
||||||
|
|
||||||
export const FullCapabilities: Story = {
|
export const FullCapabilities: Story = {
|
||||||
args: {
|
args: {
|
||||||
name: "Everything Server",
|
initializeResult: fullResult,
|
||||||
version: "2.1.0",
|
clientCapabilities: fullClientCaps,
|
||||||
protocolVersion: "2025-03-26",
|
|
||||||
transport: "stdio",
|
transport: "stdio",
|
||||||
serverCapabilities: [
|
|
||||||
{ capability: "tools", supported: true, count: 12 },
|
|
||||||
{ capability: "resources", supported: true, count: 8 },
|
|
||||||
{ capability: "prompts", supported: true, count: 5 },
|
|
||||||
{ capability: "logging", supported: true },
|
|
||||||
{ capability: "completions", supported: true },
|
|
||||||
],
|
|
||||||
clientCapabilities: [
|
|
||||||
{ capability: "roots", supported: true, count: 3 },
|
|
||||||
{ capability: "sampling", supported: true },
|
|
||||||
{ capability: "elicitation", supported: true },
|
|
||||||
{ capability: "experimental", supported: true },
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MinimalCapabilities: Story = {
|
export const MinimalCapabilities: Story = {
|
||||||
args: {
|
args: {
|
||||||
name: "Simple Server",
|
initializeResult: {
|
||||||
version: "1.0.0",
|
protocolVersion: "2025-03-26",
|
||||||
protocolVersion: "2025-03-26",
|
serverInfo: { name: "Simple Server", version: "1.0.0" },
|
||||||
|
capabilities: {
|
||||||
|
tools: { listChanged: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
clientCapabilities: {
|
||||||
|
roots: { listChanged: true },
|
||||||
|
},
|
||||||
transport: "streamable-http",
|
transport: "streamable-http",
|
||||||
serverCapabilities: [
|
|
||||||
{ capability: "tools", supported: true, count: 2 },
|
|
||||||
{ capability: "resources", supported: false },
|
|
||||||
{ capability: "prompts", supported: false },
|
|
||||||
],
|
|
||||||
clientCapabilities: [
|
|
||||||
{ capability: "roots", supported: true },
|
|
||||||
{ capability: "sampling", supported: false },
|
|
||||||
{ capability: "elicitation", supported: false },
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WithInstructions: Story = {
|
export const WithInstructions: Story = {
|
||||||
args: {
|
args: {
|
||||||
name: "Guided Server",
|
initializeResult: fullResult,
|
||||||
version: "1.5.0",
|
clientCapabilities: {
|
||||||
protocolVersion: "2025-03-26",
|
roots: { listChanged: true },
|
||||||
|
sampling: {},
|
||||||
|
},
|
||||||
transport: "stdio",
|
transport: "stdio",
|
||||||
serverCapabilities: [
|
|
||||||
{ capability: "tools", supported: true, count: 4 },
|
|
||||||
{ capability: "resources", supported: true, count: 2 },
|
|
||||||
{ capability: "prompts", supported: true, count: 1 },
|
|
||||||
],
|
|
||||||
clientCapabilities: [
|
|
||||||
{ capability: "roots", supported: true },
|
|
||||||
{ capability: "sampling", supported: true },
|
|
||||||
],
|
|
||||||
instructions:
|
|
||||||
"This server provides access to the project management system. Use the list_projects tool first to discover available projects before querying tasks. Rate limiting applies: max 60 requests per minute.",
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WithOAuth: Story = {
|
export const WithOAuth: Story = {
|
||||||
args: {
|
args: {
|
||||||
name: "Authenticated Server",
|
initializeResult: {
|
||||||
version: "3.0.0",
|
protocolVersion: "2025-03-26",
|
||||||
protocolVersion: "2025-03-26",
|
serverInfo: { name: "Authenticated Server", version: "3.0.0" },
|
||||||
|
capabilities: {
|
||||||
|
tools: { listChanged: true },
|
||||||
|
resources: { subscribe: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
clientCapabilities: {
|
||||||
|
roots: { listChanged: true },
|
||||||
|
},
|
||||||
transport: "streamable-http",
|
transport: "streamable-http",
|
||||||
serverCapabilities: [
|
oauth: {
|
||||||
{ capability: "tools", supported: true, count: 6 },
|
|
||||||
{ capability: "resources", supported: true, count: 3 },
|
|
||||||
],
|
|
||||||
clientCapabilities: [
|
|
||||||
{ capability: "roots", supported: true },
|
|
||||||
{ capability: "sampling", supported: false },
|
|
||||||
],
|
|
||||||
oauthDetails: {
|
|
||||||
authUrl: "https://auth.example.com/oauth2/authorize",
|
authUrl: "https://auth.example.com/oauth2/authorize",
|
||||||
scopes: ["read", "write", "admin"],
|
scopes: ["read", "write", "admin"],
|
||||||
accessToken: "eyJhbGciOiJSUzI1NiIs...truncated",
|
accessToken: "eyJhbGciOiJSUzI1NiIs...truncated",
|
||||||
|
|
|
||||||
|
|
@ -7,20 +7,27 @@ import {
|
||||||
Text,
|
Text,
|
||||||
Title,
|
Title,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
|
import type {
|
||||||
|
ClientCapabilities,
|
||||||
|
InitializeResult,
|
||||||
|
} from "@modelcontextprotocol/sdk/types.js";
|
||||||
|
import type { ServerType } from "@inspector/core/mcp/types.js";
|
||||||
import {
|
import {
|
||||||
CapabilityItem,
|
CapabilityItem,
|
||||||
type CapabilityItemProps,
|
type CapabilityKey,
|
||||||
} from "../../elements/CapabilityItem/CapabilityItem";
|
} from "../../elements/CapabilityItem/CapabilityItem";
|
||||||
|
|
||||||
|
export interface OAuthDetails {
|
||||||
|
authUrl?: string;
|
||||||
|
scopes?: string[];
|
||||||
|
accessToken?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ServerInfoContentProps {
|
export interface ServerInfoContentProps {
|
||||||
name: string;
|
initializeResult: InitializeResult;
|
||||||
version: string;
|
clientCapabilities: ClientCapabilities;
|
||||||
protocolVersion: string;
|
transport: ServerType;
|
||||||
transport: string;
|
oauth?: OAuthDetails;
|
||||||
serverCapabilities: CapabilityItemProps[];
|
|
||||||
clientCapabilities: CapabilityItemProps[];
|
|
||||||
instructions?: string;
|
|
||||||
oauthDetails?: { authUrl?: string; scopes?: string[]; accessToken?: string };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ValueText = Text.withProps({
|
const ValueText = Text.withProps({
|
||||||
|
|
@ -32,26 +39,37 @@ function formatScopes(scopes: string[]): string {
|
||||||
return scopes.join(", ");
|
return scopes.join(", ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCapabilityEntries(
|
||||||
|
capabilities: Record<string, unknown>,
|
||||||
|
): { capability: CapabilityKey; supported: boolean }[] {
|
||||||
|
return Object.entries(capabilities).map(([key, value]) => ({
|
||||||
|
capability: key as CapabilityKey,
|
||||||
|
supported: value != null,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
export function ServerInfoContent({
|
export function ServerInfoContent({
|
||||||
name,
|
initializeResult,
|
||||||
version,
|
|
||||||
protocolVersion,
|
|
||||||
transport,
|
|
||||||
serverCapabilities,
|
|
||||||
clientCapabilities,
|
clientCapabilities,
|
||||||
instructions,
|
transport,
|
||||||
oauthDetails,
|
oauth,
|
||||||
}: ServerInfoContentProps) {
|
}: ServerInfoContentProps) {
|
||||||
|
const { serverInfo, protocolVersion, capabilities, instructions } =
|
||||||
|
initializeResult;
|
||||||
|
|
||||||
|
const serverCaps = getCapabilityEntries(capabilities);
|
||||||
|
const clientCaps = getCapabilityEntries(clientCapabilities);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
<Title order={3}>Server Information</Title>
|
<Title order={3}>Server Information</Title>
|
||||||
|
|
||||||
<SimpleGrid cols={2}>
|
<SimpleGrid cols={2}>
|
||||||
<Text size="sm">Name</Text>
|
<Text size="sm">Name</Text>
|
||||||
<ValueText>{name}</ValueText>
|
<ValueText>{serverInfo.name}</ValueText>
|
||||||
|
|
||||||
<Text size="sm">Version</Text>
|
<Text size="sm">Version</Text>
|
||||||
<ValueText>{version}</ValueText>
|
<ValueText>{serverInfo.version}</ValueText>
|
||||||
|
|
||||||
<Text size="sm">Protocol</Text>
|
<Text size="sm">Protocol</Text>
|
||||||
<ValueText>{protocolVersion}</ValueText>
|
<ValueText>{protocolVersion}</ValueText>
|
||||||
|
|
@ -63,23 +81,21 @@ export function ServerInfoContent({
|
||||||
<SimpleGrid cols={2}>
|
<SimpleGrid cols={2}>
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
<Title order={5}>Server Capabilities</Title>
|
<Title order={5}>Server Capabilities</Title>
|
||||||
{serverCapabilities.map((cap) => (
|
{serverCaps.map((cap) => (
|
||||||
<CapabilityItem
|
<CapabilityItem
|
||||||
key={cap.capability}
|
key={cap.capability}
|
||||||
capability={cap.capability}
|
capability={cap.capability}
|
||||||
supported={cap.supported}
|
supported={cap.supported}
|
||||||
count={cap.count}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
<Title order={5}>Client Capabilities</Title>
|
<Title order={5}>Client Capabilities</Title>
|
||||||
{clientCapabilities.map((cap) => (
|
{clientCaps.map((cap) => (
|
||||||
<CapabilityItem
|
<CapabilityItem
|
||||||
key={cap.capability}
|
key={cap.capability}
|
||||||
capability={cap.capability}
|
capability={cap.capability}
|
||||||
supported={cap.supported}
|
supported={cap.supported}
|
||||||
count={cap.count}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
@ -92,26 +108,26 @@ export function ServerInfoContent({
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{oauthDetails && (
|
{oauth && (
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
<Title order={5}>OAuth Details</Title>
|
<Title order={5}>OAuth Details</Title>
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
{oauthDetails.authUrl && (
|
{oauth.authUrl && (
|
||||||
<SimpleGrid cols={2}>
|
<SimpleGrid cols={2}>
|
||||||
<Text size="sm">Auth URL</Text>
|
<Text size="sm">Auth URL</Text>
|
||||||
<Code>{oauthDetails.authUrl}</Code>
|
<Code>{oauth.authUrl}</Code>
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
)}
|
)}
|
||||||
{oauthDetails.scopes && oauthDetails.scopes.length > 0 && (
|
{oauth.scopes && oauth.scopes.length > 0 && (
|
||||||
<SimpleGrid cols={2}>
|
<SimpleGrid cols={2}>
|
||||||
<Text size="sm">Scopes</Text>
|
<Text size="sm">Scopes</Text>
|
||||||
<ValueText>{formatScopes(oauthDetails.scopes)}</ValueText>
|
<ValueText>{formatScopes(oauth.scopes)}</ValueText>
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
)}
|
)}
|
||||||
{oauthDetails.accessToken && (
|
{oauth.accessToken && (
|
||||||
<SimpleGrid cols={2}>
|
<SimpleGrid cols={2}>
|
||||||
<Text size="sm">Access Token</Text>
|
<Text size="sm">Access Token</Text>
|
||||||
<Code>{oauthDetails.accessToken}</Code>
|
<Code>{oauth.accessToken}</Code>
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,16 @@
|
||||||
|
import type { InspectorServerSettings } from "@inspector/core/mcp/types.js";
|
||||||
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 { ServerSettingsForm } from "./ServerSettingsForm";
|
import { ServerSettingsForm } from "./ServerSettingsForm";
|
||||||
|
|
||||||
|
const defaultSettings: InspectorServerSettings = {
|
||||||
|
connectionMode: "proxy",
|
||||||
|
headers: [],
|
||||||
|
metadata: [],
|
||||||
|
connectionTimeout: 30000,
|
||||||
|
requestTimeout: 60000,
|
||||||
|
};
|
||||||
|
|
||||||
const meta: Meta<typeof ServerSettingsForm> = {
|
const meta: Meta<typeof ServerSettingsForm> = {
|
||||||
title: "Groups/ServerSettingsForm",
|
title: "Groups/ServerSettingsForm",
|
||||||
component: ServerSettingsForm,
|
component: ServerSettingsForm,
|
||||||
|
|
@ -25,55 +34,51 @@ type Story = StoryObj<typeof ServerSettingsForm>;
|
||||||
|
|
||||||
export const DefaultSettings: Story = {
|
export const DefaultSettings: Story = {
|
||||||
args: {
|
args: {
|
||||||
connectionMode: "proxy",
|
settings: defaultSettings,
|
||||||
headers: [],
|
|
||||||
metadata: [],
|
|
||||||
connectionTimeout: 30000,
|
|
||||||
requestTimeout: 60000,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WithHeaders: Story = {
|
export const WithHeaders: Story = {
|
||||||
args: {
|
args: {
|
||||||
connectionMode: "proxy",
|
settings: {
|
||||||
headers: [
|
...defaultSettings,
|
||||||
{ key: "Authorization", value: "Bearer token-abc-123" },
|
headers: [
|
||||||
{ key: "X-Custom-Header", value: "custom-value" },
|
{ key: "Authorization", value: "Bearer token-abc-123" },
|
||||||
],
|
{ key: "X-Custom-Header", value: "custom-value" },
|
||||||
metadata: [],
|
],
|
||||||
connectionTimeout: 30000,
|
},
|
||||||
requestTimeout: 60000,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WithOAuth: Story = {
|
export const WithOAuth: Story = {
|
||||||
args: {
|
args: {
|
||||||
connectionMode: "direct",
|
settings: {
|
||||||
headers: [],
|
...defaultSettings,
|
||||||
metadata: [],
|
connectionMode: "direct",
|
||||||
connectionTimeout: 30000,
|
oauthClientId: "my-client-id",
|
||||||
requestTimeout: 60000,
|
oauthClientSecret: "super-secret-value",
|
||||||
oauthClientId: "my-client-id",
|
oauthScopes: "read write admin",
|
||||||
oauthClientSecret: "super-secret-value",
|
},
|
||||||
oauthScopes: "read write admin",
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AllConfigured: Story = {
|
export const AllConfigured: Story = {
|
||||||
args: {
|
args: {
|
||||||
connectionMode: "proxy",
|
settings: {
|
||||||
headers: [
|
connectionMode: "proxy",
|
||||||
{ key: "Authorization", value: "Bearer token-abc-123" },
|
headers: [
|
||||||
{ key: "X-Request-Id", value: "req-456" },
|
{ key: "Authorization", value: "Bearer token-abc-123" },
|
||||||
],
|
{ key: "X-Request-Id", value: "req-456" },
|
||||||
metadata: [
|
],
|
||||||
{ key: "userId", value: "user-789" },
|
metadata: [
|
||||||
{ key: "sessionId", value: "session-012" },
|
{ key: "userId", value: "user-789" },
|
||||||
],
|
{ key: "sessionId", value: "session-012" },
|
||||||
connectionTimeout: 15000,
|
],
|
||||||
requestTimeout: 45000,
|
connectionTimeout: 15000,
|
||||||
oauthClientId: "my-client-id",
|
requestTimeout: 45000,
|
||||||
oauthClientSecret: "super-secret-value",
|
oauthClientId: "my-client-id",
|
||||||
oauthScopes: "read write",
|
oauthClientSecret: "super-secret-value",
|
||||||
|
oauthScopes: "read write",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,7 @@ import {
|
||||||
Text,
|
Text,
|
||||||
TextInput,
|
TextInput,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
|
import type { InspectorServerSettings } from "@inspector/core/mcp/types.js";
|
||||||
export interface KeyValuePair {
|
|
||||||
key: string;
|
|
||||||
value: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ServerSettingsSection =
|
export type ServerSettingsSection =
|
||||||
| "connectionMode"
|
| "connectionMode"
|
||||||
|
|
@ -23,14 +19,7 @@ export type ServerSettingsSection =
|
||||||
| "oauth";
|
| "oauth";
|
||||||
|
|
||||||
export interface ServerSettingsFormProps {
|
export interface ServerSettingsFormProps {
|
||||||
connectionMode: "proxy" | "direct";
|
settings: InspectorServerSettings;
|
||||||
headers: KeyValuePair[];
|
|
||||||
metadata: KeyValuePair[];
|
|
||||||
connectionTimeout: number;
|
|
||||||
requestTimeout: number;
|
|
||||||
oauthClientId?: string;
|
|
||||||
oauthClientSecret?: string;
|
|
||||||
oauthScopes?: string;
|
|
||||||
expandedSections: ServerSettingsSection[];
|
expandedSections: ServerSettingsSection[];
|
||||||
onExpandedSectionsChange: (sections: ServerSettingsSection[]) => void;
|
onExpandedSectionsChange: (sections: ServerSettingsSection[]) => void;
|
||||||
onConnectionModeChange: (mode: string) => void;
|
onConnectionModeChange: (mode: string) => void;
|
||||||
|
|
@ -75,7 +64,7 @@ function KeyValueRows({
|
||||||
onChange,
|
onChange,
|
||||||
onRemove,
|
onRemove,
|
||||||
}: {
|
}: {
|
||||||
items: KeyValuePair[];
|
items: { key: string; value: string }[];
|
||||||
onChange: (index: number, key: string, value: string) => void;
|
onChange: (index: number, key: string, value: string) => void;
|
||||||
onRemove: (index: number) => void;
|
onRemove: (index: number) => void;
|
||||||
}) {
|
}) {
|
||||||
|
|
@ -105,14 +94,7 @@ function KeyValueRows({
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ServerSettingsForm({
|
export function ServerSettingsForm({
|
||||||
connectionMode,
|
settings,
|
||||||
headers,
|
|
||||||
metadata,
|
|
||||||
connectionTimeout,
|
|
||||||
requestTimeout,
|
|
||||||
oauthClientId,
|
|
||||||
oauthClientSecret,
|
|
||||||
oauthScopes,
|
|
||||||
expandedSections,
|
expandedSections,
|
||||||
onExpandedSectionsChange,
|
onExpandedSectionsChange,
|
||||||
onConnectionModeChange,
|
onConnectionModeChange,
|
||||||
|
|
@ -145,12 +127,12 @@ export function ServerSettingsForm({
|
||||||
<Accordion.Panel>
|
<Accordion.Panel>
|
||||||
<Select
|
<Select
|
||||||
data={CONNECTION_MODE_OPTIONS}
|
data={CONNECTION_MODE_OPTIONS}
|
||||||
value={connectionMode}
|
value={settings.connectionMode}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
if (value) onConnectionModeChange(value);
|
if (value) onConnectionModeChange(value);
|
||||||
}}
|
}}
|
||||||
description={
|
description={
|
||||||
connectionMode === "proxy"
|
settings.connectionMode === "proxy"
|
||||||
? "Route through inspector proxy (required for STDIO)"
|
? "Route through inspector proxy (required for STDIO)"
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
|
|
@ -168,11 +150,11 @@ export function ServerSettingsForm({
|
||||||
</HintText>
|
</HintText>
|
||||||
<AddButton onClick={onAddHeader}>+ Add Header</AddButton>
|
<AddButton onClick={onAddHeader}>+ Add Header</AddButton>
|
||||||
</Group>
|
</Group>
|
||||||
{headers.length === 0 ? (
|
{settings.headers.length === 0 ? (
|
||||||
<EmptyHint>No custom headers configured</EmptyHint>
|
<EmptyHint>No custom headers configured</EmptyHint>
|
||||||
) : (
|
) : (
|
||||||
<KeyValueRows
|
<KeyValueRows
|
||||||
items={headers}
|
items={settings.headers}
|
||||||
onChange={onHeaderChange}
|
onChange={onHeaderChange}
|
||||||
onRemove={onRemoveHeader}
|
onRemove={onRemoveHeader}
|
||||||
/>
|
/>
|
||||||
|
|
@ -191,11 +173,11 @@ export function ServerSettingsForm({
|
||||||
</HintText>
|
</HintText>
|
||||||
<AddButton onClick={onAddMetadata}>+ Add Metadata</AddButton>
|
<AddButton onClick={onAddMetadata}>+ Add Metadata</AddButton>
|
||||||
</Group>
|
</Group>
|
||||||
{metadata.length === 0 ? (
|
{settings.metadata.length === 0 ? (
|
||||||
<EmptyHint>No request metadata configured</EmptyHint>
|
<EmptyHint>No request metadata configured</EmptyHint>
|
||||||
) : (
|
) : (
|
||||||
<KeyValueRows
|
<KeyValueRows
|
||||||
items={metadata}
|
items={settings.metadata}
|
||||||
onChange={onMetadataChange}
|
onChange={onMetadataChange}
|
||||||
onRemove={onRemoveMetadata}
|
onRemove={onRemoveMetadata}
|
||||||
/>
|
/>
|
||||||
|
|
@ -211,13 +193,13 @@ export function ServerSettingsForm({
|
||||||
<NumberInput
|
<NumberInput
|
||||||
label="Connection Timeout"
|
label="Connection Timeout"
|
||||||
suffix=" ms"
|
suffix=" ms"
|
||||||
value={connectionTimeout}
|
value={settings.connectionTimeout}
|
||||||
onChange={handleTimeoutChange("connectionTimeout")}
|
onChange={handleTimeoutChange("connectionTimeout")}
|
||||||
/>
|
/>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
label="Request Timeout"
|
label="Request Timeout"
|
||||||
suffix=" ms"
|
suffix=" ms"
|
||||||
value={requestTimeout}
|
value={settings.requestTimeout}
|
||||||
onChange={handleTimeoutChange("requestTimeout")}
|
onChange={handleTimeoutChange("requestTimeout")}
|
||||||
/>
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
@ -234,12 +216,12 @@ export function ServerSettingsForm({
|
||||||
</HintText>
|
</HintText>
|
||||||
<TextInput
|
<TextInput
|
||||||
label="Client ID"
|
label="Client ID"
|
||||||
value={oauthClientId ?? ""}
|
value={settings.oauthClientId ?? ""}
|
||||||
onChange={(e) => onOAuthChange("clientId", e.currentTarget.value)}
|
onChange={(e) => onOAuthChange("clientId", e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label="Client Secret"
|
label="Client Secret"
|
||||||
value={oauthClientSecret ?? ""}
|
value={settings.oauthClientSecret ?? ""}
|
||||||
type="password"
|
type="password"
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
onOAuthChange("clientSecret", e.currentTarget.value)
|
onOAuthChange("clientSecret", e.currentTarget.value)
|
||||||
|
|
@ -247,7 +229,7 @@ export function ServerSettingsForm({
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
label="Scopes"
|
label="Scopes"
|
||||||
value={oauthScopes ?? ""}
|
value={settings.oauthScopes ?? ""}
|
||||||
onChange={(e) => onOAuthChange("scopes", e.currentTarget.value)}
|
onChange={(e) => onOAuthChange("scopes", e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
|
import { AppShell } from "@mantine/core";
|
||||||
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 { AppShell } from "@mantine/core";
|
|
||||||
import { ViewHeader } from "./ViewHeader";
|
import { ViewHeader } from "./ViewHeader";
|
||||||
|
|
||||||
const meta: Meta<typeof ViewHeader> = {
|
const meta: Meta<typeof ViewHeader> = {
|
||||||
|
|
@ -23,7 +23,7 @@ type Story = StoryObj<typeof ViewHeader>;
|
||||||
export const Connected: Story = {
|
export const Connected: Story = {
|
||||||
args: {
|
args: {
|
||||||
connected: true,
|
connected: true,
|
||||||
serverName: "my-mcp-server",
|
serverInfo: { name: "my-mcp-server", version: "1.2.0" },
|
||||||
status: "connected",
|
status: "connected",
|
||||||
latencyMs: 23,
|
latencyMs: 23,
|
||||||
activeTab: "Tools",
|
activeTab: "Tools",
|
||||||
|
|
|
||||||
|
|
@ -9,15 +9,17 @@ import {
|
||||||
Title,
|
Title,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { useComputedColorScheme } from "@mantine/core";
|
import { useComputedColorScheme } from "@mantine/core";
|
||||||
|
import type { Implementation } from "@modelcontextprotocol/sdk/types.js";
|
||||||
import { MdLightMode, MdDarkMode } from "react-icons/md";
|
import { MdLightMode, MdDarkMode } from "react-icons/md";
|
||||||
|
import type { ConnectionStatus } from "@inspector/core/mcp/types.js";
|
||||||
import { ServerStatusIndicator } from "../../elements/ServerStatusIndicator/ServerStatusIndicator";
|
import { ServerStatusIndicator } from "../../elements/ServerStatusIndicator/ServerStatusIndicator";
|
||||||
import mcpLogo from "../../../theme/assets/MCP.svg";
|
import mcpLogo from "../../../theme/assets/MCP.svg";
|
||||||
import mcpLogoDark from "../../../theme/assets/MCP-dark.svg";
|
import mcpLogoDark from "../../../theme/assets/MCP-dark.svg";
|
||||||
|
|
||||||
interface ConnectedProps {
|
interface ConnectedProps {
|
||||||
connected: true;
|
connected: true;
|
||||||
serverName: string;
|
serverInfo: Implementation;
|
||||||
status: "connected" | "connecting" | "error";
|
status: ConnectionStatus;
|
||||||
latencyMs?: number;
|
latencyMs?: number;
|
||||||
activeTab: string;
|
activeTab: string;
|
||||||
availableTabs: string[];
|
availableTabs: string[];
|
||||||
|
|
@ -120,7 +122,7 @@ export function ViewHeader(props: ViewHeaderProps) {
|
||||||
<LogoLink>
|
<LogoLink>
|
||||||
<LogoImage src={colorScheme === "dark" ? mcpLogoDark : mcpLogo} />
|
<LogoImage src={colorScheme === "dark" ? mcpLogoDark : mcpLogo} />
|
||||||
</LogoLink>
|
</LogoLink>
|
||||||
<ServerName>{props.serverName}</ServerName>
|
<ServerName>{props.serverInfo.name}</ServerName>
|
||||||
</LeftSection>
|
</LeftSection>
|
||||||
|
|
||||||
<CenterSection>
|
<CenterSection>
|
||||||
|
|
|
||||||
|
|
@ -46,48 +46,56 @@ function makeServerCallbacks(): Pick<
|
||||||
|
|
||||||
const connectedStdioServer: ServerCardProps = {
|
const connectedStdioServer: ServerCardProps = {
|
||||||
name: "Local Dev Server",
|
name: "Local Dev Server",
|
||||||
version: "1.2.0",
|
config: {
|
||||||
transport: "stdio",
|
command: "npx @modelcontextprotocol/server-filesystem /home/user/projects",
|
||||||
|
},
|
||||||
|
info: { name: "Local Dev Server", version: "1.2.0" },
|
||||||
|
connection: { status: "connected" },
|
||||||
connectionMode: "Direct",
|
connectionMode: "Direct",
|
||||||
command: "npx @modelcontextprotocol/server-filesystem /home/user/projects",
|
|
||||||
status: "connected",
|
|
||||||
canTestClientFeatures: true,
|
canTestClientFeatures: true,
|
||||||
...makeServerCallbacks(),
|
...makeServerCallbacks(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const disconnectedStdioServer: ServerCardProps = {
|
const disconnectedStdioServer: ServerCardProps = {
|
||||||
name: "Database Tools",
|
name: "Database Tools",
|
||||||
version: "0.9.1",
|
config: {
|
||||||
transport: "stdio",
|
command: "python -m mcp_server_sqlite --db-path ./data.db",
|
||||||
|
},
|
||||||
|
info: { name: "Database Tools", version: "0.9.1" },
|
||||||
|
connection: { status: "disconnected" },
|
||||||
connectionMode: "Direct",
|
connectionMode: "Direct",
|
||||||
command: "python -m mcp_server_sqlite --db-path ./data.db",
|
|
||||||
status: "disconnected",
|
|
||||||
canTestClientFeatures: false,
|
canTestClientFeatures: false,
|
||||||
...makeServerCallbacks(),
|
...makeServerCallbacks(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const failedHttpServer: ServerCardProps = {
|
const failedHttpServer: ServerCardProps = {
|
||||||
name: "Remote API Server",
|
name: "Remote API Server",
|
||||||
version: "2.0.0",
|
config: {
|
||||||
transport: "streamable-http",
|
type: "streamable-http",
|
||||||
connectionMode: "Streamable HTTP",
|
url: "https://api.example.com/mcp",
|
||||||
command: "https://api.example.com/mcp",
|
|
||||||
status: "error",
|
|
||||||
retryCount: 3,
|
|
||||||
error: {
|
|
||||||
message: "Connection refused",
|
|
||||||
details: "ECONNREFUSED 127.0.0.1:8080 - The server may not be running.",
|
|
||||||
},
|
},
|
||||||
|
info: { name: "Remote API Server", version: "2.0.0" },
|
||||||
|
connection: {
|
||||||
|
status: "error",
|
||||||
|
retryCount: 3,
|
||||||
|
error: {
|
||||||
|
message: "Connection refused",
|
||||||
|
details: "ECONNREFUSED 127.0.0.1:8080 - The server may not be running.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
connectionMode: "Streamable HTTP",
|
||||||
canTestClientFeatures: false,
|
canTestClientFeatures: false,
|
||||||
...makeServerCallbacks(),
|
...makeServerCallbacks(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const connectingHttpServer: ServerCardProps = {
|
const connectingHttpServer: ServerCardProps = {
|
||||||
name: "Staging Server",
|
name: "Staging Server",
|
||||||
transport: "streamable-http",
|
config: {
|
||||||
|
type: "streamable-http",
|
||||||
|
url: "https://staging.example.com/mcp",
|
||||||
|
},
|
||||||
|
connection: { status: "connecting" },
|
||||||
connectionMode: "Streamable HTTP",
|
connectionMode: "Streamable HTTP",
|
||||||
command: "https://staging.example.com/mcp",
|
|
||||||
status: "connecting",
|
|
||||||
canTestClientFeatures: false,
|
canTestClientFeatures: false,
|
||||||
...makeServerCallbacks(),
|
...makeServerCallbacks(),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ export function ServerListScreen({
|
||||||
const [activeServer, setActiveServer] = useState<string | undefined>();
|
const [activeServer, setActiveServer] = useState<string | undefined>();
|
||||||
|
|
||||||
function handleToggleList() {
|
function handleToggleList() {
|
||||||
setCompact((prev) => (prev === true ? false : true));
|
setCompact((prev) => !prev);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,7 @@ const meta: Meta<typeof ConnectedView> = {
|
||||||
component: ConnectedView,
|
component: ConnectedView,
|
||||||
parameters: { layout: "fullscreen" },
|
parameters: { layout: "fullscreen" },
|
||||||
args: {
|
args: {
|
||||||
serverName: "my-mcp-server",
|
serverInfo: { name: "my-mcp-server", version: "1.0.0" },
|
||||||
status: "connected",
|
status: "connected",
|
||||||
latencyMs: 23,
|
latencyMs: 23,
|
||||||
availableTabs: allTabs,
|
availableTabs: allTabs,
|
||||||
|
|
@ -705,7 +705,10 @@ export const LimitedTabs: Story = {
|
||||||
|
|
||||||
export const LongServerName: Story = {
|
export const LongServerName: Story = {
|
||||||
args: {
|
args: {
|
||||||
serverName: "my-very-long-server-name-that-might-overflow-is",
|
serverInfo: {
|
||||||
|
name: "my-very-long-server-name-that-might-overflow-is",
|
||||||
|
version: "1.0.0",
|
||||||
|
},
|
||||||
children: <Text>Tools screen content</Text>,
|
children: <Text>Tools screen content</Text>,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import { AppShell } from "@mantine/core";
|
import { AppShell } from "@mantine/core";
|
||||||
|
import type { Implementation } from "@modelcontextprotocol/sdk/types.js";
|
||||||
|
import type { ConnectionStatus } from "@inspector/core/mcp/types.js";
|
||||||
import { ViewHeader } from "../../groups/ViewHeader/ViewHeader";
|
import { ViewHeader } from "../../groups/ViewHeader/ViewHeader";
|
||||||
|
|
||||||
export interface ConnectedLayoutProps {
|
export interface ConnectedLayoutProps {
|
||||||
serverName: string;
|
serverInfo: Implementation;
|
||||||
status: "connected" | "connecting" | "error";
|
status: ConnectionStatus;
|
||||||
latencyMs?: number;
|
latencyMs?: number;
|
||||||
activeTab: string;
|
activeTab: string;
|
||||||
availableTabs: string[];
|
availableTabs: string[];
|
||||||
|
|
@ -15,7 +17,7 @@ export interface ConnectedLayoutProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ConnectedView({
|
export function ConnectedView({
|
||||||
serverName,
|
serverInfo,
|
||||||
status,
|
status,
|
||||||
latencyMs,
|
latencyMs,
|
||||||
activeTab,
|
activeTab,
|
||||||
|
|
@ -30,7 +32,7 @@ export function ConnectedView({
|
||||||
<AppShell.Header>
|
<AppShell.Header>
|
||||||
<ViewHeader
|
<ViewHeader
|
||||||
connected
|
connected
|
||||||
serverName={serverName}
|
serverInfo={serverInfo}
|
||||||
status={status}
|
status={status}
|
||||||
latencyMs={latencyMs}
|
latencyMs={latencyMs}
|
||||||
activeTab={activeTab}
|
activeTab={activeTab}
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,38 @@ function makeServerCallbacks(): Pick<
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function makeStdioServer(
|
||||||
|
name: string,
|
||||||
|
version: string,
|
||||||
|
command: string,
|
||||||
|
): ServerCardProps {
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
config: { command },
|
||||||
|
info: { name, version },
|
||||||
|
connection: { status: "disconnected" },
|
||||||
|
connectionMode: "Via Proxy",
|
||||||
|
canTestClientFeatures: false,
|
||||||
|
...makeServerCallbacks(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeHttpServer(
|
||||||
|
name: string,
|
||||||
|
version: string,
|
||||||
|
url: string,
|
||||||
|
): ServerCardProps {
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
config: { type: "streamable-http", url },
|
||||||
|
info: { name, version },
|
||||||
|
connection: { status: "disconnected" },
|
||||||
|
connectionMode: "Direct",
|
||||||
|
canTestClientFeatures: false,
|
||||||
|
...makeServerCallbacks(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export const Empty: Story = {
|
export const Empty: Story = {
|
||||||
args: {
|
args: {
|
||||||
children: (
|
children: (
|
||||||
|
|
@ -68,37 +100,21 @@ export const WithServers: Story = {
|
||||||
children: (
|
children: (
|
||||||
<ServerListScreen
|
<ServerListScreen
|
||||||
servers={[
|
servers={[
|
||||||
{
|
makeStdioServer(
|
||||||
name: "everything-server",
|
"everything-server",
|
||||||
version: "1.0.0",
|
"1.0.0",
|
||||||
transport: "stdio",
|
"npx -y @modelcontextprotocol/server-everything",
|
||||||
connectionMode: "Via Proxy",
|
),
|
||||||
command: "npx -y @modelcontextprotocol/server-everything",
|
makeStdioServer(
|
||||||
status: "disconnected",
|
"filesystem-server",
|
||||||
canTestClientFeatures: false,
|
"0.6.2",
|
||||||
...makeServerCallbacks(),
|
"npx -y @modelcontextprotocol/server-filesystem /home/user",
|
||||||
},
|
),
|
||||||
{
|
makeHttpServer(
|
||||||
name: "filesystem-server",
|
"remote-server",
|
||||||
version: "0.6.2",
|
"2.1.0",
|
||||||
transport: "stdio",
|
"https://api.example.com/mcp",
|
||||||
connectionMode: "Via Proxy",
|
),
|
||||||
command:
|
|
||||||
"npx -y @modelcontextprotocol/server-filesystem /home/user",
|
|
||||||
status: "disconnected",
|
|
||||||
canTestClientFeatures: false,
|
|
||||||
...makeServerCallbacks(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "remote-server",
|
|
||||||
version: "2.1.0",
|
|
||||||
transport: "streamable-http",
|
|
||||||
connectionMode: "Direct",
|
|
||||||
command: "https://api.example.com/mcp",
|
|
||||||
status: "disconnected",
|
|
||||||
canTestClientFeatures: false,
|
|
||||||
...makeServerCallbacks(),
|
|
||||||
},
|
|
||||||
]}
|
]}
|
||||||
onAddManually={fn()}
|
onAddManually={fn()}
|
||||||
onImportConfig={fn()}
|
onImportConfig={fn()}
|
||||||
|
|
@ -117,16 +133,11 @@ function SettingsModalStory() {
|
||||||
<UnconnectedView onToggleTheme={fn()}>
|
<UnconnectedView onToggleTheme={fn()}>
|
||||||
<ServerListScreen
|
<ServerListScreen
|
||||||
servers={[
|
servers={[
|
||||||
{
|
makeStdioServer(
|
||||||
name: "everything-server",
|
"everything-server",
|
||||||
version: "1.0.0",
|
"1.0.0",
|
||||||
transport: "stdio",
|
"npx -y @modelcontextprotocol/server-everything",
|
||||||
connectionMode: "Via Proxy",
|
),
|
||||||
command: "npx -y @modelcontextprotocol/server-everything",
|
|
||||||
status: "disconnected",
|
|
||||||
canTestClientFeatures: false,
|
|
||||||
...makeServerCallbacks(),
|
|
||||||
},
|
|
||||||
]}
|
]}
|
||||||
onAddManually={fn()}
|
onAddManually={fn()}
|
||||||
onImportConfig={fn()}
|
onImportConfig={fn()}
|
||||||
|
|
@ -158,17 +169,19 @@ function SettingsModalStory() {
|
||||||
styles={{ title: { flex: 1 } }}
|
styles={{ title: { flex: 1 } }}
|
||||||
>
|
>
|
||||||
<ServerSettingsForm
|
<ServerSettingsForm
|
||||||
connectionMode="proxy"
|
settings={{
|
||||||
headers={[
|
connectionMode: "proxy",
|
||||||
{ key: "Authorization", value: "Bearer token-abc-123" },
|
headers: [
|
||||||
{ key: "X-Request-Id", value: "req-456" },
|
{ key: "Authorization", value: "Bearer token-abc-123" },
|
||||||
]}
|
{ key: "X-Request-Id", value: "req-456" },
|
||||||
metadata={[{ key: "userId", value: "user-789" }]}
|
],
|
||||||
connectionTimeout={30000}
|
metadata: [{ key: "userId", value: "user-789" }],
|
||||||
requestTimeout={60000}
|
connectionTimeout: 30000,
|
||||||
oauthClientId="my-client-id"
|
requestTimeout: 60000,
|
||||||
oauthClientSecret="super-secret-value"
|
oauthClientId: "my-client-id",
|
||||||
oauthScopes="read write"
|
oauthClientSecret: "super-secret-value",
|
||||||
|
oauthScopes: "read write",
|
||||||
|
}}
|
||||||
expandedSections={expandedSections}
|
expandedSections={expandedSections}
|
||||||
onExpandedSectionsChange={setExpandedSections}
|
onExpandedSectionsChange={setExpandedSections}
|
||||||
onConnectionModeChange={fn()}
|
onConnectionModeChange={fn()}
|
||||||
|
|
@ -195,127 +208,66 @@ export const ManyServers: Story = {
|
||||||
children: (
|
children: (
|
||||||
<ServerListScreen
|
<ServerListScreen
|
||||||
servers={[
|
servers={[
|
||||||
{
|
makeStdioServer(
|
||||||
name: "everything-server",
|
"everything-server",
|
||||||
version: "1.0.0",
|
"1.0.0",
|
||||||
transport: "stdio",
|
"npx -y @modelcontextprotocol/server-everything",
|
||||||
connectionMode: "Via Proxy",
|
),
|
||||||
command: "npx -y @modelcontextprotocol/server-everything",
|
makeStdioServer(
|
||||||
status: "disconnected",
|
"filesystem-server",
|
||||||
canTestClientFeatures: false,
|
"0.6.2",
|
||||||
...makeServerCallbacks(),
|
"npx -y @modelcontextprotocol/server-filesystem /home/user",
|
||||||
},
|
),
|
||||||
{
|
makeHttpServer(
|
||||||
name: "filesystem-server",
|
"remote-api-server",
|
||||||
version: "0.6.2",
|
"2.1.0",
|
||||||
transport: "stdio",
|
"https://api.example.com/mcp",
|
||||||
connectionMode: "Via Proxy",
|
),
|
||||||
command:
|
makeStdioServer(
|
||||||
"npx -y @modelcontextprotocol/server-filesystem /home/user",
|
"postgres-server",
|
||||||
status: "disconnected",
|
"1.3.0",
|
||||||
canTestClientFeatures: false,
|
"npx -y @modelcontextprotocol/server-postgres",
|
||||||
...makeServerCallbacks(),
|
),
|
||||||
},
|
makeStdioServer(
|
||||||
{
|
"github-server",
|
||||||
name: "remote-api-server",
|
"0.9.1",
|
||||||
version: "2.1.0",
|
"npx -y @modelcontextprotocol/server-github",
|
||||||
transport: "streamable-http",
|
),
|
||||||
connectionMode: "Direct",
|
makeHttpServer(
|
||||||
command: "https://api.example.com/mcp",
|
"slack-server",
|
||||||
status: "disconnected",
|
"1.1.0",
|
||||||
canTestClientFeatures: false,
|
"https://slack-mcp.example.com/mcp",
|
||||||
...makeServerCallbacks(),
|
),
|
||||||
},
|
makeStdioServer(
|
||||||
{
|
"memory-server",
|
||||||
name: "postgres-server",
|
"0.2.0",
|
||||||
version: "1.3.0",
|
"npx -y @modelcontextprotocol/server-memory",
|
||||||
transport: "stdio",
|
),
|
||||||
connectionMode: "Via Proxy",
|
makeStdioServer(
|
||||||
command: "npx -y @modelcontextprotocol/server-postgres",
|
"puppeteer-server",
|
||||||
status: "disconnected",
|
"1.0.3",
|
||||||
canTestClientFeatures: false,
|
"npx -y @modelcontextprotocol/server-puppeteer",
|
||||||
...makeServerCallbacks(),
|
),
|
||||||
},
|
makeStdioServer(
|
||||||
{
|
"sqlite-server",
|
||||||
name: "github-server",
|
"0.5.1",
|
||||||
version: "0.9.1",
|
"npx -y @modelcontextprotocol/server-sqlite ./data.db",
|
||||||
transport: "stdio",
|
),
|
||||||
connectionMode: "Via Proxy",
|
makeStdioServer(
|
||||||
command: "npx -y @modelcontextprotocol/server-github",
|
"brave-search-server",
|
||||||
status: "disconnected",
|
"1.2.0",
|
||||||
canTestClientFeatures: false,
|
"npx -y @modelcontextprotocol/server-brave-search",
|
||||||
...makeServerCallbacks(),
|
),
|
||||||
},
|
makeHttpServer(
|
||||||
{
|
"google-maps-server",
|
||||||
name: "slack-server",
|
"0.8.0",
|
||||||
version: "1.1.0",
|
"https://maps-mcp.example.com/mcp",
|
||||||
transport: "streamable-http",
|
),
|
||||||
connectionMode: "Direct",
|
makeStdioServer(
|
||||||
command: "https://slack-mcp.example.com/mcp",
|
"sequential-thinking-server",
|
||||||
status: "disconnected",
|
"1.4.2",
|
||||||
canTestClientFeatures: false,
|
"npx -y @modelcontextprotocol/server-sequential-thinking",
|
||||||
...makeServerCallbacks(),
|
),
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "memory-server",
|
|
||||||
version: "0.2.0",
|
|
||||||
transport: "stdio",
|
|
||||||
connectionMode: "Via Proxy",
|
|
||||||
command: "npx -y @modelcontextprotocol/server-memory",
|
|
||||||
status: "disconnected",
|
|
||||||
canTestClientFeatures: false,
|
|
||||||
...makeServerCallbacks(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "puppeteer-server",
|
|
||||||
version: "1.0.3",
|
|
||||||
transport: "stdio",
|
|
||||||
connectionMode: "Via Proxy",
|
|
||||||
command: "npx -y @modelcontextprotocol/server-puppeteer",
|
|
||||||
status: "disconnected",
|
|
||||||
canTestClientFeatures: false,
|
|
||||||
...makeServerCallbacks(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "sqlite-server",
|
|
||||||
version: "0.5.1",
|
|
||||||
transport: "stdio",
|
|
||||||
connectionMode: "Via Proxy",
|
|
||||||
command: "npx -y @modelcontextprotocol/server-sqlite ./data.db",
|
|
||||||
status: "disconnected",
|
|
||||||
canTestClientFeatures: false,
|
|
||||||
...makeServerCallbacks(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "brave-search-server",
|
|
||||||
version: "1.2.0",
|
|
||||||
transport: "stdio",
|
|
||||||
connectionMode: "Via Proxy",
|
|
||||||
command: "npx -y @modelcontextprotocol/server-brave-search",
|
|
||||||
status: "disconnected",
|
|
||||||
canTestClientFeatures: false,
|
|
||||||
...makeServerCallbacks(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "google-maps-server",
|
|
||||||
version: "0.8.0",
|
|
||||||
transport: "streamable-http",
|
|
||||||
connectionMode: "Direct",
|
|
||||||
command: "https://maps-mcp.example.com/mcp",
|
|
||||||
status: "disconnected",
|
|
||||||
canTestClientFeatures: false,
|
|
||||||
...makeServerCallbacks(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "sequential-thinking-server",
|
|
||||||
version: "1.4.2",
|
|
||||||
transport: "stdio",
|
|
||||||
connectionMode: "Via Proxy",
|
|
||||||
command: "npx -y @modelcontextprotocol/server-sequential-thinking",
|
|
||||||
status: "disconnected",
|
|
||||||
canTestClientFeatures: false,
|
|
||||||
...makeServerCallbacks(),
|
|
||||||
},
|
|
||||||
]}
|
]}
|
||||||
onAddManually={fn()}
|
onAddManually={fn()}
|
||||||
onImportConfig={fn()}
|
onImportConfig={fn()}
|
||||||
|
|
|
||||||
|
|
@ -173,6 +173,22 @@ export interface InspectorResourceSubscription {
|
||||||
lastUpdated?: Date;
|
lastUpdated?: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
export interface InspectorServerSettings {
|
||||||
|
connectionMode: "proxy" | "direct";
|
||||||
|
headers: { key: string; value: string }[];
|
||||||
|
metadata: { key: string; value: string }[];
|
||||||
|
connectionTimeout: number;
|
||||||
|
requestTimeout: number;
|
||||||
|
oauthClientId?: string;
|
||||||
|
oauthClientSecret?: string;
|
||||||
|
oauthScopes?: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draft state for importing a server from registry JSON. Owned by the
|
* Draft state for importing a server from registry JSON. Owned by the
|
||||||
* ImportServerJsonPanel wiring layer. `parsed` is typed `unknown` until the
|
* ImportServerJsonPanel wiring layer. `parsed` is typed `unknown` until the
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue