Skip to main content
Glama
open-banking-mcp

open-banking-mcp

by pimvanoerle

open-banking-mcp

An MCP server that gives Claude read-only access to UK bank accounts through TrueLayer's Data API — 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 both the sandbox and a real bank connection. Single-user, personal-scale; not hardened for multi-tenant use.

Install

git clone https://github.com/pimvanoerle/open-banking-mcp.git
cd open-banking-mcp
python3 -m venv .venv
.venv/bin/pip install -e ".[dev]"
.venv/bin/pytest

On an Intel Mac, that install fails while building cryptography unless you have a Rust toolchain — upstream dropped macOS x86_64 wheels at version 49. Use the bundled constraints file instead:

.venv/bin/pip install -e ".[dev]" -c constraints-macos-intel.txt

That pin is deliberately not in pyproject.toml, so everyone else keeps getting current cryptography security updates.

Related MCP server: Bank

Setup

1. Create a TrueLayer app

  1. Sign up at console.truelayer.com — free, instant, Google/GitHub SSO or email.

  2. New accounts start in sandbox, which is where you want to begin: a mock bank, no rate limits, no real money.

  3. Click Create App + and choose a client id — 4–30 lowercase letters and digits. It cannot be changed later. Sandbox ids get a sandbox- prefix automatically; the live id is the same name without it.

  4. Copy the client secret — shown once. More can be minted later under Settings (up to 6 at a time).

  5. Add a redirect URI of http://localhost:8080/callback. It must match byte for byte, scheme and path included — a mismatch is TrueLayer's own documented most-common cause of auth failures.

2. Configure

Copy .env.example to .env and fill it in:

TRUELAYER_CLIENT_ID=sandbox-yourapp
TRUELAYER_CLIENT_SECRET=...
TRUELAYER_REDIRECT_URI=http://localhost:8080/callback
TRUELAYER_ENV=sandbox

Load it before running commands:

set -a; . ./.env; set +a

Every .env* file is gitignored except .env.example.

Variable

Default

Purpose

TRUELAYER_CLIENT_ID

—

Required

TRUELAYER_CLIENT_SECRET

—

Required

TRUELAYER_ENV

sandbox

sandbox or production

TRUELAYER_REDIRECT_URI

http://localhost:8080/callback

Must be registered in the console

TRUELAYER_SCOPES

all Data API scopes

Space-separated scope list

TRUELAYER_PROVIDERS

uk-cs-mock / uk-ob-all uk-oauth-all

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-<env>.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

open-banking-mcp auth

This opens a browser for consent and stores the tokens in your OS keychain.

In the sandbox, pick the mock bank and log in as john / doe — john1/doe1 through john100/doe100 give different account shapes. The sandbox reports its provider id as mock, not uk-cs-mock, so that's the name status and logout expect.

open-banking-mcp status                 # connected banks + consent countdown
open-banking-mcp logout ob-santander-personal

4. Going live

The console has a Sandbox / Live toggle. The same app exists in both, but they are separate environments: separate client ids, separate secrets, separate redirect URI registrations. Register your callback again on the Live side — it does not carry over.

Keep live credentials in their own file:

cp .env.example .env.production
# fill in the live client id and secret, set TRUELAYER_ENV=production
set -a; . ./.env.production; set +a
open-banking-mcp auth

New secrets can take up to 15 minutes to become active. If the first attempt fails with a 400, wait before assuming something is broken.

Sandbox and production data never mix. Tokens are keyed by environment in the keychain, the provider index is grouped by environment, and each environment gets its own cache file. Without that separation, a mock bank's balance could be reported as real money.

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 hard 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 locally instead.

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:

{
  "data": { "current": 4049.13, "available": 7049.13, "currency": "GBP" },
  "as_of": "2026-09-13T06:43:52Z",
  "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 whether it landed — every read 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, which is precisely the pattern that trips the rate limiter. It pulls pending transactions too — usually what "did my payment go through" actually means.

If TrueLayer does throttle you, a sync stops at the first 429 rather than walking the remaining accounts; continuing would burn the rest of the allowance and fail anyway. Whatever synced before the limit stays cached.

Scheduling the daily sync

17 6 * * *  cd ~/dev/openbanking-mcp && set -a && . ./.env.production && set +a && .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

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

Security

This server holds credentials and a mirror of your financial history. What that means in practice:

Read-only. The requested scopes are info accounts balance transactions cards direct_debits standing_orders offline_access — all Data API. There are no payment scopes and no payment endpoints in this codebase. The worst case is data exposure, not money movement.

On disk, under ~/.open-banking-mcp/ (directory mode 0700):

File

Mode

Contents

cache-<env>.db

0600

Balances, transaction history, account numbers, identity

tokens.json

0600

OAuth tokens, only if you opt out of the keychain

providers.json

0644

Provider ids only — no secrets, no financial data

SQLite journal and WAL side-car files are restricted too; they hold the same data as the database.

Tokens go in the OS keychain by default (service open-banking-mcp, account <env>:<provider_id>). The plaintext provider index exists because keyrings cannot be enumerated. Set TRUELAYER_TOKEN_FILE for a 0600 JSON file instead, on Linux or CI with no keyring daemon; the server falls back to this automatically when it finds no usable keyring backend.

Client secrets belong in .env / .env.production, never in the repo, never in a shell history, never pasted into a chat. If one leaks, rotate it in the console — your bank connection survives, because the refresh token is already issued.

Consider where the data ends up. If you wire this into a chat assistant, your balances and transactions travel wherever that assistant's messages go. That's a deliberate decision worth making up front.

Revoking access is always available from your banking app or TrueLayer, and consent expires on its own after 90 days.

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, amber at 21 days and red at 7.

Note that a refresh response often omits refresh_token entirely rather than rotating it. The client carries the existing one forward when that happens; dropping it would break every subsequent refresh.

Development

.venv/bin/pytest            # 63 tests, no network required

Tests mock TrueLayer's HTTP endpoints, so the suite runs offline. The interactive OAuth flow is covered end to end by driving the real localhost callback server with a fake browser.

Licence

MIT

Available Tools

12 tools
cache_statusA

Show what the local cache holds and when it was last refreshed.

Inputs: no named parameters.

get_balanceB

Get the current and available balance for one account.

Use fresh=true when the answer must reflect a payment made moments ago.

Inputs: account_id (required), fresh, provider_id.

get_balancesC

Get balances for every account and card at once -- the 'how am I doing' view.

Inputs: fresh, provider_id.

get_card_balanceC

Get a card's balance, credit limit and payment due details.

Inputs: card_id (required), fresh, provider_id.

get_identityC

Get the account holder's identity as the bank holds it.

Inputs: fresh, provider_id.

get_transactionsA

Query transactions across accounts and cards.

Dates are YYYY-MM-DD and the range is inclusive. search matches the description or merchant name. Omit account_id to search every account. fresh=true re-pulls one account from the bank first, so it needs account_id.

Inputs: fresh, limit, search, to_date, +4 more.

list_accountsA

List bank accounts, with sort code and account number where available.

Set fresh=true to bypass the local cache and query the bank directly.

Inputs: fresh, provider_id.

list_banksB

List connected banks and when each was last synced.

Inputs: no named parameters.

list_cardsC

List credit and debit cards.

Inputs: fresh, provider_id.

list_direct_debitsC

List direct debits on an account (Open Banking providers only).

Inputs: account_id (required), fresh, provider_id.

list_standing_ordersB

List standing orders on an account (Open Banking providers only).

Inputs: account_id (required), fresh, provider_id.

sync_nowA

Pull everything from the bank into the local cache.

Normally a scheduled job does this daily; call it when you need the whole cache refreshed rather than one account.

Inputs: provider_id, history_days.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    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

    ActivitySlowing
    ResponsivenessLimited data

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      B
      quality
      D
      maintenance
      Enables read-only access to personal bank accounts via the TrueLayer API, allowing querying of accounts, balances, transactions, and generating financial analytics such as spending by category, subscriptions, and monthly summaries.
      9
      1
      MIT
    • A
      license
      Not graded
      quality
      B
      maintenance
      Enables Claude to securely access your own European bank accounts read-only via PSD2, letting you check balances, transactions, and set up alerts through natural language.
      1
      MIT
    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables natural-language access to personal finance data, including consolidated balances, monthly overviews, transaction search, credit card invoices, recurring bills, budgets, category/tag reports, cash flow forecasts, and Open Finance status, with optional read/write tools.
      MIT
    • A
      license
      Not graded
      quality
      B
      maintenance
      Enables MCP clients such as Claude to read Monzo personal, joint and business account balances, pots and transactions, search transaction history, and break down spending by category and merchant. It handles OAuth token refresh and locally caches transactions so history beyond Monzo's 90-day API window remains available.
      1
      GPL 3.0