diff --git a/clients/web/src/App.tsx b/clients/web/src/App.tsx index a1a313e7..7d64073c 100644 --- a/clients/web/src/App.tsx +++ b/clients/web/src/App.tsx @@ -1,41 +1,59 @@ import { - Title, - Text, - Container, - Stack, - useMantineColorScheme, - useComputedColorScheme, ActionIcon, + Container, Group, + Stack, + Text, + Title, + useComputedColorScheme, + useMantineColorScheme, } from "@mantine/core"; +import { MdDarkMode, MdLightMode } from "react-icons/md"; + +const PageContainer = Container.withProps({ + size: "sm", + py: "xl", +}); + +const PageStack = Stack.withProps({ + align: "center", + gap: "md", +}); + +const ThemeToggle = ActionIcon.withProps({ + variant: "default", + size: "lg", + "aria-label": "Toggle color scheme", +}); + +const SubtitleText = Text.withProps({ + c: "var(--inspector-text-secondary)", +}); function App() { const { setColorScheme } = useMantineColorScheme(); const computedColorScheme = useComputedColorScheme("light"); + const isDark = computedColorScheme === "dark"; + const ThemeIcon = isDark ? MdLightMode : MdDarkMode; - const toggleColorScheme = () => { - setColorScheme(computedColorScheme === "dark" ? "light" : "dark"); - }; + function toggleColorScheme() { + setColorScheme(isDark ? "light" : "dark"); + } return ( - - + + MCP Inspector - - {computedColorScheme === "dark" ? "\u2600" : "\u263E"} - + + + - + Web client for the Model Context Protocol Inspector - - - + + + ); } diff --git a/clients/web/src/components/elements/ConnectionToggle/ConnectionToggle.stories.tsx b/clients/web/src/components/elements/ConnectionToggle/ConnectionToggle.stories.tsx index faa765db..2ed0b159 100644 --- a/clients/web/src/components/elements/ConnectionToggle/ConnectionToggle.stories.tsx +++ b/clients/web/src/components/elements/ConnectionToggle/ConnectionToggle.stories.tsx @@ -6,8 +6,7 @@ const meta: Meta = { title: "Elements/ConnectionToggle", component: ConnectionToggle, args: { - onConnect: fn(), - onDisconnect: fn(), + onToggle: fn(), }, }; diff --git a/clients/web/src/components/elements/ConnectionToggle/ConnectionToggle.tsx b/clients/web/src/components/elements/ConnectionToggle/ConnectionToggle.tsx index 5dcb6265..9ab70e2b 100644 --- a/clients/web/src/components/elements/ConnectionToggle/ConnectionToggle.tsx +++ b/clients/web/src/components/elements/ConnectionToggle/ConnectionToggle.tsx @@ -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} /> ); } diff --git a/clients/web/src/components/elements/ServerStatusIndicator/ServerStatusIndicator.tsx b/clients/web/src/components/elements/ServerStatusIndicator/ServerStatusIndicator.tsx index 58f65cfc..fc4858e2 100644 --- a/clients/web/src/components/elements/ServerStatusIndicator/ServerStatusIndicator.tsx +++ b/clients/web/src/components/elements/ServerStatusIndicator/ServerStatusIndicator.tsx @@ -1,10 +1,15 @@ import { Group, Paper, Text } from "@mantine/core"; +import { useMediaQuery } from "@mantine/hooks"; import type { ConnectionStatus } from "@inspector/core/mcp/types.js"; export interface ServerStatusIndicatorProps { status: ConnectionStatus; latencyMs?: number; retryCount?: number; + // Override for the default viewport-based label visibility. Useful when + // the indicator renders inside a constrained container (sidebar, card) + // where the global media query doesn't reflect available space. + showLabel?: boolean; } const statusColorVar: Record = { @@ -41,14 +46,19 @@ export function ServerStatusIndicator({ status, latencyMs, retryCount, + showLabel: showLabelProp, }: ServerStatusIndicatorProps) { + const wideViewport = useMediaQuery("(min-width: 1200px)"); + const showLabel = showLabelProp ?? wideViewport; + const label = getLabel(status, latencyMs, retryCount); return ( - {getLabel(status, latencyMs, retryCount)} + {showLabel && {label}} ); } diff --git a/clients/web/src/components/elements/TransportBadge/TransportBadge.tsx b/clients/web/src/components/elements/TransportBadge/TransportBadge.tsx index 129d88e1..2a553967 100644 --- a/clients/web/src/components/elements/TransportBadge/TransportBadge.tsx +++ b/clients/web/src/components/elements/TransportBadge/TransportBadge.tsx @@ -7,7 +7,7 @@ export interface TransportBadgeProps { const transportLabel: Record = { stdio: "STDIO", - sse: "SSE", + sse: "HTTP", "streamable-http": "HTTP", }; diff --git a/clients/web/src/components/groups/ImportServerJsonPanel/ImportServerJsonPanel.stories.tsx b/clients/web/src/components/groups/ImportServerJsonPanel/ImportServerJsonPanel.stories.tsx index 63acd629..1b84e316 100644 --- a/clients/web/src/components/groups/ImportServerJsonPanel/ImportServerJsonPanel.stories.tsx +++ b/clients/web/src/components/groups/ImportServerJsonPanel/ImportServerJsonPanel.stories.tsx @@ -1,7 +1,13 @@ +import type { InspectorServerJsonDraft } from "@inspector/core/mcp/types.js"; import type { Meta, StoryObj } from "@storybook/react-vite"; import { fn } from "storybook/test"; import { ImportServerJsonPanel } from "./ImportServerJsonPanel"; +const emptyDraft: InspectorServerJsonDraft = { + rawText: "", + envOverrides: {}, +}; + const meta: Meta = { title: "Groups/ImportServerJsonPanel", component: ImportServerJsonPanel, @@ -13,10 +19,8 @@ const meta: Meta = { onServerNameChange: fn(), onAddServer: fn(), onCancel: fn(), - selectedPackageIndex: 0, envVars: [], - serverName: "", - validationResults: [], + validation: [], }, }; @@ -25,28 +29,31 @@ type Story = StoryObj; export const Empty: Story = { args: { - jsonContent: "", + draft: emptyDraft, }, }; export const ValidJson: Story = { args: { - jsonContent: JSON.stringify( - { - name: "my-mcp-server", - version: "1.0.0", - packages: [ - { - registryType: "npm", - identifier: "my-mcp-server", - runtimeHint: "node", - }, - ], - }, - null, - 2, - ), - validationResults: [ + draft: { + rawText: JSON.stringify( + { + name: "my-mcp-server", + version: "1.0.0", + packages: [ + { + registryType: "npm", + identifier: "my-mcp-server", + runtimeHint: "node", + }, + ], + }, + null, + 2, + ), + envOverrides: {}, + }, + validation: [ { type: "success", message: "Valid server.json format detected" }, { type: "success", message: "Package configuration is valid" }, { type: "info", message: "1 package found" }, @@ -56,31 +63,38 @@ export const ValidJson: Story = { export const MultiplePackages: Story = { args: { - jsonContent: JSON.stringify( - { - name: "multi-server", - packages: [ - { - registryType: "npm", - identifier: "@scope/server", - runtimeHint: "node", - }, - { - registryType: "pip", - identifier: "server-python", - runtimeHint: "python3", - }, - ], - }, - null, - 2, - ), - validationResults: [ + draft: { + rawText: JSON.stringify( + { + name: "multi-server", + packages: [ + { + registryType: "npm", + identifier: "@scope/server", + runtimeHint: "node", + }, + { + registryType: "pip", + identifier: "server-python", + runtimeHint: "python3", + }, + ], + }, + null, + 2, + ), + envOverrides: {}, + }, + validation: [ { type: "success", message: "Valid server.json format detected" }, { type: "info", message: "2 packages found - select one to install" }, ], packages: [ - { registryType: "npm", identifier: "@scope/server", runtimeHint: "node" }, + { + registryType: "npm", + identifier: "@scope/server", + runtimeHint: "node", + }, { registryType: "pip", identifier: "server-python", @@ -92,25 +106,28 @@ export const MultiplePackages: Story = { export const WithEnvVars: Story = { args: { - jsonContent: JSON.stringify( - { - name: "api-server", - packages: [ - { - registryType: "npm", - identifier: "api-server", - runtimeHint: "node", - }, - ], - envVars: [ - { name: "API_KEY", required: true }, - { name: "API_URL", required: false }, - ], - }, - null, - 2, - ), - validationResults: [ + draft: { + rawText: JSON.stringify( + { + name: "api-server", + packages: [ + { + registryType: "npm", + identifier: "api-server", + runtimeHint: "node", + }, + ], + envVars: [ + { name: "API_KEY", required: true }, + { name: "API_URL", required: false }, + ], + }, + null, + 2, + ), + envOverrides: {}, + }, + validation: [ { type: "success", message: "Valid server.json format detected" }, { type: "warning", @@ -136,31 +153,36 @@ export const WithEnvVars: Story = { export const FullyConfigured: Story = { args: { - jsonContent: JSON.stringify( - { - name: "full-server", - version: "2.1.0", - packages: [ - { - registryType: "npm", - identifier: "@org/full-server", - runtimeHint: "node", - }, - { - registryType: "docker", - identifier: "org/full-server:latest", - runtimeHint: "docker", - }, - ], - envVars: [ - { name: "TOKEN", required: true }, - { name: "DEBUG", required: false }, - ], - }, - null, - 2, - ), - validationResults: [ + draft: { + rawText: JSON.stringify( + { + name: "full-server", + version: "2.1.0", + packages: [ + { + registryType: "npm", + identifier: "@org/full-server", + runtimeHint: "node", + }, + { + registryType: "docker", + identifier: "org/full-server:latest", + runtimeHint: "docker", + }, + ], + envVars: [ + { name: "TOKEN", required: true }, + { name: "DEBUG", required: false }, + ], + }, + null, + 2, + ), + selectedPackageIndex: 1, + envOverrides: { TOKEN: "sk-abc123", DEBUG: "true" }, + nameOverride: "My Custom Server", + }, + validation: [ { type: "success", message: "Valid server.json format detected" }, { type: "success", message: "All required fields present" }, { type: "info", message: "2 packages found" }, @@ -181,7 +203,6 @@ export const FullyConfigured: Story = { runtimeHint: "docker", }, ], - selectedPackageIndex: 1, envVars: [ { name: "TOKEN", @@ -196,6 +217,5 @@ export const FullyConfigured: Story = { value: "true", }, ], - serverName: "My Custom Server", }, }; diff --git a/clients/web/src/components/groups/ImportServerJsonPanel/ImportServerJsonPanel.tsx b/clients/web/src/components/groups/ImportServerJsonPanel/ImportServerJsonPanel.tsx index da19f528..68dd8e1e 100644 --- a/clients/web/src/components/groups/ImportServerJsonPanel/ImportServerJsonPanel.tsx +++ b/clients/web/src/components/groups/ImportServerJsonPanel/ImportServerJsonPanel.tsx @@ -9,6 +9,7 @@ import { Textarea, Title, } from "@mantine/core"; +import type { InspectorServerJsonDraft } from "@inspector/core/mcp/types.js"; export interface ValidationResult { type: "success" | "warning" | "info" | "error"; @@ -29,12 +30,10 @@ export interface EnvVarInfo { } export interface ImportServerJsonPanelProps { - jsonContent: string; - validationResults: ValidationResult[]; + draft: InspectorServerJsonDraft; + validation: ValidationResult[]; packages?: PackageInfo[]; - selectedPackageIndex: number; envVars: EnvVarInfo[]; - serverName: string; onJsonChange: (content: string) => void; onValidate: () => void; onSelectPackage: (index: number) => void; @@ -64,12 +63,10 @@ function formatPackageLabel(pkg: PackageInfo): string { } export function ImportServerJsonPanel({ - jsonContent, - validationResults, + draft, + validation, packages, - selectedPackageIndex, envVars, - serverName, onJsonChange, onValidate, onSelectPackage, @@ -85,7 +82,7 @@ export function ImportServerJsonPanel({ Paste server.json content or drag and drop a file: