stripe-reporting-mcp
Provides persistent schema-on-the-fly storage and audit logging, allowing agents to save and retrieve documents across sessions.
Provides read-only reporting on Stripe balances, payments, customers, payouts, reconciliation, and risk alerts.
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., "@stripe-reporting-mcpshow me the current balance in USD"
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.
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 testingWorks in
stripemode with a liveSTRIPE_SECRET_KEYReturns 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 |
| Return available and pending balances by currency |
| Return gross, refund, and net revenue totals over time |
| List normalized payment records with filters and pagination |
| Return details for a single payment, including refunds and disputes |
| List customers with derived payment summaries |
| Return a customer summary plus payment history |
| List payout records with filters and pagination |
| Return payment, refund, fee, payout, and balance totals for reconciliation |
| 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 buildRun in demo mode:
npm run dev:mcpRun in live Stripe mode:
export MCP_MODE=stripe
export STRIPE_SECRET_KEY=sk_test_...
npm run dev:mcpEnvironment
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 runsFor 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) |
|
|
|
Find documents by filter |
|
|
|
List user's collections + counts |
|
|
|
Delete by filter (no empty filter) |
|
|
|
Explicitly create a collection |
|
|
|
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)
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 requestMCP 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:httpStartup 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 | ||
| Returns mode, mongo connection state, version | ||
| Equivalent of | ||
|
| ||
| `?from=&to=¤cy=&group_by=day | week | month&include_refunds=true` |
|
| ||
| Single payment with refunds and disputes | ||
|
| ||
|
| ||
|
| ||
|
| ||
| `?from=&to=&severity=low | medium | high&limit=` |
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 highAfter 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-30Commands:
Command | Notes |
| Prints mode, tool list, resource list |
|
|
|
|
|
|
| Charge ID or PaymentIntent ID |
|
|
|
|
|
|
|
|
|
|
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.tsPass 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.tsManage the registration:
claude mcp list # confirm it shows up
claude mcp get stripe-reporting # inspect the config
claude mcp remove stripe-reporting # unregisterOption 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.jsExample 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 buildLicense
MIT. See LICENSE.
This server cannot be installed
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/ohrytskov/stripe-reporting-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server