--- kind: task slug: mcp-claim-task-tool title: MCP claim_task tool — open an agent_runs row and return the prompt plan_slug: agent-coordination epic_slug: mcp-claim-complete status: in_progress priority: P2 tenant_id: global owner: unassigned cursor_todo_id: null updated_at: "2026-06-02" --- # Task summary A new MCP tool `claim_task` that an agent calls at session start. It flips the backlog item to `in_progress`, opens an `agent_runs` row, and returns the effective workflow prompt + run id. ## Description ### Contract Register in `apps/mcp-server/src/tools/claim-task.ts` (mirror the structure of `create-object.ts`). Input zod schema: ```typescript { workspace: string, // slug or UUID, resolved via resolveWorkspaceHandle backlogItemId: string, // uuid actorUserId?: string, // optional — defaults to null ("system / dev session") notes?: string, // short opener text the agent can attach to the run } ``` Behavior: 1. Resolve the workspace handle (existing helper). 2. Look up the backlog item; verify it belongs to the resolved workspace. 404 otherwise. 3. Check for an existing open `agent_runs` row (`finished_at IS NULL`) for this `backlog_item_id`: - If one exists *with the same `actor_user_id`*: return it (idempotent re-claim). Refresh its `notes` if provided. - If one exists *with a different actor* (or actor is null on both sides): return error `ALREADY_CLAIMED` with the existing actor id (or "anonymous"). 4. Insert a new `agent_runs` row: `workspace_id`, `backlog_item_id`, `actor_user_id`, `started_at=now()`, `notes`. 5. Update the backlog item: `status='in_progress'` (only if currently `ready` or `draft`; leave alone if already `in_progress` or `done`). 6. Compute and return: - `runId`: the new `agent_runs.id` - `workflowPrompt`: result of `resolveWorkflowPrompt` - `backlogItem`: a minimal snapshot (`title`, `body_markdown`, `status`) ### Output shape Use the existing `toolOk` / `toolErr` helpers in `apps/mcp-server/src/tools/tool-result.ts`. Return as structured tool output so the in-session model can pattern-match. ### Audit log Write an `audit_log` row (`action: "task.claimed"`, `target_type: "agent_run"`, `target_id: runId`, `metadata: { backlog_item_id, prior_status }`). Assumes `audit_log` from `Plan-multitenant-saas-hardening` has landed; if not, file a follow-up to add audit writes when it does. ### Anti-goals - No re-fetching from external systems. The tool only touches Echodo's DB. - No "soft lock" mechanic (timeout-based claims). If a run goes stale, the operator (or `complete_task`) ends it explicitly. ## Subtasks - [x] Created `apps/mcp-server/src/tools/claim-task.ts`. - [x] Registered in `apps/mcp-server/src/tools/index.ts` (along with `complete_task` in the same commit so both register atomically). - [x] Implemented the 6-step behavior with zod validation (workspace handle, backlogItemId UUID, optional actorUserId UUID nullable, notes max 2000 chars). - [x] Audit log write inside the same transaction as the insert + status flip. When `actorUserId` is null, stamps `metadata.system_actor = "mcp:claim_task"` per the audit conventions. - [ ] Operator-side smoke test against a running MCP client — not runnable in the agent's environment without an MCP harness. Documented for the operator. ### Design decisions captured - **Tenancy fence on backlog-item lookup.** When the resolved workspace doesn't match the item's `workspace_id`, we refuse with a "doesn't belong to workspace" error rather than a 404. Prevents cross-tenant existence fishing. - **`shouldTransition` is gated to `ready` / `draft` only.** Never overwrite a deliberate `blocked` / `done` / `in_progress`. Per-spec. - **Idempotent re-claim refreshes `notes` only.** `started_at` is sacred — the second claim still represents the same session window. `notes` is the only field the agent can usefully amend on a re-claim. - **All three writes (run insert + item update + audit insert) happen in one transaction.** A partial claim (run exists but item never transitioned, or vice versa) is unreachable. - **`workflowPrompt` is resolved on every successful return, including idempotent re-claims.** Cheap (three SELECTs max), and a re-claim might span code changes that altered the inheritance chain — always returning the freshest prompt is the safe default. ## Owner or assignee Unassigned ## Status ready ## Estimation M ## Acceptance criteria - [x] Successful claim returns `runId`, `workflowPrompt`, `workflowPromptSource`, `backlogItem`, and the resolved workspace summary. - [x] Re-claim by same actor returns the same `runId` with `reused: true`. - [x] Claim by different actor errors with `ALREADY_CLAIMED: backlog item is already claimed by (run )`. - [x] Backlog item transitions to `in_progress` only if previously `ready` or `draft`. Confirmed by inspection of the `shouldTransition` gate in `claim-task.ts`. ## Links to related Epic / Plan - Epic: `./Epic-mcp-claim-complete.md` - Plan: `../Plan-agent-coordination.md`