stripe-reporting-mcp
Read-only Stripe finance, ops, and risk reporting exposed via MCP, HTTP API, and CLI. Enables querying balances, payments, customers, payouts, reconciliation, and risk alerts without mutating Stripe state.
README
Stripe Reporting
Read-only Stripe finance, ops, and risk reporting — exposed three ways from one TypeScript backend:
- MCP server (stdio) for AI agents in Claude Code and other MCP clients
- HTTP API (Express) for dashboards, scripts, and non-MCP integrations
- CLI (
stripe-reporting) for ad-hoc inspection, scripts, and CI checks
All three transports share the same reporting backend, the same demo/stripe modes, and write to the same MongoDB audit log.
This project is designed as a demo-first reporting layer for teams that need visibility into Stripe without the ability to move money. It normalizes balances, payments, customers, payouts, reconciliation, and risk alerts.
Same backend, three front doors. Click for the full capture. See also
docs/walkthrough.md,docs/architecture.md, anddocs/decisions.md.
What It Is
- Read-only Stripe reporting backend built with TypeScript
- Three transports — MCP, HTTP API, CLI — all backed by the same service layer
- Works in
demomode with seeded fixtures for local testing - Works in
stripemode with a liveSTRIPE_SECRET_KEY - Returns both human-readable summaries and structured JSON
- Includes deterministic risk rules instead of vague AI-only scoring
- Optional MongoDB-backed audit log captures every MCP / HTTP / CLI call
What It Is Not
- It does not create charges, invoices, refunds, or payouts
- It does not mutate Stripe state in any way
- It is not a treasury or stablecoin transfer agent
- It is not claiming production-hardening beyond demo and local validation
Tools
| Tool | Purpose |
|---|---|
get_balance_summary |
Return available and pending balances by currency |
get_revenue_summary |
Return gross, refund, and net revenue totals over time |
list_payments |
List normalized payment records with filters and pagination |
get_payment |
Return details for a single payment, including refunds and disputes |
list_customers |
List customers with derived payment summaries |
get_customer_payments |
Return a customer summary plus payment history |
list_payouts |
List payout records with filters and pagination |
get_reconciliation_report |
Return payment, refund, fee, payout, and balance totals for reconciliation |
get_risk_alerts |
Return deterministic risk alerts such as failure spikes and payout delays |
Resources
ops://capabilitiesfinance://glossaryrisk://rules
Modes
demo
Uses seeded fixtures so you can test the full MCP experience without Stripe credentials.
stripe
Uses the Stripe API in read-only fashion through your secret key.
Current live-mode behavior:
- reads balances, charges, customers, refunds, disputes, payouts, and balance transactions
- normalizes Stripe objects into a stable MCP response shape
- is intended for demo and internal tooling workflows, not a compliance-grade reporting system
Quick Start
Install and validate locally:
npm install
npm test
npm run self-check
npm run build
Run in demo mode:
npm run dev:mcp
Run in live Stripe mode:
export MCP_MODE=stripe
export STRIPE_SECRET_KEY=sk_test_...
npm run dev:mcp
Environment
See env.example for the supported variables.
Main variables:
MCP_MODE=demo|stripeSTRIPE_SECRET_KEY=...MCP_DEFAULT_LOOKBACK_DAYS=30MCP_DEFAULT_PAGE_SIZE=20MCP_MAX_PAGE_SIZE=100MCP_STRIPE_MAX_PAGES=10HTTP_HOST=127.0.0.1(loopback only by default; set to0.0.0.0to expose)HTTP_PORT=3000MONGODB_URI=mongodb://localhost:27017(required for storage primitives + audit log)MONGODB_DB_NAME=stripe_reportingSTRIPE_REPORTING_AUTH_REQUIRED=false(set totrueto gate storage with Google OAuth)STRIPE_REPORTING_TOKEN_FILE=~/.stripe-reporting/credentials.jsonSTRIPE_REPORTING_TOKEN_TTL_DAYS=90(issued tokens expire after this many days)GOOGLE_OAUTH_CLIENT_ID=...(only whenAUTH_REQUIRED=true)GOOGLE_OAUTH_CLIENT_SECRET=...(only whenAUTH_REQUIRED=true)
Audit logging in MongoDB
When MONGODB_URI is set, every call across all three transports —
HTTP, MCP, and CLI — is recorded in a MongoDB requestlogs collection
(endpoint, method, query, status, duration, error code/message, timestamp).
Distinguish them via the method field:
db.requestlogs.find({method: "GET"}) // HTTP
db.requestlogs.find({method: "MCP"}) // MCP tool / resource invocations
db.requestlogs.find({method: "CLI"}) // CLI subcommand runs
For MCP, endpoint is tool:<name> (e.g. tool:get_balance_summary) or
resource:<uri> (e.g. resource:ops://capabilities). Tool failures land
with status: 400 for invalid_request/invalid_config and status: 500
otherwise; the errorCode/errorMessage mirror the AppError surface.
Logging is fully best-effort and asynchronous — Mongo write failures are logged to stderr but never break the reporting response.
Persistent memory: schema-on-the-fly storage
This is what makes the combo (MCP + MongoDB + Express + AI agents) more than the sum of its parts: the agent has memory that survives sessions, shared across all three transports, audited like everything else.
Five primitive operations exposed identically through MCP, HTTP, and CLI:
| Operation | MCP tool | HTTP endpoint | CLI command |
|---|---|---|---|
| Save a JSON document (collection auto-created) | save |
POST /storage/:collection/save |
save <collection> <json> |
| Find documents by filter | find |
POST /storage/:collection/find |
find <collection> [--filter] |
| List user's collections + counts | list_collections |
GET /storage/collections |
list-collections |
| Delete by filter (no empty filter) | delete |
POST /storage/:collection/delete |
delete <collection> --filter |
| Explicitly create a collection | create_collection |
POST /storage/collections |
create-collection <name> |
Schema is created on the fly. A new collection appears the first time
you save into it. Mongo's permissiveness is the right tool here — agents
can introduce new buckets without migrations as the conversation evolves.
Validation: collection names match /^[a-z][a-z0-9_]{2,63}$/; the system
collections (requestlogs, users) are reserved.
Every saved document is automatically tagged with _userId and
_createdAt. Find/delete filters are scoped to the caller's _userId
so users can't read or delete each other's data. When auth is off
(STRIPE_REPORTING_AUTH_REQUIRED=false) all docs share _userId: "default"
— fine for single-user demos.
What an agent might save
Examples of natural agent + user collaborations during a chat:
- "Pin this report as 'Monday morning check'." →
save("pinned_queries", {name, tool, args}) - "Flag these three customers — I want to look at them again next session." →
save("flagged_customers", {customer_id, reason}) - "Note that customer X is approved out-of-band by the CFO." →
save("annotations", {entity_type, entity_id, note}) - "Open a case for this dispute." →
save("cases", {dispute_id, evidence: [...], status: "open"}) - "Tag these 50 customers as 'high-LTV cohort'." →
save("cohorts", {name, members}) - "Lock April's books here, with these numbers." →
save("closed_periods", {period, totals})
None of these collections need to exist beforehand. The agent reaches for
save("flagged_customers", ...) and the bucket appears. Next session,
the agent calls list_collections to see what the user has accumulated,
and find to read it back.
Important — passive storage, not active monitoring. These primitives save/read/delete documents. Nothing scans Stripe periodically and fires alerts. A "flagged customer" is a saved row; the agent only acts on it when you ask, e.g. "go check on the customers I flagged last week." True monitoring would require a scheduler/worker — see Future work in this README.
Demo flow
# Empty state
mongosh stripe_reporting --eval "show collections"
# -> requestlogs
# Agent (or user) flags a customer to revisit
stripe-reporting save flagged_customers '{"customer_id": "cus_abc", "reason": "fraud risk"}'
# Schema appeared on the fly
mongosh stripe_reporting --eval "show collections"
# -> requestlogs, flagged_customers
# Read it back
stripe-reporting find flagged_customers --pretty
# Same thing via HTTP — same Mongo, same answer
curl -X POST http://127.0.0.1:3000/storage/flagged_customers/find \
-H 'content-type: application/json' -d '{}' | jq
# Same thing via MCP — Claude Code calls the `find` tool
# Audit trail
mongosh stripe_reporting --eval 'db.requestlogs.find({endpoint: /tool:save|tool:find/}).pretty()'
Authentication (optional)
By default the demo is single-user, local-only, no auth. Storage
primitives all use _userId = "default" and the HTTP server binds to
127.0.0.1. This is the right shape for "my personal Stripe co-pilot
running on my laptop."
When you want multi-user, flip one env var:
STRIPE_REPORTING_AUTH_REQUIRED=true
GOOGLE_OAUTH_CLIENT_ID=...
GOOGLE_OAUTH_CLIENT_SECRET=...
Setting up the Google OAuth client (~5 minutes, free)
- Go to https://console.cloud.google.com/apis/credentials.
- Create or select a project.
- Configure the OAuth consent screen if prompted: User type "External", fill in app name + support email, save. You don't need verification for personal/internal use — the app stays in "Testing" mode and you add yourself as a test user.
- Click Create credentials → OAuth client ID.
- Choose Application type: Desktop app (this is important — the loopback-redirect flow this CLI uses requires the Desktop type, not Web). Name it anything, e.g. "stripe-reporting CLI".
- Copy the resulting Client ID and Client secret into your
.env:
STRIPE_REPORTING_AUTH_REQUIRED=true
GOOGLE_OAUTH_CLIENT_ID=<client id>.apps.googleusercontent.com
GOOGLE_OAUTH_CLIENT_SECRET=<client secret>
That's it — no redirect URIs to register. Google allows any
http://localhost:<port> redirect for Desktop-type clients without
pre-registration.
Running the login flow
Once per user, on each machine:
stripe-reporting login
# Opens browser, signs in with Google, saves an API token to
# ~/.stripe-reporting/credentials.json (mode 0600)
stripe-reporting whoami
# {"authenticated": true, "userId": "google_<sub>", "email": "alice@…",
# "expiresAt": "...", "expiresInDays": 90}
stripe-reporting logout
# Revokes the token server-side, deletes the local credentials file.
Token expiry. Tokens issued at login expire after
STRIPE_REPORTING_TOKEN_TTL_DAYS (default 90). When a token expires,
HTTP requests get 401 auth_expired and whoami surfaces the
failure — re-run stripe-reporting login to issue a fresh token.
Pre-existing tokens issued before this field was introduced have no
expiry set; they continue to work indefinitely until you log out.
After login:
- HTTP requires
Authorization: Bearer <api-token>on every request - MCP startup reads the token from the credentials file → tags storage by the authenticated user
- CLI subcommands read the same credentials file
- Storage docs are scoped by Google
sub(stable across email changes, rotations, etc.) - Audit log entries are tagged with
userId— filter therequestlogscollection by{userId: "<google-sub>"}
Identity model. Google answers "who is this." Stripe credentials answer "what data can they read." The two are intentionally decoupled — the Stripe key stays in env, never crosses the wire to our server.
Threat model. With HTTP_HOST=127.0.0.1 (default) the API is only
reachable from localhost, so even without auth the data is as safe as
your shell account. Set HTTP_HOST=0.0.0.0 only if you've also set
AUTH_REQUIRED=true. For genuine multi-tenant hosted deployment,
upgrade the bearer-token flow to Stripe Connect OAuth — the auth
boundary is one middleware (src/middleware/auth.ts); the rest of the
system is unchanged.
HTTP API (Express + MongoDB)
In addition to the MCP transport, the same reporting backend is exposed as a
read-only HTTP API. When MONGODB_URI is set, every request is recorded in a
MongoDB requestlogs collection.
Run it locally:
npm run dev:http # tsx, demo mode
# or, after npm run build:
npm run start:http
Startup note: the HTTP server takes ~5 seconds to bind on first launch because Mongoose has a heavy module-init cost. If you script against it (CI, smoke tests, health checks), wait for the
[http] ... listening on :<port>line on stdout — or poll/healthwith a retry — before sending real traffic. The MCP entrypoint (dev:mcp) is not affected; it does not import Mongoose.
Endpoints (all GET):
| Endpoint | Notes |
|---|---|
/health |
Returns mode, mongo connection state, version |
/capabilities |
Equivalent of ops://capabilities |
/balance/summary |
?as_of=¤cy= |
/revenue/summary |
`?from=&to=¤cy=&group_by=day |
/payments |
?from=&to=&status=&customer_id=&email=¤cy=&limit=&starting_after= |
/payments/:id |
Single payment with refunds and disputes |
/customers |
?email=&created_from=&created_to=&limit=&starting_after= |
/customers/:id/payments |
?from=&to=&status=&limit=&starting_after= |
/payouts |
?from=&to=&status=¤cy=&limit=&starting_after= |
/reconciliation/report |
?from=&to=¤cy= |
/risk/alerts |
`?from=&to=&severity=low |
Response envelope:
{"ok": true, "data": {...}}
{"ok": false, "error": {"code": "invalid_request", "message": "...", "details": {...}}}
CLI
Every MCP tool is also available as a stripe-reporting subcommand, useful
for ad-hoc inspection, scripts, and CI checks. It uses the same backend
(demo or live Stripe) and the same Mongo audit log.
Run during development:
npm run dev:cli -- --help
npm run dev:cli -- balance-summary --currency USD
npm run dev:cli -- list-payments --limit 5 --pretty
npm run dev:cli -- get-payment pay_demo_6
npm run dev:cli -- customer-payments cus_demo_bob --from 2026-01-01
npm run dev:cli -- risk-alerts --severity high
After npm run build the CLI is at dist/cliIndex.js and exposed via the
stripe-reporting bin entry, so a global npm install -g . (or symlink via
npm link) gives you the binary on $PATH:
stripe-reporting capabilities --pretty
MCP_MODE=stripe STRIPE_SECRET_KEY=sk_live_... \
stripe-reporting revenue-summary --from 2026-04-01 --to 2026-04-30
Commands:
| Command | Notes |
|---|---|
capabilities |
Prints mode, tool list, resource list |
balance-summary |
--as-of, --currency |
revenue-summary |
--from, --to, --currency, --group-by, --include-refunds |
list-payments |
--from, --to, --status, --customer-id, --email, --currency, --limit, --starting-after |
get-payment <payment_id> |
Charge ID or PaymentIntent ID |
list-customers |
--email, --created-from, --created-to, --limit, --starting-after |
customer-payments <customer_id> |
--from, --to, --status, --limit, --starting-after |
list-payouts |
--from, --to, --status, --currency, --limit, --starting-after |
reconciliation-report |
--from, --to, --currency |
risk-alerts |
--from, --to, --severity, --limit |
Global flags: --pretty (pretty-print JSON), --help / -h (use with a
command for command-specific help).
Output: stdout is the JSON result on success. On error the JSON error
envelope is written to stderr and the process exits with 2 (validation
errors) or 1 (everything else), so if cli ...; then works in scripts.
Claude Code Setup
This repo includes a project-scoped MCP config in .mcp.json:
{
"mcpServers": {
"stripe-reporting": {
"command": "node",
"args": ["./node_modules/tsx/dist/cli.mjs", "src/index.ts"]
}
}
}
Option A — project-scoped via .mcp.json (recommended)
The bundled .mcp.json is auto-discovered when you start Claude Code from
this repo. Steps:
npm installin the repo root.- Optionally export
MCP_MODE=stripeandSTRIPE_SECRET_KEY=.... - Run
claudefrom this directory. - Approve the project-scoped
stripe-reportingMCP server when prompted. - Verify with
/mcpinside Claude Code — the server should appear connected. - Ask Claude to call reporting tools such as
get_revenue_summaryorget_risk_alerts.
Option B — register it explicitly with claude mcp add
If you want it available outside this directory (e.g. user-scope across all projects), register it once with the CLI:
# project scope (writes to ./.mcp.json)
claude mcp add stripe-reporting \
--scope project \
-- node ./node_modules/tsx/dist/cli.mjs src/index.ts
# or user scope (available in every project on this machine)
claude mcp add stripe-reporting \
--scope user \
--env MCP_MODE=demo \
-- node /absolute/path/to/this/repo/node_modules/tsx/dist/cli.mjs \
/absolute/path/to/this/repo/src/index.ts
Pass live-mode credentials via --env:
claude mcp add stripe-reporting \
--scope user \
--env MCP_MODE=stripe \
--env STRIPE_SECRET_KEY=sk_test_... \
-- node /absolute/path/to/this/repo/node_modules/tsx/dist/cli.mjs \
/absolute/path/to/this/repo/src/index.ts
Manage the registration:
claude mcp list # confirm it shows up
claude mcp get stripe-reporting # inspect the config
claude mcp remove stripe-reporting # unregister
Option C — point at a built artifact
After npm run build, you can register the compiled JS instead of running
through tsx:
claude mcp add stripe-reporting \
--scope user \
-- node /absolute/path/to/this/repo/dist/index.js
Example Prompts
- "What is our current Stripe balance by currency?"
- "Summarize net revenue for the last 30 days by week."
- "Show me failed or disputed payments from the last 14 days."
- "List customers created this month and include their payment totals."
- "Give me a reconciliation summary for this month."
- "What risk alerts should finance review today?"
Public Demo Positioning
If you publish this repo publicly, the honest framing is:
- read-only Stripe reporting MCP
- demo-first, safe for agent visibility use cases
- live Stripe mode available, but not marketed as a full production finance system
That is a strong, credible scope for a public demo.
Validation Status
Validated locally with:
npm test
npm run self-check
npm run build
License
MIT. See LICENSE.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。




