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

@ -12,6 +12,7 @@ const defaultSettings: InspectorServerSettings = {
metadata: [],
connectionTimeout: 30000,
requestTimeout: 60000,
taskTtl: 60000,
};
function InteractiveRender(args: ServerSettingsFormProps) {
@ -166,6 +167,7 @@ export const AllConfigured: Story = {
],
connectionTimeout: 15000,
requestTimeout: 45000,
taskTtl: 45000,
oauthClientId: "my-client-id",
oauthClientSecret: "super-secret-value",
oauthScopes: "read write",

View file

@ -12,6 +12,7 @@ const emptySettings: InspectorServerSettings = {
metadata: [],
connectionTimeout: 30000,
requestTimeout: 60000,
taskTtl: 60000,
};
const populatedSettings: InspectorServerSettings = {
@ -19,6 +20,7 @@ const populatedSettings: InspectorServerSettings = {
metadata: [{ key: "userId", value: "u-1" }],
connectionTimeout: 30000,
requestTimeout: 60000,
taskTtl: 60000,
oauthClientId: "cid",
oauthClientSecret: "secret",
oauthScopes: "read",
@ -197,6 +199,36 @@ describe("ServerSettingsForm", () => {
expect(typeof call[1]).toBe("number");
});
it("invokes onTimeoutChange with the taskTtl field when typing in Task TTL", async () => {
const user = userEvent.setup();
const onTimeoutChange = vi.fn();
renderWithMantine(
<ServerSettingsForm
{...baseHandlers}
onTimeoutChange={onTimeoutChange}
settings={emptySettings}
expandedSections={["timeouts"]}
/>,
);
const ttlInput = screen.getByLabelText(/Task TTL/);
await user.type(ttlInput, "9");
expect(onTimeoutChange).toHaveBeenCalled();
const call = onTimeoutChange.mock.calls[0];
expect(call[0]).toBe("taskTtl");
expect(typeof call[1]).toBe("number");
});
it("renders the Task TTL value from settings", () => {
renderWithMantine(
<ServerSettingsForm
{...baseHandlers}
settings={{ ...emptySettings, taskTtl: 45000 }}
expandedSections={["timeouts"]}
/>,
);
expect(screen.getByLabelText(/Task TTL/)).toHaveValue("45000 ms");
});
it("invokes onOAuthChange when typing in client id", async () => {
const user = userEvent.setup();
const onOAuthChange = vi.fn();

View file

@ -30,7 +30,7 @@ export interface ServerSettingsFormProps {
onRemoveMetadata: (index: number) => void;
onMetadataChange: (index: number, key: string, value: string) => void;
onTimeoutChange: (
field: "connectionTimeout" | "requestTimeout",
field: "connectionTimeout" | "requestTimeout" | "taskTtl",
value: number,
) => void;
onOAuthChange: (oauth: OAuthSettings) => void;
@ -105,7 +105,7 @@ export function ServerSettingsForm({
onOAuthChange,
}: ServerSettingsFormProps) {
const handleTimeoutChange =
(field: "connectionTimeout" | "requestTimeout") =>
(field: "connectionTimeout" | "requestTimeout" | "taskTtl") =>
(value: number | string) => {
const numValue =
typeof value === "string" ? parseInt(value, 10) || 0 : value;
@ -193,6 +193,13 @@ export function ServerSettingsForm({
value={settings.requestTimeout}
onChange={handleTimeoutChange("requestTimeout")}
/>
<NumberInput
label="Task TTL"
suffix=" ms"
min={1}
value={settings.taskTtl}
onChange={handleTimeoutChange("taskTtl")}
/>
</Group>
</Accordion.Panel>
</Accordion.Item>