Skip to main content
Glama
README.md
# ⚡ sprinter

**A self-hosted, gated work-backlog tracker with priority scoring, a filterable/sortable UI, and a built-in [MCP](https://modelcontextprotocol.io) server — so AI agents can pull the next item, do the work, and report progress back.**

sprinter was built to drive an **autonomous remediation loop**: a fleet of agents queries the backlog over MCP (`next_item`), implements a fix, and marks the item done (`update_item`) — every change audit-logged. It works just as well as a plain human backlog board.

<!-- Add screenshots here: docs/backlog.png, docs/board.png -->

---

## Why it exists

Most issue trackers are built for humans clicking around. sprinter is built so an **agent** can:

```
loop:
  item = mcp.next_item(category="security")   # highest-priority open item
  patch = implement_fix(item)
  mcp.update_item(item.id, status="fixed", notes="patch at ...")
```

…while a human watches the same data in a clean web UI (filter, sort, board, drill-down) and the database stays the single source of truth.

## Features

- **Priority scoring** — every item carries Urgency / Impact / Effort (1–10); priority = `U×I/E`, so cheap-but-valuable work floats to the top. Re-scoring recomputes automatically.
- **Filter & sort everything** — by app, category, severity, status, free-text search, priority/effort ranges; server-side pagination (`LIMIT/OFFSET` + count) keeps it fast on thousands of items.
- **Board view** — kanban by status (`open → in_progress → fixed → done`).
- **Apps registry** — track items against the apps/repos they belong to (URL, repo path, stack, LOC, open/critical/high counts).
- **Systemic themes** — roll up issues that recur across many apps into portfolio-level initiatives.
- **MCP server** — 9 tools (`list_apps`, `query_items`, `get_item`, `next_item`, `update_item`, `add_item`, `list_themes`, `stats`, `get_app`) over JSON-RPC; bearer-token gated. Point any MCP client at it.
- **Gated by default** — the whole app sits behind a login (scrypt + HMAC-signed session cookie, login rate-limiting); the MCP endpoint requires a bearer token. Nothing renders pre-auth.
- **Change log** — every status/score/notes mutation is recorded with actor + timestamp.

## Stack

FastAPI · PostgreSQL (any PG-wire-compatible engine works) · vanilla-JS single-page UI (no build step) · a dependency-light MCP JSON-RPC server. One small container + a database.

## Quickstart

```bash
git clone https://github.com/danimoya/sprinter && cd sprinter
cp .env.example .env
# set DB_PASSWORD, SPRINTER_PASSWORD, SPRINTER_SESSION_SECRET (openssl rand -hex 32),
# and SPRINTER_MCP_TOKEN if you want the MCP endpoint enabled
docker compose up -d --build
docker exec sprinter python ingest.py     # loads the synthetic demo seed from ./demo
open http://localhost:8000                 # log in with SPRINTER_USER / SPRINTER_PASSWORD
```

The repo ships a **synthetic demo dataset** (`demo/`) — fictional apps and findings — so it's useful the moment it's up. Replace it with your own `apps.json` / `backlog.json` / `themes.json` (same shape) and re-run `ingest.py`.

## MCP

Enable by setting `SPRINTER_MCP_TOKEN`, then register the endpoint with any MCP client:

```bash
claude mcp add --transport http sprinter http://localhost:8000/mcp \
  --header "Authorization: Bearer $SPRINTER_MCP_TOKEN"
```

`next_item` returns the single highest-priority open item — ideal as the head of an autonomous fix loop. `update_item` moves it through `in_progress → fixed → done`. All write tools require the token; reads do too.

## Data model

- **apps** — `key, name, urls[], repo_dir, profile, stack[], datastores[], loc, open_items, critical, high, max_priority`
- **items** — `id (stable hash), app, category (security|dast|infra|stub|improvement|feature), title, description, severity, cwe, owasp, location, evidence, remediation, confidence, urgency, impact, effort, priority, status, assignee, notes, …`
- **themes** — cross-cutting initiatives spanning ≥3 apps
- **change_log** — audit trail of every mutation

Status lifecycle: `open → in_progress → fixed (work done, pending deploy) → done (live)`, plus `wontfix` and `rejected`.

## Security notes

- Set strong values for `SPRINTER_PASSWORD`, `SPRINTER_SESSION_SECRET`, and `SPRINTER_MCP_TOKEN`. With no password set, login is disabled (fail-closed). With no MCP token, the MCP endpoint returns 401.
- Put it behind a TLS-terminating reverse proxy in production and drop the `ports:` mapping (proxy by container name).
- The schema deliberately avoids `UNIQUE`-on-`TEXT`; idempotency is handled in `ingest.py` (full refresh) and `update_item` (by id).

## License

MIT © Daniel Moya — see [LICENSE](LICENSE).