Skip to content

Commit 1c406ea

Browse files
committed
Added UI to provide additional _meta values
1 parent f94f2d8 commit 1c406ea

File tree

4 files changed

+188
-19
lines changed

4 files changed

+188
-19
lines changed

client/src/App.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -698,7 +698,11 @@ const App = () => {
698698
cacheToolOutputSchemas(response.tools);
699699
};
700700

701-
const callTool = async (name: string, params: Record<string, unknown>) => {
701+
const callTool = async (
702+
name: string,
703+
params: Record<string, unknown>,
704+
meta?: Record<string, unknown>,
705+
) => {
702706
lastToolCallOriginTabRef.current = currentTabRef.current;
703707

704708
try {
@@ -710,6 +714,7 @@ const App = () => {
710714
arguments: params,
711715
_meta: {
712716
progressToken: progressTokenRef.current++,
717+
...(meta ?? {}),
713718
},
714719
},
715720
},
@@ -1008,10 +1013,14 @@ const App = () => {
10081013
setNextToolCursor(undefined);
10091014
cacheToolOutputSchemas([]);
10101015
}}
1011-
callTool={async (name, params) => {
1016+
callTool={async (
1017+
name: string,
1018+
params: Record<string, unknown>,
1019+
meta?: Record<string, unknown>,
1020+
) => {
10121021
clearError("tools");
10131022
setToolResult(null);
1014-
await callTool(name, params);
1023+
await callTool(name, params, meta);
10151024
}}
10161025
selectedTool={selectedTool}
10171026
setSelectedTool={(tool) => {

client/src/components/ToolResults.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ const ToolResults = ({
156156
)}
157157
{structuredResult._meta && (
158158
<div className="mb-4">
159-
<h5 className="font-semibold mb-2 text-sm">Meta:</h5>
159+
<h5 className="font-semibold mb-2 text-sm">Meta Schema:</h5>
160160
<div className="bg-gray-50 dark:bg-gray-900 p-3 rounded-lg">
161161
<JsonView data={structuredResult._meta} />
162162
</div>

client/src/components/ToolsTab.tsx

Lines changed: 117 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,11 @@ const ToolsTab = ({
4242
tools: Tool[];
4343
listTools: () => void;
4444
clearTools: () => void;
45-
callTool: (name: string, params: Record<string, unknown>) => Promise<void>;
45+
callTool: (
46+
name: string,
47+
params: Record<string, unknown>,
48+
meta?: Record<string, unknown>,
49+
) => Promise<void>;
4650
selectedTool: Tool | null;
4751
setSelectedTool: (tool: Tool | null) => void;
4852
toolResult: CompatibilityCallToolResult | null;
@@ -55,6 +59,9 @@ const ToolsTab = ({
5559
const [isToolRunning, setIsToolRunning] = useState(false);
5660
const [isOutputSchemaExpanded, setIsOutputSchemaExpanded] = useState(false);
5761
const [isMetaExpanded, setIsMetaExpanded] = useState(false);
62+
const [metaEntries, setMetaEntries] = useState<
63+
{ id: string; key: string; value: string }[]
64+
>([]);
5865

5966
useEffect(() => {
6067
const params = Object.entries(
@@ -221,6 +228,102 @@ const ToolsTab = ({
221228
);
222229
},
223230
)}
231+
<div className="pb-4">
232+
<div className="flex items-center justify-between mb-2">
233+
<h4 className="text-sm font-semibold">Meta:</h4>
234+
<Button
235+
size="sm"
236+
variant="outline"
237+
className="h-6 px-2"
238+
onClick={() =>
239+
setMetaEntries((prev) => [
240+
...prev,
241+
{
242+
id:
243+
(
244+
globalThis as unknown as {
245+
crypto?: { randomUUID?: () => string };
246+
}
247+
).crypto?.randomUUID?.() ||
248+
Math.random().toString(36).slice(2),
249+
key: "",
250+
value: "",
251+
},
252+
])
253+
}
254+
>
255+
Add Pair
256+
</Button>
257+
</div>
258+
{metaEntries.length === 0 ? (
259+
<p className="text-xs text-muted-foreground">
260+
No meta pairs.
261+
</p>
262+
) : (
263+
<div className="space-y-20">
264+
{metaEntries.map((entry, index) => (
265+
<div
266+
key={entry.id}
267+
className="flex items-center gap-2 w-full"
268+
>
269+
<Label
270+
htmlFor={`meta-key-${entry.id}`}
271+
className="text-xs shrink-0"
272+
>
273+
Key
274+
</Label>
275+
<Input
276+
id={`meta-key-${entry.id}`}
277+
value={entry.key}
278+
placeholder="e.g. requestId"
279+
onChange={(e) => {
280+
const value = e.target.value;
281+
setMetaEntries((prev) =>
282+
prev.map((m, i) =>
283+
i === index ? { ...m, key: value } : m,
284+
),
285+
);
286+
}}
287+
className="h-8 flex-1"
288+
/>
289+
<Label
290+
htmlFor={`meta-value-${entry.id}`}
291+
className="text-xs shrink-0"
292+
>
293+
Value
294+
</Label>
295+
<Input
296+
id={`meta-value-${entry.id}`}
297+
value={entry.value}
298+
placeholder="e.g. 12345"
299+
onChange={(e) => {
300+
const value = e.target.value;
301+
setMetaEntries((prev) =>
302+
prev.map((m, i) =>
303+
i === index ? { ...m, value } : m,
304+
),
305+
);
306+
}}
307+
className="h-8 flex-1"
308+
/>
309+
<Button
310+
size="sm"
311+
variant="ghost"
312+
className="h-8 w-8 p-0 ml-auto shrink-0"
313+
onClick={() =>
314+
setMetaEntries((prev) =>
315+
prev.filter((_, i) => i !== index),
316+
)
317+
}
318+
aria-label={`Remove meta pair ${index + 1}`}
319+
>
320+
-
321+
</Button>
322+
</div>
323+
))}
324+
</div>
325+
)}
326+
</div>
224327
{selectedTool.outputSchema && (
225328
<div className="bg-gray-50 dark:bg-gray-900 p-3 rounded-lg">
226329
<div className="flex items-center justify-between mb-2">
@@ -262,7 +365,7 @@ const ToolsTab = ({
262365
selectedTool._meta && (
263366
<div className="bg-gray-50 dark:bg-gray-900 p-3 rounded-lg">
264367
<div className="flex items-center justify-between mb-2">
265-
<h4 className="text-sm font-semibold">Meta:</h4>
368+
<h4 className="text-sm font-semibold">Meta Schema:</h4>
266369
<Button
267370
size="sm"
268371
variant="ghost"
@@ -295,7 +398,18 @@ const ToolsTab = ({
295398
onClick={async () => {
296399
try {
297400
setIsToolRunning(true);
298-
await callTool(selectedTool.name, params);
401+
const meta = metaEntries.reduce<Record<string, unknown>>(
402+
(acc, { key, value }) => {
403+
if (key.trim() !== "") acc[key] = value;
404+
return acc;
405+
},
406+
{},
407+
);
408+
await callTool(
409+
selectedTool.name,
410+
params,
411+
Object.keys(meta).length ? meta : undefined,
412+
);
299413
} finally {
300414
setIsToolRunning(false);
301415
}

client/src/components/__tests__/ToolsTab.test.tsx

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,13 @@ describe("ToolsTab", () => {
109109
fireEvent.click(submitButton);
110110
});
111111

112-
expect(defaultProps.callTool).toHaveBeenCalledWith(mockTools[1].name, {
113-
count: 42,
114-
});
112+
expect(defaultProps.callTool).toHaveBeenCalledWith(
113+
mockTools[1].name,
114+
{
115+
count: 42,
116+
},
117+
undefined,
118+
);
115119
});
116120

117121
it("should allow typing negative numbers", async () => {
@@ -130,9 +134,13 @@ describe("ToolsTab", () => {
130134
fireEvent.click(submitButton);
131135
});
132136

133-
expect(defaultProps.callTool).toHaveBeenCalledWith(mockTools[0].name, {
134-
num: -42,
135-
});
137+
expect(defaultProps.callTool).toHaveBeenCalledWith(
138+
mockTools[0].name,
139+
{
140+
num: -42,
141+
},
142+
undefined,
143+
);
136144
});
137145

138146
it("should disable button and change text while tool is running", async () => {
@@ -580,20 +588,20 @@ describe("ToolsTab", () => {
580588
selectedTool: toolWithMeta,
581589
});
582590

583-
expect(screen.getByText("Meta:")).toBeInTheDocument();
591+
expect(screen.getByText("Meta Schema:")).toBeInTheDocument();
584592
expect(
585593
screen.getByRole("button", { name: /expand/i }),
586594
).toBeInTheDocument();
587595
});
588596

589-
it("should toggle meta expansion", () => {
597+
it("should toggle meta schema expansion", () => {
590598
renderToolsTab({
591599
tools: [toolWithMeta],
592600
selectedTool: toolWithMeta,
593601
});
594602

595-
// There might be multiple Expand buttons (Output Schema, Meta). We need the one within Meta section
596-
const metaHeading = screen.getByText("Meta:");
603+
// There might be multiple Expand buttons (Output Schema, Meta Schema). We need the one within Meta Schema section
604+
const metaHeading = screen.getByText("Meta Schema:");
597605
const metaContainer = metaHeading.closest("div");
598606
expect(metaContainer).toBeTruthy();
599607
const toggleButton = within(metaContainer as HTMLElement).getByRole(
@@ -619,7 +627,45 @@ describe("ToolsTab", () => {
619627
});
620628
});
621629

622-
describe("ToolResults Meta", () => {
630+
describe("Meta submission", () => {
631+
it("should send meta values when provided", async () => {
632+
const callToolMock = jest.fn(async () => {});
633+
634+
renderToolsTab({ selectedTool: mockTools[0], callTool: callToolMock });
635+
636+
// Add a meta key/value pair
637+
const addPairButton = screen.getByRole("button", { name: /add pair/i });
638+
await act(async () => {
639+
fireEvent.click(addPairButton);
640+
});
641+
642+
// Fill key and value
643+
const keyInputs = screen.getAllByLabelText(/key/i);
644+
const valueInputs = screen.getAllByLabelText(/value/i);
645+
expect(keyInputs.length).toBeGreaterThan(0);
646+
expect(valueInputs.length).toBeGreaterThan(0);
647+
648+
await act(async () => {
649+
fireEvent.change(keyInputs[0], { target: { value: "requestId" } });
650+
fireEvent.change(valueInputs[0], { target: { value: "abc123" } });
651+
});
652+
653+
// Run tool
654+
const runButton = screen.getByRole("button", { name: /run tool/i });
655+
await act(async () => {
656+
fireEvent.click(runButton);
657+
});
658+
659+
expect(callToolMock).toHaveBeenCalledTimes(1);
660+
expect(callToolMock).toHaveBeenLastCalledWith(
661+
mockTools[0].name,
662+
expect.any(Object),
663+
{ requestId: "abc123" },
664+
);
665+
});
666+
});
667+
668+
describe("ToolResults Meta Schema", () => {
623669
it("should display meta information when present in toolResult", () => {
624670
const resultWithMeta = {
625671
content: [],
@@ -632,7 +678,7 @@ describe("ToolsTab", () => {
632678
});
633679

634680
// Only ToolResults meta should be present since selectedTool has no _meta
635-
expect(screen.getAllByText("Meta:")).toHaveLength(1);
681+
expect(screen.getAllByText("Meta Schema:")).toHaveLength(1);
636682
expect(screen.getByText(/info/i)).toBeInTheDocument();
637683
expect(screen.getByText(/version/i)).toBeInTheDocument();
638684
});

0 commit comments

Comments
 (0)