tb_claim
Claim a task in ready status with all dependencies resolved for execution. Provide task ID and agent name.
Instructions
Claim a task for execution. Only claims tasks in 'ready' status with all dependencies resolved.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| task_id | Yes | Task ID to claim | |
| agent | Yes | Agent or developer claiming the task |
Implementation Reference
- src/tools/task-board.ts:194-251 (handler)The handler function for the 'tb_claim' tool. It claims a task by checking that the task exists, is in 'ready' or 'backlog' status, has all dependencies resolved (no blockers), and then updates the task status to 'in_progress' with an assignee and claimed_at timestamp.
server.tool( "tb_claim", "Claim a task for execution. Only claims tasks in 'ready' status with all dependencies resolved.", { task_id: z.string().max(256).describe("Task ID to claim"), agent: z.string().max(256).regex(/^[a-zA-Z0-9_.-]+$/).describe("Agent or developer claiming the task"), }, async ({ task_id, agent }) => { const db = getDb(); const task = db.prepare(`SELECT * FROM tasks WHERE id = ?`).get(task_id) as Record<string, unknown> | undefined; if (!task) { return { content: [{ type: "text" as const, text: JSON.stringify({ error: "Task not found" }) }] }; } if (task.status !== "ready" && task.status !== "backlog") { return { content: [{ type: "text" as const, text: JSON.stringify({ error: `Task is in "${task.status}" status, cannot claim` }) }], }; } // Check dependencies const deps = JSON.parse(task.dependencies as string) as string[]; if (deps.length > 0) { const placeholders = deps.map(() => "?").join(","); const blockers = db .prepare(`SELECT id, status FROM tasks WHERE id IN (${placeholders}) AND status != 'done'`) .all(...deps) as Record<string, unknown>[]; if (blockers.length > 0) { return { content: [ { type: "text" as const, text: JSON.stringify({ error: "Blocked by unfinished dependencies", blockers: blockers.map((b) => ({ id: b.id, status: b.status })), }), }, ], }; } } const now = new Date().toISOString(); db.prepare( `UPDATE tasks SET status = 'in_progress', assignee = ?, claimed_at = ?, updated_at = ? WHERE id = ?` ).run(agent, now, now, task_id); return { content: [ { type: "text" as const, text: JSON.stringify({ claimed: true, task_id, agent, status: "in_progress" }), }, ], }; } - src/tools/task-board.ts:197-200 (schema)Input schema for the tb_claim tool, defining task_id (string max 256) and agent (string max 256, regex pattern for valid identifiers).
{ task_id: z.string().max(256).describe("Task ID to claim"), agent: z.string().max(256).regex(/^[a-zA-Z0-9_.-]+$/).describe("Agent or developer claiming the task"), }, - src/server.ts:18-20 (registration)Registration of the task board tools (including tb_claim) on the MCP server via registerTaskBoardTools.
registerSddTools(server); registerTaskBoardTools(server); registerFileTools(server); - src/tools/task-board.ts:7-252 (registration)The registerTaskBoardTools function that registers all task board tools including tb_claim on the MCP server.
export function registerTaskBoardTools(server: McpServer): void { // ── Create Board ─────────────────────────────────── const TaskInputSchema = z.object({ title: z.string().min(3).max(512), description: z.string().max(65536).default(""), priority: z.enum(TASK_PRIORITIES).default("p2"), spec_ref: z.string().max(512).optional(), acceptance_criteria: z.string().max(65536).default(""), dependencies: z.array(z.string()).default([]), }); server.tool( "tb_create_board", "Create a new task board for a project. Optionally include tasks inline to create board + all tasks in a single atomic call (avoids N separate tb_add_task calls).", { project: z.string().max(256).regex(/^[a-zA-Z0-9_.-]+$/).describe("Project identifier (e.g. my-project)"), name: z.string().max(256).describe("Board name"), tasks: z.array(TaskInputSchema).max(100).optional().describe("Optional: tasks to create with the board. Each task: {title, description?, priority?, spec_ref?, acceptance_criteria?, dependencies?}. Dependencies reference other task titles or indices."), }, async ({ project, name, tasks }) => { const db = getDb(); const boardId = generateId("board"); if (!tasks || tasks.length === 0) { db.prepare( `INSERT INTO boards (id, project, name) VALUES (?, ?, ?)` ).run(boardId, project, name); return { content: [ { type: "text" as const, text: JSON.stringify({ created: true, board_id: boardId, project, name, task_count: 0 }), }, ], }; } // Atomic: create board + all tasks in one transaction const insertBoard = db.prepare( `INSERT INTO boards (id, project, name) VALUES (?, ?, ?)` ); const insertTask = db.prepare( `INSERT INTO tasks (id, board_id, title, description, priority, spec_ref, acceptance_criteria, dependencies, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)` ); const taskIds: string[] = []; const taskIdMap: Record<string, string> = {}; // Pre-generate IDs so dependencies can reference them for (let i = 0; i < tasks.length; i++) { const id = generateId("task"); taskIds.push(id); taskIdMap[tasks[i].title] = id; taskIdMap[String(i)] = id; } const tx = db.transaction(() => { insertBoard.run(boardId, project, name); for (let i = 0; i < tasks.length; i++) { const t = tasks[i]; // Resolve dependency references (by title or index) to generated IDs const resolvedDeps = t.dependencies.map((dep) => taskIdMap[dep] || dep); // Tasks with no dependencies start as "ready", others as "backlog" const status = resolvedDeps.length === 0 ? "ready" : "backlog"; insertTask.run( taskIds[i], boardId, t.title, t.description, t.priority, t.spec_ref || null, t.acceptance_criteria, JSON.stringify(resolvedDeps), status ); } }); tx(); return { content: [ { type: "text" as const, text: JSON.stringify({ created: true, board_id: boardId, project, name, task_count: tasks.length, task_ids: taskIds, }), }, ], }; } ); // ── Add Task ─────────────────────────────────────── server.tool( "tb_add_task", "Add a task to an existing board. Every task should reference a spec and have acceptance criteria.", { board_id: z.string().max(256).describe("Board ID"), title: z.string().min(3).max(512).describe("Task title"), description: z.string().max(65536).default("").describe("Task description"), priority: z.enum(TASK_PRIORITIES).default("p2").describe("Priority: p0 (critical), p1 (high), p2 (medium), p3 (low)"), spec_ref: z.string().max(512).optional().describe("Reference to spec document"), acceptance_criteria: z.string().max(65536).default("").describe("Acceptance criteria for completion"), dependencies: z.array(z.string()).default([]).describe("Task IDs this task depends on"), }, async ({ board_id, title, description, priority, spec_ref, acceptance_criteria, dependencies }) => { const db = getDb(); const board = db.prepare(`SELECT id FROM boards WHERE id = ?`).get(board_id); if (!board) { return { content: [{ type: "text" as const, text: JSON.stringify({ error: `Board ${board_id} not found` }) }], }; } const id = generateId("task"); db.prepare( `INSERT INTO tasks (id, board_id, title, description, priority, spec_ref, acceptance_criteria, dependencies) VALUES (?, ?, ?, ?, ?, ?, ?, ?)` ).run(id, board_id, title, description, priority, spec_ref || null, acceptance_criteria, JSON.stringify(dependencies)); return { content: [ { type: "text" as const, text: JSON.stringify({ created: true, task_id: id, board_id, title, priority }), }, ], }; } ); // ── Get Board Status ─────────────────────────────── server.tool( "tb_status", "Get the current status of a task board with all tasks grouped by status.", { board_id: z.string().describe("Board ID"), }, async ({ board_id }) => { const db = getDb(); const board = db.prepare(`SELECT * FROM boards WHERE id = ?`).get(board_id) as Record<string, unknown> | undefined; if (!board) { return { content: [{ type: "text" as const, text: JSON.stringify({ error: `Board ${board_id} not found` }) }], }; } const tasks = db.prepare( `SELECT * FROM tasks WHERE board_id = ? ORDER BY CASE priority WHEN 'p0' THEN 0 WHEN 'p1' THEN 1 WHEN 'p2' THEN 2 WHEN 'p3' THEN 3 END, created_at ASC` ).all(board_id) as Record<string, unknown>[]; const grouped: Record<string, unknown[]> = {}; for (const status of TASK_STATUSES) { grouped[status] = tasks.filter((t) => t.status === status); } const summary = { total: tasks.length, by_status: Object.fromEntries( TASK_STATUSES.map((s) => [s, grouped[s].length]) ), }; return { content: [ { type: "text" as const, text: JSON.stringify({ board, tasks: grouped, summary }), }, ], }; } ); // ── Claim Task ───────────────────────────────────── server.tool( "tb_claim", "Claim a task for execution. Only claims tasks in 'ready' status with all dependencies resolved.", { task_id: z.string().max(256).describe("Task ID to claim"), agent: z.string().max(256).regex(/^[a-zA-Z0-9_.-]+$/).describe("Agent or developer claiming the task"), }, async ({ task_id, agent }) => { const db = getDb(); const task = db.prepare(`SELECT * FROM tasks WHERE id = ?`).get(task_id) as Record<string, unknown> | undefined; if (!task) { return { content: [{ type: "text" as const, text: JSON.stringify({ error: "Task not found" }) }] }; } if (task.status !== "ready" && task.status !== "backlog") { return { content: [{ type: "text" as const, text: JSON.stringify({ error: `Task is in "${task.status}" status, cannot claim` }) }], }; } // Check dependencies const deps = JSON.parse(task.dependencies as string) as string[]; if (deps.length > 0) { const placeholders = deps.map(() => "?").join(","); const blockers = db .prepare(`SELECT id, status FROM tasks WHERE id IN (${placeholders}) AND status != 'done'`) .all(...deps) as Record<string, unknown>[]; if (blockers.length > 0) { return { content: [ { type: "text" as const, text: JSON.stringify({ error: "Blocked by unfinished dependencies", blockers: blockers.map((b) => ({ id: b.id, status: b.status })), }), }, ], }; } } const now = new Date().toISOString(); db.prepare( `UPDATE tasks SET status = 'in_progress', assignee = ?, claimed_at = ?, updated_at = ? WHERE id = ?` ).run(agent, now, now, task_id); return { content: [ { type: "text" as const, text: JSON.stringify({ claimed: true, task_id, agent, status: "in_progress" }), }, ], }; } ); - src/database/index.ts:12-30 (helper)The getDb helper function used by the tb_claim handler to obtain a SQLite database connection.
export function getDb(): Database.Database { if (db) return db; fs.mkdirSync(path.dirname(DB_PATH), { recursive: true }); db = new Database(DB_PATH); db.pragma("journal_mode = WAL"); db.pragma("synchronous = normal"); db.pragma("cache_size = -32000"); db.pragma("temp_store = memory"); db.pragma("busy_timeout = 5000"); db.pragma("foreign_keys = ON"); const versionInfo = db.prepare("SELECT sqlite_version() as version").get() as { version: string }; console.error(`ForgeSpec MCP: SQLite ${versionInfo.version}, WAL mode enabled`); initSchema(db); return db; }