Taskade Autonomous Pipeline
Use Case: Wire MCP tools into Taskade's autonomous agent pipeline so the agent can discover tools, dispatch tasks, persist results, and recover from failures without human intervention.
Prerequisites
- A Taskade project with API access enabled
- MCP server running and exposing tools (see database-queries and api-routers patterns)
- Node.js 18+ for the bridge server
Setup & Configuration
The bridge sits between Taskade's agent loop and your MCP server. It translates Taskade task events into MCP tool calls and writes results back to the project.
src/bridge/taskade-mcp.tstypescript
import { createServerFn } from "@tanstack/react-start";
import { z } from "zod";
const TaskSchema = z.object({
taskId: z.string(),
action: z.enum(["query_database", "bookmark_url", "search_web"]),
params: z.record(z.unknown()),
});
export const dispatchTask = createServerFn({ method: "POST" })
.validator((d: unknown) => TaskSchema.parse(d))
.handler(async ({ data }) => {
// Route to the correct MCP tool
switch (data.action) {
case "query_database":
return handleDbQuery(data.params);
case "bookmark_url":
return handleBookmark(data.params);
case "search_web":
return handleWebSearch(data.params);
default:
throw new Error(`Unknown action: ${data.action}`);
}
});Core Implementation
Autonomous pipeline with retry, persistence, and result logging:
src/bridge/autonomous-pipeline.tstypescript
interface PipelineTask {
id: string;
action: string;
params: Record<string, unknown>;
status: "pending" | "running" | "done" | "failed";
retries: number;
}
const MAX_RETRIES = 3;
const BACKOFF_MS = [1000, 5000, 15000];
export async function runPipeline(task: PipelineTask) {
const db = getDb();
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
try {
await db.execute({
sql: "UPDATE pipeline SET status = ? WHERE id = ?",
args: ["running", task.id],
});
const result = await dispatchTask({
data: { taskId: task.id, action: task.action as any, params: task.params },
});
await db.execute({
sql: "UPDATE pipeline SET status = ?, result = ?, completed_at = datetime('now') WHERE id = ?",
args: ["done", JSON.stringify(result), task.id],
});
return result;
} catch (error) {
if (attempt < MAX_RETRIES) {
await sleep(BACKOFF_MS[attempt]);
continue;
}
await db.execute({
sql: "UPDATE pipeline SET status = ?, error = ? WHERE id = ?",
args: ["failed", String(error), task.id],
});
throw error;
}
}
}
function sleep(ms: number) {
return new Promise((r) => setTimeout(r, ms));
}Register the pipeline as a Taskade integration endpoint:
src/bridge/register.tstypescript
// Register with Taskade's webhook endpoint
const TASKADE_API = "https://api.taskade.com/v1";
const TASKADE_TOKEN = process.env.TASKADE_API_TOKEN;
export async function registerWebhook(webhookUrl: string) {
const res = await fetch(`${TASKADE_API}/webhooks`, {
method: "POST",
headers: {
Authorization: `Bearer ${TASKADE_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
url: webhookUrl,
events: ["task.created", "task.updated"],
secret: process.env.WEBHOOK_SECRET,
}),
});
if (!res.ok) throw new Error(`Webhook registration failed: ${res.status}`);
return res.json();
}Deployment Notes
- Taskade rate limits: 60 requests per minute for the API. Queue dispatching with a simple debounce.
- State persistence: The agent state (pending tasks, partial results) is ephemeral in-memory. Use the database-queries pattern to persist checkpoints.
- Timeout: Taskade expects responses within 30s. For long-running MCP tools, use an async pattern where the bridge acknowledges immediately and the agent polls for results.
- Error recovery: Transient failures (network, rate limit) should retry with exponential backoff. Hard failures (invalid tool, bad args) should log and skip.
- Auth: Keep the Taskade API token and MCP server secrets in environment variables. Rotate tokens via a scheduled server function.