plaid-mcp
The plaid-mcp server is a read-only MCP adapter that connects LLMs (like Claude or ChatGPT) to personal financial data via Plaid or Teller, enabling natural language analysis of bank accounts, transactions, investments, liabilities, and debt.
Account Management
Link new bank accounts, list all linked institutions (with status), and remove/unlink institutions
Accounts & Balances
List all accounts across linked institutions and fetch live balances (optionally filtered by account)
Transactions
Sync, force-refresh, query (by date, category, merchant, account, amount), and fuzzy-search transactions
Summarize spending grouped by category, subcategory, merchant, or account
Investments
View current holdings (tickers, quantities, market value, cost basis)
Retrieve brokerage transaction history (buys, sells, dividends, fees)
Liabilities
Access credit card APRs, balances, and due dates; student loan and mortgage details
Identity & Income
Retrieve account holder names, emails, phones, and addresses
Detect bank-verified income streams (requires Plaid Income product)
Debt Analysis
Set/clear custom APR overrides for linked accounts
Add, update, remove, and list external debts (BNPL, medical bills, 401k loans)
Project debt payoff using avalanche (highest APR first) or snowball (lowest balance first) strategies, with amortization timelines and promo expiration warnings
Other
Data is stored locally (SQLite) with file-permission security; can run locally or remotely with TLS/auth
Supports a terminal UI for direct browsing of accounts and transactions
Compatible with MCP clients (Claude Desktop, ChatGPT, etc.) and optionally configurable with payment rails (MPP/x402)
Can be exposed via Caddy for secure HTTPS access when running in HTTP mode, allowing remote MCP clients to connect to the Plaid financial data server with TLS encryption.
Provides read-only access to Chase bank accounts through Plaid integration, allowing users to analyze transactions, balances, investments, liabilities, and financial data from their Chase accounts.
Can be exposed via Cloudflare Tunnel for secure remote access when running in HTTP mode, enabling MCP clients to connect to the Plaid financial data server through Cloudflare's infrastructure.
Supports configuration through .env files for Plaid API credentials and server settings, allowing users to securely manage authentication and environment variables for the financial data integration.
Hosts the plaid-mcp server source code and documentation, enabling users to clone, install, and run the server from the GitHub repository for accessing financial data through Plaid.
Can be exposed via ngrok for secure tunneling when running in HTTP mode, allowing remote MCP clients to connect to the Plaid financial data server through ngrok's tunneling service.
Provides installation method for the plaid-mcp server, enabling isolated installation and execution of the financial data integration tool for accessing bank accounts through Plaid.
Server implementation language for the plaid-mcp integration, enabling execution of the financial data server that connects to Plaid API for accessing bank accounts and transactions.
Stores access tokens and cached financial data in SQLite database, providing local persistence for Plaid authentication tokens and transaction data from linked bank accounts.
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., "@plaid-mcpshow me my spending on dining out last month"
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.
plaid-mcp
A read-only Model Context Protocol server that lets Claude, ChatGPT, or any MCP-compatible client analyze your real bank, credit card, loan, and brokerage data through Plaid or Teller.
Bring your own credentials, run the server locally (or behind TLS on a small VPS), link your accounts, and then just ask:
"What did I spend on groceries in March?" "Show me my credit card APRs sorted by balance." "Which of my holdings are down more than 10% this year?" "I have a 0% promo on my AA card — given that, which debt should I attack first?"
Everything runs locally. Access tokens stay in a chmod-600 SQLite file on your machine. The server never makes outbound calls except to Plaid's API.
Table of contents
Why this exists
Consumer financial data is locked inside whichever app happens to be connected to your bank. If you want to ask questions of it — "compare my March spending YoY," "what's my real effective APR across all cards," "project when I pay off this debt if I add $200/month" — you end up either exporting CSVs or trusting a SaaS aggregator with your credentials.
This project is a thin, read-only adapter: Plaid on one side, the LLM of your choice on the other. The LLM gets a small set of well-documented tools (transactions, balances, holdings, liabilities, identity, income, debt analysis). You keep your tokens.
Features
Read-only tools grouped by Plaid product area:
Accounts & balances —
list_accounts,get_balancesTransactions —
sync_transactions,refresh_transactions,get_transactions,search_transactions,spending_summaryInvestments —
get_holdings,get_investment_transactionsLiabilities —
get_liabilities(credit cards, student loans, mortgages with APRs and due dates)Identity & income —
get_identity,get_incomeDebt analysis —
set_account_override,add_external_debt,summarize_debt(avalanche/snowball with amortized payoff projections)Account management —
link_account,complete_linking,list_linked_institutions,remove_institution
All access tokens and cached transactions live in SQLite at ~/.plaid-mcp/plaid.db by default. Nothing leaves your machine except Plaid API calls.
Quick start
The easiest install is pipx or uv tool — both put plaid-mcp on your PATH in an isolated venv.
# 1. Install (pick one)
pipx install plaid-mcp # or: uv tool install plaid-mcp
# or: pip install plaid-mcp
# 2. Configure — any of these work:
# a) ~/.plaid-mcp/.env
# b) project-local .env (if you're running from a clone)
# c) inline env vars in your MCP client config (see "Claude Desktop" below)
mkdir -p ~/.plaid-mcp && cat > ~/.plaid-mcp/.env <<EOF
PLAID_CLIENT_ID=your_client_id
PLAID_SECRET=your_secret
PLAID_ENV=production
EOF
# 3. Link your first bank in the browser
plaid-mcp link
# 4. Wire it into Claude Desktop (see below) and ask:
# "list my accounts"
# "sync my transactions then summarize spending last month"Prefer to run from source? See Development.
Total setup time if you already have Plaid credentials: ~5 minutes.
Setup (detailed)
1. Get Plaid credentials
You need your own Plaid developer account — don't share a client_id or reuse someone else's credentials. Plaid's terms tie each account to the person who signed up, and the Production trial tier is intended for you to link your own accounts.
Sign up at dashboard.plaid.com. It's free.
In Developers → Keys, note your
client_idand one of two secrets:Sandbox secret — fake test data.
user_good/pass_goodlogs intoins_109508("First Platypus Bank"). Great for smoke-testing without real data.Production secret — real banks. New accounts get a Production trial (~10 linked items) without going through billing review. No time limit on the trial; it caps at 10 items.
Under Team Settings → Products, request access to Transactions, Investments, Liabilities, and Identity. Most approve instantly. Income requires a brief review.
Plaid retired the separate Development environment in late 2024. New accounts get Sandbox + a Production trial instead.
PLAID_ENV=developmentstill works here for backward compatibility; it's silently routed to Production.
2. Install
Choose whichever you prefer — all three drop a plaid-mcp executable on your PATH:
pipx install plaid-mcp # isolated venv, recommended
uv tool install plaid-mcp # same idea, uv-native
pip install plaid-mcp # into your current envOr from source (for development or running unreleased changes):
git clone https://github.com/t-rhex/plaid-mcp
cd plaid-mcp
uv sync # or: pip install -e .Requires Python 3.10+.
3. Configure
cp .env.example .env
$EDITOR .envRequired fields:
PLAID_CLIENT_ID=your_client_id_here
PLAID_SECRET=your_secret_here
PLAID_ENV=production # or: sandboxCommon optional overrides:
# What Plaid products to request during linking.
# PLAID_PRODUCTS — the bank MUST support these (link fails otherwise).
# PLAID_OPTIONAL_PRODUCTS — requested if supported; link doesn't fail if not.
PLAID_PRODUCTS=transactions
PLAID_OPTIONAL_PRODUCTS=investments,liabilities,identity
PLAID_COUNTRY_CODES=US # or: US,CA,GB,ES,FR,IE,NL,DE,IT
PLAID_MCP_DB=~/.plaid-mcp/plaid.db # tilde gets expanded; file is chmod 600
PLAID_CLIENT_NAME=plaid-mcp # shown to the user inside Plaid Link
# For remote deployment only:
# MCP_AUTH_TOKEN=<random 32-byte token> # required for HTTP mode
# PLAID_WEBHOOK_URL=https://yourhost/webhook # if using webhook-driven link completionWhy the two product lists? Plaid requires every product you list under PLAID_PRODUCTS to be supported by the bank at link time. Citi doesn't have brokerage, Fidelity doesn't have liabilities, etc. PLAID_OPTIONAL_PRODUCTS are requested "if the bank supports them" via Plaid's required_if_supported_products — so one .env works across banks and brokers.
4. Link your first account
uv run python -m plaid_mcp link
# => Open this URL in your browser: https://cdn.plaid.com/link/v2/stable/link.html?...
# => After completing, press Enter.Open the URL, pick your bank, complete the OAuth flow (for most banks this redirects to your bank's site and back), and return to the terminal.
You can also link new accounts directly from inside Claude/ChatGPT after the server is wired up — just say "link a new account" and follow the link it returns.
Choosing a provider
plaid-mcp speaks to two bank-data providers behind a shared adapter. Pick the one that matches what you want to analyze:
Plaid (default) | Teller | |
Environment variable |
|
|
Checking / savings / credit | ✓ | ✓ |
Balances | ✓ | ✓ |
Transactions (categorized) | ✓ (cursor sync) | ✓ (live date range) |
Identity | ✓ | ✓ |
Investment holdings + trades | ✓ | ✗ |
Liabilities (APRs, due dates) | ✓ | ✗ |
Student loans / mortgages | ✓ | ✗ |
Income detection | ✓ | ✗ |
Debt avalanche/snowball tools | ✓ | ✗ (needs APRs) |
Free personal-use tier | 10 linked items | 100 live connections |
Transparent per-call pricing | Contact sales | Published rate card |
Generic tools (list_accounts, get_balances, get_transactions, search_transactions, get_identity) work on either provider. Plaid-only tools (everything else) return a clean capability error when PROVIDER=teller, so Teller users aren't left with confusing tracebacks.
You can freely switch by changing PROVIDER in your .env — each provider stores its enrollments independently (Plaid in SQLite, Teller in ~/.plaid-mcp/teller/enrollment.json), so nothing is lost.
Teller setup
# 1. Register at dashboard.teller.io (free), grab your Application ID
# 2. Download certificate.zip; move to ~/.plaid-mcp/teller/ (0600)
# 3. Add to .env:
PROVIDER=teller
TELLER_APPLICATION_ID=app_xxxxxxxxxxxxxxxxxxxxx
TELLER_ENV=sandbox # sandbox needs no cert; dev/prod do
TELLER_CERT_PATH=~/.plaid-mcp/teller/certificate.pem
TELLER_KEY_PATH=~/.plaid-mcp/teller/private_key.pem
# 4. Link your first bank (either in your terminal or from the TUI)
plaid-mcp teller connect
# 5. Smoke-test
plaid-mcp teller probeSandbox credentials in Teller Connect: username / password against any bank. That returns a real sandbox access_token you can actually query.
Connecting it to an MCP client
Claude Desktop (local, stdio)
Edit ~/Library/Application Support/Claude/claude_desktop_config.json on macOS or %APPDATA%\Claude\claude_desktop_config.json on Windows.
If you installed via pipx / uv tool / pip (recommended):
{
"mcpServers": {
"plaid": {
"command": "plaid-mcp",
"env": {
"PLAID_CLIENT_ID": "<your client_id>",
"PLAID_SECRET": "<your secret>",
"PLAID_ENV": "production",
"PLAID_PRODUCTS": "transactions",
"PLAID_OPTIONAL_PRODUCTS": "investments,liabilities,identity"
}
}
}
}If plaid-mcp isn't found on Claude Desktop's PATH, use the absolute path that which plaid-mcp prints.
If you're running from a clone:
{
"mcpServers": {
"plaid": {
"command": "uv",
"args": [
"--directory", "/absolute/path/to/plaid-mcp",
"run", "python", "-m", "plaid_mcp"
],
"env": {
"PLAID_CLIENT_ID": "...",
"PLAID_SECRET": "...",
"PLAID_ENV": "production"
}
}
}
}Restart Claude Desktop. You should see "plaid" appear in the tools menu.
Claude.ai web or ChatGPT (remote, HTTPS)
Run in HTTP mode:
MCP_AUTH_TOKEN=$(python -c 'import secrets; print(secrets.token_urlsafe(32))') \
uv run python -m plaid_mcp serve --host 0.0.0.0 --port 8080Expose it with TLS (Caddy, Cloudflare Tunnel, ngrok). In Claude.ai or ChatGPT add it as a Custom Connector / MCP Connector and paste the bearer token. Never expose this server without TLS and MCP_AUTH_TOKEN set.
Other clients
Anything that speaks MCP will work. The server is built on FastMCP, which supports both stdio and HTTP transports.
Terminal UI
If you prefer browsing your accounts directly rather than asking an LLM, run:
plaid-mcp tuiOpens a Textual app with Accounts + Transactions screens, works with either provider, zero-friction navigation (q to quit, r to refresh, a/t to switch screens, c to link a new bank through Teller Connect without leaving the terminal).
Tool reference
All dates are ISO strings (
YYYY-MM-DD). Amounts follow Plaid's convention: positive for outflows (spending), negative for inflows (deposits).
Account management
link_account()— Start a new Plaid Link session. Returns ahosted_link_urlthe user opens in their browser to authenticate with their bank.complete_linking(link_token, timeout_seconds=180)— Finalize a Link session once the user has completed it in the browser.list_linked_institutions()— Every institution currently linked, with account counts and any errors.remove_institution(item_id)— Unlink an institution and purge its cached data.
Accounts & balances
list_accounts()— All accounts across all linked institutions, from the local cache. Fast.get_balances(account_id=None)— Live balance lookup (hits Plaid, not the cache). Optionally filter to one account.
Transactions
sync_transactions(wait_for_ready=True, wait_timeout_seconds=60)— Pull the latest transactions into the local cache using Plaid's cursor-based/transactions/sync. Incremental and idempotent. On first call after linking, Plaid runs the historical pull asynchronously; this tool blocks briefly untilHISTORICAL_UPDATE_COMPLETE.refresh_transactions(item_id=None)— Ask Plaid to re-pull from the bank right now. Use when a user just made a purchase and wants to see it, or when data looks stale. Asynchronous — wait 30–60s then callsync_transactions. Some smaller banks don't support on-demand refresh.get_transactions(start_date, end_date, account_id=?, category=?, merchant=?, min_amount=?, max_amount=?, limit=500)— Query the cache. Filter by any combination of the above.search_transactions(query, start_date=?, end_date=?, limit=100)— Fuzzy search across transaction description + merchant name.spending_summary(start_date, end_date, group_by="category")— Aggregate spending.group_bycan becategory,subcategory,merchant, oraccount.
Investments
get_holdings(account_id=None)— Current positions: tickers, quantities, market value, cost basis.get_investment_transactions(start_date, end_date, account_id=None, limit=250)— Buys, sells, dividends, fees.
Liabilities
get_liabilities()— Credit cards (statement balance, minimum payment, due dates, APRs), student loans (balance, interest rate, payoff date, servicer), mortgages (principal, rate, maturity, next payment).
Identity & income
get_identity(account_id=None)— Account-holder names, emails, phones, addresses as reported by each institution.get_income()— Bank-detected income streams. Requires Plaid's Income product enabled in your dashboard.
Debt analysis
Plaid's liabilities data covers the basics but misses things — notably promotional APRs on credit cards (0% intro offers, balance-transfer promos). This layer lets you annotate what Plaid missed and run honest payoff math.
Not financial advice. These tools do straightforward amortization math and rank debts by APR or balance — nothing more. For decisions that meaningfully affect your finances (debt consolidation, refinancing, tax implications of early payoff, etc.), talk to a CFP, CPA, or attorney. The outputs here are a starting point for a conversation with a professional, not a substitute for one.
set_account_override(account_id, effective_apr=?, promo_expires=?, note=?)— Record the true APR for a linked card. Afterpromo_expires, analysis reverts to Plaid's reported purchase APR.clear_account_override(account_id)— Remove an override.list_overrides()— List all overrides.add_external_debt(name, balance, apr, minimum_payment=0.0, next_payment_due_date=?, promo_expires=?, note=?)— Track a debt that isn't behind a linked Plaid account (BNPL, medical, 401(k) loans, small lenders). APR is a percentage (e.g.18.5, not0.185).update_external_debt(debt_id, ...)— Partial update to any field.remove_external_debt(debt_id)list_external_debts()summarize_debt(strategy="avalanche", extra_monthly_payment=0.0, today=?)— Merges Plaid credit cards + overrides + external debts, ranks them, and projects payoff:avalanche(default) — highest effective APR first, minimizes interest paid.snowball— lowest balance first, fastest sense of progress.Returns total balance, monthly interest accrual at current rates, priority debt, amortized payoff timelines (minimum-only vs. with the extra payment), and warnings for promos expiring within 60 days. Flags debts whose minimum payment can't even cover monthly interest.
Example workflows
"Summarize my spending last month"
You: sync my transactions then show me top 10 merchants by spend in March 2026
LLM: [calls sync_transactions_tool, then spending_summary_tool with group_by=merchant]
Top merchants in March 2026:
Whole Foods $412.80 (14 transactions)
Amazon $287.43 (9 transactions)
..."Which debt should I pay first?" — with a 0% promo
Plaid says your AA card is 26.49%, but it's actually on a 0% promo until 2027. By default, an LLM would tell you to attack the AA card first (because it sees 26.49%) — which is exactly wrong. Fix it:
You: my AA card is actually at 0% promo APR until 2027-01-01. set that override,
then run an avalanche summary.
LLM: [set_account_override_tool(account_id="...", effective_apr=0.0,
promo_expires="2027-01-01")]
[summarize_debt_tool(strategy="avalanche")]
Priority: Costco Anywhere Visa, balance $1,204, effective APR 18.74%
(was going to recommend the AA card at 26.49%, but you have a 0% override
until 2027-01-01 — AA is now ranked last.)
At minimum-only payments, the Costco card pays off in 78 months with
$1,142 in interest. Add $200/mo and you're done in 7 months paying $96
total — saves you ~$1,046 in interest."Track a BNPL I opened outside the linked banks"
You: I just took out a $1,500 Affirm loan for a mattress, 0% APR for 12 months,
$125/mo minimum. Add it.
LLM: [add_external_debt_tool(name="Affirm Mattress", balance=1500, apr=0,
minimum_payment=125, promo_expires=<in 12mo>)]
Added (ext_a1b2c3d4e5f6). Included in summarize_debt going forward."Show me stale data or link errors"
You: are any of my linked accounts broken?
LLM: [calls list_linked_institutions_tool]
Chase — 5 accounts, last sync 3h ago, no errors.
Citi — 4 accounts, last_error: "ITEM_LOGIN_REQUIRED" (your password likely
changed; re-link via `link a new account`)."I just made a purchase and want to see it"
You: I just paid at Whole Foods 10 minutes ago, refresh and show it.
LLM: [refresh_transactions_tool()]
[waits ~45 seconds]
[sync_transactions_tool()]
[search_transactions_tool(query="whole foods", start_date=<today>)]
Found it: $84.12 at WHOLE FOODS MARKET today.How linking works under the hood
This server uses Plaid's Hosted Link flow so you never need to embed a web widget. The tool sequence an LLM follows to add a bank:
link_account— creates alink_tokenwith ahosted_linkobject, returns thehosted_link_urland the rawlink_token.User opens that URL in a browser, completes the OAuth flow with their bank.
complete_linking(link_token)— polls/link/token/getuntil it sees apublic_tokeninlink_sessions[].results.item_add_results[], exchanges it for a permanentaccess_token, and caches the account list.
Per Plaid's docs, webhooks (SESSION_FINISHED event) are the recommended production mechanism for retrieving the public_token. This server uses polling instead because it requires no public endpoint — fine for personal CLI / stdio use. If you deploy remotely and want webhook-driven completion, set PLAID_WEBHOOK_URL in .env and add a webhook handler (not included yet — PRs welcome).
Paid hosted mode
plaid-mcp ships three payment rails; operators pick one via PAYWALL=<rail>. Tool discovery (tools/list, initialize) stays free across all of them — only tools/call is metered.
MPP (recommended) — Tempo stablecoin + Stripe cards
The Machine Payments Protocol via pympp. Use this if you want either:
Pure crypto — USDC on Tempo L2, no Stripe account required, wallet-to-wallet.
Traditional cards — any Stripe-supported method (requires a Stripe account).
Both, advertised in the same 402 — clients pick based on what they have.
uv sync --extra mpp
# or: pip install 'plaid-mcp[mpp]'PAYWALL=mpp
MPP_METHODS=tempo,stripe # tempo | stripe | tempo,stripe
# Tempo rail (only needed when 'tempo' is in MPP_METHODS):
MPP_DESTINATION_ADDRESS=0x... # Your Tempo wallet that receives USDC
MPP_NETWORK=tempo-testnet # tempo-testnet | tempo-mainnet
# MPP_ALLOW_MAINNET=1 # required to accept real USDC on Tempo
# Stripe rail (only needed when 'stripe' is in MPP_METHODS):
STRIPE_SECRET_KEY=sk_live_... # your Stripe API secret
STRIPE_CURRENCY=usd
# STRIPE_PAYMENT_METHOD_TYPES=card,apple_payHow the 402 works: on an unpaid tools/call, the server returns 402 Payment Required with one WWW-Authenticate: Payment ... header per configured method. A well-behaved MPP client picks the method it can satisfy (Tempo if it has a USDC wallet, Stripe if it has a card), signs a credential, and replays with Authorization: Payment <credential>. The server routes the incoming credential back to the matching rail based on the challenge method field. On success, a Payment-Receipt header carries the settlement receipt.
x402 (alternative) — Coinbase CDP or x402.org facilitator
Trustless HTTP 402 on Base. Use this for agents speaking Coinbase Agentic Wallets, CDP Agent Kit, or Cloudflare Agents — those clients have mature x402 support today. Base mainnet needs Coinbase CDP facilitator auth; Base Sepolia works against x402.org's hosted facilitator out of the box.
uv sync --extra cdp # only required for Base mainnetPAYWALL=x402
X402_RECEIVING_ADDRESS=0x... # Base address that receives USDC
X402_NETWORK=base-sepolia # base-sepolia (testnet) | base (mainnet)
# X402_ALLOW_MAINNET=1 # required to actually open mainnet
# X402_FACILITATOR_URL= # optional (defaults to https://x402.org/facilitator)None (default)
PAYWALL=noneNo paywall. Suitable for personal stdio use via Claude Desktop and for self-hosted HTTP deployments where you gate access with MCP_AUTH_TOKEN instead.
Client-side support
MPP clients — pympp ships with a Python client; Stripe-side, any SDK that can create a PaymentIntent with the challenge amount and return the confirmation token works.
Claude Desktop / Claude Code / Cursor (x402) — install Coinbase's x402 MCP bridge alongside
plaid-mcp. The bridge holds the Base wallet and does the signing;plaid-mcpstays crypto-naive.OpenAI Agents SDK / LangChain (x402) —
pip install "x402[httpx]", wrap the tool's HTTP client with the x402 client middleware.CDP Agent Kit (x402) — native x402 actions; nothing extra to install.
ChatGPT Custom Connectors — no wallet primitive today; use an API-key fallback if you need this audience (not implemented yet).
Default prices
Default prices live in src/plaid_mcp/payments/prices.py (10¢ for most tools, 50¢ for summarize_debt_tool). Override per-tool by forking or by building your own PriceTable if you embed this as a library. The price table is shared across all rails — an MPP-tempo caller and an x402-Base caller pay the same cents for the same tool.
Verified end-to-end
Live facilitator round-trips ship under pytest markers. Set the relevant private key env var and run:
# x402 on Base Sepolia (needs ~cent of testnet USDC)
X402_TESTNET_PRIVATE_KEY=0x... \
X402_RECEIVING_ADDRESS=0x<your-wallet-or-throwaway> \
uv run pytest -v -m x402_testnet
# MPP on Tempo testnet (xfail today; see test docstring)
MPP_TESTNET_PRIVATE_KEY=0x... uv run pytest -v -m mpp_testnetGet x402 testnet USDC from Circle's faucet (pick Base Sepolia).
Deploying with Docker / Fly.io
A reference Dockerfile, docker-compose.yml, and Fly.io config live at the repo root + deploy/. See deploy/README.md for the step-by-step.
# Local hosted-mode smoke test
docker compose up --build
# Fly.io one-shot
fly apps create plaid-mcp
fly volumes create plaid_mcp_data --region iad --size 1
fly secrets set $(grep -v '^#' .env | xargs)
fly deploy --config deploy/fly.tomlThe container runs plaid-mcp serve --host 0.0.0.0 --port 8080 as a non-root user, persists ~/.plaid-mcp/ on a named volume, and survives restarts with all Plaid items + Teller enrollment intact. Fly terminates TLS at the edge; self-hosting behind Caddy/Traefik/nginx works the same way.
Security notes
Access tokens are stored in SQLite at
PLAID_MCP_DB(default~/.plaid-mcp/plaid.db). On macOS and Linux the file is chmod'd to0600.Read-only, by design. There are no tools that move money, create transfers, or modify anything upstream. Plaid's
/transfer/*endpoints are not exposed. The worst a prompt-injected LLM can do is read your data — not move it.If you run it remotely, put it behind TLS and set
MCP_AUTH_TOKENto a random string. Never expose it over plain HTTP or without auth.If you deploy this for anyone other than yourself, you need to complete Plaid's Production Enablement review first. The Production trial tier covers personal use; hosting a multi-user instance on your
client_idwithout review violates Plaid's terms. Every user running their own copy with their own Plaid credentials is fine — that's the intended open-source usage.Plaid's terms prohibit storing bank credentials — and this server never sees them. Plaid Link handles credentials directly with the institution; this server only gets a per-user access token.
The LLM sees your financial data while it's answering questions. Choose a provider you trust, and consider running against the Sandbox environment first to get a feel for what flows through context.
Troubleshooting
INVALID_PRODUCT: Your account is not enabled for <product>
You requested a product in PLAID_PRODUCTS that your Plaid dashboard isn't approved for. Go to Team Settings → Products, request access, and wait for approval. Or drop the product from PLAID_PRODUCTS (and put it in PLAID_OPTIONAL_PRODUCTS instead if you still want it when available).
"No investment accounts" when linking a non-brokerage bank (e.g. Citi)
You had investments in PLAID_PRODUCTS, which makes Plaid reject banks without brokerage. Move investments from PLAID_PRODUCTS to PLAID_OPTIONAL_PRODUCTS and re-link.
sync_transactions returns 0 transactions right after linking
Plaid's historical pull is async. The server blocks up to 60s for HISTORICAL_UPDATE_COMPLETE — but some banks (notoriously Citi) can take hours to backfill. Check list_linked_institutions for last_error. If there's no error, just wait and try again; the status field in the sync response tells you what Plaid is up to.
refresh_transactions returns PRODUCT_NOT_READY
Some smaller institutions don't support on-demand refresh. Plaid will still refresh them on its normal schedule (every few hours). This is an institution limitation, not a bug in the server.
Tokens got lost / I want to start over
Remove the SQLite DB: rm ~/.plaid-mcp/plaid.db. All your cached transactions and tokens go with it. You'll need to re-link every institution.
The LLM hallucinates numbers
Ask it to call the tools explicitly: "call sync_transactions_tool, then call spending_summary_tool with...". Also: instruct the LLM to always cite the tool output it's reasoning from. Model choice matters; the default instructions in the MCP server nudge toward tool use.
Claude Desktop doesn't see the tools
Confirm uv is on your PATH (which uv). Claude Desktop's launchd-style environment often doesn't inherit your shell PATH. You may need the absolute path (e.g. "command": "/Users/you/.cargo/bin/uv"). Restart Claude Desktop after config changes.
Development
uv sync --extra dev # or: pip install -e ".[dev]"
ruff check .
pytest # unit + MCP smoke tests (no credentials needed)
pytest -m sandbox # end-to-end tests against Plaid SandboxSandbox tests read credentials from .env.test at the repo root (git-ignored). Create it when you want to run them:
PLAID_CLIENT_ID=your_sandbox_client_id
PLAID_SECRET=your_sandbox_secretSandbox tests use /sandbox/public_token/create to skip Plaid Link entirely, so no browser needed.
CI (GitHub Actions) runs on every push:
Unit + MCP smoke tests on Python 3.10 / 3.11 / 3.12.
Sandbox integration tests, if
PLAID_CLIENT_ID_SANDBOXandPLAID_SECRET_SANDBOXare set as repository secrets.
Contributions
Welcome — particularly:
Additional Plaid product coverage (Assets, Statements, Signal).
Webhook handling for real-time transaction sync.
Export tools (write summaries to CSV / Markdown / Google Sheets).
Better LLM prompting for the debt workflows.
Please keep all tools read-only. No PR that introduces write endpoints (transfers, bill pay, account modification) will be merged.
License
MIT. See LICENSE.
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
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/t-rhex/plaid-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server