From ecb85fb860881afd4c2e4fe5a079b3abdb91e99c Mon Sep 17 00:00:00 2001 From: Fuksss Date: Thu, 12 Nov 2026 09:49:59 +0100 Subject: [PATCH] fix(server): run_scheduled_task_now returns why a started run failed The tool's response dropped lastRunError when a run started but its dispatch failed (threadId: null, lastRunStatus: "failed"). The field already exists on the task and is already a plain message with no stack trace. Pass it through. --- apps/server/src/mcp/toolkits/core.test.ts | 48 +++++++++++++++++++ .../src/mcp/toolkits/thread/handlers.ts | 1 + apps/server/src/mcp/toolkits/thread/tools.ts | 1 + 3 files changed, 50 insertions(+) diff --git a/apps/server/src/mcp/toolkits/core.test.ts b/apps/server/src/mcp/toolkits/core.test.ts index 2c3d4026a71fe..69ffbacd7221c 100644 --- a/apps/server/src/mcp/toolkits/core.test.ts +++ b/apps/server/src/mcp/toolkits/core.test.ts @@ -815,6 +815,54 @@ it.effect.each([ ), ); +it.effect("run_scheduled_task_now reports why a started run failed to dispatch", () => + Effect.gen(function* () { + const server = yield* McpServer.McpServer; + const result = yield* server + .callTool({ name: "run_scheduled_task_now", arguments: { taskId: "task-full-access" } }) + .pipe( + Effect.provideService( + McpInvocationContext.McpInvocationContext, + clientScope("full-access"), + ), + Effect.provideService(McpSchema.McpServerClient, client), + ); + expect(result.isError).toBe(false); + expect(result.structuredContent).toMatchObject({ + threadId: null, + lastRunStatus: "failed", + lastRunError: "the provider is disabled", + }); + }).pipe( + Effect.provide( + McpHttpServer.layerThreadToolkit.pipe( + Layer.provideMerge(McpHttpServer.layerInProcessMcpServer), + Layer.provide(NodeCrypto.layer), + Layer.provide(Layer.mock(ThreadManagement.ThreadManagementService)({})), + Layer.provide(Layer.mock(ThreadSearch.ThreadSearch)({})), + Layer.provide(Layer.mock(PeerForwarding.PeerForwarding)({})), + Layer.provide(Layer.mock(PeerLinkRequests.PeerLinkRequests)({})), + Layer.provide(Layer.mock(PeerLinks.PeerLinks)({})), + Layer.provide( + Layer.mock(ScheduledTaskService.ScheduledTaskService)({ + list: () => + Effect.succeed({ tasks: [scheduledTask("task-full-access", "full-access")] }), + runNow: () => + Effect.succeed({ + threadId: null, + task: { + ...(scheduledTask("task-full-access", "full-access") as object), + lastRunStatus: "failed" as const, + lastRunError: "the provider is disabled", + }, + } as never), + }), + ), + ), + ), + ), +); + function scheduledTask(id: string, runtimeMode: "auto" | "full-access"): never { return { id, diff --git a/apps/server/src/mcp/toolkits/thread/handlers.ts b/apps/server/src/mcp/toolkits/thread/handlers.ts index 32e45b5d0a1c0..ab8d3e9c14283 100644 --- a/apps/server/src/mcp/toolkits/thread/handlers.ts +++ b/apps/server/src/mcp/toolkits/thread/handlers.ts @@ -118,6 +118,7 @@ export const layer = McpToolAccess.toLayer(ThreadToolkit, { link: McpInvocationContext.threadLinkFor(scope, threadId), }), lastRunStatus: task.lastRunStatus, + lastRunError: task.lastRunError, runCount: task.runCount, nextRunAt: task.nextRunAt, }; diff --git a/apps/server/src/mcp/toolkits/thread/tools.ts b/apps/server/src/mcp/toolkits/thread/tools.ts index c6670c4e953d3..cb6c64aa331d6 100644 --- a/apps/server/src/mcp/toolkits/thread/tools.ts +++ b/apps/server/src/mcp/toolkits/thread/tools.ts @@ -229,6 +229,7 @@ const ScheduledTaskRunTool = Tool.make("run_scheduled_task_now", { threadId: ScheduledTask.fields.threadId, link: Schema.optional(Schema.String), lastRunStatus: ScheduledTask.fields.lastRunStatus, + lastRunError: ScheduledTask.fields.lastRunError, runCount: NonNegativeInt, nextRunAt: ScheduledTask.fields.nextRunAt, }),