Skip to main content
Glama
silkyrich

Monzo Claude Connector

by silkyrich
README.md
# Monzo → Claude Connector

![Claude ⇄ Cloudflare Worker ⇄ Monzo](assets/monzo-claude-integration.png)

A remote [MCP](https://modelcontextprotocol.io) server, running on a Cloudflare
Worker, that gives Claude **read-only** access to your Monzo account over OAuth.

Once connected in claude.ai, you can ask things like *"what's my Monzo balance?"*,
*"list my recent transactions"*, or *"how much is in my savings pots?"* in any
chat — web, desktop, or phone. Authenticate once; it follows you everywhere.
No laptop, no local server, nothing to keep running.

> **Not affiliated with Monzo.** This is a personal-use tool built on Monzo's
> public [developer API](https://docs.monzo.com). Use at your own risk, and read
> [Security model](#security-model) before deploying.

## What it exposes

| Tool | Description |
|------|-------------|
| `get_accounts` | Open accounts (personal, joint, Flex, rewards) with ids/sort codes |
| `get_balance` | Balance, total incl. pots, and spent-today — one account or all |
| `list_transactions` | Recent transactions (last 90 days), filterable by date |
| `get_transaction` | Full detail on a single transaction |
| `list_pots` | Savings pots and their balances |

**Read-only by design**: no payments, transfers, or pot movements — the tools
simply don't exist, so no amount of prompt injection can move money.

## Architecture

```
Claude (claude.ai)  ──OAuth──>  This Worker  ──OAuth──>  Monzo API
                                     │
                     workers-oauth-provider (KV)  +  McpAgent (Durable Object)
```

- [`@cloudflare/workers-oauth-provider`](https://github.com/cloudflare/workers-oauth-provider)
  is the OAuth server Claude authenticates to — it handles dynamic client
  registration, `/authorize`, `/token`, and grant storage in KV.
- [`src/monzo-handler.ts`](src/monzo-handler.ts) bridges that to Monzo's OAuth
  (authorize → magic-link email → in-app approval → callback).
- [`src/mcp.ts`](src/mcp.ts) is the `McpAgent` — one Durable Object per
  connected user, holding that user's Monzo tokens and refreshing them
  automatically via the refresh token.
- [`src/monzo.ts`](src/monzo.ts) is the thin Monzo API client.

## Security model

Worth understanding before you deploy — the design isolates users by
construction:

- **No standing bank credential in the Worker.** The only Worker-level secrets
  are the Monzo OAuth `client_id`/`client_secret`, which can exchange
  authorization codes but cannot read any account data by themselves.
- **Tokens live per-grant, not per-Worker.** When a user completes the OAuth
  dance, their Monzo tokens are stored encrypted inside *their* grant
  (`completeAuthorization({ props: ...tokens })`). Every MCP request is resolved
  through the caller's bearer token to *their* grant, *their* Durable Object,
  *their* Monzo account. There is no request that reaches another user's data.
- **Unauthenticated requests get 401.** The `/mcp` endpoint only answers to
  bearer tokens the Worker itself issued.
- **Monzo adds its own gates.** Developer-portal OAuth clients only authorize
  the client owner (plus explicitly added collaborators), and every login
  requires approval in the Monzo app on the account holder's phone (SCA).
- **Scopes are read-only** and the payment/transfer endpoints are never called.

Residual risks are the usual ones for any connector: protect the deployed
secrets, and treat your claude.ai session like the credential it is.

## Setup

1. **Register a Monzo OAuth client** at <https://developers.monzo.com> —
   **Confidentiality: Confidential** (required for refresh tokens).
   Redirect URL: `https://<your-worker>.workers.dev/callback`.

2. **Fill secrets** in `.dev.vars` (gitignored) for local dev:
   ```
   MONZO_CLIENT_ID=oauth2client_...
   MONZO_CLIENT_SECRET=mnzconf...
   COOKIE_ENCRYPTION_KEY=<openssl rand -hex 32>
   ```

3. **Create the KV namespace** and paste the returned id into
   `wrangler.jsonc` (replacing `<YOUR_OAUTH_KV_NAMESPACE_ID>`):
   ```
   npx wrangler kv namespace create OAUTH_KV
   ```

4. **Deploy and push secrets:**
   ```
   npx wrangler deploy
   npx wrangler secret put MONZO_CLIENT_ID
   npx wrangler secret put MONZO_CLIENT_SECRET
   npx wrangler secret put COOKIE_ENCRYPTION_KEY
   ```

5. **Add the connector in claude.ai** → Settings → Connectors → Add custom
   connector → `https://<your-worker>.workers.dev/mcp` → authorize. You'll be
   bounced through Monzo's login (magic-link email + approve in the Monzo app),
   then Claude shows the five tools.

Works in Claude Code too:
```
claude mcp add --transport http monzo https://<your-worker>.workers.dev/mcp
```

## Notes & limitations

- Monzo only exposes the **last 90 days** of transactions unless you
  re-authenticate immediately after approving access; this connector accepts
  that rolling window.
- Monzo developer clients are **single-user** unless Monzo approves your app
  for production — fine for the personal-use case this is built for.
- Secrets live in `.dev.vars` (local) and Wrangler secrets (prod) — never in
  git. The `.gitignore` enforces this.

## License

[MIT](LICENSE) — do what you like, no warranty. Monzo, Claude, and Cloudflare
names and logos belong to their respective owners.