Skip to content
This repository was archived by the owner on Jun 1, 2026. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/app/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ export const dict = {
"cron.error.interval.min": "Interval must be at least 1 minute",
"cron.error.timeout.min": "Timeout must be at least 1 minute",
"cron.error.validation.fix": "Please fix the highlighted fields",
"cron.field.mcpServers": "MCP servers",
"cron.field.mcpServers.empty": "No MCP servers configured. Add servers in Settings > MCP.",
"cron.field.mcpServers.help": "Only enabled MCP servers will be available to this task. By default all servers are off.",
"cron.rail.title": "Sessions",
"cron.rail.tasks": "Scheduled tasks",
"cron.rail.empty": "No sessions yet",
Expand Down
48 changes: 48 additions & 0 deletions packages/app/src/pages/cron.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Dialog } from "@codeplane-ai/ui/dialog"
import { Icon } from "@codeplane-ai/ui/icon"
import { IconButton } from "@codeplane-ai/ui/icon-button"
import { Select } from "@codeplane-ai/ui/select"
import { Switch } from "@codeplane-ai/ui/switch"
import { Tag } from "@codeplane-ai/ui/tag"
import { TextField } from "@codeplane-ai/ui/text-field"
import { Tooltip } from "@codeplane-ai/ui/tooltip"
Expand Down Expand Up @@ -394,6 +395,7 @@ type EditorState = {
agent: string
model: string
status: CronStatus
mcpServers: string[]
errors: { name?: string; prompt?: string; schedule?: string; timeout?: string; form?: string }
}

Expand Down Expand Up @@ -460,6 +462,14 @@ function CronEditorDialog(props: {
return result.sort((a, b) => a.group.localeCompare(b.group) || a.label.localeCompare(b.label))
})

const mcpServerNames = createMemo<string[]>(() => {
const config = globalSync.data.config.mcp ?? {}
return Object.keys(config).filter((name) => {
const entry = config[name]
return entry && typeof entry === "object" && "type" in entry
}).sort()
})

const initial = (): EditorState => {
const e = props.existing
if (!e)
Expand All @@ -474,6 +484,7 @@ function CronEditorDialog(props: {
agent: "",
model: "",
status: "active",
mcpServers: [],
errors: {},
}
return {
Expand All @@ -488,6 +499,7 @@ function CronEditorDialog(props: {
agent: e.agent ?? "",
model: e.model ?? "",
status: e.status,
mcpServers: e.mcpServers ?? [],
errors: {},
}
}
Expand Down Expand Up @@ -562,6 +574,7 @@ function CronEditorDialog(props: {
agent: store.agent.trim() || null,
model: store.model.trim() || null,
status: store.status,
mcpServers: store.mcpServers.length > 0 ? store.mcpServers : null,
}
Comment on lines 574 to 578
await CronClient.update(conn, props.existing.id, body)
} else {
Expand All @@ -576,6 +589,7 @@ function CronEditorDialog(props: {
agent: store.agent.trim() || undefined,
model: store.model.trim() || undefined,
status: store.status,
mcpServers: store.mcpServers.length > 0 ? store.mcpServers : undefined,
}
Comment on lines 589 to 593
await CronClient.create(conn, body)
}
Expand Down Expand Up @@ -728,6 +742,40 @@ function CronEditorDialog(props: {
onSelect={(o) => setStore("model", o ? `${o.providerID}/${o.modelID}` : "")}
/>
</div>
<div class="flex flex-col gap-2">
<label class="text-12-medium text-text-weak">{language.t("cron.field.mcpServers")}</label>
<Show
when={mcpServerNames().length > 0}
fallback={
<div class="text-12-regular text-text-weak italic">
{language.t("cron.field.mcpServers.empty")}
</div>
}
>
<div class="rounded-lg border border-border-weak-base bg-surface-base px-3 py-2 flex flex-col gap-1">
<div class="text-11-regular text-text-weak pb-1">{language.t("cron.field.mcpServers.help")}</div>
<For each={mcpServerNames()}>
{(name) => (
<div class="flex items-center justify-between gap-3 py-0.5">
<span class="text-13-regular text-text-base truncate">{name}</span>
<Switch
checked={store.mcpServers.includes(name)}
size="small"
onChange={(enabled: boolean) => {
setStore(
"mcpServers",
enabled
? [...store.mcpServers, name]
: store.mcpServers.filter((s) => s !== name),
)
}}
/>
</div>
)}
</For>
</div>
</Show>
</div>
<Show when={store.errors.form}>
<div class="rounded-md border border-border-critical-base bg-surface-critical-base/10 px-3 py-2 text-12-regular text-text-critical-base whitespace-pre-wrap">
{store.errors.form}
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/utils/cron-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export type CronTask = {
status: CronStatus
timeoutMs?: number
maxRetries?: number
mcpServers?: string[]
lastRunID?: string
lastRunAt?: number
lastRunStatus?: CronRunStatus
Expand Down Expand Up @@ -55,6 +56,7 @@ export type CronCreateInput = {
status?: CronStatus
timeoutMs?: number
maxRetries?: number
mcpServers?: string[]
}

export type CronUpdateInput = {
Expand All @@ -68,6 +70,7 @@ export type CronUpdateInput = {
status?: CronStatus
timeoutMs?: number | null
maxRetries?: number | null
mcpServers?: string[] | null
}

function authHeaders(server: ServerConnection.HttpBase): Record<string, string> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-- Adds cron-task-level MCP server affordances. Each scheduled task can list
-- which MCP servers should be enabled for its runs (null = all, []=none).
ALTER TABLE `cron_task` ADD COLUMN `mcp_servers` text;
Loading
Loading