{
  "markdown": "# saga-mcp\n\n[![npm](https://img.shields.io/npm/v/saga-mcp)](https://www.npmjs.com/package/saga-mcp)\n[![npm downloads](https://img.shields.io/npm/dm/saga-mcp)](https://www.npmjs.com/package/saga-mcp)\n[![license](https://img.shields.io/npm/l/saga-mcp)](https://github.com/spranab/saga-mcp/blob/master/LICENSE)\n[![IdeaCred](https://ideacred.com/api/badge/spranab/saga-mcp)](https://ideacred.com/profile/spranab)\n\nYour coding agent loses the plan between sessions. You come back tomorrow and\nit has no idea which of the five things you agreed on are done, which one is\nblocked on which, or why you rejected the second approach — because the plan\nlived in the context window, or in a `TODO.md` nobody updates.\n\nsaga-mcp gives the agent a real tracker instead: a SQLite file in your project\nholding projects, epics, tasks, subtasks, dependencies, comments, notes and\ndecisions, exposed as 40 MCP tools. The agent writes to it as it works and\nreads the dashboard when it comes back. No accounts, no external service, no\nnetwork calls — the database is a file you own.\n\n## Install (60 seconds)\n\nClaude Code — add to your project's `.mcp.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"saga\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"saga-mcp\"],\n      \"env\": { \"DB_PATH\": \"/absolute/path/to/your/project/.tracker.db\" }\n    }\n  }\n}\n```\n\nRestart the client. `DB_PATH` is the only setting; the file and schema are\ncreated on first use.\n\n## What it looks like\n\n**You:** \"Set up tracking for the e-commerce API and plan out auth.\"\n\n```\ntracker_init({ project_name: \"E-Commerce API\" })\nepic_create({ project_id: 1, name: \"Authentication\", priority: \"high\" })\ntask_create({ epic_id: 1, title: \"Design auth schema\", priority: \"critical\" })\ntask_create({ epic_id: 1, title: \"Implement JWT auth\", depends_on: [1] })\ntask_create({ epic_id: 1, title: \"Add OAuth2 Google login\", depends_on: [2] })\n```\n\nTasks 2 and 3 come back **blocked** — their dependencies aren't done. Finish\ntask 1 and task 2 unblocks itself.\n\n**Next session, you:** \"Where were we?\"\n\n```\ntracker_dashboard({})\n→ \"E-Commerce API: 5 tasks across 2 epics. 40% complete.\n   Active: Authentication (2/3 done). Next up: Product Catalog (2 tasks).\n   1 blocked task(s).\"\n```\n\nPlus the structured data behind it: stats, epics, blocked and overdue tasks,\nrecent activity, notes.\n\n## Features\n\n- **Full hierarchy**: Projects > Epics > Tasks > Subtasks\n- **Task dependencies**: Express sequencing with auto-block/unblock when deps are met\n- **Description lock**: Stop agents rewriting a task's spec when they meant to leave a comment\n- **Subtask ordering & dependencies**: Explicit order, and checklist items that wait on siblings\n- **Comments**: Threaded discussions on tasks — leave breadcrumbs across sessions, with reversible soft-delete\n- **Web UI**: `saga-web` serves a local dashboard for browsing *and* editing the same database\n- **Templates**: Reusable task sets with `{variable}` substitution\n- **Dashboard**: One tool call gives full overview with natural language summary\n- **SQLite**: Self-contained `.tracker.db` file per project — zero setup, no external database\n- **Activity log**: Every mutation is automatically tracked with old/new values\n- **Notes system**: Decisions, context, meeting notes, blockers — all searchable\n- **Batch operations**: Create multiple subtasks or update multiple tasks in one call\n- **40 focused tools**: With MCP safety annotations on every tool\n- **Import/export**: Full project backup and migration as JSON (with dependencies and comments)\n- **Source references**: Link tasks to specific code locations\n- **Auto time tracking**: Hours computed automatically from activity log\n- **Cross-platform**: Works on macOS, Windows, and Linux\n\n## Other clients\n\n### Claude Code\n\nAdd to your project's `.mcp.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"saga\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"saga-mcp\"],\n      \"env\": {\n        \"DB_PATH\": \"/absolute/path/to/your/project/.tracker.db\"\n      }\n    }\n  }\n}\n```\n\n### With Claude Desktop\n\nAdd to your Claude Desktop config (`claude_desktop_config.json`):\n\n```json\n{\n  \"mcpServers\": {\n    \"saga\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"saga-mcp\"],\n      \"env\": {\n        \"DB_PATH\": \"/absolute/path/to/your/project/.tracker.db\"\n      }\n    }\n  }\n}\n```\n\n### Manual install\n\n```bash\nnpm install -g saga-mcp\nDB_PATH=./my-project/.tracker.db saga-mcp\n```\n\n## Configuration\n\nsaga-mcp requires a single environment variable:\n\n| Variable | Required | Description |\n|----------|----------|-------------|\n| `DB_PATH` | Yes | Absolute path to the `.tracker.db` SQLite file. The file and schema are auto-created on first use. |\n| `SAGA_PROJECT` | No | Scope every tool to one project, by id or name. Set this per repo when several repos share one database. Unset, tools read across the whole file. |\n| `SAGA_TOOLS` | No | `full` (default) lists all 33 tools. `core` lists only the 13 an ordinary tracking session needs, cutting ~3,300 tokens of context per session. Tools left off the list still work if called by name. |\n\nNo API keys, no accounts, no external services. Everything is stored locally in the SQLite file you specify.\n\n### Token cost\n\nThe tool list is context every session pays before any work happens, and list responses are\ncontext it pays again on every call. Both are kept deliberately small:\n\n- Responses are compact JSON — no pretty-print indentation, which measured 20-27% of every response\n- `task_list` rows omit nulls and `metadata`, and truncate descriptions to 120 characters\n  (call `task_get` for a task's full text) — 19-39% smaller depending on how long your descriptions run\n- `activity_log` omits null columns and the row id (no tool takes one) — about 27% smaller\n- `tracker_search` returns previews rather than whole records — about 47% smaller; follow up with\n  `task_get` or `note_list` for the full text\n- `SAGA_TOOLS=core` drops the listed tool surface from ~6,000 to ~2,700 tokens\n\n`note_list` deliberately keeps full note content — it is the retrieval tool, not a preview.\n\nSet `SAGA_TOOLS=core` when an agent only tracks work; leave it unset when you want templates,\nimport/export, session diffs and the rest discoverable.\n\n## Tools\n\n### Getting Started\n\n| Tool | Description | Annotations |\n|------|-------------|-------------|\n| `tracker_init` | Initialize tracker and create first project | `readOnly: false`, `idempotent: true` |\n| `tracker_next` | What to work on next, with the reason and what is blocked | `readOnly: true` |\n| `tracker_dashboard` | Full project overview with natural language summary | `readOnly: true` |\n\n### Projects\n\n| Tool | Description | Annotations |\n|------|-------------|-------------|\n| `project_create` | Create a new project | `readOnly: false` |\n| `project_list` | List projects with completion stats | `readOnly: true` |\n| `project_update` | Update project (archive to soft-delete) | `readOnly: false`, `idempotent: true` |\n\n### Epics\n\n| Tool | Description | Annotations |\n|------|-------------|-------------|\n| `epic_create` | Create an epic within a project | `readOnly: false` |\n| `epic_list` | List epics with task counts | `readOnly: true` |\n| `epic_archive` | Archive/unarchive an epic, hiding it and its tasks from listings | `readOnly: false`, `idempotent: true` |\n| `epic_update` | Update an epic | `readOnly: false`, `idempotent: true` |\n\n### Tasks\n\n| Tool | Description | Annotations |\n|------|-------------|-------------|\n| `task_create` | Create a task with optional dependencies | `readOnly: false` |\n| `task_list` | List/filter tasks with dependency info | `readOnly: true` |\n| `task_get` | Get task with subtasks, notes, comments, and dependencies | `readOnly: true` |\n| `task_update` | Update task (auto-logs, auto-blocks/unblocks) | `readOnly: false`, `idempotent: true` |\n| `task_lock_description` | Lock/unlock a description so agents can't rewrite it | `readOnly: false`, `idempotent: true` |\n| `task_reorder` | Set the order of an epic's tasks | `readOnly: false`, `idempotent: true` |\n| `task_delete` | Remove a `todo` task (soft delete, restorable) | `readOnly: false`, `idempotent: true` |\n| `task_restore` | Restore a removed task | `readOnly: false`, `idempotent: true` |\n| `task_batch_update` | Update multiple tasks at once | `readOnly: false`, `idempotent: true` |\n\n### Subtasks\n\n| Tool | Description | Annotations |\n|------|-------------|-------------|\n| `subtask_create` | Create subtask(s) — supports batch | `readOnly: false` |\n| `subtask_update` | Update title/status/position; `depends_on` and `blocks` set ordering | `readOnly: false`, `idempotent: true` |\n| `subtask_reorder` | Set the order of a task's subtasks in one call | `readOnly: false`, `idempotent: true` |\n| `subtask_delete` | Delete subtask(s) — supports batch | `destructive: true`, `idempotent: true` |\n\n### Comments\n\n| Tool | Description | Annotations |\n|------|-------------|-------------|\n| `comment_add` | Add a comment to a task (threaded discussion) | `readOnly: false` |\n| `comment_list` | List comments on a task (removed ones hidden unless `include_deleted`) | `readOnly: true` |\n| `comment_delete` | Remove a comment — soft delete, row kept for audit | `readOnly: false`, `idempotent: true` |\n| `comment_restore` | Restore a removed comment | `readOnly: false`, `idempotent: true` |\n\n### Templates\n\n| Tool | Description | Annotations |\n|------|-------------|-------------|\n| `template_create` | Create a reusable task template with `{variable}` placeholders | `readOnly: false` |\n| `template_list` | List available templates | `readOnly: true` |\n| `template_apply` | Apply template to create tasks with variable substitution | `readOnly: false` |\n| `template_delete` | Delete a template | `destructive: true`, `idempotent: true` |\n\n### Notes\n\n| Tool | Description | Annotations |\n|------|-------------|-------------|\n| `note_save` | Create or update a note (upsert) | `readOnly: false` |\n| `note_list` | List notes with filters | `readOnly: true` |\n| `note_search` | Full-text search across notes | `readOnly: true` |\n| `note_delete` | Delete a note | `destructive: true`, `idempotent: true` |\n\n### Intelligence\n\n| Tool | Description | Annotations |\n|------|-------------|-------------|\n| `tracker_search` | Cross-entity search (projects, epics, tasks, notes) | `readOnly: true` |\n| `activity_log` | View change history with filters | `readOnly: true` |\n| `tracker_session_diff` | Show what changed since a given timestamp — call at session start | `readOnly: true` |\n\n### Import / Export\n\n| Tool | Description | Annotations |\n|------|-------------|-------------|\n| `tracker_export` | Export full project as nested JSON (includes dependencies and comments) | `readOnly: true` |\n| `tracker_import` | Import project from JSON (matching export format) | `readOnly: false` |\n\n## Usage Examples\n\n### Example 1: Starting a project with dependencies\n\n**User prompt:** \"Set up tracking for my new e-commerce API project\"\n\n**Tool calls:**\n```\ntracker_init({ project_name: \"E-Commerce API\", project_description: \"REST API for online store\" })\nepic_create({ project_id: 1, name: \"Authentication\", priority: \"high\" })\ntask_create({ epic_id: 1, title: \"Design auth schema\", priority: \"critical\" })\ntask_create({ epic_id: 1, title: \"Implement JWT auth\", priority: \"high\", depends_on: [1] })\ntask_create({ epic_id: 1, title: \"Add OAuth2 Google login\", priority: \"medium\", depends_on: [2] })\n```\n\n**Result:** Task 2 and 3 are auto-blocked because their dependencies aren't done yet. When task 1 is marked done, task 2 auto-unblocks.\n\n### Example 2: Resuming work with dashboard summary\n\n**Tool calls:**\n```\ntracker_dashboard({})\n```\n\n**Response includes a natural language summary:**\n```\n\"E-Commerce API: 5 tasks across 2 epics. 40% complete. Active: Authentication (2/3 done). Next up: Product Catalog (2 tasks). 1 blocked task(s).\"\n```\n\nPlus the full structured data (stats, epics, blocked tasks, overdue tasks, activity, notes).\n\n### Example 3: Using templates for repeated workflows\n\n**Create a template:**\n```\ntemplate_create({\n  name: \"feature_workflow\",\n  description: \"Standard feature implementation\",\n  tasks: [\n    { \"title\": \"Design {feature} API\", \"priority\": \"critical\", \"estimated_hours\": 2 },\n    { \"title\": \"Implement {feature}\", \"priority\": \"high\", \"estimated_hours\": 8 },\n    { \"title\": \"Write tests for {feature}\", \"priority\": \"high\", \"estimated_hours\": 4 },\n    { \"title\": \"Document {feature}\", \"priority\": \"medium\", \"estimated_hours\": 1 }\n  ]\n})\n```\n\n**Apply it:**\n```\ntemplate_apply({ template_id: 1, epic_id: 2, variables: { \"feature\": \"user auth\" } })\n```\n\nCreates 4 tasks: \"Design user auth API\", \"Implement user auth\", \"Write tests for user auth\", \"Document user auth\".\n\n### Example 4: Task comments as decision trail\n\n```\ncomment_add({ task_id: 5, content: \"Investigated root cause: CORS headers missing on preflight\" })\ncomment_add({ task_id: 5, content: \"Fixed by adding OPTIONS handler. Tested with curl.\" })\ntask_update({ id: 5, status: \"done\" })\n```\n\nComments persist across sessions — next time an agent calls `task_get(5)`, it sees the full discussion thread.\n\nIf a comment turns out to be wrong, retract it without losing the trail:\n\n```\ncomment_delete({ id: 12, reason: \"Root cause was wrong — it was a proxy timeout\", deleted_by: \"pranab\" })\n```\n\nThe row stays in the database and in the activity log. `comment_list` and `task_get` skip it,\n`comment_list({ task_id: 5, include_deleted: true })` shows it with its reason, and\n`comment_restore({ id: 12 })` brings it back. Nothing an agent removes is unrecoverable.\n\n## One database, many projects\n\nsaga-mcp works either way: a `.tracker.db` per repo (portable, keeps unrelated work apart),\nor one shared database that every repo points at.\n\nThe shared setup needs one extra thing. `projects` is the top-level table, so a shared file holds\nseveral projects — but `task_list`, `note_list`, `activity_log` and `tracker_search` read across\nthe whole file unless told otherwise. An agent in repo B would see repo A's tasks. Set\n`SAGA_PROJECT` per repo and each agent sees only its own:\n\n```json\n{\n  \"mcpServers\": {\n    \"saga\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"saga-mcp\"],\n      \"env\": {\n        \"DB_PATH\": \"/Users/you/saga/central.tracker.db\",\n        \"SAGA_PROJECT\": \"Payments platform\"\n      }\n    }\n  }\n}\n```\n\n`SAGA_PROJECT` takes a project id or a project name (case-insensitive), and fails on startup with\nthe list of real projects if it matches neither. Every scoped tool also accepts an explicit\n`project_id` argument, which wins over the environment variable.\n\n| Setup | What to set | Result |\n|-------|-------------|--------|\n| One database per repo | `DB_PATH` | Nothing to scope — one project per file |\n| Shared database, per-repo agents | `DB_PATH` + `SAGA_PROJECT` | Each agent sees only its project |\n| Shared database, one agent over everything | `DB_PATH` | Tools read across all projects |\n\nWith neither `SAGA_PROJECT` nor a `project_id`, `tracker_dashboard` falls back to the first project\nin the file and says so — the response carries `other_projects` and the summary explains that the\nproject was a guess, rather than silently reporting on the wrong repo.\n\nThe web UI is unaffected either way: its project switcher lists every project in the database, and\neach tab is scoped to the selected one.\n\n## Forgiving input\n\nSmaller models routinely send an array parameter as a *string* containing JSON.\nEvery array-taking tool accepts that, so a batch does not silently collapse into one record:\n\n```\nsubtask_create({ task_id: 3, titles: '[\"Write it\",\"Test it\"]' })   # 2 subtasks\nsubtask_create({ task_id: 3, titles: \"- Write it\n- Test it\" })    # 2 subtasks\ntask_batch_update({ ids: \"[4,5]\", status: \"done\" })               # both tasks\ntask_create({ epic_id: 1, title: \"x\", tags: \"billing, urgent\" })  # 2 tags\n```\n\nCoercion stops where intent becomes ambiguous. A comma inside a *title* is left alone —\n`\"Design the API, then implement it\"` is one subtask, not two — while a comma in a tag or an id\nlist is a separator, because neither can contain one. Anything genuinely unusable is refused with\na message naming what arrived and what was wanted, rather than a leaked `ids.map is not a function`.\n\n## Asking what to do next\n\n`tracker_dashboard` hands an agent everything and leaves it to reason. `tracker_next` answers the\nquestion:\n\n```\ntracker_next()\n  -> Work on #12 'Write the adapter' — already in progress, high priority, in the\n     active epic 'Provider swap'. Next step: implement. Also overdue: #18 'Renew cert'.\n     3 other task(s) are blocked.\n```\n\nOne recommendation with the reason, the next unfinished subtask inside it, a couple of\nalternatives, and anything overdue or blocked. About a third the size of the dashboard.\n\nThe ordering rule worth knowing: **continuing beats starting.** A task already in progress outranks\nan untouched one that is overdue or higher priority, because abandoning work in flight just leaves\ntwo things unfinished — the overdue work is named in the summary instead. Blocked tasks are never\nrecommended, archived epics and removed tasks are skipped, and subtask dependencies decide which\nstep comes next inside the chosen task.\n\nWhen nothing is actionable it says what to unblock rather than returning an empty answer:\n\n```\nNothing is actionable: all 4 remaining task(s) are blocked.\nUnblocking #7 'the keystone' would release 3 of them.\n```\n\n## Ordering and dependencies\n\n`task_list` sorts by priority by default, which is usually what an agent wants but ignores any\norder you arranged by hand. `sort_by: \"manual\"` reads back the order `task_reorder` set:\n\n```\ntask_reorder({ epic_id: 2, ordered_ids: [8, 5, 6] })\ntask_list({ epic_id: 2, sort_by: \"manual\" })     # 8, 5, 6\n```\n\nAnything omitted from `ordered_ids` keeps its relative position at the end. `sort_order` runs\nascending — lower sorts first — and in the web UI you can drag tasks into place inside an epic.\n\nTask dependencies auto-block and auto-unblock:\n\n```\ntask_update({ id: 9, depends_on: [8] })   # 9 becomes blocked while 8 is open\n```\n\nRe-evaluation runs whenever a blocker's *doneness* changes in either direction, so reopening a\nfinished blocker blocks its dependents again, and clearing the last dependency releases them.\nCircular dependencies are refused with the loop named, for tasks and subtasks alike — anything\nin a cycle would be blocked forever. The web UI shows a banner at the top of a blocked task\nnaming what it waits on, with a picker to add or remove dependencies.\n\n## Getting old work out of the way\n\nAn epic list that is mostly finished work, and tasks an agent created that should have been\nsubtasks, are context you pay for on every call.\n\n```\nepic_archive({ id: 4 })            # the epic and its tasks drop out of listings\ntask_delete({ id: 12, reason: \"should have been a subtask\" })\n```\n\nArchiving is deliberately **not** the `cancelled` status: `cancelled` means \"we decided not to do\nthis\", while most of what you want to archive is *completed*. Archived epics and their tasks\ndisappear from `epic_list`, `tracker_dashboard`, `task_list` and `tracker_search` — including the\nstatistics, not just the lists — and come back with `include_archived`.\n\nNothing vanishes silently. The dashboard says what it left out:\n\n```\nHidden: 2 archived epic(s) and 1 removed task(s) — pass include_archived to include them.\n```\n\n`task_delete` is the same soft delete comments have, restricted to tasks still in `todo`:\nanything further along has comments, time tracking and an activity log that removing it would\nstrand, and a task other tasks depend on is refused outright so nothing is left blocked forever.\nThe row is kept, `task_restore` brings it back, and `tracker_export` includes archived and removed\nrows because a backup that omits things is not a backup.\n\n## Keeping agents on the rails\n\nTwo guards for the ways an agent goes wrong on a long task.\n\n**A locked description.** Agents sometimes rewrite a task's description to record progress, when\nthey meant to add a comment — and the spec you agreed on is gone. Lock it and `task_update` refuses:\n\n```\ntask_lock_description({ id: 12 })\ntask_update({ id: 12, description: \"...\" })\n  -> Task 12's description is locked and was not changed. Record progress with\n     comment_add instead, or unlock it in the web UI if the description is genuinely wrong.\n```\n\nEverything else about the task stays editable — the point is to protect the spec, not freeze the\ntask. The lock cannot be cleared as a side effect of an ordinary `task_update`; it takes a\ndeliberate `task_lock_description` call or the lock toggle in the web UI, and both are logged.\n\nThis is a guard against confusion, not an adversarial control: an agent that is told to unlock\nstill can. It turns a silent overwrite into a visible, reversible decision.\n\n**Subtask order and dependencies.** New subtasks are appended in order rather than all landing at\nposition 0, `subtask_reorder` sets the order in one call (or drag them in the UI), and a subtask\ncan wait on its siblings:\n\n```\nsubtask_update({ id: 8, depends_on: [5, 6] })    # 8 waits for 5 and 6\nsubtask_update({ id: 4, blocks: [5, 6, 7, 8] })  # a bug that holds up the rest\n```\n\nReads carry `depends_on` and `blocked`, and the block is **enforced on write**: starting or\nfinishing a subtask whose prerequisites are unmet is refused, and so is completing a task whose\nchecklist is still open.\n\n```\nsubtask_update({ id: 8, status: \"in_progress\" })\n  -> Subtask 8 cannot be started — it waits on #5 'write the parser' (todo).\n     Finish those first, or pass force: true to override deliberately (the override is logged).\n```\n\n`force: true` is the way past, for when a person has decided the blocker no longer applies. It\nworks on `subtask_update`, `task_update` and `task_batch_update`, and every override is written to\nthe activity log naming what was skipped. The web UI asks for confirmation and then sends it.\n\nThe distinction that matters is between an agent quietly ignoring a blocker and someone choosing\nto override one. Dependencies stay\nwithin one task — a checklist item waiting on something under a *different* task is a task-level\ndependency, and `task_update depends_on` already models that. Cycles are refused with the loop\nspelled out.\n\n## Web UI\n\nEverything above is agent-facing. `saga-web` puts the same database in a browser — for the times\nwhen reviewing a spec an agent just wrote, or fixing one field by hand, is faster than another prompt.\n\n```bash\nnpx -p saga-mcp saga-web ./.tracker.db --open\n```\n\nOr against a database you already point your MCP server at:\n\n```bash\nsaga-web --db ~/saga/central.tracker.db --port 8080\n```\n\n| Option | Default | Description |\n|--------|---------|-------------|\n| `--db <path>` | `$DB_PATH` | Database to open. A positional path works too. |\n| `--port <n>` | first free from `4319` | Omit it and saga-web takes the first free port, so one instance per project just works. `--port N` binds exactly N and fails if taken; `--port 0` lets the OS choose. Also `SAGA_WEB_PORT`. |\n| `--host <addr>` | `127.0.0.1` | Bind address. Local-only by default. |\n| `--read-only` | off | Serve the UI with every editing control removed. |\n| `--open` | off | Open the UI in your default browser. |\n\nWhat you get:\n\n- **Overview** — stats, per-epic progress, blocked and overdue tasks\n- **Board** — kanban across the five task statuses; drag a card to change its status\n- **Epics** — the full Epic → Task → Subtask tree, which is the fastest way to review a spec an agent just wrote\n- **Notes** and **Activity** — decisions and the complete change history\n- **Markdown** — descriptions, comments and notes render headings, tables, lists, code and links. Agent-written content is escaped before any markdown rule runs, so raw HTML can never reach the page, and only http/https/mailto links are followed\n- **Archived section** — archived epics collapse below a divider, with a \"show archived (N)\" toggle\n- **Task drawer** — edit any field, comment, remove or restore a comment, lock the description, drag subtasks into order, and set which subtasks wait on which. Each subtask has one control carrying its whole state (todo / in progress / done, or blocked), and the drawer resizes by dragging its edge\n- **Project switcher** — every project in the database, so one central `.tracker.db` covers all your repos; every tab, including Activity, is scoped to the selected project\n- **Shareable, refreshable URLs** — the open project, tab and task live in the address bar, so a browser refresh puts you back where you were and back/forward move between tasks. A ⟳ button in the task drawer re-reads that task without a page reload, for picking up what an agent just wrote\n\nWrites from the UI call the *same handlers* the MCP tools do, so edits you make by hand are\nvalidated identically and land in the same activity log as the agent's — an agent calling\n`tracker_dashboard` after you fix something sees the fix and how it happened.\n\nA few deliberate limits: it binds to `127.0.0.1` unless you ask otherwise, it has no\nauthentication (don't put it on a shared network), and it will not create a database — point it\nat one your MCP server already uses. Separate `.tracker.db` files are not yet aggregated into\none view; a single database with multiple projects is.\n\n## How It Works\n\nsaga-mcp stores everything in a single SQLite file (`.tracker.db`) per project. The database is auto-created on first use with all tables and indexes — no migration step needed.\n\n### Hierarchy\n\n```\nProject\n  └── Epic (feature/workstream)\n        └── Task (unit of work)\n              ├── Subtask (checklist item)\n              ├── Comment (discussion thread)\n              └── Dependencies (blocked by other tasks)\n```\n\n### Task Dependencies\n\nTasks can depend on other tasks. When you set `depends_on: [2, 3]` on a task:\n- The task is auto-blocked if any dependency isn't `done`\n- When a dependency is marked `done`, downstream tasks are re-evaluated\n- If all dependencies are met, the blocked task auto-unblocks to `todo`\n\n### Note Types\n\nNotes replace scattered markdown files. Each note has a type:\n\n| Type | Use case |\n|------|----------|\n| `general` | Free-form notes |\n| `decision` | Architecture/design decisions |\n| `context` | Conversation context for future sessions |\n| `meeting` | Meeting notes |\n| `technical` | Technical details, specs |\n| `blocker` | Blockers and issues |\n| `progress` | Progress updates |\n| `release` | Release notes |\n\n### Activity Log\n\nEvery create, update, and delete is automatically recorded:\n\n```json\n{\n  \"summary\": \"Task 'Fix CORS issue' status: blocked -> done\",\n  \"action\": \"status_changed\",\n  \"entity_type\": \"task\",\n  \"entity_id\": 15,\n  \"field_name\": \"status\",\n  \"old_value\": \"blocked\",\n  \"new_value\": \"done\",\n  \"created_at\": \"2026-02-21T18:30:00\"\n}\n```\n\n## Privacy Policy\n\nsaga-mcp is a fully local, offline tool. It does **not**:\n\n- Collect any user data\n- Send any data to external servers\n- Require internet access after installation\n- Use analytics, telemetry, or tracking of any kind\n\nAll data is stored exclusively in the local SQLite file specified by `DB_PATH`. You own your data completely. Uninstalling saga-mcp and deleting the `.tracker.db` file removes all traces.\n\nFor questions about privacy, open an issue at https://github.com/spranab/saga-mcp/issues.\n\n## Development\n\n```bash\ngit clone https://github.com/spranab/saga-mcp.git\ncd saga-mcp\nnpm install\nnpm run build\nDB_PATH=./test.db npm start\n\n# the web UI against the same database\nnode dist/web/index.js ./test.db --open\n\nnpm test     # unit and integration, ~140 tests, no network\nnpm run e2e  # release gate: packs a tarball, installs it, drives the real binaries\n```\n\n### Releasing\n\nPublishing to npm is irreversible — a version number can never be reused — so it is the *last*\nstep, and it is triggered by publishing a GitHub release, not by pushing a tag.\n\n```bash\n# 1. bump the version in package.json, manifest.json and server.json, then merge\n# 2. tag it. Nothing is published yet.\ngit tag -a v1.9.0 -m \"v1.9.0 — ...\" && git push origin v1.9.0\n\n# 3. verify the tagged build: this packs the tarball that would be published\n#    and drives it end to end, including an upgrade from an older database.\nnpm run e2e\n\n# 4. publish the release. This fires the publish workflow.\ngh release create v1.9.0 --notes-file notes.md\n```\n\nThe workflow re-runs the suite against the tagged commit, refuses a tag that does not match\n`package.json`, refuses a version already on npm, and sends a GitHub *pre-release* to the `next`\ndist-tag so it never becomes what `npm install saga-mcp` gives people. A failed publish can be\nretried against the same tag with `gh workflow run \"Publish to npm\" -f tag=v1.9.0`.\n\n## Support\n\n- **Issues**: https://github.com/spranab/saga-mcp/issues\n- **Repository**: https://github.com/spranab/saga-mcp\n\n## Related projects\n\nPart of a set of agent infrastructure built by one person, meant to be used\ntogether:\n\n- [yantrikdb-mcp](https://github.com/yantrikos/yantrikdb-mcp) — persistent\n  cognitive memory for the same agent: what it learned, not what it planned.\n- [brainstorm-mcp](https://github.com/spranab/brainstorm-mcp) — multi-model\n  debate before you commit a plan to the tracker.\n- [swarmcode](https://github.com/spranab/swarmcode) — real-time channel\n  between Claude Code instances on different machines.\n- [truenas-mcp](https://github.com/spranab/truenas-mcp) — 278 TrueNAS SCALE\n  actions behind one hierarchical tool.\n- [mcpier](https://github.com/spranab/mcpier) — self-hosted MCP control plane\n  that keeps API keys off your clients.\n\n## License\n\nMIT\n",
  "bytes": 29633,
  "sha": "ca44fdff09d154d3832bfda5b8560c705943a9df472f0b094538b9ab5704a622",
  "repo_slug": "spranab/saga-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_spranab_saga_mcp_59327bfb/readme"
}