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>
This commit is contained in:
cliffhall 2026-04-18 16:57:10 -04:00
commit 92ecfcbd6c

View file

@ -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<string, unknown>;
isExecuting: boolean;
progress?: { percent: number; description?: string };
progress?: Pick<
ProgressNotification["params"],
"progress" | "total" | "message"
>;
onFormChange: (values: Record<string, unknown>) => 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 (
<Stack gap="md" miw={0}>
<ToolTitle>{resolveTitle(name, title)}</ToolTitle>
{hasAnyAnnotation(annotations) && annotations && (
<Group gap="xs">
{annotations.audience && (
<AnnotationBadge
facet="audience"
value={
annotations.audience.split(", ") as ("user" | "assistant")[]
}
/>
)}
{annotations.readOnly && (
{annotations.readOnlyHint && (
<AnnotationBadge facet="readOnlyHint" value={true} />
)}
{annotations.destructive && (
{annotations.destructiveHint && (
<AnnotationBadge facet="destructiveHint" value={true} />
)}
{annotations.longRunning && (
<AnnotationBadge facet="longRunHint" value={true} />
{annotations.idempotentHint && (
<AnnotationBadge facet="idempotentHint" value={true} />
)}
{annotations.openWorldHint && (
<AnnotationBadge facet="openWorldHint" value={true} />
)}
{annotations.hints && <HintsText>{annotations.hints}</HintsText>}
</Group>
)}
@ -107,20 +88,13 @@ export function ToolDetailPanel({
<Divider />
<SchemaForm
schema={schema}
schema={inputSchema}
values={formValues}
onChange={onFormChange}
disabled={isExecuting}
/>
{progress && (
<ProgressDisplay
params={{
progress: progress.percent,
message: progress.description,
}}
/>
)}
{progress && <ProgressDisplay params={progress} />}
<Group justify="flex-end">
{isExecuting && <CancelButton onClick={onCancel}>Cancel</CancelButton>}