backlogr
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@backlogrcreate a task to research vendor pricing, due Friday, assigned to me"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Backlogr
A small, self-hosted system of record for a team of AI agents' work (research, purchases, edits, outreach, code). Agents talk to it over MCP or a CLI. Tasks are the objects; the MCP tool verbs are task_*. Rule: no task, no work.
Python, SQLite (WAL), append-only update log enforced by triggers.
MCP over streamable HTTP (or stdio), plus a JSON API used by the CLI. Bearer-token auth on API/MCP;
/healthzis unauthenticated.Nightly JSON export and SQLite backup inside the service.
Documentation
Related MCP server: pith
Rule for every agent (paste verbatim)
No task, no work: before you start anything for the user, create or find its task in Backlogr (
task_create/backlogr new), and record every status change, blocker and result on that task (task_update/backlogr update,backlogr done) as it happens, using your bot name as the actor.
Run the server
git clone '<repo-url>' ~/backlogr && cd ~/backlogr
python3 -m venv .venv && .venv/bin/pip install -e '.[test]'
mkdir -p data backups exports && chmod 700 data backups exports
cp deploy/env.example .env && chmod 600 .env # fill in BACKLOGR_TOKEN etc.
set -a && . ./.env && set +a && .venv/bin/backlogr-server
curl -fsS http://127.0.0.1:8796/healthz # okAs a systemd user service: install -D -m 644 deploy/backlogr.service ~/.config/systemd/user/backlogr.service && systemctl --user daemon-reload && systemctl --user enable --now backlogr (and loginctl enable-linger $USER to start at boot).
Expose it only on a private network or behind an authenticating reverse proxy, for example Tailscale serve under a path: tailscale serve --bg --https=443 --set-path /backlogr http://127.0.0.1:8796, then BACKLOGR_URL=https://backlogr.example.invalid/backlogr. Add that host name to BACKLOGR_ALLOWED_HOSTS for MCP transport checks; enforce Host restrictions for all routes at the proxy. Use TLS and authentication before any public exposure. Routes: $BACKLOGR_URL/mcp, $BACKLOGR_URL/healthz, $BACKLOGR_URL/api/<op>.
In a container
Release tags publish ghcr.io/oceanswave/backlogr:<version> (and :latest). The image runs as a
non-root user, keeps the DB, exports and backups in the /data volume, and has a health check.
cp deploy/env.example .env && chmod 600 .env # set BACKLOGR_TOKEN; path settings are overridden
docker compose up -d # or: docker build -t backlogr . && docker run ...
curl -fsS http://127.0.0.1:8796/healthz
docker compose exec backlogr backlogr list # CLI inside the container, local modecompose.yaml publishes the port on loopback only; put an authenticating proxy such as tailscale serve
in front, and add its host name to BACKLOGR_ALLOWED_HOSTS. Inside the container the server binds
0.0.0.0 so the published port reaches it.
Configuration
See the complete reference for all environment variables, including deployment settings.
Variable | Used by | Default |
| server (required for HTTP) | none |
| server, local CLI |
|
| server |
|
| server bind IPs (comma list) |
|
| extra MCP Host headers (proxy DNS names) | none |
| nightly backup ( | off / |
| nightly export, CLI |
|
| CLI remote mode (and client configs below) | unset = local DB |
| CLI remote mode |
|
| name in messages and default person for |
|
| people whose | unset |
| bot names that can never be | none |
| if set, the only names accepted as | unset |
| dashboard time zone, card grouping, Tailscale header auth | local zone / none / off |
Install the MCP
Server name backlogr, streamable HTTP at $BACKLOGR_URL/mcp, header Authorization: Bearer <token>. Keep the token in an env var or a mode-600 file; never commit it or paste it into chat.
Claude Code (project
.mcp.jsonor~/.claude.json; Claude Code expands${VAR}):{"mcpServers": {"backlogr": {"type": "http", "url": "${BACKLOGR_URL}/mcp", "headers": {"Authorization": "Bearer ${BACKLOGR_TOKEN}"}}}}Codex (
~/.codex/config.toml, withBACKLOGR_TOKENexported in the launching shell):[mcp_servers.backlogr] url = "https://<your-host>/backlogr/mcp" bearer_token_env_var = "BACKLOGR_TOKEN"Cursor (
~/.cursor/mcp.json):{"mcpServers": {"backlogr": {"url": "https://<your-host>/backlogr/mcp", "headers": {"Authorization": "Bearer ${env:BACKLOGR_TOKEN}"}}}}Stdio-only clients (runs on the server host, reads
.envthere):{"mcpServers": {"backlogr": {"command": "ssh", "args": ["<user>@<server-host>", "cd backlogr && set -a && . ./.env && .venv/bin/backlogr-server --stdio"]}}}Any other client: streamable HTTP, the URL above, the bearer header.
Install the skill
The agent skill is skills/backlogr/SKILL.md (CLI, MCP tools, status flow, rules). Copy or symlink the folder into your agent's skills directory:
git clone '<repo-url>' ~/src/backlogr
ln -s ~/src/backlogr/skills/backlogr ~/.claude/skills/backlogr # Claude Code
ln -s ~/src/backlogr/skills/backlogr ~/.codex/skills/backlogr # CodexOther agents: point them at AGENTS.md, or paste the rule above into their instructions.
MCP tools
task_create, task_update (status/due/owner/approval_needed/note), task_add_link, task_get, task_list (owner, status or comma list, overdue, stale_hours, due_before, q, source, include_closed, limit, offset), task_flags (overdue, stale, blocked, waiting_on_owner, created_recently), task_stale (hours), task_waiting (waiting_on_owner queue with asks), task_import, task_act. Fields: id, title, done_means, owner_bot, due (ISO), status (todo|in_progress|waiting_on_owner|blocked|done|declined), source (direct_to_bot|via_coordinator|bot_initiated), approval_needed, ask (required when moving to waiting_on_owner), waiting_since, created_at, updated_at, updates[], links[].
CLI (backlogr)
Remote when BACKLOGR_URL is set (token from BACKLOGR_TOKEN_FILE), else the local DB (BACKLOGR_DB).
backlogr new "Order sample tee" --owner Mint --source via_coordinator --by Coordinator --done-means "tee arrives" --due 2026-10-30 --approval --key mint:sample-tee # --key: retries return the same task
backlogr update T-abc123 --by Mint --status blocked --note "needs card"
backlogr update T-abc123 --by Mint --due none # clear the due date (MCP/API: due="")
backlogr update T-abc123 --by Mint --link https://example.com/order --kind order
backlogr done T-abc123 --by Mint --evidence "https://example.com/pr/12" --test unit=pass --test e2e=pass # --evidence required; --test when done_means mentions tests
backlogr list [--owner Mint] [--status todo,in_progress] [--q PR] [--source via_coordinator] [--due-before 2026-11-01] [--overdue] [--stale-hours 24] [--all] [--limit 50] [--offset 0]
backlogr flags [--stale 24] [--recent 24]
backlogr waiting # waiting_on_owner queue: exact ask + age, oldest first
backlogr approve T-id --by Forge --approver Operator --source "Operator in chat 11:17" # human approval record
backlogr stale [--hours 2] # in_progress with no note in N hours, by owner
backlogr get T-abc123 [--short] backlogr import file.json --by Coordinator backlogr export [--path P]new, update and done print one line (T-abc123 in_progress); add --json (before or after the subcommand) for the full row. Other commands print a readable summary or JSON with --json.
Deploying to a host with deploy.sh
scripts/deploy.sh updates an existing install (a git clone with a .venv and a systemd user unit, as in Run the server) on a host that has no GitHub access. Ship the release as a git bundle:
git bundle create /tmp/backlogr.bundle --all --tags
scp /tmp/backlogr.bundle "$DEPLOY_HOST:/tmp/backlogr.bundle"
# on the host, from the install dir:
scripts/deploy.sh --bundle /tmp/backlogr.bundle --ref vX.Y.Z --dry-run # preview: fetches refs, changes no files, DB or service
scripts/deploy.sh --bundle /tmp/backlogr.bundle --ref vX.Y.ZIt refuses to run (exit 4) if tracked files in the install are modified. Otherwise it backs up the DB to backups/pre-deploy-<ts>.db, checks out the ref, runs pip install -e ., restarts the service and waits for /healthz (5 s per try, 40 tries). If pip install, the restart or healthz fails, it rolls the code back to the previous commit and exits 1; schema changes are not reverted, so the DB backup is kept. Without --ref it deploys the highest v* tag; without --bundle it fetches from origin.
Env | Default |
|
|
|
|
| environment, else |
Tests
.venv/bin/python -m pytest -q tests (unit, CLI local + remote, auth 401s, live MCP HTTP end-to-end). ensure.sh health-checks $BACKLOGR_URL. Migrating from the earlier "task manager" deployment: MIGRATION.md.
License
MIT (see license for copyright attribution)
Diagnostics and restore
backlogr --version prints the installed code version. backlogr doctor prints configuration without the token value, including token presence and token-file permissions. Locally it checks DB writability and schema version/columns; remotely it checks /healthz and an authenticated list with limit=1. Exit status is 0 when all checks pass, 1 otherwise. Remote token files should have mode 600. Other CLI failures, including unreadable files, invalid import JSON, and network errors, print error: ... on stderr and exit 2.
List pagination is optional: --limit N --offset N (MCP/API: limit, offset), with no limit by default. Both must be non-negative integers; pagination applies after filtering.
backlogr export writes tasks plus rules and playbooks. backlogr import export.json --by Bot accepts either the exported {"tasks": [...], "rules": [...], "playbooks": [...]} object or a task list; rules and playbooks are upserted as in rules import. Normal imports, including API/MCP snapshots, enforce approval, evidence, and test gates; snapshot history and derived metadata are not restored. For trusted local recovery, backlogr import export.json --by Bot --restore preserves IDs, timestamps, task fields, update history, and links verbatim, bypassing creation gates. Remote --restore is rejected; unset BACKLOGR_URL for local use. Existing IDs are left as they are and reported in skipped; the result also lists created IDs and per-item errors. SQLite backups remain the primary restore method.
One-call human actions (act)
backlogr act T-id done|na|question|later|undo [--reason TEXT] [--by NAME] (MCP: task_act, API op act). --by defaults to BACKLOGR_OWNER (owner when unset). Set BACKLOGR_OWNER=Operator on the server and CLI clients to retain that name.
action | effect |
| status |
| status |
| status unchanged. |
| adds a note without changing status; |
| restores status, evidence, approval, tests and question to what they were before the last act. This works only if nothing else changed the task since. |
Every act is logged in the history as act <action> by <by>.... Each act is checked inside the write lock. If the task changed after it was read, the act is refused with "retry". Names in BACKLOGR_HUMANS (comma-separated) never count as bots, even after they act on tasks. The HTTP API accepts only the parameters exposed by the corresponding MCP tool; unknown fields return HTTP 400 (unknown field: x). Request bodies over 1 MiB return HTTP 413.
Dashboard mapping: DASH T-x DONE → act T-x done; DASH T-x N/A[: r] → act T-x na --reason r; DASH T-x QUESTION: q → act T-x question --reason q.
Rules and Playbooks (1.5.0)
Store reusable rules alongside tasks, and group them into playbooks with optional
skill names and test commands. IDs are supplied by you. A rule needs nonempty
rule text; its title and other descriptive fields are optional.
backlogr rules add M1 --title "Check examples" --rule "Examples must run." --test "python -m pytest" --category quality --by Agent
backlogr playbooks add P-check --name "Example checks" --rules M1 --skill example-check --test "python -m pytest"
backlogr rules list --playbook P-check --testable yes --json
backlogr rules get M1
backlogr rules untested
backlogr rules remove M1 # also: backlogr playbooks remove P-check (keeps its rules)
backlogr playbooks list
backlogr playbooks get P-check --json
backlogr rules import tests/fixtures/rules-playbooks.json --by Agent --jsonrules add accepts --evidence, --source, --applies-to, --playbook,
--testable yes|no, and --replace. Duplicate rule IDs fail unless --replace
is given; replacement supplies the full editable record, preserving creation
metadata. --by defaults to BACKLOGR_OWNER for rule creation. Playbook add
upserts by ID; --description is optional. An omitted --rules preserves links;
an explicit list replaces additional links. Unknown rule/playbook references fail.
A rule's testable defaults to true when it has a test. Empty or whitespace-only
tests and tests starting with UNTESTED (case-insensitive, ignoring leading
whitespace) always derive false. Explicit true with an empty test is rejected.
rules untested and rules list --untested return every rule with
testable=false, just like rules list --testable no. This includes empty tests,
UNTESTED placeholders, and rules explicitly added or imported with
testable=false despite descriptive test text. Rules with testable=true are
excluded. MCP/API rule_untested uses the same flag-based filter.
rules list also accepts --category. --json works at every command level.
The same authenticated HTTP API and MCP expose rule_list, rule_get,
rule_add, rule_untested, rule_remove, playbook_list, playbook_get,
playbook_add, playbook_remove, and rules_import. API operations use those exact names: POST to
$BACKLOGR_URL/api/rule_get with {"id":"M1"}, for example. See the
agent skill for signatures and
import format for relationships and bulk import.
Live dashboard
Open /dashboard on your Backlogr server (or /backlogr/dashboard under a
/backlogr reverse-proxy prefix) for the live dashboard. The page reads
SQLite on every request and refreshes every 60 seconds. It shows task counts,
assignees, decisions waiting on the owner (BACKLOGR_OWNER), task cards (grouped by
optional BACKLOGR_DASHBOARD_CATEGORIES), the latest 40 finished
tasks, up to 80 timeline events from the last 48 hours, and rules/playbooks.
/dashboard.json serves the same computed model for other read-only clients.
Both routes accept GET and HEAD only and require the Authorization: Bearer …
API credential. Behind tailscale serve you can set
BACKLOGR_TRUST_TAILSCALE_HEADERS=1 to also accept the Tailscale-User-Login
identity header it adds; enable that only when the listener is reachable solely
through tailscale serve, because a direct client could forge the header. The
existing BACKLOGR_ALLOWED_HOSTS configuration also applies. The dashboard sends
a strict Content-Security-Policy and has no scripts, task action controls, or
external assets. Times use BACKLOGR_TZ (default: the server's local zone).
This server cannot be deployed
Maintenance
Related MCP Connectors
- DazbenchOAuthapp.dazbench
Task management your AI agents can actually run. One line becomes a context-ready task over MCP.
Task & board management for AI agents + humans. Kanban, comments, digests via MCP.
Work management where AI agents are first-class members: tasks, projects, memory over hosted MCP
System-of-record notebook for AI coding agents: pages, datastores, tasks, skills over MCP.
Related MCP Servers
- AlicenseNot gradedqualityAmaintenanceServer-enforced workflow discipline for AI agents. An MCP server providing persistent work items, dependency graphs, quality gates, and actor attribution. Schemas define what agents must produce — the server blocks the call if they don't. Works with any MCP-compatible client.209MIT
- AlicenseNot gradedqualityDmaintenanceMCP server for task management that enables AI agents to read, create, update tasks, and track work sessions, allowing agents and humans to collaborate on the same task board.5 npm10MIT
- AlicenseNot gradedqualityAmaintenanceAgent-first project management MCP server that enables AI agents to manage tasks, spaces, lists, boards, subtasks, comments, and automations via natural language, with full audit trail and real-time sync.4,718 npmMIT
- AlicenseNot gradedqualityAmaintenanceAn MCP server enabling AI coding agents to interact with a task tracker that enforces rules server-side, replacing markdown files and hooks with a database, CLI, and web UI.02MIT