diff --git a/clients/web/src/components/groups/ToolControls/ToolControls.stories.tsx b/clients/web/src/components/groups/ToolControls/ToolControls.stories.tsx index 9a5cc96a..87dd8719 100644 --- a/clients/web/src/components/groups/ToolControls/ToolControls.stories.tsx +++ b/clients/web/src/components/groups/ToolControls/ToolControls.stories.tsx @@ -1,4 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; +import type { Tool } from "@modelcontextprotocol/sdk/types.js"; import { fn } from "storybook/test"; import { ToolControls } from "./ToolControls"; @@ -15,22 +16,20 @@ const meta: Meta = { export default meta; type Story = StoryObj; -const sampleTools = [ +const sampleTools: Tool[] = [ { name: "send_message", title: "Send Message", - selected: false, - onClick: fn(), + inputSchema: { type: "object" }, }, { name: "create_record", title: "Create Record", - selected: false, - onClick: fn(), + inputSchema: { type: "object" }, }, - { name: "delete_records", selected: false, onClick: fn() }, - { name: "list_users", selected: false, onClick: fn() }, - { name: "batch_process", selected: false, onClick: fn() }, + { name: "delete_records", inputSchema: { type: "object" } }, + { name: "list_users", inputSchema: { type: "object" } }, + { name: "batch_process", inputSchema: { type: "object" } }, ]; export const Default: Story = { @@ -41,9 +40,8 @@ export const Default: Story = { export const WithSelection: Story = { args: { - tools: sampleTools.map((t) => - t.name === "create_record" ? { ...t, selected: true } : t, - ), + tools: sampleTools, + selectedName: "create_record", }, }; diff --git a/clients/web/src/components/groups/ToolControls/ToolControls.tsx b/clients/web/src/components/groups/ToolControls/ToolControls.tsx index 33db7738..cc533276 100644 --- a/clients/web/src/components/groups/ToolControls/ToolControls.tsx +++ b/clients/web/src/components/groups/ToolControls/ToolControls.tsx @@ -1,11 +1,12 @@ import { useState } from "react"; import { Group, ScrollArea, Stack, TextInput, Title } from "@mantine/core"; +import type { Tool } from "@modelcontextprotocol/sdk/types.js"; import { ListChangedIndicator } from "../../elements/ListChangedIndicator/ListChangedIndicator"; import { ToolListItem } from "../ToolListItem/ToolListItem"; -import type { ToolListItemProps } from "../ToolListItem/ToolListItem"; export interface ToolControlsProps { - tools: ToolListItemProps[]; + tools: Tool[]; + selectedName?: string; listChanged: boolean; onRefreshList: () => void; onSelectTool: (name: string) => void; @@ -17,6 +18,7 @@ function listMaxHeight(): string { export function ToolControls({ tools, + selectedName, listChanged, onRefreshList, onSelectTool, @@ -47,8 +49,11 @@ export function ToolControls({ {filteredTools.map((tool) => ( onSelectTool(tool.name)} + tool={tool} + selected={tool.name === selectedName} + onClick={() => { + if (tool.name !== selectedName) onSelectTool(tool.name); + }} /> ))} diff --git a/clients/web/src/components/groups/ToolDetailPanel/ToolDetailPanel.stories.tsx b/clients/web/src/components/groups/ToolDetailPanel/ToolDetailPanel.stories.tsx index 559a0a22..9760b8c1 100644 --- a/clients/web/src/components/groups/ToolDetailPanel/ToolDetailPanel.stories.tsx +++ b/clients/web/src/components/groups/ToolDetailPanel/ToolDetailPanel.stories.tsx @@ -1,4 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; +import type { Tool } from "@modelcontextprotocol/sdk/types.js"; import { fn } from "storybook/test"; import { ToolDetailPanel } from "./ToolDetailPanel"; @@ -17,83 +18,102 @@ const meta: Meta = { export default meta; type Story = StoryObj; +const sendMessageTool: Tool = { + name: "send_message", + inputSchema: { + type: "object", + properties: { + message: { type: "string", description: "The message to send" }, + }, + required: ["message"], + }, +}; + +const createRecordTool: Tool = { + name: "create_record", + description: "Creates a new record with the given parameters", + inputSchema: { + type: "object", + properties: { + title: { type: "string", description: "Record title" }, + count: { type: "number", description: "Number of items" }, + enabled: { + type: "boolean", + description: "Whether the record is active", + }, + }, + required: ["title"], + }, +}; + +const deleteRecordsTool: Tool = { + name: "delete_records", + description: "Deletes records matching the given criteria", + annotations: { + readOnlyHint: true, + destructiveHint: true, + }, + inputSchema: { + type: "object", + properties: { + pattern: { type: "string", description: "Pattern to match records" }, + }, + }, +}; + +const longQueryTool: Tool = { + name: "long_query", + description: "Runs a long database query", + inputSchema: { + type: "object", + properties: { + query: { type: "string", description: "SQL query to execute" }, + }, + }, +}; + +const batchProcessTool: Tool = { + name: "batch_process", + description: "Processes items in batch", + inputSchema: { + type: "object", + properties: { + batchSize: { type: "number", description: "Number of items per batch" }, + }, + }, +}; + export const SimpleStringParam: Story = { args: { - name: "send_message", - schema: { - type: "object", - properties: { - message: { type: "string", description: "The message to send" }, - }, - required: ["message"], - }, + tool: sendMessageTool, }, }; export const MultipleParams: Story = { args: { - name: "create_record", - description: "Creates a new record with the given parameters", - schema: { - type: "object", - properties: { - title: { type: "string", description: "Record title" }, - count: { type: "number", description: "Number of items" }, - enabled: { - type: "boolean", - description: "Whether the record is active", - }, - }, - required: ["title"], - }, + tool: createRecordTool, }, }; export const WithAnnotations: Story = { args: { - name: "delete_records", - description: "Deletes records matching the given criteria", - annotations: { - audience: "admin", - readOnly: true, - destructive: true, - }, - schema: { - type: "object", - properties: { - pattern: { type: "string", description: "Pattern to match records" }, - }, - }, + tool: deleteRecordsTool, }, }; export const Executing: Story = { args: { - name: "long_query", - description: "Runs a long database query", + tool: longQueryTool, isExecuting: true, - schema: { - type: "object", - properties: { - query: { type: "string", description: "SQL query to execute" }, - }, - }, formValues: { query: "SELECT * FROM users" }, }, }; export const WithProgress: Story = { args: { - name: "batch_process", - description: "Processes items in batch", + tool: batchProcessTool, isExecuting: true, - progress: { percent: 60, description: "Processing step 3 of 5" }, - schema: { - type: "object", - properties: { - batchSize: { type: "number", description: "Number of items per batch" }, - }, - }, + progress: { progress: 3, total: 5, message: "Processing step 3 of 5" }, formValues: { batchSize: 100 }, }, }; diff --git a/clients/web/src/components/groups/ToolDetailPanel/ToolDetailPanel.tsx b/clients/web/src/components/groups/ToolDetailPanel/ToolDetailPanel.tsx index 2673d960..72b5f1c3 100644 --- a/clients/web/src/components/groups/ToolDetailPanel/ToolDetailPanel.tsx +++ b/clients/web/src/components/groups/ToolDetailPanel/ToolDetailPanel.tsx @@ -1,26 +1,21 @@ import { Button, Divider, Group, Stack, Text } from "@mantine/core"; +import type { + ProgressNotification, + Tool, + ToolAnnotations, +} from "@modelcontextprotocol/sdk/types.js"; import { AnnotationBadge } from "../../elements/AnnotationBadge/AnnotationBadge"; import { ProgressDisplay } from "../../elements/ProgressDisplay/ProgressDisplay"; import { SchemaForm } from "../SchemaForm/SchemaForm"; -import type { JsonSchema } from "../SchemaForm/SchemaForm"; - -export interface ToolAnnotations { - audience?: string; - readOnly?: boolean; - destructive?: boolean; - longRunning?: boolean; - hints?: string; -} export interface ToolDetailPanelProps { - name: string; - title?: string; - description?: string; - annotations?: ToolAnnotations; - schema: JsonSchema; + tool: Tool; formValues: Record; isExecuting: boolean; - progress?: { percent: number; description?: string }; + progress?: Pick< + ProgressNotification["params"], + "progress" | "total" | "message" + >; onFormChange: (values: Record) => void; onExecute: () => void; onCancel: () => void; @@ -32,19 +27,13 @@ const ToolTitle = Text.withProps({ truncate: "end", }); -const HintsText = Text.withProps({ - size: "xs", - c: "dimmed", - fs: "italic", -}); - const DescriptionText = Text.withProps({ size: "sm", c: "dimmed", }); const CancelButton = Button.withProps({ - variant: "light", + variant: "subtle", color: "red", }); @@ -55,20 +44,16 @@ function resolveTitle(name: string, title?: string): string { function hasAnyAnnotation(annotations?: ToolAnnotations): boolean { return !!( annotations && - (annotations.audience || - annotations.readOnly || - annotations.destructive || - annotations.longRunning || - annotations.hints) + (annotations.title || + annotations.readOnlyHint || + annotations.destructiveHint || + annotations.idempotentHint || + annotations.openWorldHint) ); } export function ToolDetailPanel({ - name, - title, - description, - annotations, - schema, + tool, formValues, isExecuting, progress, @@ -76,29 +61,25 @@ export function ToolDetailPanel({ onExecute, onCancel, }: ToolDetailPanelProps) { + const { name, title, description, annotations, inputSchema } = tool; + return ( {resolveTitle(name, title)} {hasAnyAnnotation(annotations) && annotations && ( - {annotations.audience && ( - - )} - {annotations.readOnly && ( + {annotations.readOnlyHint && ( )} - {annotations.destructive && ( + {annotations.destructiveHint && ( )} - {annotations.longRunning && ( - + {annotations.idempotentHint && ( + + )} + {annotations.openWorldHint && ( + )} - {annotations.hints && {annotations.hints}} )} @@ -107,20 +88,13 @@ export function ToolDetailPanel({ - {progress && ( - - )} + {progress && } {isExecuting && Cancel} diff --git a/clients/web/src/components/groups/ToolListItem/ToolListItem.stories.tsx b/clients/web/src/components/groups/ToolListItem/ToolListItem.stories.tsx index cc3c03b5..86936a46 100644 --- a/clients/web/src/components/groups/ToolListItem/ToolListItem.stories.tsx +++ b/clients/web/src/components/groups/ToolListItem/ToolListItem.stories.tsx @@ -1,4 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; +import type { Tool } from "@modelcontextprotocol/sdk/types.js"; import { fn } from "storybook/test"; import { ToolListItem } from "./ToolListItem"; @@ -13,31 +14,44 @@ const meta: Meta = { export default meta; type Story = StoryObj; +const weatherTool: Tool = { + name: "get_weather", + inputSchema: { type: "object" }, +}; + +const weatherToolWithTitle: Tool = { + name: "get_weather", + title: "Get Weather", + inputSchema: { type: "object" }, +}; + export const Default: Story = { args: { - name: "get_weather", + tool: weatherTool, selected: false, }, }; export const Selected: Story = { args: { - name: "get_weather", + tool: weatherTool, selected: true, }, }; export const WithTitle: Story = { args: { - name: "get_weather", - title: "Get Weather", + tool: weatherToolWithTitle, selected: false, }, }; export const LongName: Story = { args: { - name: "this_is_a_very_long_tool_name_that_might_need_to_wrap_across_multiple_lines_in_the_ui", + tool: { + name: "this_is_a_very_long_tool_name_that_might_need_to_wrap_across_multiple_lines_in_the_ui", + inputSchema: { type: "object" }, + }, selected: false, }, }; diff --git a/clients/web/src/components/groups/ToolListItem/ToolListItem.tsx b/clients/web/src/components/groups/ToolListItem/ToolListItem.tsx index bbe8c427..fa4c86f9 100644 --- a/clients/web/src/components/groups/ToolListItem/ToolListItem.tsx +++ b/clients/web/src/components/groups/ToolListItem/ToolListItem.tsx @@ -1,8 +1,8 @@ import { Stack, Text, UnstyledButton } from "@mantine/core"; +import type { Tool } from "@modelcontextprotocol/sdk/types.js"; export interface ToolListItemProps { - name: string; - title?: string; + tool: Tool; selected: boolean; onClick: () => void; } @@ -22,12 +22,8 @@ function resolveLabel(name: string, title?: string): string { return title ?? name; } -export function ToolListItem({ - name, - title, - selected, - onClick, -}: ToolListItemProps) { +export function ToolListItem({ tool, selected, onClick }: ToolListItemProps) { + const { name, title } = tool; return ( = { export default meta; type Story = StoryObj; +const emptyResult: CallToolResult = { + content: [], +}; + +const textResult: CallToolResult = { + content: [ + { + type: "text", + text: "The current weather in San Francisco is 65°F and sunny.", + }, + ], +}; + +const jsonResult: CallToolResult = { + content: [ + { + type: "text", + text: '{"temperature":65,"unit":"fahrenheit","condition":"sunny","city":"San Francisco"}', + }, + ], +}; + +const imageResult: CallToolResult = { + content: [ + { + type: "image", + data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==", + mimeType: "image/png", + }, + ], +}; + +const mixedResult: CallToolResult = { + content: [ + { + type: "text", + text: "Here is the generated image based on your description:", + }, + { + type: "image", + data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==", + mimeType: "image/png", + }, + ], +}; + export const Empty: Story = { args: { - content: [], + result: emptyResult, }, }; export const TextResult: Story = { args: { - content: [ - { - type: "text", - text: "The current weather in San Francisco is 65°F and sunny.", - }, - ], + result: textResult, }, }; export const JsonResult: Story = { args: { - content: [ - { - type: "json", - text: '{"temperature":65,"unit":"fahrenheit","condition":"sunny","city":"San Francisco"}', - }, - ], + result: jsonResult, }, }; export const ImageResult: Story = { args: { - content: [ - { - type: "image", - data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==", - mimeType: "image/png", - }, - ], + result: imageResult, }, }; export const MixedContent: Story = { args: { - content: [ - { - type: "text", - text: "Here is the generated image based on your description:", - }, - { - type: "image", - data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==", - mimeType: "image/png", - }, - ], + result: mixedResult, }, }; diff --git a/clients/web/src/components/groups/ToolResultPanel/ToolResultPanel.tsx b/clients/web/src/components/groups/ToolResultPanel/ToolResultPanel.tsx index 86956c37..13a41e91 100644 --- a/clients/web/src/components/groups/ToolResultPanel/ToolResultPanel.tsx +++ b/clients/web/src/components/groups/ToolResultPanel/ToolResultPanel.tsx @@ -1,16 +1,9 @@ import { Button, Group, Stack, Text, Title } from "@mantine/core"; -import type { ContentBlock } from "@modelcontextprotocol/sdk/types.js"; +import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { ContentViewer } from "../../elements/ContentViewer/ContentViewer"; -export interface ResultContentItem { - type: string; - text?: string; - data?: string; - mimeType?: string; -} - export interface ToolResultPanelProps { - content: ResultContentItem[]; + result: CallToolResult; onClear: () => void; } @@ -19,41 +12,24 @@ const ClearButton = Button.withProps({ size: "sm", }); -function toContentBlock(item: ResultContentItem): ContentBlock | null { - if (item.text !== undefined) { - return { type: "text", text: item.text }; - } - if (item.data !== undefined && item.mimeType?.startsWith("image")) { - return { type: "image", data: item.data, mimeType: item.mimeType }; - } - if (item.data !== undefined && item.mimeType) { - return { type: "audio", data: item.data, mimeType: item.mimeType }; - } - return null; -} - -export function ToolResultPanel({ content, onClear }: ToolResultPanelProps) { +export function ToolResultPanel({ result, onClear }: ToolResultPanelProps) { return ( Results Clear - {content.length === 0 ? ( + {result.content.length === 0 ? ( No results yet ) : ( <> - {content.map((item, index) => { - const block = toContentBlock(item); - if (!block) return null; - return ( - - ); - })} + {result.content.map((block, index) => ( + + ))} )} diff --git a/clients/web/src/components/screens/ToolsScreen/ToolsScreen.stories.tsx b/clients/web/src/components/screens/ToolsScreen/ToolsScreen.stories.tsx index a1725a02..4748f1fb 100644 --- a/clients/web/src/components/screens/ToolsScreen/ToolsScreen.stories.tsx +++ b/clients/web/src/components/screens/ToolsScreen/ToolsScreen.stories.tsx @@ -1,9 +1,8 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; +import type { Tool } from "@modelcontextprotocol/sdk/types.js"; import { fn } from "storybook/test"; import { ToolsScreen } from "./ToolsScreen"; -import type { ToolListItemProps } from "../../groups/ToolListItem/ToolListItem"; -import type { ToolDetailPanelProps } from "../../groups/ToolDetailPanel/ToolDetailPanel"; -import type { ToolResultPanelProps } from "../../groups/ToolResultPanel/ToolResultPanel"; +import type { ToolCallState } from "./ToolsScreen"; const meta: Meta = { title: "Screens/ToolsScreen", @@ -13,85 +12,65 @@ const meta: Meta = { listChanged: false, onRefreshList: fn(), onSelectTool: fn(), + onCallTool: fn(), + onCancelCall: fn(), + onClearResult: fn(), }, }; export default meta; type Story = StoryObj; -const sampleTools: ToolListItemProps[] = [ +const sampleTools: Tool[] = [ { name: "send_message", title: "Send Message", - selected: false, - onClick: fn(), + inputSchema: { type: "object" }, }, { name: "create_record", title: "Create Record", - selected: false, - onClick: fn(), - }, - { - name: "delete_records", - selected: false, - onClick: fn(), - }, - { name: "list_users", selected: false, onClick: fn() }, - { - name: "batch_process", - selected: false, - onClick: fn(), + description: "Creates a new record with the given parameters", + inputSchema: { + type: "object", + properties: { + title: { type: "string", description: "Record title" }, + count: { type: "number", description: "Number of items" }, + enabled: { + type: "boolean", + description: "Whether the record is active", + }, + }, + required: ["title"], + }, }, + { name: "delete_records", inputSchema: { type: "object" } }, + { name: "list_users", inputSchema: { type: "object" } }, + { name: "batch_process", inputSchema: { type: "object" } }, ]; -const selectedToolData: ToolDetailPanelProps = { - name: "create_record", - title: "Create Record", - description: "Creates a new record with the given parameters", - schema: { - type: "object", - properties: { - title: { type: "string", description: "Record title" }, - count: { type: "number", description: "Number of items" }, - enabled: { type: "boolean", description: "Whether the record is active" }, - }, - required: ["title"], +const resultState: ToolCallState = { + status: "ok", + result: { + content: [ + { + type: "text", + text: JSON.stringify( + { + id: 42, + title: "New Record", + count: 5, + enabled: true, + createdAt: "2026-03-17T12:00:00Z", + }, + null, + 2, + ), + }, + ], }, - formValues: {}, - isExecuting: false, - onFormChange: fn(), - onExecute: fn(), - onCancel: fn(), }; -const resultData: ToolResultPanelProps = { - content: [ - { - type: "text", - text: JSON.stringify( - { - id: 42, - title: "New Record", - count: 5, - enabled: true, - createdAt: "2026-03-17T12:00:00Z", - }, - null, - 2, - ), - }, - ], - onClear: fn(), -}; - -function toolsWithSelected(selectedName: string): ToolListItemProps[] { - return sampleTools.map((tool) => ({ - ...tool, - selected: tool.name === selectedName, - })); -} - export const NoSelection: Story = { args: { tools: sampleTools, @@ -100,16 +79,16 @@ export const NoSelection: Story = { export const ToolSelected: Story = { args: { - tools: toolsWithSelected("create_record"), - selectedTool: selectedToolData, + tools: sampleTools, + selectedToolName: "create_record", }, }; export const WithResult: Story = { args: { - tools: toolsWithSelected("create_record"), - selectedTool: selectedToolData, - result: resultData, + tools: sampleTools, + selectedToolName: "create_record", + callState: resultState, }, }; @@ -119,43 +98,34 @@ export const LongToolName: Story = { ...sampleTools, { name: "organization_internal_database_complex_multi_table_join_query_with_aggregation_and_filtering", - selected: true, - onClick: fn(), + description: + "Executes a complex multi-table join query across the organization's internal database with support for aggregation functions, nested filtering, and pagination of large result sets.", + annotations: { + readOnlyHint: true, + }, + inputSchema: { + type: "object", + properties: { + primary_table_name: { + type: "string", + description: "The main table to query from", + }, + join_configuration: { + type: "string", + description: "JSON configuration for table joins", + }, + aggregation_functions: { + type: "string", + description: + "Comma-separated list of aggregation functions to apply", + }, + }, + required: ["primary_table_name"], + }, }, ], - selectedTool: { - name: "organization_internal_database_complex_multi_table_join_query_with_aggregation_and_filtering", - annotations: { - readOnly: true, - longRunning: true, - }, - description: - "Executes a complex multi-table join query across the organization's internal database with support for aggregation functions, nested filtering, and pagination of large result sets.", - schema: { - type: "object", - properties: { - primary_table_name: { - type: "string", - description: "The main table to query from", - }, - join_configuration: { - type: "string", - description: "JSON configuration for table joins", - }, - aggregation_functions: { - type: "string", - description: - "Comma-separated list of aggregation functions to apply", - }, - }, - required: ["primary_table_name"], - }, - formValues: {}, - isExecuting: false, - onFormChange: fn(), - onExecute: fn(), - onCancel: fn(), - }, + selectedToolName: + "organization_internal_database_complex_multi_table_join_query_with_aggregation_and_filtering", }, }; diff --git a/clients/web/src/components/screens/ToolsScreen/ToolsScreen.tsx b/clients/web/src/components/screens/ToolsScreen/ToolsScreen.tsx index e6636cfe..fbfdad05 100644 --- a/clients/web/src/components/screens/ToolsScreen/ToolsScreen.tsx +++ b/clients/web/src/components/screens/ToolsScreen/ToolsScreen.tsx @@ -1,18 +1,34 @@ +import { useState } from "react"; import { Card, Flex, Stack, Text } from "@mantine/core"; +import type { + CallToolResult, + ProgressNotification, + Tool, +} from "@modelcontextprotocol/sdk/types.js"; import { ToolControls } from "../../groups/ToolControls/ToolControls"; import { ToolDetailPanel } from "../../groups/ToolDetailPanel/ToolDetailPanel"; import { ToolResultPanel } from "../../groups/ToolResultPanel/ToolResultPanel"; -import type { ToolListItemProps } from "../../groups/ToolListItem/ToolListItem"; -import type { ToolDetailPanelProps } from "../../groups/ToolDetailPanel/ToolDetailPanel"; -import type { ToolResultPanelProps } from "../../groups/ToolResultPanel/ToolResultPanel"; + +export interface ToolCallState { + status: "idle" | "pending" | "ok" | "error"; + result?: CallToolResult; + error?: string; + progress?: Pick< + ProgressNotification["params"], + "progress" | "total" | "message" + >; +} export interface ToolsScreenProps { - tools: ToolListItemProps[]; - selectedTool?: ToolDetailPanelProps; - result?: ToolResultPanelProps; + tools: Tool[]; + selectedToolName?: string; + callState?: ToolCallState; listChanged: boolean; onRefreshList: () => void; onSelectTool: (name: string) => void; + onCallTool: (name: string, args: Record) => void; + onCancelCall?: () => void; + onClearResult?: () => void; } const ScreenLayout = Flex.withProps({ @@ -46,18 +62,28 @@ const EmptyState = Text.withProps({ export function ToolsScreen({ tools, - selectedTool, - result, + selectedToolName, + callState, listChanged, onRefreshList, onSelectTool, + onCallTool, + onCancelCall, + onClearResult, }: ToolsScreenProps) { + const [formValues, setFormValues] = useState>({}); + const selectedTool = selectedToolName + ? tools.find((t) => t.name === selectedToolName) + : undefined; + const isExecuting = callState?.status === "pending"; + return ( {selectedTool ? ( - + onCallTool(selectedTool.name, formValues)} + onCancel={() => onCancelCall?.()} + /> ) : ( Select a tool to view details )} - {result ? ( - + {callState?.result ? ( + onClearResult?.()} + /> ) : ( Results will appear here )} diff --git a/clients/web/src/components/views/ConnectedView/ConnectedView.stories.tsx b/clients/web/src/components/views/ConnectedView/ConnectedView.stories.tsx index d5f6dcde..fd8baa01 100644 --- a/clients/web/src/components/views/ConnectedView/ConnectedView.stories.tsx +++ b/clients/web/src/components/views/ConnectedView/ConnectedView.stories.tsx @@ -173,228 +173,194 @@ export const ToolsActive: Story = { { name: "send_message", title: "Send Message", - selected: false, - onClick: fn(), + inputSchema: { type: "object" }, }, { name: "create_record", title: "Create Record", - selected: true, - onClick: fn(), + description: "Creates a new record with the given parameters", + inputSchema: { + type: "object", + properties: { + title: { type: "string", description: "Record title" }, + count: { type: "number", description: "Number of items" }, + enabled: { + type: "boolean", + description: "Whether the record is active", + }, + }, + required: ["title"], + }, }, { name: "delete_records", title: "Delete Records", - selected: false, - onClick: fn(), + inputSchema: { type: "object" }, }, { name: "list_users", title: "List Users", - selected: false, - onClick: fn(), + inputSchema: { type: "object" }, }, { name: "batch_process", title: "Batch Process", - selected: false, - onClick: fn(), + inputSchema: { type: "object" }, }, { name: "search_files", title: "Search Files", - selected: false, - onClick: fn(), + inputSchema: { type: "object" }, }, { name: "read_file", title: "Read File", - selected: false, - onClick: fn(), + inputSchema: { type: "object" }, }, { name: "write_file", title: "Write File", - selected: false, - onClick: fn(), + inputSchema: { type: "object" }, }, { name: "get_schema", title: "Get Schema", - selected: false, - onClick: fn(), + inputSchema: { type: "object" }, }, { name: "run_query", title: "Run Query", - selected: false, - onClick: fn(), + inputSchema: { type: "object" }, }, { name: "create_migration", title: "Create Migration", - selected: false, - onClick: fn(), + inputSchema: { type: "object" }, }, { name: "validate_config", title: "Validate Config", - selected: false, - onClick: fn(), + inputSchema: { type: "object" }, }, { name: "deploy_service", title: "Deploy Service", - selected: false, - onClick: fn(), + inputSchema: { type: "object" }, }, { name: "rollback_deploy", title: "Rollback Deploy", - selected: false, - onClick: fn(), + inputSchema: { type: "object" }, }, { name: "check_health", title: "Check Health", - selected: false, - onClick: fn(), + inputSchema: { type: "object" }, }, { name: "generate_report", title: "Generate Report", - selected: false, - onClick: fn(), + inputSchema: { type: "object" }, }, { name: "export_csv", title: "Export CSV", - selected: false, - onClick: fn(), + inputSchema: { type: "object" }, }, { name: "import_data", title: "Import Data", - selected: false, - onClick: fn(), + inputSchema: { type: "object" }, }, { name: "send_notification", title: "Send Notification", - selected: false, - onClick: fn(), + inputSchema: { type: "object" }, }, { name: "schedule_task", title: "Schedule Task", - selected: false, - onClick: fn(), + inputSchema: { type: "object" }, }, { name: "cancel_task", title: "Cancel Task", - selected: false, - onClick: fn(), + inputSchema: { type: "object" }, }, { name: "list_connections", title: "List Connections", - selected: false, - onClick: fn(), + inputSchema: { type: "object" }, }, { name: "refresh_cache", title: "Refresh Cache", - selected: false, - onClick: fn(), + inputSchema: { type: "object" }, }, { name: "purge_logs", title: "Purge Logs", - selected: false, - onClick: fn(), + inputSchema: { type: "object" }, }, { name: "encrypt_secret", title: "Encrypt Secret", - selected: false, - onClick: fn(), + inputSchema: { type: "object" }, }, { name: "rotate_keys", title: "Rotate Keys", - selected: false, - onClick: fn(), + inputSchema: { type: "object" }, }, { name: "sync_resources", title: "Sync Resources", - selected: false, - onClick: fn(), + inputSchema: { type: "object" }, }, { name: "compile_template", title: "Compile Template", - selected: false, - onClick: fn(), + inputSchema: { type: "object" }, }, { name: "analyze_performance", title: "Analyze Performance", - selected: false, - onClick: fn(), + inputSchema: { type: "object" }, }, { name: "resize_image", title: "Resize Image", - selected: false, - onClick: fn(), + inputSchema: { type: "object" }, }, ]} - selectedTool={{ - name: "create_record", - title: "Create Record", - description: "Creates a new record with the given parameters", - schema: { - type: "object", - properties: { - title: { type: "string", description: "Record title" }, - count: { type: "number", description: "Number of items" }, - enabled: { - type: "boolean", - description: "Whether the record is active", + selectedToolName="create_record" + callState={{ + status: "ok", + result: { + content: [ + { + type: "text", + text: JSON.stringify( + { + id: 42, + title: "New Record", + count: 5, + enabled: true, + createdAt: "2026-03-17T12:00:00Z", + }, + null, + 2, + ), }, - }, - required: ["title"], + ], }, - formValues: {}, - isExecuting: false, - onFormChange: fn(), - onExecute: fn(), - onCancel: fn(), - }} - result={{ - content: [ - { - type: "text", - text: JSON.stringify( - { - id: 42, - title: "New Record", - count: 5, - enabled: true, - createdAt: "2026-03-17T12:00:00Z", - }, - null, - 2, - ), - }, - ], - onClear: fn(), }} listChanged={true} onRefreshList={fn()} onSelectTool={fn()} + onCallTool={fn()} + onCancelCall={fn()} + onClearResult={fn()} /> ), },