Skip to main content
Glama
pimvanoerle

open-banking-mcp

by pimvanoerle
README.md
# open-banking-mcp

An MCP server that gives Claude read-only access to UK bank accounts through
[TrueLayer's Data API](https://docs.truelayer.com/docs/data-api-basics) — one
integration covering Monzo, Starling, HSBC, Barclays, Lloyds, NatWest,
Nationwide, Santander and most other UK banks.

Read-only by design: this server requests Data API scopes only. There is no
code path here that can move money.

> **Status:** working against TrueLayer's sandbox. Not yet tried against a
> real bank connection.

## Setup

### 1. Create a TrueLayer console account

1. Sign up at [console.truelayer.com](https://console.truelayer.com) — free,
   instant, Google/GitHub SSO or email.
2. New accounts start in the **sandbox** environment, which is what you want
   first. Sandbox has a mock bank, no rate limits and no real money.
3. Click **Create App +** and give it a client id: 4–30 lowercase letters and
   digits, no special characters. **It cannot be changed later.** Sandbox apps
   get a `sandbox-` prefix automatically.
4. Copy the **client secret** from the credentials screen — it is shown once.
   You can mint more later under the app's Settings page.
5. Under the app's settings, add a **redirect URI** of
   `http://localhost:8080/callback`. This must match byte for byte what the
   server sends, including the scheme and path; a mismatch is the single most
   common cause of auth failures.

### 2. Configure

```bash
export TRUELAYER_CLIENT_ID=sandbox-yourapp
export TRUELAYER_CLIENT_SECRET=...
export TRUELAYER_REDIRECT_URI=http://localhost:8080/callback
export TRUELAYER_ENV=sandbox          # or "production"
```

Optional:

| Variable | Default | Purpose |
|---|---|---|
| `TRUELAYER_SCOPES` | all Data API scopes | Space-separated scope list |
| `TRUELAYER_PROVIDERS` | `uk-cs-mock` (sandbox) | Which banks to offer at consent |
| `TRUELAYER_USE_KEYRING` | `1` | Set `0` to store tokens in a file |
| `TRUELAYER_TOKEN_FILE` | `~/.open-banking-mcp/tokens.json` | Implies file storage |
| `TRUELAYER_CACHE_FILE` | `~/.open-banking-mcp/cache.db` | Local SQLite cache |
| `TRUELAYER_MAX_AGE_HOURS` | `25` | Age past which cached data is flagged stale |
| `TRUELAYER_HISTORY_DAYS` | `365` | How far back a full sync pulls |
| `TRUELAYER_PSU_IP` | unset | End user's IP; lifts rate limits for user-present calls |

### 3. Connect a bank

```bash
open-banking-mcp auth
```

This opens a browser, you pick a bank and consent, and the tokens land in your
macOS Keychain (or a `0600` JSON file on systems without a keyring).

In the sandbox, choose the mock bank and log in with username `john`, password
`doe` — `john1`/`doe1` through `john100`/`doe100` also work for testing
different account shapes. Note that the sandbox reports its provider id as
`mock`, not `uk-cs-mock`, so that is the name `status` and `logout` expect.

```bash
open-banking-mcp status          # connected banks + consent countdown
open-banking-mcp logout uk-ob-monzo
```

## How it reads data

Every read is served from a **local SQLite cache** by default, refreshed by a
daily sync. This is not just a speed trick: TrueLayer caches responses for an
hour and throttles unattended callers to roughly 4 calls a day unless the
request carries an `X-PSU-IP` header saying a human is present. An agent
checking your accounts on a schedule is exactly the throttled case, so it reads
from the cache instead.

```bash
open-banking-mcp sync     # pull everything; run this daily
open-banking-mcp cache    # what's stored, and when it last synced
```

Every response says how old it is:

```json
{
  "data": { "current": 12.0, "available": 112.0, "currency": "GBP" },
  "as_of": "2026-09-11T16:26:19Z",
  "age": "3 hours old",
  "source": "cache",
  "stale": false
}
```

Past `TRUELAYER_MAX_AGE_HOURS` (default 25 — a daily sync plus slack) `stale`
flips to true and a `warning` field is added, so an agent reporting a balance
can say how current it is rather than implying it is live.

**When you need live figures** — you just sent a payment and want to know if it
landed — every tool takes `fresh=true`, which bypasses the cache, queries the
bank, and writes the result back:

```
get_balance(account_id="...", fresh=true)
get_transactions(account_id="...", fresh=true)   # also pulls pending
```

`fresh=true` on transactions needs a specific `account_id`: a live refresh is
per-account, not a whole-portfolio sweep. It pulls pending transactions too,
which is usually what "did my payment go through" actually means.

### Scheduling the daily sync

On macOS, a launchd agent or a cron line is enough:

```
17 6 * * *  cd ~/dev/openbanking-mcp && .venv/bin/open-banking-mcp sync >> ~/.open-banking-mcp/sync.log 2>&1
```

## MCP tools

| Tool | What it does |
|---|---|
| `list_banks` | Connected banks and last sync time |
| `list_accounts` / `list_cards` | Accounts and cards |
| `get_balance` / `get_card_balance` | One balance |
| `get_balances` | Every balance plus per-currency totals |
| `get_transactions` | Query by date range, account, or text search |
| `list_standing_orders` / `list_direct_debits` | Recurring payments |
| `get_identity` | Account holder details |
| `sync_now` | Refresh the whole cache |
| `cache_status` | What the cache holds |

All except `list_banks`, `sync_now` and `cache_status` accept `fresh`.

### Connecting it to Claude

```json
{
  "mcpServers": {
    "open-banking": {
      "command": "/absolute/path/to/.venv/bin/open-banking-mcp-server",
      "env": {
        "TRUELAYER_CLIENT_ID": "sandbox-yourapp",
        "TRUELAYER_CLIENT_SECRET": "...",
        "TRUELAYER_ENV": "sandbox"
      }
    }
  }
}
```

## Consent expiry

Open Banking consent lasts **90 days** under FCA rules, then you must
re-authorise in a browser — there is no way around this. `status` shows the
countdown per bank and colours it amber at 21 days and red at 7.

## Token storage

Tokens go in the OS keychain by default (service name `open-banking-mcp`, one
entry per bank). A plaintext list of *which* banks are connected — no secrets —
lives at `~/.open-banking-mcp/providers.json`, because keyrings can't be
enumerated.

Set `TRUELAYER_TOKEN_FILE` to use a `0600` JSON file instead, on Linux or CI
where no keyring daemon is running. The server falls back to this automatically
if it finds no usable keyring backend.

## Development

```bash
python3 -m venv .venv && .venv/bin/pip install -e ".[dev]"
.venv/bin/pytest
```

## Licence

MIT

TDQS

B3.3/5.0

Scored across 12 tools

Disambiguation5/5

Each tool targets a distinct resource or aggregation level: banks, accounts, cards, individual balances, aggregate balances, transactions, standing orders, direct debits, identity, and cache operations. Although get_balance, get_card_balance, and get_balances all relate to balances, their scopes are clearly separated by description.

Naming Consistency4/5

Most tools follow a predictable verb_noun pattern (list_banks, get_balance, list_standing_orders). Two outliers—sync_now and cache_status—break the pattern slightly, but the overall set remains readable and consistent.

Tool Count5/5

12 tools is well within the typical 3-15 range for a focused domain server and each tool earns its place by covering a distinct part of the open banking read surface.

Completeness4/5

The server provides broad read-only coverage for open banking data: banks, accounts, cards, balances, transactions, standing orders, direct debits, identity, and cache management. Minor gaps exist—such as payment initiation, payee/beneficiary listing, or individual transaction lookup—but these are likely outside the intended read-only scope.

Maintenance

ActivityMaintained
ResponsivenessNo issues