diff --git a/clients/web/src/components/groups/ToolDetailPanel/ToolDetailPanel.stories.tsx b/clients/web/src/components/groups/ToolDetailPanel/ToolDetailPanel.stories.tsx
index 9dbee5daa..c53cd81c5 100644
--- a/clients/web/src/components/groups/ToolDetailPanel/ToolDetailPanel.stories.tsx
+++ b/clients/web/src/components/groups/ToolDetailPanel/ToolDetailPanel.stories.tsx
@@ -71,6 +71,26 @@ const longQueryTool: Tool = {
},
};
+const ICON_DATA_URL =
+ "data:image/svg+xml;utf8," +
+ encodeURIComponent(
+ '',
+ );
+
+const iconedTool: Tool = {
+ name: "send_message",
+ title: "Send Message",
+ description: "Sends a message to the recipient",
+ icons: [{ src: ICON_DATA_URL }],
+ inputSchema: {
+ type: "object",
+ properties: {
+ message: { type: "string", description: "The message to send" },
+ },
+ required: ["message"],
+ },
+};
+
const batchProcessTool: Tool = {
name: "batch_process",
description: "Processes items in batch",
@@ -94,6 +114,12 @@ export const MultipleParams: Story = {
},
};
+export const WithIcon: Story = {
+ args: {
+ tool: iconedTool,
+ },
+};
+
export const WithAnnotations: Story = {
args: {
tool: deleteRecordsTool,
diff --git a/clients/web/src/components/groups/ToolDetailPanel/ToolDetailPanel.test.tsx b/clients/web/src/components/groups/ToolDetailPanel/ToolDetailPanel.test.tsx
index 8c87f98bf..95a93ad3e 100644
--- a/clients/web/src/components/groups/ToolDetailPanel/ToolDetailPanel.test.tsx
+++ b/clients/web/src/components/groups/ToolDetailPanel/ToolDetailPanel.test.tsx
@@ -27,6 +27,18 @@ const titledTool: Tool = {
},
};
+const ICON_SRC = "data:image/svg+xml,%3Csvg/%3E";
+
+const iconedTool: Tool = {
+ name: "send_message",
+ title: "Send Message",
+ icons: [{ src: ICON_SRC }],
+ inputSchema: {
+ type: "object",
+ properties: {},
+ },
+};
+
const annotatedTool: Tool = {
name: "delete_records",
description: "Deletes records",
@@ -66,6 +78,17 @@ describe("ToolDetailPanel", () => {
).toBeInTheDocument();
});
+ it("renders the first icon when tool.icons is present", () => {
+ renderWithMantine();
+ const img = screen.getByRole("presentation");
+ expect(img).toHaveAttribute("src", ICON_SRC);
+ });
+
+ it("does not render an icon when tool.icons is missing", () => {
+ renderWithMantine();
+ expect(screen.queryByRole("presentation")).not.toBeInTheDocument();
+ });
+
it("does not render the description when none is provided", () => {
renderWithMantine();
expect(
diff --git a/clients/web/src/components/groups/ToolDetailPanel/ToolDetailPanel.tsx b/clients/web/src/components/groups/ToolDetailPanel/ToolDetailPanel.tsx
index 18780ee36..a3f5be717 100644
--- a/clients/web/src/components/groups/ToolDetailPanel/ToolDetailPanel.tsx
+++ b/clients/web/src/components/groups/ToolDetailPanel/ToolDetailPanel.tsx
@@ -1,4 +1,4 @@
-import { Button, Divider, Group, Stack, Text } from "@mantine/core";
+import { Button, Divider, Group, Image, Stack, Text } from "@mantine/core";
import type {
ProgressNotification,
Tool,
@@ -24,6 +24,19 @@ export interface ToolDetailPanelProps {
onCancel: () => void;
}
+const TitleRow = Group.withProps({
+ gap: "sm",
+ wrap: "nowrap",
+ align: "center",
+ miw: 0,
+});
+
+const ToolIcon = Image.withProps({
+ w: 24,
+ h: 24,
+ fit: "contain",
+});
+
const ToolTitle = Text.withProps({
fw: 700,
size: "lg",
@@ -59,11 +72,15 @@ export function ToolDetailPanel({
onExecute,
onCancel,
}: ToolDetailPanelProps) {
- const { name, title, description, annotations, inputSchema } = tool;
+ const { name, title, description, icons, annotations, inputSchema } = tool;
+ const iconSrc = icons?.[0]?.src;
return (
- {resolveDisplayLabel(name, title)}
+
+ {iconSrc && }
+ {resolveDisplayLabel(name, title)}
+
{hasAnyAnnotation(annotations) && annotations && (
{annotations.readOnlyHint && (
diff --git a/clients/web/src/components/groups/ToolListItem/ToolListItem.stories.tsx b/clients/web/src/components/groups/ToolListItem/ToolListItem.stories.tsx
index 86936a46d..e9bfc3187 100644
--- a/clients/web/src/components/groups/ToolListItem/ToolListItem.stories.tsx
+++ b/clients/web/src/components/groups/ToolListItem/ToolListItem.stories.tsx
@@ -25,6 +25,19 @@ const weatherToolWithTitle: Tool = {
inputSchema: { type: "object" },
};
+const ICON_DATA_URL =
+ "data:image/svg+xml;utf8," +
+ encodeURIComponent(
+ '',
+ );
+
+const weatherToolWithIcon: Tool = {
+ name: "get_weather",
+ title: "Get Weather",
+ icons: [{ src: ICON_DATA_URL }],
+ inputSchema: { type: "object" },
+};
+
export const Default: Story = {
args: {
tool: weatherTool,
@@ -46,6 +59,13 @@ export const WithTitle: Story = {
},
};
+export const WithIcon: Story = {
+ args: {
+ tool: weatherToolWithIcon,
+ selected: false,
+ },
+};
+
export const LongName: Story = {
args: {
tool: {
diff --git a/clients/web/src/components/groups/ToolListItem/ToolListItem.test.tsx b/clients/web/src/components/groups/ToolListItem/ToolListItem.test.tsx
index 749edbc47..f7b5aadbe 100644
--- a/clients/web/src/components/groups/ToolListItem/ToolListItem.test.tsx
+++ b/clients/web/src/components/groups/ToolListItem/ToolListItem.test.tsx
@@ -9,6 +9,8 @@ const tool: Tool = {
inputSchema: { type: "object" },
};
+const ICON_SRC = "data:image/svg+xml,%3Csvg/%3E";
+
describe("ToolListItem", () => {
it("renders the name when title is missing", () => {
renderWithMantine(
@@ -43,4 +45,23 @@ describe("ToolListItem", () => {
renderWithMantine( {}} />);
expect(screen.getByRole("button")).toBeInTheDocument();
});
+
+ it("renders the first icon when tool.icons is present", () => {
+ renderWithMantine(
+ {}}
+ />,
+ );
+ const img = screen.getByRole("presentation");
+ expect(img).toHaveAttribute("src", ICON_SRC);
+ });
+
+ it("does not render an icon when tool.icons is missing", () => {
+ renderWithMantine(
+ {}} />,
+ );
+ expect(screen.queryByRole("presentation")).not.toBeInTheDocument();
+ });
});
diff --git a/clients/web/src/components/groups/ToolListItem/ToolListItem.tsx b/clients/web/src/components/groups/ToolListItem/ToolListItem.tsx
index 03ce97e97..184036dd1 100644
--- a/clients/web/src/components/groups/ToolListItem/ToolListItem.tsx
+++ b/clients/web/src/components/groups/ToolListItem/ToolListItem.tsx
@@ -1,4 +1,4 @@
-import { Stack, Text, UnstyledButton } from "@mantine/core";
+import { Group, Image, Stack, Text, UnstyledButton } from "@mantine/core";
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
import { resolveDisplayLabel } from "../../../utils/toolUtils";
@@ -19,8 +19,28 @@ const ItemSubLabel = Text.withProps({
truncate: true,
});
+const ItemBody = Stack.withProps({
+ gap: 2,
+ flex: 1,
+ miw: 0,
+});
+
+const Row = Group.withProps({
+ gap: "sm",
+ wrap: "nowrap",
+ align: "flex-start",
+});
+
+const ToolIcon = Image.withProps({
+ w: 20,
+ h: 20,
+ fit: "contain",
+});
+
export function ToolListItem({ tool, selected, onClick }: ToolListItemProps) {
- const { name, title } = tool;
+ const { name, title, icons } = tool;
+ const iconSrc = icons?.[0]?.src;
+
return (
-
- {resolveDisplayLabel(name, title)}
- {title && {name}}
-
+
+ {iconSrc && }
+
+ {resolveDisplayLabel(name, title)}
+ {title && {name}}
+
+
);
}