openbanking-mcp
Sends a daily brief of bank account balances and upcoming transactions to Todoist as tasks.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@openbanking-mcpWhat are my balances and upcoming debits?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
openbanking-mcp — read your accounts, and warn BEFORE
The repository is called
openbanking-mcp, the command is calledbankread. This is not an oversight: the repository name says what it is — an MCP server on top of Open Banking — because that's how you find it. The command name says what you do with it, andbankread doctorrolls off the tongue better thanopenbanking-mcp doctor. The configuration paths (~/.config/bankread, keychainbankread-enablebanking) follow the command, not the repository.
Read-only access to bank accounts via PSD2 (AIS license), plus a daily brief to
Todoist. Python 3.11+, zero third-party dependencies: urllib for HTTP, hand-written
JSON-RPC for the MCP server. Runs on the Mac, not in a cloud.
⚠ The data provider is yours to choose — read before starting
This repository was born with a GoCardless Bank Account Data client (formerly Nordigen), which was the obvious free path. Checked on 2026-08-20: this service is closed to new sign-ups and being phased out.
bankreadlib/gocardless.pyis therefore only useful to those who already had an account.The free replacement for personal use is Enable Banking, whose Restricted Production tier gives real production data on only the accounts you connect yourself — which is exactly the use case here, and without a licence to pay for. The Enable Banking client is written (
bankreadlib/enablebanking.py): self-service sign-up by email, authentication via RS256-signed JWT — no dependencies, the standard library is enough (bankreadlib/rs256.py). The roundup of workable solutions for BoursoBank and the three findings that changed the code are indocs/fournisseurs.md.One thing remains that no one can do for you: create the Enable Banking account, declare your bank accounts there, and sign the consent. The application ID and private key are personal — there is nothing to share, and that's where you need to start.
Everything else in the repository is provider-independent.
The problem it solves, and the one it doesn't
BoursoBank already sends "your balance is low". Caisse d'Épargne already sends "money in". Amazon sends "out for delivery", Leboncoin "the seller has confirmed". The information already arrives — it just arrives too late and in the wrong place.
too late: a threshold alert fires AFTER the direct debit. On the morning of the 15th, the taxes have gone through, the account is low, and the alert notes it;
in the wrong place: in the middle of two hundred emails, most of which go unread.
This folder is therefore not going to look for missing information. It does the one thing no one does: the subtraction. Today's balance minus what will hit by the end of the month. Neither the bank nor the tax office can do it — neither one sees the other.
What it doesn't solve: nothing here replaces actually looking at your accounts. A new, never-seen direct debit is invisible to a recurrence detector.
Related MCP server: bank.mcp
Read-only, and not out of politeness
The connection goes through the provider's PSD2 / AIS licence (Enable Banking, free Restricted Production tier). AIS is account information aggregation. Payment initiation is a separate licence (PIS), which this token doesn't have and the bank won't grant it. The worst-case scenario of a leak is reading a history, never a money movement. That's the only reason this folder has the right to exist.
Three practical consequences:
the provider credentials live in the macOS keychain, not in a file — a file ends up in a backup tar, an unfortunate
cat, an rsync to a NAS. For Enable Banking, that includes the RSA private key:bankread secrets --setreads it, stores it, and reminds you to delete the downloaded.pem;the bank consent expires — three to six months depending on what the bank grants, and it's the bank's response that is recorded, not our request. Nothing can renew it for you: you have to go back to your bank's site.
doctorwarns at D-14 (not at D-1, where a weekend would be enough to invalidate everything);all of it runs on the Mac. Not in a cloud session, not in a container.
Getting started
See what it does, right away
./bankread demoAn invented account, 400 days of fabricated history, an imaginary balance — and the real detection and real projection running on it. Nothing is written outside a temporary folder.
What the demo shows, and which is the whole point:
2026-08-23 ±3j -890.00 → 394.55 Loyer … [loyer]
2026-08-25 ±3j -412.00 → -17.45 Dgfip Impot … [impots]
2026-08-29 ±3j 2450.00 → 2432.55 Salaire …
⚠ passe sous 300 € le 2026-08-25 (-17.45 €), poussé par « Dgfip Impot Revenu »
(1 motif(s) incertain(s) non comptés — la vraie trajectoire peut être plus basse)The rent still leaves you above the floor; it's the taxes that push you below, five days before the salary. Your bank will tell you on the morning of the 25th. And the last line is the most important: the property tax has only appeared twice in the history, so it's not believed, so it doesn't count — and the projection itself says it's optimistic rather than pretending otherwise.
Install it, or not
Three ways, in order of the commitment they require:
git clone https://github.com/Beennnn/openbanking-mcp && cd openbanking-mcp && ./bankread doctor
uvx --from git+https://github.com/Beennnn/openbanking-mcp bankread doctor # sans rien installer
pipx install git+https://github.com/Beennnn/openbanking-mcp # à demeureThe clone first, and not out of habit: a tool that reads bank accounts is read
before being installed. ./bankread works from the clone, without installing anything at all.
Then, in order
# 0. créer l'application chez le fournisseur : https://enablebanking.com/sign-in/
# Control Panel → API applications → environnement « Production », clé générée.
# L'URL de retour à déclarer est http://127.0.0.1:8788/callback (à l'identique).
# 1. les deux identifiants (le second est le CHEMIN du .pem téléchargé)
bankread secrets --set # ils vont dans le trousseau, pas sur le disque
# 2. trouver sa banque, puis signer le consentement (ouvre le site de la banque)
bankread banks bourso # relève le NOM EXACT que renvoie l'API
bankread link "BoursoBank" # puis, séparément :
bankread link "Caisse d'Epargne"
# 3. vérifier
bankread doctor
bankread project --days 45 --floor 300
# 4. le brief de 7 h 30 → tâches Todoist
launchd/install.sh
launchctl kickstart -k "gui/$(id -u)/com.bankread.brief-quotidien" # essai immédiatThe BoursoBank case, concretely
It's the bank this repository was written against, so here's what to expect
rather than a generality. Another bank will behave differently — and
bankread banks is what's authoritative, not this file:
~90 days of history, no more. MONTHLY recurring items — rent, EDF, monthly tax payments — come out from the first read: three occurrences are enough. Annual ones wait for the registry to accumulate (see above).
Validation goes through the BoursoBank app (PSD2 strong authentication). The
bankread linkflow opens the site, then the phone rings. So you need to have it on hand, and repeat this gesture every 90 days.Don't count on Wicount 360 aggregation. BoursoBank already aggregates the Caisse d'Épargne account and sends alerts on it, but PSD2 gives access to accounts held by the queried bank, not to what it aggregates from elsewhere. The Caisse d'Épargne account therefore needs its own
bankread link, with its own 90-day consent. (To be confirmed at first connection: if external accounts appear in the flow, so much the better.)Joint accounts are chosen in the bank's consent screen. Only check what you really want to read: what's checked goes into the local registry, and the registry doesn't forget.
bankread banks bourso gives the exact name — at Enable Banking a bank is designated
by its NAME and country, not by a technical identifier — and the maximum consent
duration that bank grants. That value is what's authoritative, not this file.
link chains automatically into the golden window retrieval: the full
history is only served within the hour after signing, then the bank falls back to
90 rolling days. Don't interrupt this step — it only replays at the next
renewal, in three to six months.
The commands
| what works, what will break, when. No network calls — a diagnostic that consumes quota is a diagnostic you don't run. |
| the balances, with their age |
| the detected recurring items and their next occurrence |
| the cross: balance minus upcoming recurring items, day by day |
| the latest transactions |
| everything at once, for a script |
| what it looks like, on a FICTITIOUS account — no bank, no sign-up |
| MCP server on stdio, six read tools — for Claude or any other MCP client |
Exit codes: 0 all good, 1 something to look at, 2 hard failure — so launchd and calling scripts can find their way.
Have an AI read the accounts
claude mcp add bankread -s user -- "$PWD/bankread" mcpOr without a clone at all, like any MCP server is distributed:
{
"mcpServers": {
"bankread": {
"command": "uvx",
"args": ["--from", "git+https://github.com/Beennnn/openbanking-mcp", "bankread", "mcp"]
}
}
}MCP is an open protocol and bankreadlib/mcp.py is hand-written JSON-RPC:
no vendor library, no token, no outbound calls. The same server
declares itself the same way in other MCP clients, and bankread json serves what isn't
one. All of it — including how to go without it entirely — is in
docs/integration.md.
Two limits paid in advance, not to rediscover
Four calls per day per account. Most banks cap at four calls per account per day
for background retrieval. Four. It's not a limit you approach, it's a limit you hit
in one morning of debugging. Hence a cache that's not a comfort: without it, the
morning brief fails one day out of two. Hence also --refresh, which is explicit and never the default.
The cap drops when a user is actually in front of the screen, which a PSU header declares to the bank. bankread only sends it if the command has a terminal: the 7:30 AM brief doesn't, so it stays capped. It's a declaration made to a bank, not a performance setting — setting it to "true" from a launchd agent would be a lie.
A short history makes a projection OPTIMISTIC, not incomplete. Not all banks
return thirteen months — BoursoBank returns about 90 days. Below
380 days, an annual recurring item (property tax, insurance, fee) hasn't been
seen twice, so it doesn't exist for the detector, so the announced trajectory
is better than the real one. It's the only place where being wrong costs
money, so upcoming, project, and doctor say it in plain words.
And above all, that's why ledger.py accumulates instead of caching. The
first draft (2026-08-20, morning) kept a snapshot: each read replaced the
previous one. With a 90-day bank, that made an annual due date not
"not yet detected" but undetectable for life — anything beyond the quarter
disappeared from the API and the cache at the same time. The ledger melts each read into
a durable file; after a year of briefs, bankread knows 365 days where the
bank only shows 90, and the property tax appears on its second pass.
bankread doctor shows the countdown.
The deposit rule, applied to a balance
No green line that hasn't been observed.
Every response from read.py carries an etat field:
| what it means |
| read just now, or recently enough to still be true |
| served from cache, with its age in plain text — read it, don't trust it |
| nothing fresh enough to claim anything |
Never a bare balance. A solde: null, etat: "inconnu" is not a zero balance, and
it's the worst possible reading of a missing figure — the MCP tool descriptions
repeat it to the model before it calls, so it doesn't learn it by making a mistake.
Same discipline on predictions: a due date seen twice isn't a due date,
it's a coincidence. It comes out marked confidence: "faible" and doesn't enter the
projection. Three regular passes, that's a fact.
And on inflows: projecting only outflows gives a trajectory that always
dips, so an alarm every day, so no alarm at all after a
week. Salary and benefits are detected by the same path and counted in
the other direction. test_le_salaire_qui_arrive_avant_evite_la_fausse_alerte guards this case.
The 7:30 AM brief
brief/brief.md is the text Claude runs every morning, launched by launchd via
brief/run-brief. It reads Gmail and the bank, and its only useful output is a small
number of Todoist tasks tagged auto-brief.
Silence is a success. A morning with nothing to report produces zero tasks — no
daily summary, no "nothing to report." Only five reasons create a task,
and the notebook ~/.config/bankread/brief-vu.json prevents the same late package from
generating one per day for two weeks.
run-brief passes a CLOSED list of tools to claude --allowedTools. No Gmail
write tool is in it: the brief reads mail, it never replies. And nothing
is left to an authorization prompt, since no one is in front of the screen to
answer at 7:30 AM.
The model called is a replaceable driver (brief/agents/), not a hardcoded call:
BANKREAD_AGENT=aucun brief/run-brief runs the brief without any AI, and the
projection still lands in the journal. Reading accounts has never needed
a model — see docs/integration.md.
launchd/install.sh installs only that agent. An install script that does more
than its name ends up reinstalling something dead, and you spend the evening
figuring out why two copies are running.
Switching providers
The coupling is thin, and it's verified by a test (test_le_contrat_tient_en_deux_methodes):
depends on the provider? | |
| yes — tokens, quotas, HTTP, consent flow |
| it chooses which one to load, and writes the contract as a |
| yes, partially — the linking flow |
| no, and not a single direct import anymore |
To wire in a third provider, all you need is a module exposing these two
methods — the Protocol in provider.py declares them, and provider.charger() makes the
choice:
balances(account_id) -> {"balances": [...]}
transactions(account_id, date_from, date_to) -> {"transactions": {"booked": [...]}}The expected format is that of the Berlin Group: balanceType / balanceAmount for
balances, bookingDate and transactionAmount signed for transactions. If the
provider speaks something else, the translation is paid for in its client and nowhere
else — see enablebanking._operation(), which restores the sign to amounts
that Enable Banking always delivers positive.
Three pitfalls encountered while writing the Enable Banking client, not to be rediscovered:
the sign of amounts is carried next to the amount (
credit_debit_indicator), not in it. Copying it as-is would count every debit as an inflow;the full history only lasts an hour after signing (see above);
the granted consent duration can be shorter than the one requested: it's the bank's response that gets recorded, otherwise
doctorannounces a valid consent two months after its death.
Tests
python3 test_bankread.py51 tests, stdlib only, no network. They mainly check the cases where the code must stay silent: two occurrences don't make a due date, a thirty-hour cache is no longer a balance, you don't project on a balance never observed. A detection that gets the date wrong doesn't crash — it announces taxes on the 12th instead of the 15th, with the same confidence.
What isn't versioned
~/.config/bankread/state.json (linked accounts, tokens, consent dates) and
~/.cache/bankread/ — specific to this machine and this consent. The provider
credentials are nowhere on disk: they're in the macOS keychain,
service bankread-enablebanking (or bankread-gocardless), RSA private key included.
⚠️ ~/.local/share/bankread/ledger/ is separate: it's the register, and it doesn't get
purged. What it contains, the bank can no longer give back — beyond 90 days, it has
forgotten. Deleting it with the cache means starting over with three months of memory. It's
stored in data and not in cache precisely to make that confusion
hard. Accepted trade-off: it's a year of bank transactions in plain text on
disk, in 0600.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceAn MCP server that exposes Enable Banking API tools for interacting with bank accounts through Open Banking. It enables users to authenticate sessions, list accounts, and fetch transaction history or balances via a secure self-hosted server.2
- AlicenseNot gradedqualityBmaintenanceMCP server that turns bank transactions into a financial digest including cash-flow forecast, spending breakdown, fee detection, and receipt reconciliation, exposing deterministic engines as JSON-RPC tools.Apache 2.0
- FlicenseNot gradedqualityBmaintenanceMCP server for personal finance management. Enables natural language expense logging, budgeting, recurring charge detection, and statement import with deterministic local calculations.
- FlicenseNot gradedqualityBmaintenanceMCP server for personal finance via Open Finance, consolidating accounts and cards and answering spending questions with aggregated numbers. Provides tools for category spending, recurring subscriptions, budgets, card bills, and installment forecasts, with data stored locally in an encrypted SQLite database.1
Related MCP Connectors
Hosted remote MCP server for YNAB on Cloudflare Workers with OAuth
Hosted MCP server for Mini Accountant: invoices, expenses, customers, analytics, tax estimates.
MCP server for Withings health data — sleep, activity, heart, and body metrics.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/Beennnn/openbanking-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server