OpenTasks
Cross-system graph for tasks and specs. Link Claude Tasks, Beads issues, and native tasks today — Jira, Linear, and other remote trackers are on the roadmap (docs/STATUS.md). Query blockers and ready work across all of them.
npm install opentasks
Quick Start
OpenTasks runs as a small daemon that the CLI, the MCP server, and the client all
talk to over a Unix socket. You don't manage it — the first command that needs
it starts one automatically (opt out with --no-autostart).
CLI
npm install -g opentasks # or run any command below via `npx opentasks …`
opentasks init # create .opentasks/ in your repo
opentasks create --type task --title "Wire up auth" --status open
opentasks create --type task --title "Add OAuth provider" --status open
opentasks ready # unblocked tasks, ready to work on
opentasks list # open tasks (closed hidden)
opentasks tree t-xxxx # a task and its blocker tree
ready / list / blocked / tree are compact, token-light views. Linking
edges, claiming tasks with a lease, and arbitrary graph queries are each one more
subcommand — run opentasks help.
MCP (Claude Code / agents)
Register the server once; the daemon auto-starts on first use:
claude mcp add opentasks -- npx opentasks mcp --scope all
…or in .mcp.json:
{
"mcpServers": {
"opentasks": { "command": "npx", "args": ["opentasks", "mcp", "--scope", "all"] }
}
}
Scopes are tasks (default), graph, annotate, context; --scope all enables
everything. The agent gets create_task, claim_task, query, events_since, and
the rest.
Programmatic (embedders)
import { createClient } from 'opentasks'
const client = createClient({ autoConnect: true })
// What's ready to work on?
const ready = await client.query({ ready: {} })
// Connect a task to an external reference
await client.link({ fromId: 't-x7k9', toId: 'beads://./bd-xyz', type: 'blocks' })
// What blocks this task (full chain)?
const blockers = await client.query({ blockers: { nodeId: 't-x7k9', transitive: true } })
await client.disconnect()
The top-level link/query/annotate exports are the lower-level forms that
take a GraphStore as their first argument — see Programmatic API
for daemon-free usage. The client.* methods above take params only and route
through the daemon.
The Problem
Claude Tasks, Beads, Jira, Linear, Taskmaster each manage their own content. None of them can express cross-system relationships. You cannot say "this Claude subtask implements that Beads issue" or "this Beads issue is blocked by that Jira ticket."
OpenTasks adds edges between them.
graph TD
subgraph Native Systems
CT["Claude Tasks<br/><small>TaskCreate / TaskUpdate</small>"]
BD["Beads<br/><small>bd new / bd show</small>"]
TM["Taskmaster<br/><small>tm task / tm prd</small>"]
JR["Jira<br/><small>REST API</small>"]
end
subgraph OpenTasks Graph Layer
E1["claude://t-abc"]
E2["beads://./bd-xyz"]
E3["jira://PROJ-123"]
E4["taskmaster://./auth-prd"]
E1 -- "blocks" --> E2
E2 -- "implements" --> E3
E4 -- "discovered-from" --> E2
end
CT -.-> E1
BD -.-> E2
JR -.-> E3
TM -.-> E4
style E1 fill:#e8f4fd,stroke:#4a90d9
style E2 fill:#e8f4fd,stroke:#4a90d9
style E3 fill:#e8f4fd,stroke:#4a90d9
style E4 fill:#e8f4fd,stroke:#4a90d9
You keep using each system's native tools. OpenTasks owns the graph.
Three Tools
Examples below use the bare tool form for brevity. Through a daemon, call them
as client.link(...) / client.query(...) / client.annotate(...) (params only);
the top-level link/query/annotate functions take a GraphStore first arg.
link()
Create or remove edges between any nodes.
await link({ fromId: 't-x7k9', toId: 'c-a2b3', type: 'implements' })
await link({ fromId: 't-setup', toId: 't-impl', type: 'blocks' })
await link({ fromId: 't-setup', toId: 't-impl', type: 'blocks', remove: true })
Edge types: blocks (cycle-checked), implements, references, related, parent-of, depends-on, discovered-from, duplicates, supersedes. Add custom types as strings.
query()
Search nodes, edges, and computed views.