FronyBoard

Project tracker for AI agents: roadmap, periods, tasks and a validation gate, in one SQLite file

DatabasesPythonv0.39.1

FronyBoard

An MCP server that gives AI agents (Claude Code and friends) a first-class project tracker.

Where Jira is an issue tracker for humans behind a web UI, FronyBoard replaces each part with something an agent can use natively:

JiraFronyBoard
DatabaseOne SQLite file in a dedicated data directory
RecordsJSON documents (roadmap, period) + Markdown
APIMCP tools
Workflow engineSchema + rule validation, run as a gate before every write
State transitionAn MCP tool call (transition_task)

The schema and operating rules were extracted from a real product's management system (31 tasks shipped through it), then generalized.

Install

Requires uv. One line registers FronyBoard in Claude Code; uvx fetches the package from PyPI on first use and caches it:

claude mcp add FronyBoard -- uvx fronyboard

Any MCP client that can launch a stdio command works the same way — the command is uvx fronyboard. It is also listed in the MCP Registry as io.github.Cafelatte1/fronyboard. From a clone, point at the checkout instead (this needs git):

git clone https://github.com/Cafelatte1/fronyboard
claude mcp add FronyBoard -- uv run --directory <path-to-clone>\backend fronyboard

This is the local (stdio) mode: the client starts the server as a child process and talks to it over a pipe. No HTTP, no network, no credentials — web.py and fauth.py are never called. Data is written to %LOCALAPPDATA%\Frony\FronyBoard\data (~/.Frony/FronyBoard/data where LOCALAPPDATA is unset); set AIRA_DATA_DIR to relocate it. Logs (JSON Lines, one line per MCP tool call plus server events) go to the sibling logs folder — FRONYBOARD_LOG_DIR overrides.

To share one FronyBoard between several machines, or to use it from the Claude and ChatGPT apps, run it as an HTTP server instead — see docs/self-hosting.md.

Project setup

Connecting the MCP server gives every session the tools and the general workflow (delivered as server instructions). What it cannot know is which FronyBoard project a codebase belongs to — declare that in the codebase itself by adding this section to its CLAUDE.md (create the file if the project has none):

## FronyBoard

This project is tracked by FronyBoard (project key: DLY).
Manage tasks through the FronyBoard MCP tools, following the FronyBoard server instructions.

Replace DLY with the project's key (register one first with create_project). The section is also the opt-in signal: a codebase without it is treated as not FronyBoard-managed.

Model

fronyboard.db
├── projects   one row per project (key e.g. DLY): the roadmap record —
│              yearly overview (goal / now / target / checklist) + quarterly milestones
└── periods    one row per opened period (e.g. 2026Q3): tasks ({KEY}-001, ...)
               + `result` (retrospective, written when the period closes)

A project is two kinds of records — the roadmap, and one record per period. Both are JSON documents; the schema and the rules that guard it are in backend/src/fronyboard/validation.py.

  • Task ids are a project-global sequence (DLY-042) — they keep counting across periods and are never reused. They are the only link between FronyBoard and a codebase: use them in branch names (feat/DLY-042/short-desc) and record the branch on the task.
  • A task is a title (v0.33.0, AIR-086) plus status, tags, depends_on, branch and two one-line notes of at most 300 characters each. Agents read titles and status; the 25-line body nobody read, and the month/week slots that only existed to schedule it, are gone. Time is meta.completed_at.
  • The two notes answer different questions at different moments (v0.38.0, AIR-090). content is written at create time and says why the task exists — the pressure behind it, or the reading chosen where the spec allowed several. check is required by transition_task(status="done") and says what proves it done — the command and its output, or an observable a reader can go and see. One free note asked before the work can only restate the plan: in a 12-session benchmark every task an agent wrote paraphrased its own title, because the note was fixed at create time and update_task was never called. Reopening a task drops its check; nothing is proven any more.
  • Reference chain: period → roadmap milestone (the quarter). That is the only one.
  • Statuses — milestones: planned | active | done; tasks: todo | in_progress | done | blocked | cancelled.
  • cancelled is the soft delete — there is no hard delete. Cancelling requires a reason, keeps the record (and its id) forever, and hides the task from queries by default (list_tasks takes include_cancelled). blocked = may resume, cancelled = will not happen; transitioning a cancelled task restores it.
  • Carry-over: a task that outlives its period is not moved — recreate it in the next period under a new id and note the mapping in the closing retrospective.
  • get_status is the whole resume — the overview, each period's open tasks, and recent_done: the ten most recently finished tasks with their content, their check and when they completed. What is already built is what a cold session needs most, and nobody makes a second call to find it (AIR-090). It is still a record of claims: the board says what an agent reported, the code is the evidence.
  • depends_on on a task lists the earlier tasks it builds on (other projects allowed). It points backwards only — there is no forward index, because storing one direction and deriving the other read as inverted often enough to be worth dropping (v0.36.0, AIR-089). The field was follows until v0.37.0; the boot migration renames it. It is a pointer, not a lock: list_tasks and get_status flag the entries not yet done as waiting_on, and nothing is ever blocked.
  • Timestamps (meta.created_at / updated_at / started_at / completed_at) are stamped by the server in naive UTC — started_at on the first in_progress transition, completed_at on done (and removed again if the task leaves done). Agents never write them.
  • The result field closes a period — the rest of the file holds only current state, so the "why it turned out this way" lives there: judgment and reasons, not counts. Its presence is what marks a period closed.

Tools

AreaTools
Projectscreate_project, update_project, list_projects, get_roadmap, get_status, validate
Roadmapset_overview, set_check, upsert_milestone
Periodsopen_period, close_period, get_retrospective
Planningcreate_task, update_task, transition_task
Querieslist_tasks, get_task, search_tasks, recent_activity

update_task and transition_task derive the project from the task id prefix (DLY-042 → DLY), so their key parameter is optional. Re-calling close_period on a closed period rewrites its retrospective.

Typical flow:

create_project → set_overview → upsert_milestone → open_period
→ create_task
→ transition_task in_progress (with branch) → ... → transition_task done
→ close_period (retrospective)

Every mutation is validated before anything is written; invalid changes are rejected with the full error list. close_period refuses while tasks are still todo or in_progress. Writes are serialized per project, so concurrent clients cannot collide on ids or lose updates.

Self-hosting

The same package also runs as an always-on HTTP server (fronyboard serve): MCP over streamable HTTP for every machine on your network, a read-only web dashboard for humans, API keys per device, and OAuth for the hosted Claude / ChatGPT apps. Authentication is delegated to FronyAuth, a separate service. None of it is needed for the stdio install above. To see the dashboard on your own machine without any of that, run fronyboard serve --local (loopback only, no login) and open http://127.0.0.1:8642. docs/self-hosting.md covers the setup; docs/operations.md is the day-2 runbook.

Development

uv run --directory backend pytest      # backend
cd frontend; npm test                  # dashboard

The repo is a monorepo. backend/src/fronyboard/ — store.py (SQLite, data root), validation.py (schema gate), service.py (operations), auth.py (bearer middleware) + fauth.py (FronyAuth client), log.py, web.py (JSON API + static serving), server.py (MCP tool surface + CLI). frontend/ — the dashboard (React + Vite), built to static files that the backend serves; its build output frontend/dist is committed so a server needs no Node toolchain.

The three docs under docs/ cover what the code cannot tell you — running this on your own machines:

Installation

Source-derived launch command. Check the maintainer’s required arguments and credentials before running:

bash
uvx fronyboard

Set up in your AI client

Merge this template into ~/Library/Application Support/Claude/claude_desktop_config.json. Keep existing servers. Add any arguments, credentials, and permissions required by the maintainer; this template has not been install-tested.

json
{
  "mcpServers": {
    "io-github-cafelatte1-fronyboard": {
      "command": "uvx",
      "args": [
        "fronyboard"
      ]
    }
  }
}

Restart Claude Desktop completely for changes to take effect. Confirm the server appears connected in the client’s tool list, then try a read-only example from its documentation.

Claude Desktop setup reference

Package

fronyboardpypi

Compatible MCP Clients

FronyBoard works with any MCP-compatible client. Copy the config snippet from the Configuration section above and add it to the file shown for your client, then restart the application.

  • Claude Desktop~/Library/Application Support/Claude/claude_desktop_config.jsonRestart Claude Desktop completely for changes to take effect.
  • Cursor~/.cursor/mcp.jsonRestart Cursor for changes to take effect.
  • VS Code.vscode/mcp.jsonReload VS Code window for changes to take effect.
  • Windsurf~/.codeium/windsurf/mcp_config.jsonRestart Windsurf for changes to take effect.
  • Claude Code.mcp.jsonSave at the project root, then start Claude Code in that project and review the MCP server approval prompt. Keep real credentials out of shared files.

Learn More