Skip to main content
Glama
ohrytskov

stripe-reporting-mcp

by ohrytskov

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 query, three transports

Same backend, three front doors. Click for the full capture. See also docs/walkthrough.md, docs/architecture.md, and docs/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 demo mode with seeded fixtures for local testing

  • Works in stripe mode with a live STRIPE_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://capabilities

  • finance://glossary

  • risk://rules

Modes

Demo mode → Stripe mode, same shape

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|stripe

  • STRIPE_SECRET_KEY=...

  • MCP_DEFAULT_LOOKBACK_DAYS=30

  • MCP_DEFAULT_PAGE_SIZE=20

  • MCP_MAX_PAGE_SIZE=100

  • MCP_STRIPE_MAX_PAGES=10

  • HTTP_HOST=127.0.0.1 (loopback only by default; set to 0.0.0.0 to expose)

  • HTTP_PORT=3000

  • MONGODB_URI=mongodb://localhost:27017 (required for storage primitives + audit log)

  • MONGODB_DB_NAME=stripe_reporting

  • STRIPE_REPORTING_AUTH_REQUIRED=false (set to true to gate storage with Google OAuth)

  • STRIPE_REPORTING_TOKEN_FILE=~/.stripe-reporting/credentials.json

  • STRIPE_REPORTING_TOKEN_TTL_DAYS=90 (issued tokens expire after this many days)

  • GOOGLE_OAUTH_CLIENT_ID=... (only when AUTH_REQUIRED=true)

  • GOOGLE_OAUTH_CLIENT_SECRET=... (only when AUTH_REQUIRED=true)

Audit logging in MongoDB

Risk alerts + audit log tail

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

Schema-on-the-fly with save / find

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)

Auth lifecycle: whoami, expiry, logout

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)

  1. Go to https://console.cloud.google.com/apis/credentials.

  2. Create or select a project.

  3. 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.

  4. Click Create credentials → OAuth client ID.

  5. 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".

  6. 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 the requestlogs collection 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 /health with 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

week

month&include_refunds=true`

/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

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 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:

  1. npm install in the repo root.

  2. Optionally export MCP_MODE=stripe and STRIPE_SECRET_KEY=....

  3. Run claude from this directory.

  4. Approve the project-scoped stripe-reporting MCP server when prompted.

  5. Verify with /mcp inside Claude Code — the server should appear connected.

  6. Ask Claude to call reporting tools such as get_revenue_summary or get_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.

A
license - permissive license
-
quality - not tested
C
maintenance

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