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: