diff --git a/client/src/components/AppsTab.tsx b/client/src/components/AppsTab.tsx index 2b6d75358..0518fca0b 100644 --- a/client/src/components/AppsTab.tsx +++ b/client/src/components/AppsTab.tsx @@ -21,6 +21,7 @@ import { getToolUiResourceUri } from "@modelcontextprotocol/ext-apps/app-bridge" import AppRenderer from "./AppRenderer"; import ListPane from "./ListPane"; import IconDisplay, { WithIcons } from "./IconDisplay"; +import SchemaFieldDescription from "./SchemaFieldDescription"; import { Label } from "@/components/ui/label"; import { Checkbox } from "@/components/ui/checkbox"; import { @@ -411,6 +412,12 @@ const AppsTab = ({ key, inputSchema, ); + const descriptionId = `${key}-description`; + const showFieldDescription = + !!prop.description && + (prop.type === "string" || + prop.type === "number" || + prop.type === "integer"); return (
@@ -496,7 +503,14 @@ const AppsTab = ({ }); }} > - + )} + {showFieldDescription && ( + + )}
); diff --git a/client/src/components/SchemaFieldDescription.tsx b/client/src/components/SchemaFieldDescription.tsx new file mode 100644 index 000000000..9f242fb0e --- /dev/null +++ b/client/src/components/SchemaFieldDescription.tsx @@ -0,0 +1,22 @@ +interface SchemaFieldDescriptionProps { + id: string; + description?: string; +} + +export default function SchemaFieldDescription({ + id, + description, +}: SchemaFieldDescriptionProps) { + if (!description) { + return null; + } + + return ( +

+ {description} +

+ ); +} diff --git a/client/src/components/ToolsTab.tsx b/client/src/components/ToolsTab.tsx index 15b85fd67..dbffab5f7 100644 --- a/client/src/components/ToolsTab.tsx +++ b/client/src/components/ToolsTab.tsx @@ -40,6 +40,7 @@ import { useEffect, useState, useRef } from "react"; import ListPane from "./ListPane"; import JsonView from "./JsonView"; import ToolResults from "./ToolResults"; +import SchemaFieldDescription from "./SchemaFieldDescription"; import { useToast } from "@/lib/hooks/useToast"; import useCopy from "@/lib/hooks/useCopy"; import IconDisplay, { WithIcons } from "./IconDisplay"; @@ -354,6 +355,12 @@ const ToolsTab = ({ const inputSchema = selectedTool.inputSchema as JsonSchemaType; const required = isPropertyRequired(key, inputSchema); + const descriptionId = `${key}-description`; + const showFieldDescription = + !!prop.description && + (prop.type === "string" || + prop.type === "number" || + prop.type === "integer"); return (
@@ -447,7 +454,15 @@ const ToolsTab = ({ } }} > - +
)} + {showFieldDescription && ( + + )}
); diff --git a/client/src/components/__tests__/AppsTab.test.tsx b/client/src/components/__tests__/AppsTab.test.tsx index 315a95ccf..233839eb9 100644 --- a/client/src/components/__tests__/AppsTab.test.tsx +++ b/client/src/components/__tests__/AppsTab.test.tsx @@ -198,6 +198,37 @@ describe("AppsTab", () => { expect(screen.getByText("Tool: fieldsApp")).toBeInTheDocument(); }); + it("should keep input field descriptions visible after values are filled", () => { + const toolWithDescription: Tool = { + name: "describedApp", + inputSchema: { + type: "object", + properties: { + query: { + type: "string", + description: "Search phrase to render", + }, + }, + }, + _meta: { ui: { resourceUri: "ui://described" } }, + } as Tool & { _meta?: { ui?: { resourceUri?: string } } }; + + renderAppsTab({ + tools: [toolWithDescription], + }); + + fireEvent.click(screen.getByText("describedApp")); + + const input = screen.getByLabelText("query"); + expect(input).toHaveAttribute("aria-describedby", "query-description"); + expect(screen.getByText("Search phrase to render")).toBeVisible(); + + fireEvent.change(input, { target: { value: "filled value" } }); + + expect(input).toHaveValue("filled value"); + expect(screen.getByText("Search phrase to render")).toBeVisible(); + }); + it("should close app renderer when close button is clicked", async () => { renderAppsTab({ tools: [mockAppTool], diff --git a/client/src/components/__tests__/ToolsTab.test.tsx b/client/src/components/__tests__/ToolsTab.test.tsx index 5678914d6..695ccb938 100644 --- a/client/src/components/__tests__/ToolsTab.test.tsx +++ b/client/src/components/__tests__/ToolsTab.test.tsx @@ -1015,6 +1015,36 @@ describe("ToolsTab", () => { expect(screen.queryByRole("combobox")).not.toBeInTheDocument(); expect(screen.getByRole("textbox")).toBeInTheDocument(); }); + + it("should keep string parameter descriptions visible after input is filled", () => { + const toolWithStringParam: Tool = { + name: "stringTool", + description: "Tool with regular string parameter", + inputSchema: { + type: "object" as const, + properties: { + text: { + type: "string" as const, + description: "Some text input", + }, + }, + }, + }; + + renderToolsTab({ + tools: [toolWithStringParam], + selectedTool: toolWithStringParam, + }); + + const input = screen.getByRole("textbox"); + expect(input).toHaveAttribute("aria-describedby", "text-description"); + expect(screen.getByText("Some text input")).toBeVisible(); + + fireEvent.change(input, { target: { value: "filled value" } }); + + expect(input).toHaveValue("filled value"); + expect(screen.getByText("Some text input")).toBeVisible(); + }); }); describe("JSON Validation Integration", () => {