Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
658 changes: 610 additions & 48 deletions clients/web/src/App.tsx

Large diffs are not rendered by default.

8 changes: 7 additions & 1 deletion clients/web/src/components/groups/ServerCard/ServerCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,13 @@ function getCommandOrUrl(config: MCPServerConfig): string {
if (config.type === "sse" || config.type === "streamable-http") {
return config.url;
}
return config.command;
// Show the full argv for stdio so the card displays the same thing
// that gets spawned. Otherwise a `command: "npx", args: ["-y", "pkg"]`
// config renders as just "npx", which is misleading.
const args = config.args ?? [];
return args.length > 0
? `${config.command} ${args.join(" ")}`
: config.command;
}

export function ServerCard({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import type {
InitializeResult,
Prompt,
Resource,
ResourceTemplate,
Task,
Tool,
} from "@modelcontextprotocol/sdk/types.js";
import type { AppBridge } from "@modelcontextprotocol/ext-apps/app-bridge";
import type {
InspectorResourceSubscription,
MessageEntry,
Expand All @@ -17,6 +19,19 @@ import { mixedEntries as demoLogs } from "../../screens/LoggingScreen/LoggingScr
import { longToolList as demoRegularTools } from "../../screens/ToolsScreen/ToolsScreen.fixtures";
import { SUN_ICON_SVG } from "../../../test/fixtures/storyIcons";
import type { TaskProgress } from "../../groups/TaskCard/TaskCard";
import type { BridgeFactory } from "../../elements/AppRenderer/AppRenderer";

// Stories never drive a real MCP App bridge — render the iframe stage with
// a no-op factory so the AppsScreen mounts without trying to postMessage to
// a real sandbox.
const noopBridgeFactory: BridgeFactory = () =>
({
sendToolInput: async () => {},
sendToolResult: async () => {},
sendToolCancelled: async () => {},
teardownResource: async () => ({}),
close: async () => {},
}) as unknown as AppBridge;

// MCP App tools — `isAppTool` detects these via `_meta.ui.resourceUri`,
// so they get filtered into the Apps tab while still appearing on Tools.
Expand Down Expand Up @@ -234,12 +249,18 @@ const demoHistory: MessageEntry[] = [
},
];

const demoInitializeResult: InitializeResult = {
protocolVersion: "2025-06-18",
capabilities: {},
serverInfo: { name: "Local Dev Server", version: "1.2.0" },
};

const meta: Meta<typeof InspectorView> = {
title: "Views/InspectorView",
component: InspectorView,
parameters: { layout: "fullscreen" },
args: {
onToggleTheme: fn(),
// Data
servers: demoServers,
tools: demoTools,
prompts: demoPrompts,
Expand All @@ -250,6 +271,57 @@ const meta: Meta<typeof InspectorView> = {
tasks: demoTasks,
progressByTaskId: demoProgressByTaskId,
history: demoHistory,

// Connection state — stories default to "disconnected"; per-story
// overrides drive the connected / error narratives.
activeServer: undefined,
connectionStatus: "disconnected",
initializeResult: undefined,
latencyMs: undefined,
errorMessage: undefined,

// Misc state
currentLogLevel: "info",
sandboxPath: "about:blank",
bridgeFactory: noopBridgeFactory,

// Callbacks — all wired to storybook spies so play functions can assert
// on dispatch. Real wiring routes these to InspectorClient methods (the
// app shell at clients/web/src/App.tsx).
onToggleTheme: fn(),
onToggleConnection: fn(),
onDisconnect: fn(),
onServerAdd: fn(),
onServerImportConfig: fn(),
onServerImportJson: fn(),
onServerInfo: fn(),
onServerSettings: fn(),
onServerEdit: fn(),
onServerClone: fn(),
onServerRemove: fn(),
onCallTool: fn(),
onRefreshTools: fn(),
onGetPrompt: fn(),
onRefreshPrompts: fn(),
onReadResource: fn(),
onSubscribeResource: fn(),
onUnsubscribeResource: fn(),
onRefreshResources: fn(),
onCancelTask: fn(),
onClearCompletedTasks: fn(),
onRefreshTasks: fn(),
onSetLogLevel: fn(),
onClearLogs: fn(),
onExportLogs: fn(),
onCopyAllLogs: fn(),
onClearHistory: fn(),
onExportHistory: fn(),
onReplayHistory: fn(),
onTogglePinHistory: fn(),
onSelectApp: fn(),
onOpenApp: fn(),
onCloseApp: fn(),
onRefreshApps: fn(),
},
};

Expand All @@ -263,3 +335,24 @@ export const NoServers: Story = {
servers: [],
},
};

// Renders the connected-state shell (full tab list, ViewHeader in connected
// mode). The other tabs still render their disconnected fixtures because
// the lists are passed through as static data — that's fine for visual
// regression / storybook play function coverage.
export const Connected: Story = {
args: {
activeServer: demoServers[0]!.id,
connectionStatus: "connected",
initializeResult: demoInitializeResult,
latencyMs: 142,
},
};

export const ConnectionError: Story = {
args: {
activeServer: demoServers[0]!.id,
connectionStatus: "error",
errorMessage: "Handshake timeout",
},
};
Loading
Loading