stripe-reporting-mcp

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.

Category
Visit Server

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 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=&currency=
/revenue/summary `?from=&to=&currency=&group_by=day
/payments ?from=&to=&status=&customer_id=&email=&currency=&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=&currency=&limit=&starting_after=
/reconciliation/report ?from=&to=&currency=
/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:

  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.

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured