feat(web): wire up MCP task support — Tasks screen, Run-as-task, status toasts (#1422) (#1424)

Wires the ported Tasks screen + core task subsystem through App.tsx: sticky Clear Completed, cancel/refresh error toasts, core-side per-task progress correlation, Run-as-task on the Tools screen (gated on server support), per-server Task TTL, and live task-status toasts.
This commit is contained in:
Cliff Hall 2026-06-04 08:49:47 -04:00 • committed by GitHub
commit ed6eda7492
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 1192 additions and 40 deletions

View file

@ -28,6 +28,7 @@ const meta: Meta<typeof ToolsScreen> = {
parameters: { layout: "fullscreen" },
args: {
listChanged: false,
serverSupportsTaskToolCalls: false,
ui: EMPTY_TOOLS_UI,
onUiChange: fn(),
onRefreshList: fn(),

View file

@ -25,6 +25,7 @@ const tools: Tool[] = [
const baseProps = {
tools,
listChanged: false,
serverSupportsTaskToolCalls: false,
ui: EMPTY_TOOLS_UI,
onUiChange: vi.fn(),
onRefreshList: vi.fn(),
@ -91,7 +92,7 @@ describe("ToolsScreen", () => {
await user.clear(field);
await user.type(field, "slow");
await user.click(screen.getByRole("button", { name: /Execute/ }));
expect(onCallTool).toHaveBeenCalledWith("gamma", { mode: "slow" });
expect(onCallTool).toHaveBeenCalledWith("gamma", { mode: "slow" }, false);
});
it("renders selection and result from props (persisted across navigation)", () => {
@ -133,7 +134,7 @@ describe("ToolsScreen", () => {
renderWithMantine(<ControlledToolsScreen onCallTool={onCallTool} />);
await user.click(screen.getByText("alpha"));
await user.click(screen.getByRole("button", { name: /Execute/ }));
expect(onCallTool).toHaveBeenCalledWith("alpha", {});
expect(onCallTool).toHaveBeenCalledWith("alpha", {}, false);
});
it("seeds schema defaults so untouched fields are sent on Execute", async () => {
@ -143,7 +144,7 @@ describe("ToolsScreen", () => {
await user.click(screen.getByText("gamma"));
// Execute without editing the form: the default must still be sent.
await user.click(screen.getByRole("button", { name: /Execute/ }));
expect(onCallTool).toHaveBeenCalledWith("gamma", { mode: "fast" });
expect(onCallTool).toHaveBeenCalledWith("gamma", { mode: "fast" }, false);
});
it("invokes onClearResult when Clear is clicked on the result panel", async () => {

View file

@ -22,6 +22,12 @@ export interface ToolsUiState {
selectedToolName?: string;
formValues: Record<string, unknown>;
search: string;
// Screen-level "Run as task" toggle, shared across tools (not per-tool):
// selecting a different tool keeps the current value. Persists across tab
// navigation like the rest of the UI state, and is only honored for the
// selected tool when its `execution.taskSupport` is "optional" (a "required"
// tool is always run as a task, "forbidden" never) — see ToolDetailPanel.
runAsTask: boolean;
}
export interface ToolsScreenProps {
@ -29,9 +35,15 @@ export interface ToolsScreenProps {
callState?: ToolCallState;
ui: ToolsUiState;
listChanged: boolean;
/** Whether the connected server advertises task-augmented tool calls. */
serverSupportsTaskToolCalls: boolean;
onUiChange: (next: ToolsUiState) => void;
onRefreshList: () => void;
onCallTool: (name: string, args: Record<string, unknown>) => void;
onCallTool: (
name: string,
args: Record<string, unknown>,
runAsTask?: boolean,
) => void;
onCancelCall?: () => void;
onClearResult?: () => void;
}
@ -92,6 +104,7 @@ export function ToolsScreen({
callState,
ui,
listChanged,
serverSupportsTaskToolCalls,
onUiChange,
onRefreshList,
onCallTool,
@ -139,10 +152,17 @@ export function ToolsScreen({
formValues={formValues}
isExecuting={isExecuting}
progress={callState?.progress}
serverSupportsTaskToolCalls={serverSupportsTaskToolCalls}
runAsTask={ui.runAsTask}
onRunAsTaskChange={(value) =>
onUiChange({ ...ui, runAsTask: value })
}
onFormChange={(values) =>
onUiChange({ ...ui, formValues: values })
}
onExecute={() => onCallTool(selectedTool.name, formValues)}
onExecute={(runAsTask) =>
onCallTool(selectedTool.name, formValues, runAsTask)
}
onCancel={() => onCancelCall?.()}
/>
) : (