plaid-mcp
by yuechen
README.md
# plaid-mcp
**Talk to your bank accounts in natural language.** `plaid-mcp` is a local
[Model Context Protocol](https://modelcontextprotocol.io) server that gives
any MCP-aware agent (Claude Code, Claude Desktop, Codex CLI, Cursor, Gemini
CLI, Hermes, OpenClaw…) read-only SQL access to your bank, credit-card,
brokerage, and mortgage data via [Plaid](https://plaid.com).
```
$ claude
> what did I spend on food last month?
→ plaid_query(sql=...)
→ "You spent $487 on food in April, down from $612 in March.
Top three merchants: Whole Foods ($143), Tartine ($89), DoorDash ($72)."
> am I behind on any credit-card payments?
→ plaid_query(sql=...)
→ "No. Your Chase Sapphire next payment is $1,243 due May 28
(statement closed May 1, 14 days remaining)."
```
Everything runs locally. Your Plaid access tokens, transactions, and
holdings live in a SQLite file at `~/.plaid-mcp/data.db` — never uploaded
anywhere, never seen by Anthropic or any LLM provider.
---
## Why?
Plaid hands you a firehose of structured financial data — transactions,
balances, holdings, liabilities — but reasoning over it in any useful way
takes either a custom dashboard or a finance-app subscription that
re-sells your own data back to you. LLMs are great at this kind of
analytical query if you put a clean schema in front of them; the
challenge is wiring it up without leaking access tokens or building a
backend.
`plaid-mcp` is the wiring. Link your banks once, run sync on a cron,
expose a single `plaid_query` tool to your agent. The agent gets a
documented set of views (`vw_plaid_transactions`, `vw_plaid_holdings`,
`vw_plaid_liabilities_mortgage`, …) with Plaid's sign conventions
already flipped to the right direction, and writes SQL against them.
---
## Quick start (Claude Desktop)
There's no fully no-terminal path — you'll open Terminal twice — but
most of the work happens in two ordinary apps: Plaid's website and
Claude Desktop. Plan on ~15 minutes including Plaid's signup.
### 1. Install plaid-mcp
Open Terminal and run:
```bash
pipx install git+https://github.com/yuechen/plaid-mcp
```
(No pipx? On macOS: `brew install pipx && pipx ensurepath`, then
restart Terminal. On other OSes:
[pipx install guide](https://pipx.pypa.io/stable/installation/).)
### 2. Get free Plaid credentials
1. Sign up at [dashboard.plaid.com](https://dashboard.plaid.com) and
verify your email.
2. Apply for the free **Trial plan** at
[dashboard.plaid.com/trial-plan](https://dashboard.plaid.com/trial-plan).
Approval is usually quick. (See [Plaid plans](#plaid-plans-which-one-do-i-need)
below if you'd rather start with sandbox/fake-bank testing.)
3. Once approved, open the Dashboard's **Keys** tab and copy your
`client_id` and your **production** secret.
### 3. Save your credentials
Create the file `~/.plaid-mcp/.env` with these three lines, pasting
the values from step 2:
```
PLAID_CLIENT_ID=paste-your-client-id-here
PLAID_SECRET=paste-your-production-secret-here
PLAID_ENV=production
```
Easiest way: open Terminal, run `mkdir -p ~/.plaid-mcp && open
~/.plaid-mcp`, then create the `.env` file in the Finder window that
pops up (Cmd-N → save as `.env`). Or use your editor of choice.
`plaid-mcp` auto-loads this file every time it runs — you only do
this once.
### 4. Link your bank
```bash
plaid-mcp link
```
Your browser opens to a Plaid-hosted page. Pick your bank, sign in
(handle any 2FA / OAuth prompts), and you're done. Run the command
again for each additional institution you want to connect.
### 5. Connect it to Claude Desktop
1. Open **Claude Desktop**.
2. **Claude → Settings → Developer → Edit Config**. (This opens
`claude_desktop_config.json` in your text editor.)
3. Find the path to the installed `plaid-mcp` binary — in Terminal:
```bash
which plaid-mcp
```
Copy what it prints.
4. Paste this into the JSON file (replacing the path with what `which`
showed you):
```json
{
"mcpServers": {
"plaid": {
"command": "/paste/the/path/from/which/here",
"args": ["serve"]
}
}
}
```
- If the file is empty (`{}`), replace it with the block above.
- If the file already has a `"mcpServers"` block, just add the
`"plaid"` entry inside it.
- If the file has *other* top-level keys (e.g. a `"preferences"`
block — some Claude builds store more than just MCP config in
this file), add `"mcpServers"` as a new top-level sibling of
whatever's already there. Don't nest it inside another key.
5. Save and **quit + reopen Claude Desktop** (not just close the
window — fully quit so it re-reads the config).
### 6. Try it
In Claude Desktop, open a new chat and ask something like:
> *What did I spend on food last month?*
Claude will call the `plaid_query` tool. The first time, it'll ask
you to approve the tool — say yes and you're off.
### Want to try it with fake data first?
If you'd rather kick the tires with fake banks before applying for
Plaid's Trial plan, use **sandbox mode** instead. It's free, instant
(no application), and lets you exercise the whole pipeline against
Plaid's test institutions with realistic synthetic data.
Two changes versus the steps above:
1. **In step 2**, copy your `sandbox` secret from the Keys tab (the
row labeled "Sandbox", not "Production") — sandbox access is on
by default, no application needed.
2. **In step 3** (`~/.plaid-mcp/.env`), use these values instead:
```
PLAID_CLIENT_ID=paste-your-client-id-here
PLAID_SECRET=paste-your-sandbox-secret-here
PLAID_ENV=sandbox
```
When you run `plaid-mcp link` and Plaid's institution picker opens,
search for **First Platypus Bank** (or any other "test" institution
in the list). When it prompts for a username and password, use:
| Field | Value |
|----------|-------------|
| Username | `user_good` |
| Password | `pass_good` |
If the sandbox prompts you for an MFA code (phone or device
verification), the canonical answer is `1111` — Plaid's sandbox
always accepts that as the correct one-time code, regardless of
which "phone number" it claims to be sending to.
You'll get a fake but realistic set of accounts, transactions,
holdings, and a mortgage to play with. Once you're satisfied, apply
for the Trial plan and swap the three `.env` values to point at
production — no other changes needed.
### Keeping your data fresh
`plaid-mcp` syncs on demand (Claude can pull fresh data mid-conversation
by calling the `plaid_sync` tool). If you want a background refresh
running quietly in the background, open Terminal and run:
```bash
plaid-mcp sync --daemon
```
Leave it running in a Terminal window or tmux. It syncs everything
every 30 minutes.
### Using a different MCP client?
`plaid-mcp serve` speaks the standard Model Context Protocol over
stdio, so it works with **Claude Code**, **Codex CLI**, **Cursor**,
**Gemini CLI**, and any other MCP-aware tool. Each has its own config
file location, but the JSON block (`command` + `args: ["serve"]`)
above is the same.
If you use **Claude Code** specifically, also drop the bundled skill
into your global skills directory so the agent knows when to use the
finance tools without prompting:
```bash
git clone https://github.com/yuechen/plaid-mcp /tmp/plaid-mcp
cp -r /tmp/plaid-mcp/skills/plaid ~/.claude/skills/plaid
```
---
## What the agent sees
A single tool, `plaid_query(sql: str)`. The tool description embeds the
full schema (views, columns, types) plus a dozen worked SQL examples
(spending by category, net-worth breakdown, top merchants in a window,
unrealized-gain leaders, …). The agent picks a query, runs it, and
gets back JSON rows.
Hard-enforced safety net:
- The connection is opened **read-only** (`?mode=ro`) — DDL/DML can't
reach the data even if the agent tries.
- Output is capped at 1000 rows.
- 5-second statement timeout.
- The agent only sees the curated `vw_plaid_*` views, not the raw
`plaid_*` tables — Plaid's positive-is-outflow amount convention is
flipped into `inflow` / `outflow` / `direction` columns so the
agent can't get it wrong.
---
## What it knows
| Plaid product | What you get |
|-----------------|------------------------------------------------------------------|
| Accounts | Balances, account types, mask, institution, last-refresh time |
| Transactions | 24 months of history, merchant enrichment, PFC categories |
| Investments | Holdings (with cost basis where available), trades, dividends |
| Liabilities | Credit-card statement state, mortgage details |
`plaid-mcp` does *not* and will not include:
- **Auth / Identity** — full routing numbers and PII. Not needed for
the read-only analytics use case.
- **Money movement** — no transfers, no payments, no payouts.
Adding either is a deliberate code change requiring a fork.
> **One thing to know about transaction history.** Plaid pulls up to
> 24 months of transactions, but the window is bound to the Item at
> *first link time* and can't be extended retroactively. If you link
> Chase today, then unlink and re-link 6 months from now, you'll lose
> the original 24-month window and start over from Plaid's default
> 90-day backstop. Use `plaid-mcp relink` (Plaid Link's update mode)
> instead of unlink+link whenever possible — it re-authenticates the
> *same* Item and preserves history.
---
## Configuration
All config is via env vars (or a `.env` file in the working directory).
| Variable | Default | Notes |
|-------------------------|------------------------|--------------------------------------------------------------------|
| `PLAID_CLIENT_ID` | — | Required. |
| `PLAID_SECRET` | — | Required. |
| `PLAID_ENV` | `sandbox` | `sandbox` / `development` / `production`. |
| `PLAID_MCP_DB` | `~/.plaid-mcp/data.db` | Where the SQLite file lives. |
| `PLAID_MCP_KEY` | auto | Fernet key for at-rest access-token encryption. Auto-generated and stored at `~/.plaid-mcp/key` (chmod 600) on first run if unset. |
| `PLAID_MCP_SYNC_EVERY` | `1800` | Seconds between syncs when running `sync --daemon`. |
---
## Architecture
```
┌──────────────────────────────────────────────────────┐
│ Your AI agent │
│ (Claude Code / Desktop / Codex / …) │
└────────────────────────┬─────────────────────────────┘
│ MCP / stdio
▼
┌──────────────────────┐
│ plaid-mcp serve │ ← read-only SQLite connection
│ (FastMCP server) │
└──────────┬───────────┘
│ SELECT only
▼
┌──────────────────────┐
│ ~/.plaid-mcp/data.db │
│ plaid_* tables │
│ vw_plaid_* views │
└──────────▲───────────┘
│ INSERT/UPDATE
│
┌──────────┴───────────┐ ┌──────────────────┐
│ plaid-mcp sync │ ──────► │ Plaid API │
│ (one-shot or daemon) │ └──────────────────┘
└──────────────────────┘
▲
│ encrypted access_token
┌──────────┴───────────┐ ┌──────────────────┐
│ plaid-mcp link │ ──────► │ Plaid Hosted │
│ (opens plaid.com URL,│ poll │ Link │
│ polls for finish) │ ◄────── │ (browser → bank) │
└──────────────────────┘ └──────────────────┘
```
Three loosely-coupled commands:
- **`plaid-mcp link`** — calls Plaid's `/link/token/create` with
Hosted Link, opens the returned `plaid.com` URL in your browser,
polls `/link/token/get` until you've finished, exchanges the
public_token, encrypts the access_token, stores it, and runs an
initial sync.
- **`plaid-mcp sync`** — pulls fresh data from Plaid for all linked
items. Idempotent. Run from cron or with `--daemon` for an in-process
loop.
- **`plaid-mcp serve`** — speaks the MCP protocol over stdio. Opens
the SQLite DB read-only and exposes `plaid_query`, `plaid_sync`, and
`plaid_status` tools.
---
## CLI reference
```
plaid-mcp link # link a NEW institution
plaid-mcp relink <item-id> # re-auth an existing institution (update mode)
plaid-mcp unlink <item-id> # permanently remove an institution
plaid-mcp items # list linked institutions
plaid-mcp sync # one-shot sync of all items
plaid-mcp sync --daemon # background loop
plaid-mcp sync --item <item-id> # sync just one item
plaid-mcp status # show sync staleness + errors
plaid-mcp serve # MCP server on stdio
```
`<item-id>` accepts the full UUID or any unique leading prefix
(`plaid-mcp items` shows the short forms).
### When to use `relink` vs `link`
| Situation | Command |
|------------------------------------------------------------|-------------------------------|
| Adding your first bank, or a different institution | `plaid-mcp link` |
| `plaid-mcp status` shows `ITEM_LOGIN_REQUIRED` for a bank | `plaid-mcp relink <item-id>` |
| `PENDING_EXPIRATION` / `USER_PERMISSION_REVOKED` | `plaid-mcp relink <item-id>` |
| You no longer want a bank connected | `plaid-mcp unlink <item-id>` |
`relink` runs Plaid Link in **update mode** — same `access_token`,
same `plaid_item_id`, same 24-month transaction-history window. A
naive `plaid-mcp link` after an error would create a duplicate Item
with only 90 days of history and silently lose your past data.
---
## Driving link / relink / unlink from your AI agent
The MCP server exposes every connection-management operation as a tool
so the agent can drive the whole flow from chat — you never have to
drop to the terminal:
| Tool | What it does |
|-------------------------------------------------------|-------------------------------------------------------------------------------------------------------------|
| `plaid_link()` | Add a new institution. Returns a `hosted_link_url`, opens it in your browser. |
| `plaid_relink(item_id)` | Re-auth an existing institution (update mode). Same browser flow. |
| `plaid_link_complete(link_token, item_id?)` | Finalize after the user finishes in the browser. Polls up to 60s; re-call if the result is `pending`. Doesn't sync — agent calls `plaid_sync(item_id)` as a follow-up. |
| `plaid_unlink(item_id, confirmed=True)` | Permanently remove an institution. Tool enforces confirmation — a call without `confirmed=True` returns an error. |
| `plaid_query(sql)` / `plaid_sync()` / `plaid_status()` | Query / refresh / inspect the local cache. |
Typical chat exchange:
> **You:** *Why don't I see any Chase transactions for the last week?*
> **Agent:** *(calls `plaid_status`)* Looks like Chase needs a re-auth
> — `ITEM_LOGIN_REQUIRED`. Want me to fix it?
> **You:** *Yes please.*
> **Agent:** *(calls `plaid_relink`)* I've opened Plaid Link in your
> browser. Sign in to Chase and let me know when you're done.
> **You:** *Done.*
> **Agent:** *(calls `plaid_link_complete`)* Re-authed Chase Sapphire
> Preferred and synced 23 new transactions. What did you want to check?
---
## Plaid plans — which one do I need?
Plaid has four account modes. Two are relevant to `plaid-mcp` users:
### Sandbox (default — kick the tires)
- **Cost:** $0 forever.
- **What it gets you:** Fake "test" institutions (First Platypus Bank,
Houndstooth Bank, etc.) with realistic data — accounts, transactions,
holdings, mortgages. Log in with `user_good` / `pass_good`.
- **What it doesn't get you:** Your actual money.
- **Setup:** Create a Plaid Dashboard account, copy the `client_id` and
the **sandbox** secret from the "Keys" tab, set `PLAID_ENV=sandbox`.
- **Hosted Link works here:** Yes, fully. No approvals needed.
### Trial plan (free, real banks — what most personal users want)
In April 2026 Plaid replaced the old "Limited Production" tier with a
free **Trial plan** for new US and Canada teams. This is the right path
if you want `plaid-mcp` to know about your real Chase / Wells Fargo /
Fidelity / Amex / Cap One accounts.
- **Cost:** $0 up to 10 linked institutions ("Items"). Beyond that you
upgrade to a paid plan.
- **What it gets you:** Live production data. Most OAuth institutions
(Chase, Bank of America, Wells Fargo, Amex, Cap One, Citi…) are
available *before* full Production approval. Same SDK, same endpoints
— the only difference is `PLAID_ENV=production` and a different
secret.
- **Setup:**
1. Create a Plaid Dashboard account at
[dashboard.plaid.com](https://dashboard.plaid.com) and verify your
email.
2. From the Dashboard homepage, click **Apply for Trial plan** (or
go directly to
[dashboard.plaid.com/trial-plan](https://dashboard.plaid.com/trial-plan)).
Fill in the short form — Plaid asks what you're building, your
contact info, etc. Approval is usually fast (minutes to a day).
3. Once approved, the "Keys" tab will show a **production** secret.
Copy it. Set in your shell:
```bash
export PLAID_CLIENT_ID=...
export PLAID_SECRET=<production secret>
export PLAID_ENV=production
```
4. Run `plaid-mcp link`. Pick your real bank. Done.
- **Hosted Link works here:** Yes. Plaid removed the "ask your account
manager to enable Hosted Link" gate, so the same `plaid-mcp link`
command works without any extra setup.
- **OAuth banks work here:** Yes. Hosted Link runs on `plaid.com`, so
Plaid handles every OAuth redirect server-side — you don't need a
domain, HTTPS endpoint, or `redirect_uri` registration.
### Production (paid — for hobbyists who outgrow Trial)
If you link more than 10 institutions, Plaid will ask you to upgrade.
Paid plans are usage-based ([current pricing](https://plaid.com/pricing/)).
Nothing in `plaid-mcp` changes; same env vars, same code.
### Limited Production (legacy — don't use)
The old free-tier-with-strings-attached for teams created before April
2026. New users should apply for the Trial plan instead. If your
account predates April 2026, switching to the Trial plan is a separate
application in the Dashboard.
---
## Known limitations
- **Transaction history is fixed at first-link time.** See the callout
in *What it knows*. Plaid won't let us back-fill once an Item exists.
Always prefer `relink` over `unlink + link` for the same institution.
- **Brokerage/bank acquisitions can create stranded data.** If an
institution is acquired and re-issues a new `plaid_item_id`, your
old Item will start failing with `INSTITUTION_NOT_RESPONDING` or
similar. There's no automatic migration — unlink the dead Item and
link the new one. The old transactions stay in the local cache
unless you `plaid-mcp unlink` (which cascade-deletes them) — that's
usually the right call, but worth knowing.
- **No Fernet key rotation.** The encryption key at `~/.plaid-mcp/key`
(or `$PLAID_MCP_KEY`) is single-use. If you replace it, every
previously linked institution becomes unrecoverable and must be
`relink`-ed. Back up the key file alongside the DB, or set
`$PLAID_MCP_KEY` from a password manager.
- **Sandbox only ships with credential-flow test banks.** OAuth-only
banks (Amex, Cap One, etc.) work fully in production via Hosted Link
but aren't reachable in sandbox testing — you'll only see the
classic test institutions there.
- **No webhook receiver yet.** Data refresh is poll-based via
`plaid-mcp sync` (cron or `--daemon`). The agent can also pull on
demand by calling the `plaid_sync` MCP tool mid-conversation.
Webhooks are on the roadmap; they require a public URL on your end.
---
## License
MIT. See [LICENSE](LICENSE).
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues