feat(mcp): expose scheduled task run-now#10562

merged1
agents/mcp-thin/thread-search
agents/mcp-thin/scheduled-tasks
openedSep 7, 2026byJJulius Marminge
View on GitHubTwo-way sync enabledPR last imported 3d ago
1 unresolved thread · Jump to file
0 / 3 viewed3 files changedLine totals unavailable

apps/server/src/mcp/toolkits/thread/handlers.ts · Earlier revision 97d14934 · right 74–103

Original diff
@@ -69,6 +71,36 @@ const readQuestion = Effect.fn("mcp.readQuestion")(function* (
   return { ...context, request, item };
 });
 export const ThreadToolkitHandlersLive = ThreadToolkit.toLayer({
+  run_scheduled_task_now: (input) =>
+    Effect.gen(function* () {
+      const { caller } = yield* readMutationCaller();
+      if (
+        caller.archivedAt !== null ||
+        caller.runtimeMode !== "full-access" ||
+        caller.interactionMode !== "default"
+      )
+        return yield* new OrchestratorMcpFailure({
+          code: "capability_denied",
+          message: "Running a scheduled task requires a live full-access/default thread.",
+        });
+      const scheduler = yield* ScheduledTasks.ScheduledTaskService;
+      const { tasks } = yield* scheduler.list().pipe(Effect.mapError(unavailable));
+      if (!tasks.some((task) => task.id === input.taskId && task.projectId === caller.projectId))
+        return yield* new OrchestratorMcpFailure({
+          code: "invalid_request",
+          message: "The task was not found in the calling project.",
+        });
+      const { task } = yield* scheduler
+        .runNow({ id: input.taskId })
+        .pipe(Effect.mapError(unavailable));
+      return {
+        taskId: task.id,
+        threadId: task.threadId,
+        lastRunStatus: task.lastRunStatus,
+        runCount: task.runCount,
+        nextRunAt: task.nextRunAt,
+      };
+    }),

apps/server/src/mcp/toolkits/thread/handlers.ts · right 74–103

M
macroscopeappBotSep 7, 2026

This adds new backend behavior (a destructive tool that dispatches a scheduler run) with no focused test. Consider covering the two guard branches (non-full-access/non-default caller -> capability_denied, task outside the calling project -> invalid_request) and the happy path using the existing test-layer patterns: Layer.mock(ThreadManagement.ThreadManagementService) as in apps/server/src/mcp/toolkits/core.test.ts, or the in-memory scheduledTaskStubLayer already wired into apps/server/src/mcp/OrchestratorMcpToolkit.integration.test.ts — test layers for the external services only, no mocking of the handler logic itself.

Posted via Macroscope — Effect Service Conventions

J
Julius MarmingeSep 7, 2026

Following the maintainer-requested test boundary: this tool calls existing ScheduledTaskService.list/runNow after a direct caller-mode and task-project filter; no scheduler execution, replay or bookkeeping implementation changes. Shared MCP registration/input/caller/error coverage remains in the stack, and ScheduledTaskService retains its isolated behavior tests. We are not adding a separate test suite merely to assert forwarding of the already-typed service call. The PR explicitly documents a new manual run per call and does not promise additional transactional authority or keyed replay.

M
macroscopeappBotSep 7, 2026

The concern remains: no existing test invokes this handler. The caller-mode and project guards are tool-local behavior, so shared registration/input coverage and isolated service tests do not exercise them. A focused handler test for those guards and the successful delegation is still warranted.