inspector/clients/web/src/components/groups/ToolDetailPanel/ToolDetailPanel.tsx
cliffhall 92ecfcbd6c Phase 2: Refactor Tools cluster to use MCP SDK types
Replace ad hoc local types with MCP SDK schema types across the Tools
group components and ToolsScreen, per the v2 UX interfaces plan.

- ToolListItem: accept `tool: Tool` instead of flat `name`/`title`
- ToolControls: accept `Tool[]` + `selectedName` instead of `ToolListItemProps[]`
- ToolDetailPanel: accept `tool: Tool` instead of flat props; use SDK
  `ToolAnnotations` (readOnlyHint/destructiveHint/idempotentHint/openWorldHint);
  use `ProgressNotification['params']` for progress
- ToolResultPanel: accept `CallToolResult` instead of `ResultContentItem[]`
- ToolsScreen: accept `Tool[]` + `selectedToolName` + `ToolCallState`;
  derive selected tool via `tools.find()`; own `formValues` state locally
- Delete local `ToolAnnotations`, `ResultContentItem` types
- Update all stories with real SDK `Tool` fixtures
- Update ConnectedView ToolsActive story

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 16:57:10 -04:00

112 lines
2.8 KiB
TypeScript

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";
export interface ToolDetailPanelProps {
tool: Tool;
formValues: Record<string, unknown>;
isExecuting: boolean;
progress?: Pick<
ProgressNotification["params"],
"progress" | "total" | "message"
>;
onFormChange: (values: Record<string, unknown>) => void;
onExecute: () => void;
onCancel: () => void;
}
const ToolTitle = Text.withProps({
fw: 700,
size: "lg",
truncate: "end",
});
const DescriptionText = Text.withProps({
size: "sm",
c: "dimmed",
});
const CancelButton = Button.withProps({
variant: "subtle",
color: "red",
});
function resolveTitle(name: string, title?: string): string {
return title ?? name;
}
function hasAnyAnnotation(annotations?: ToolAnnotations): boolean {
return !!(
annotations &&
(annotations.title ||
annotations.readOnlyHint ||
annotations.destructiveHint ||
annotations.idempotentHint ||
annotations.openWorldHint)
);
}
export function ToolDetailPanel({
tool,
formValues,
isExecuting,
progress,
onFormChange,
onExecute,
onCancel,
}: ToolDetailPanelProps) {
const { name, title, description, annotations, inputSchema } = tool;
return (
<Stack gap="md" miw={0}>
<ToolTitle>{resolveTitle(name, title)}</ToolTitle>
{hasAnyAnnotation(annotations) && annotations && (
<Group gap="xs">
{annotations.readOnlyHint && (
<AnnotationBadge facet="readOnlyHint" value={true} />
)}
{annotations.destructiveHint && (
<AnnotationBadge facet="destructiveHint" value={true} />
)}
{annotations.idempotentHint && (
<AnnotationBadge facet="idempotentHint" value={true} />
)}
{annotations.openWorldHint && (
<AnnotationBadge facet="openWorldHint" value={true} />
)}
</Group>
)}
{description && <DescriptionText>{description}</DescriptionText>}
<Divider />
<SchemaForm
schema={inputSchema}
values={formValues}
onChange={onFormChange}
disabled={isExecuting}
/>
{progress && <ProgressDisplay params={progress} />}
<Group justify="flex-end">
{isExecuting && <CancelButton onClick={onCancel}>Cancel</CancelButton>}
<Button
size="md"
onClick={onExecute}
disabled={isExecuting}
loading={isExecuting}
>
Execute Tool
</Button>
</Group>
</Stack>
);
}