# bank-statement-sheet-mcp

An MCP server that turns a PDF bank statement into transaction data **that arrives with a pass/fail reconciliation verdict**, so an agent knows whether the numbers are safe to write into a ledger.

**Your PDF is uploaded over HTTPS to the Bank Statement Sheet API** (`bankstatementsheet.com`), where it is parsed and checked. It is processed in memory; upload records are deleted within 24 hours and transactions are not permanently stored. If that is not acceptable for a given file, don't pass it — or point `BSS_API_URL` at your own deployment.

## Why the verdict matters

Any tool can pull numbers out of a PDF. The problem is that a wrong number looks exactly like a right one: a bookkeeper imports it, the books don't balance, and nobody knows which row lied.

So every extracted row is checked against the statement's **own printed running balance**:

```
balance[i] + amount[i-1] === balance[i-1]
```

The result travels with the data. When it fails, `verdict.ledger_safe` is `false` and **the transactions are withheld** — an agent that pipes `result.transactions` into a voucher tool gets nothing to post. Retrieving them takes a second, explicit call, which is visible in the transcript for a human to see.

## Install

```json
{
  "mcpServers": {
    "bank-statement-sheet": {
      "command": "npx",
      "args": ["-y", "bank-statement-sheet-mcp"],
      "env": { "BSS_API_KEY": "bss_live_..." }
    }
  }
}
```

`BSS_API_KEY` is optional. Without it the server works on the free anonymous tier: 5 conversions per month, Excel and CSV export. Mint a key at [bankstatementsheet.com/dashboard](https://bankstatementsheet.com/dashboard) — a key uses the same plan and daily page limit as the account it belongs to.

Try it with no account and no file of your own:

> Convert the demo bank statement and show me the reconciliation verdict.

## Tools

### `convert_bank_statement`

| Input | Notes |
|---|---|
| `file_path` | Absolute path to a PDF on this machine. |
| `bank` | Defaults to auto-detect, which also handles banks with no dedicated parser. |
| `order` | `chronological` (default, oldest first — what ledger imports expect) or `statement`. |
| `include_unverified_rows` | Only after a failed verdict, and only after telling the user. |
| `save_as` | `xlsx`, `csv` free; `fortnox`, `ofx`, `json` need Pro. |
| `use_demo_statement` | Converts a hosted synthetic sample. No quota, no real data. |

Returns `verdict` (status, `ledger_safe`, checks passed/total, method), `transactions` (each row flagged `verified`), `totals`, `source` (including the SHA-256 of the file, as an audit anchor), and any `files` written.

### `export_conversion`

Writes a conversion to xlsx / csv / fortnox / ofx / json. Files whose data failed the check get an `-UNVERIFIED` filename suffix and an in-file warning — both come from the server and neither should be stripped.

Conversions are held server-side for 10 minutes. Past that, this tool re-converts the same file automatically (which costs one conversion from your quota) — but only after confirming the file still hashes to what was originally converted, and it reports loudly if the second verdict differs from the first.

### `check_account_status`

Plan, quota remaining, and whether your API key was actually recognised. Worth calling if conversions are being refused: an unrecognised key silently falls back to the anonymous tier.

## Configuration

| Variable | Default | Purpose |
|---|---|---|
| `BSS_API_KEY` | — | Your key. Unset → anonymous tier. |
| `BSS_API_URL` | the hosted API | Point at your own deployment. |
| `BSS_ALLOWED_DIRS` | cwd + `~/Downloads` | Colon-separated directories PDFs may be read from. |
| `BSS_ALLOW_ANY_PATH` | `0` | Set `1` to disable the allowlist. |
| `BSS_MAX_FILE_MB` | `50` | |
| `BSS_TIMEOUT_MS` | `180000` | |
| `BSS_OUTPUT_DIR` | alongside the input PDF | Where `save_as` writes. |

The directory allowlist exists because this server reads local files and uploads them. The realistic threat is not a careless user, it's a prompt-injected agent asking for `~/Documents/tax-return.pdf`. Paths are resolved through `realpath` first, so a symlink inside an allowed directory cannot point outside it, and a file is rejected unless its bytes actually begin with `%PDF-`.

There is no URL input, deliberately: it would turn this into a fetcher running with your network position, aimable at cloud metadata endpoints or intranet hosts, for a capability the agent already has.

## What this server will not do

- Return extracted rows without a verdict attached.
- Hand over rows that failed the balance check without an explicit, separate request.
- Rename a file to remove the server's `-UNVERIFIED` marker.
- Silently re-upload a file whose bytes changed since it was converted.

## Licence

MIT. The extraction engine and the accuracy gate run server-side and are not part of this package.
