Skip to main content
Glama
pete-life

Billy MCP

by pete-life
README.md
# Billy MCP

A local MCP server for Billy bookkeeping: complete accounting reads, receipt collection/provenance, reviewed purchase and journal drafts, payments, and reconciliation of existing bank postings. Generic company support: **the API token determines the company**. This is an independent community project, not an official Billy or Shine product.

## Run

Requires Node.js 22.13+ (built-in SQLite).

```sh
git clone https://github.com/pete-life/billy-mcp.git
cd billy-mcp
npm ci --ignore-scripts
npm run build
npm run setup
npm start
```

Create a company API token in Billy under Settings → Access tokens. Setup asks for it with hidden terminal input, fetches the connected company and asks you to confirm that identity. It writes an owner-readable `credentials.env` outside the repository, under `~/.local/share/billy-mcp/`. Do not paste tokens in chat. `npm start` loads that file automatically.

Alternatively set `BILLY_ACCESS_TOKEN` in the MCP process environment. The company ID is discovered automatically. `BILLY_ORGANIZATION_ID` is optional: set it to enforce an expected company in addition to the token. A mismatched token is rejected. Setup records this extra guard automatically.

Each company has separate local receipt metadata, plans and vendor registry rows. Use a different `BILLY_DATA_DIR` for fully separate account profiles. Do not share a profile directory across machines or network filesystems; execution locking is local SQLite.

Generic MCP client configuration for this checkout:

```json
{
  "mcpServers": {
    "billy": {
      "command": "node",
      "args": ["/absolute/path/to/billy-mcp/dist/launch.js"]
    }
  }
}
```

`command` must resolve to Node 22.13+ in your desktop client's environment; an absolute Node path is safest. This file is a configuration example, not an automatic edit to your client settings.

## Included tools

| Tool | Purpose |
|---|---|
| `billy_status` | Connected company, write switches and receipt inbox |
| `billy_list` / `billy_get` | Typed resource selection, supported filters, full pagination |
| `billy_period_overview` | Unreconciled bank lines, possible existing postings and receipts |
| `billy_import_receipt` / `billy_receipts` | Archive originals, deduplicate bytes and retain provenance |
| `billy_save_vendor` / `billy_vendors` | Vendor billing accounts and retrieval/access status |
| `billy_prepare` | Preview a concrete financial operation and current record snapshots |
| `billy_plan` / `billy_refresh_plan` | Inspect or refresh an unexecuted proposal |
| `billy_execute` | Execute a reviewed proposal once, then read back the outcome |
| `billy_journal` | Inspect completed, rejected and uncertain operations |

The `bookkeeping-period` MCP prompt loads the [bookkeeping skill](skills/billy-bookkeeping/SKILL.md). It orchestrates Gmail/Drive/local files/**vendor portals** through the agent's existing connectors and browser tools. Those external tools and logged-in sessions are not bundled in this MCP server. Original files are downloaded into the configured inbox, read by the agent and imported with source and extracted invoice metadata. This project does not contain a universal authenticated vendor scraper or built-in OCR.

## Reusable bookkeeping skill

The [bookkeeping skill](skills/billy-bookkeeping/SKILL.md) contains tool recipes, exception handling and an optional learning routine. The MCP prompt loads the same file. To use it directly in a skill-aware client, install or link the `skills/billy-bookkeeping` directory according to that client's instructions.

Choose your own model. Store company-specific mappings, mailbox selection and billing portal access outside the public repository. External mail, file and browser tools must be supplied by the calling agent.

The learning routine records evidence privately under the configured data directory. Any reusable public documentation change must exclude company records and secrets. Learning does not authorize additional accounting actions.

## Supported operations

`billy_prepare` accepts a validated discriminated operation:

- `upload_receipt`: upload an archived PDF/PNG/JPEG and confirm the attachment exists.
- `create_contact`: create a customer/supplier with duplicate name/registration checks.
- `create_bill`: create a **draft**, using an uploaded receipt, supplier, invoice date/number, currency, explicit tax mode and account/tax-coded lines. Supplier name and extracted totals must match.
- `create_journal`: create a balanced **draft**, with receipt or explicit no-receipt reason plus bank-line identity. Tax-coded journal expansion is not live-verified; use a purchase bill for VAT-coded expenses.
- `approve`: approve a reviewed bill, invoice or journal draft. Bills require attached evidence.
- `create_payment`: register a same-currency bill/invoice payment or an explicitly evidenced full foreign-currency supplier-bill settlement from a base-currency bank account against an existing unpaid balance and an identified bank line. All payments require a live, explicitly unapproved single-line bank match without existing subject associations; this is rechecked before writing. Equal instalments on separate bank lines remain separate operations. This is bookkeeping, not a bank transfer.
- `reconcile`: associate one bank line's existing match with an existing bank-account posting and approve it. No expense is created. This is separately gated and live-verified for the documented single-line DKK flow.

## Execution guarantees and limits

Writes are off by default. To enable them after setup/acceptance, set `BILLY_ALLOW_WRITES=true` in the local credentials file or process environment. This enables the capability; it does not authorize arbitrary financial actions. The calling agent must have authorization for the concrete action or reviewed batch. The `authorization` text is an audit assertion, not a human-approval security boundary.

Each preview has a persisted ID and hash binding the operation **and the current records**. Execution claims a company-wide SQLite lock, validates fresh data, writes and reads back. Repeated execution returns stored evidence. Another changed request is still another operation: callers must not use different wording/amounts to bypass an uncertain write.

- No POST/PUT automatic retries. Read-only 429/temporary server errors have bounded retries and a timeout.
- A crash, timeout, malformed response or failed verification after sending a write is **unknown**, never “failed, safe to retry”. Unknown/executing plans block subsequent writes for that company. A definitive first-write 400/401/403/404/409/422/429 rejection is retryable only after a fresh preview. Recovery of genuinely uncertain writes requires operator investigation against Billy and the interactive local command described below.
- Preview expires after 30 minutes; changed data requires refreshing and reviewing the new hash.
- Receipt import restricts paths to the configured inbox, rejects escaping symlinks, checks PDF/PNG/JPEG signatures and limits size to 20 MB. SHA-256 identifies the original bytes. It does not prove invoice validity or correct extraction.
- Duplicate bill checks compare invoice references across contacts with matching name/registration number, plus same-date/amount candidates. They cannot prove uniqueness when existing supplier identity, references and dates are all different or missing. Inspect existing postings first; journal/payment bank-line IDs prevent local duplicate actions.
- A sequence of remote API calls is not an atomic Billy transaction. If reconciliation stops after association creation, it is unknown and must be investigated.
- Concurrent manual edits in Billy cannot be fully locked by this server. Fresh snapshots reduce but cannot eliminate that race.

## Live verification status

**Live purchase flow verified on 2026-09-22:** company-token connection, original PDF upload, attachment linking, Danish VAT purchase draft, approval and independent balanced-ledger readback were verified through the live API. Supplier legal-name changes can be matched by verified country and registration number. Build and 37 automated tests pass. A same-currency DKK supplier payment and single-posting bank reconciliation were also live-verified on 2026-09-22, including independent balanced-ledger and zero-balance checks; see [live acceptance](docs/live-acceptance.md).

`BILLY_ALLOW_BANK_MATCHING` defaults to false independently of other writes. Public documentation labels match relationships read-only and does not explain the full approval sequence. The implemented single-posting sequence follows the separate documented association resource; the single-line DKK flow was subsequently live-validated; other variants remain unverified.

Current explicit exceptions: foreign-currency payments outside full supplier-bill/base-currency-bank settlement, grouped/partial bank matches, credit notes/refunds, automatic VAT-return filing, VAT-period settlement, subscription changes, bank transfers, remote hosting, background scheduling, bulk batch atomicity and automatic recovery of unknown writes, and tax-coded journal expansion.

## Operator recovery

After inspecting the real Billy records and resolving any partial operation in Billy, run:

```sh
npm run recover -- PLAN_ID applied
# Or, only after establishing that no remote changes occurred:
npm run recover -- PLAN_ID not_applied
```

This is an interactive local operator command, never an MCP tool. It requires an evidence note and confirmation of the plan ID, records the decision and never writes to Billy. It refuses recovery of an executing plan while the recorded executor process is alive. `applied` becomes a terminal state that cannot replay; `not_applied` allows a refreshed preview. Incorrect operator assertions are not detectable automatically, so inspect the entire operation first.

## Development

```sh
npm run check
```

Tests use temporary local files and simulated Billy responses. The stdio test starts the compiled MCP server and performs protocol initialization, tool discovery, validation errors and prompt retrieval. No test touches production accounting data.

Official references: [Billy API](https://www.billy.dk/api/), [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk/tree/v1.x). API documentation checked 2026-09-22.

## License and contributions

MIT licensed. See [LICENSE](LICENSE). Contributions are welcome; read [CONTRIBUTING.md](CONTRIBUTING.md). For security reports, see [SECURITY.md](SECURITY.md). This is an early release with deliberately limited accounting flows; review the supported operations and verification limits before enabling writes.

TDQS

A3.7/5.0

Scored across 13 tools

Disambiguation5/5

Each tool targets a distinct action or resource: read/list, status, receipt import/list, vendor save/list, and proposal lifecycle (prepare/plan/refresh/execute/journal). Although plan and journal both deal with proposals, plan is single-proposal inspection and journal is a history listing, so boundaries remain clear.

Naming Consistency4/5

All tools share the billy_ prefix and use straightforward action or resource names, but the set mixes verb forms (billy_list, billy_get, billy_prepare) with noun forms for list/status operations (billy_status, billy_receipts, billy_vendors, billy_journal). This is a minor consistency issue, not a confusing one.

Tool Count5/5

13 tools is well within the ideal range and each tool maps to a distinct step in the workflow: reading, status, receipt archive, vendor registry, and the prepare/plan/refresh/execute proposal pipeline. There is no obvious tool that is redundant or missing at the count level.

Completeness4/5

The proposal pipeline covers prepare, inspect, refresh, execute, and journal, and the read/import/list tools cover most inputs and outputs. The main gaps are a lack of an explicit cancel/abandon operation for proposals and no direct tool for uploading receipts into Billy; agents can usually work around these via execute attachments and by ignoring rejected proposals.

Maintenance

ActivityMaintained
ResponsivenessNo issues