Development
Layout
Section titled “Layout”Directoryavenir_mcp/ — the server (see Architecture)
- …
Directorytests/ — unit tests, protocol tests, documentation and hygiene tests
- …
Directoryevals/ — the demo plan, a stand-in for YNAB’s API, the agent evaluation
- …
Directorydocsgen/ — generates the tool reference, the error catalogue and the examples
- …
Directorydocs/ — this site (Astro Starlight, English, French, Spanish, German, Dutch)
- …
- AGENTS.md — the contract for contributors, human or not
- justfile — every command below
- pyproject.toml, uv.lock — the Python project, locked
Set up
Section titled “Set up”git clone https://github.com/mathbeal/avenir-mcp && cd avenir-mcpuv sync # Python 3.14 and the locked dependenciesjust check # every gate CI runsThe checks
Section titled “The checks”just check runs, in CI’s order:
| Check | Command | Fails when |
|---|---|---|
| format | ruff format --check |
a file is not formatted |
| lint | pylint |
the score is below 10.00 |
| ruff | ruff check |
a docstring of the package is not in Google style or leaves out an argument, the return value or an exception raised; imports are not sorted; bandit’s security checks find a risky pattern (TLS not verified, a shell command, a password in the code…) |
| docstrings | pydoclint |
a docstring of the package lists the arguments in another order than the signature, gives a type the annotation already gives, or is a bare summary for a function that takes arguments or returns a value |
| YNAB API terms | pytest -m ynab_terms, python -m docsgen.terms |
a rule of YNAB’s API terms is broken (naming, attribution word for word, YNAB’s own image, a token for its owner only, the hourly limit), or YNAB changed its terms since the date checked; the README badge shows it |
| dependencies | deptry . |
the code imports a package pyproject.toml does not declare, or declares one it does not use |
| types | mypy (strict) |
any type error |
| tests | pytest |
a test fails, any warning is raised, or coverage of lines or branches is below 100 % |
| vocabulary | lexdrift check avenir_mcp --baseline lexdrift.lock |
a new word appears for an idea already named |
| lock | uv lock --check |
uv.lock does not match pyproject.toml |
CI also runs lychee on every link of the README and the documentation (just links,
and every Monday, since a page elsewhere can disappear), typos, zizmor on the workflows, reuse lint (every file states its copyright and licence), two builds of the wheel and the sdist that must be identical bit for bit (just reproducible), pip-audit on the locked dependencies,
gitleaks on every commit of the history (just secrets, with Docker),
the documentation build with the tests of its own code, CodeQL and the OpenSSF Scorecard
(once the repository is public), a weekly comparison of YNAB’s API with its snapshot
(just api-drift), and the official MCP Inspector
(just inspect), which checks the server as a client sees it on the demo plan: its
lists, the portability of its tool schemas, and one call of each kind. The test suite runs on Linux and macOS with Python 3.12,
3.13 and 3.14, and on Windows with Python 3.14 (on a pull request, macOS tries 3.14 only:
its runners cost ten Linux ones); coverage is enforced on Linux and macOS,
where the file-permission checks apply. Python 3.15 runs too, as an experiment: a failure there is a warning,
not a red check. On
a pull request, every commit must be signed off and the title must start with the kind of
change. Two checks must be green: CI passed, which stands for every job of the
quality workflow, and MCP Inspector, a workflow of its own so that its badge shows it
alone.
Dependencies
Section titled “Dependencies”Renovate opens pull requests every Monday morning:
| Update | Handling |
|---|---|
| development tools, CI actions, the documentation site (minor and patch) | grouped, merged by itself once CI passes |
fastmcp, httpx, pydantic — they run in front of users’ plans |
one pull request each, reviewed by hand; the evaluation runs before a fastmcp update |
| any major version | one pull request each, reviewed by hand |
| a known vulnerability | at once, whatever the day |
A new release waits seven days before Renovate proposes it, so a broken or malicious version withdrawn quickly never reaches the project.
| Kind | Files | What they check |
|---|---|---|
| Unit | test_classifier, test_triage, test_writes, test_reconcile, test_forecast, test_analytics, test_journal, test_client |
the pure logic and the HTTP client, with invented data |
| Properties | test_properties |
with Hypothesis, rules that hold for any input: amounts round-trip, spreading keeps every cent, months add up, cursors, bank text, confirmation fingerprints |
| Protocol | test_protocol*, test_context, test_policy |
tools through an in-memory MCP client: schemas, annotations, confirmation paths, undo |
| Documentation | test_docs |
generated pages and examples match the code; every page exists in each translation; every environment variable is documented |
| API coverage | test_api_coverage |
every operation of YNAB’s API is used by a tool, planned or left out with a reason; the client calls only documented paths |
| Hygiene | test_hygiene |
no IBAN, token, statement, home path or Finder copy in the repository |
| Evaluation | test_evals |
the evaluation’s own checks are right |
Tests are written first. No test calls YNAB.
just mutate runs mutation testing (mutmut) on the modules that compute: it changes the
code on purpose, some 1,500 times, and checks that a test fails each time. The changes no
test notices point to the tests to write; many are harmless, such as a message’s wording.
just bench times the modules that compute on a generated five-year plan, about 9,000
transactions (pytest-benchmark). On a laptop the slowest, preparing a page of suggestions,
takes about 30 ms: a tool’s time is spent waiting for YNAB, not computing.
just fuzz runs the Hypothesis properties with 50,000 examples each instead of 100 (a few
minutes; just fuzz 200000 for longer), on what comes from outside: bank labels, damaged
journals, HTTP headers, amounts, cursors. A failure is saved and replayed, minimised, by
the next run. CI does it every night with 200,000. The first run found a journal byte that
was not text (0x80) surfacing as a bare codec error instead of the line to fix.
Documentation
Section titled “Documentation”uv run python -m docsgen # tool pages, error catalogue, examplesjust docs # build the site in every language; broken links failjust docs-serve # live preview on http://localhost:4321/avenir-mcp/docsgen runs avenir-mcp on the demo plan and writes what it really answers, with the
date fixed and random ids replaced. Hand-written pages exist in English, French,
Spanish, German and Dutch; a test fails when one is missing.
Hosting
Section titled “Hosting”The site is published on GitHub Pages by the docs workflow, and on Cloudflare Pages
(avenir-mcp.pages.dev), which builds it from the repository with npm run build:cloudflare.
Both serve it under /avenir-mcp/; on Cloudflare the root redirects there. GitHub Pages
cannot send security headers; Cloudflare Pages sends those of docs/cloudflare/_headers
with every file: a Content-Security-Policy, HSTS, X-Content-Type-Options: nosniff,
X-Frame-Options: DENY, a referrer policy, a permissions policy and an opener policy.
The policy allows the site’s own files and each inline script and style of the built
pages by its SHA-256, computed at build time; 'wasm-unsafe-eval' lets the search run
its WebAssembly, and style attributes are allowed through style-src-attr only. The build
fails, in CI too, when a required header is missing or weakened. The DOCS_SITE
environment variable sets the address used in canonical links and the sitemap.
Evaluation
Section titled “Evaluation”just evaluate # all tasks, Sonnet, about 1 USD of your Claude planuv run python -m evals.run --task classify --model haikuSee Evaluation.
Commits and releases
Section titled “Commits and releases”- Commit messages start with a type —
feat:,fix:,docs:,refactor:,test:,build:,ci:,chore:— becauseCHANGELOG.mdis generated from them by git-cliff. - To release:
just changelog, commit, tagvX.Y.Zmatchingavenir_mcp.__version__, and publish a GitHub release. Thepublishworkflow builds twice with the tagged commit’s date, stops unless both builds agree, runstwine check --strictand uploads to PyPI through Trusted Publishing: no token is stored. - Then the MCP registry:
server.jsoncarries the same version (a test checks it); runmcp-publisher login github, thenmcp-publisher publish. PyPI ownership is proven by themcp-name:line of the README.
Unofficial project. We are not affiliated, associated, or in any way officially connected with YNAB or any of its subsidiaries or affiliates. The official YNAB website can be found at https://www.ynab.com. The names YNAB and You Need A Budget, as well as related names, tradenames, marks, trademarks, emblems, and images are registered trademarks of YNAB. avenir-mcp is provided as is, without warranty, and is not financial advice. Legal notice · Privacy