@suavesav/stripe-connect-mcp
v0.1.1
Published
Read-only MCP server for the Stripe API with full Connect (stripe_account) support
Readme
stripe-connect-mcp
Read-only MCP server for the Stripe API. Unlike the hosted Stripe MCP, this one:
- Exposes
list_connected_accountsandretrieve_account(Connect platform endpoints). - Passes
Stripe-Accounton every list/retrieve call via an optionalconnected_account_idargument, so you can inspect any connected account. - Redacts learner PII from every response by default, so names, emails, card last4, free-form metadata, and similar fields never reach the LLM. See PII protection.
Setup
npm install
npm run buildSet STRIPE_SECRET_KEY (secret or restricted key with read scopes + Connect read):
cp .env.example .env
# edit .envRegister with Claude Code
Add to ~/.claude.json under mcpServers:
"stripe-connect": {
"type": "stdio",
"command": "node",
"args": ["/absolute/path/to/stripe-connect-mcp/dist/index.js"],
"env": {
"STRIPE_SECRET_KEY": "rk_live_..."
}
}Restart Claude Code.
Tools
All list/retrieve tools accept optional connected_account_id (forwarded as Stripe-Account).
Connect
list_connected_accounts,retrieve_account
Transactions
list_charges,retrieve_chargelist_payment_intents,retrieve_payment_intentlist_checkout_sessions(line items + products expanded by default)list_refunds,list_disputes
Customers & billing
list_customers,retrieve_customerlist_invoices,retrieve_invoice(line items + products expanded by default)list_subscriptions,retrieve_subscription
Catalog
list_products,list_prices
Money movement
retrieve_balance,list_balance_transactionslist_payoutslist_transfers(platform-only)list_application_fees(platform-only)
Other
list_events
PII protection
Every tool response is passed through a redaction layer before it reaches the LLM. The goal is to keep learner PII (names, emails, phones, addresses, card last4, bank account fields, free-form metadata, etc.) out of chat transcripts entirely. When you legitimately need the underlying value, use the Stripe dashboard — IDs (cus_…, ch_…, pi_…) are preserved in responses so they're directly pasteable into dashboard URLs.
What gets redacted (replaced with "[REDACTED]"):
email,phone,address,shipping,billing_details,customer_detailsreceipt_email(Charge, PaymentIntent)customer_email,customer_name,customer_phone,customer_address,customer_shipping,customer_tax_ids(Invoice, CheckoutSession)tax_id/tax_idsstatement_descriptor/statement_descriptor_suffixsupport_email/support_phone- Card fields:
last4,fingerprint,exp_month,exp_year,network_token - Bank account fields:
routing_number,account_holder_name,account_holder_type client_secret(PaymentIntent, SetupIntent) — sensitive token, never returned to the LLMnameanddescriptionon customer/charge/account/etc. objects (kept on catalog objects: product, price, plan, coupon, tax_rate, shipping_rate)
Metadata is handled by an allowlist at config/pii-allowlist.json. Keys on the list pass through; everything else is silently dropped. Add keys via PR with a rationale in the description (e.g. "add course_id — internal course identifier, not learner-identifying"). The default list is empty.
Bypass (not recommended). Set STRIPE_MCP_ALLOW_PII=true in the server's env to disable the redactor for the session. A loud stderr warning is emitted at startup. Intended for internal debugging only — the LLM will receive and persist raw PII in the transcript.
Removed tools. search_stripe and stripe_get were removed because neither the request (queries, paths, params) nor the response could be safely scoped — both carried arbitrary PII into LLM context. Use the curated list/retrieve tools; if you need an endpoint they don't cover, go to the Stripe dashboard or add a curated tool.
Local testing
STRIPE_SECRET_KEY=rk_... npx @modelcontextprotocol/inspector node dist/index.jsTests
Unit tests are pure and offline:
npm testThe snapshot suite hits Stripe test mode and compares response shapes (keys + types, no values) against committed JSON snapshots in test/snapshot/snapshots/. It detects Stripe API drift:
STRIPE_SECRET_KEY=sk_test_... npm run test:snapshot # check for drift
STRIPE_SECRET_KEY=sk_test_... UPDATE=1 npm run test:snapshot # regenerate snapshotsRequires a test-mode key with write scopes on customers, products, prices, subscriptions, payment_intents, invoices, checkout_sessions — the suite seeds these once (idempotent, tagged with metadata.drift_seed="1") so lists are never empty. The seeded PaymentIntent is confirmed with pm_card_visa so a charge and balance transaction exist to snapshot.
Drift-check workflow
.github/workflows/drift-check.yml runs the snapshot suite on a schedule (Mondays 09:00 UTC) and on manual workflow_dispatch. When it detects shape drift, it regenerates snapshots, re-verifies, and opens a PR (drift/stripe-shapes) containing the new JSON files. The PR body includes the failing-run diff summary; the Files Changed tab is the structural diff itself.
Reviewing a drift PR: inspect the test/snapshot/snapshots/*.json diff. Added fields → Stripe added something (usually safe to merge as-is). Removed fields → if any tool code depends on that field, update the tool alongside accepting the snapshot. Type changes → almost always need a code change.
Secrets the workflow expects:
STRIPE_TEST_SECRET_KEY— required. Test-mode key with the scopes above.DRIFT_PR_TOKEN— optional but recommended. A PAT withcontents:write+pull_requests:writeon this repo. Without it the workflow falls back toGITHUB_TOKEN, which opens the PR but does not re-trigger CI (a GitHub platform rule — prevents workflow loops). Fine-grained PATs expire; rotate before the date stored in the token.
Forcing a run: Actions tab → Stripe drift check → Run workflow. A clean run exits 0 and opens no PR.
Notes
- All amounts are in Stripe minor units (cents for USD, yen for JPY, etc.).
list_*returns Stripe's native shape:{ data, has_more, next_page? }. Usestarting_afterwith the last record's id to paginate.list_transfersandlist_application_feesonly make sense on the platform account. If you passconnected_account_idto them, Stripe will likely error — that's expected.
