Skip to main content
Glama
srik4442

Payment Ops MCP Server

by srik4442

header

Typing SVG

Python Stripe MCP SQLite Tests Safety


An MCP server that lets Claude handle payment support โ€” look up customers, review charges, and issue refunds โ€” by orchestrating between Stripe and an internal orders database, with human approval required before any money moves.


๐Ÿ’ก Why This Exists

Stripe ships an official MCP server โ€” but it only knows Stripe. Real companies wrap the payment provider with internal data and business rules. This server is that orchestration layer:

  • ๐Ÿ”— Joins Stripe payment data with internal order records via stripe_customer_id

  • ๐Ÿ“… Enforces refund policy (no refunds older than 90 days)

  • ๐Ÿ™‹ Requires human confirmation before moving money

  • ๐Ÿ“ Writes a full audit log of every refund to the orders DB

That's the layer every company has to build itself. The provider can't.


Related MCP server: Stripe MCP Server

๐Ÿ—๏ธ Architecture

Live Architecture


๐Ÿ› ๏ธ Tools

Tool

Type

What it does

lookup_customer

๐Ÿ” Read

Merges internal DB record + live Stripe status by email

list_payments

๐Ÿ” Read

Lists recent Stripe PaymentIntents for a customer

get_order_history

๐Ÿ” Read

Returns full order history from internal DB

issue_refund

โœ๏ธ Write

Two-step: preview โ†’ confirm โ†’ refund Stripe + log DB

create_payment_link

โœ๏ธ Write

Generates a Stripe test payment link

revenue_summary

๐Ÿ” Read

Aggregates paid orders over a date range

flag_for_review

โœ๏ธ Write

Flags a charge in the audit log for manual review

Plus 2 resources (customer://email, payment://charge_id) and 2 prompts (daily_revenue_report, find_refund_candidates).


๐Ÿ”’ Safety Design

Three layers โ€” never trust the model alone for money-moving actions:

  1. ๐Ÿ™‹ Human-in-the-loop gate โ€” issue_refund requires confirmed=True, which the model only sets after explicitly asking the user

  2. ๐Ÿ“… Server-side business rule โ€” refunds older than 90 days are rejected regardless of what the model requests

  3. ๐Ÿ—๏ธ Least-privilege Stripe key โ€” restricted key scoped to Charges + Customers + Refunds only, limits blast radius


๐Ÿ—„๏ธ Database Schema

customers
  id (uuid, pk)
  email (unique)
  name
  stripe_customer_id    โ† join key to Stripe

orders
  id (uuid, pk)
  customer_id (fk)
  stripe_charge_id      โ† Stripe PaymentIntent ID (pi_xxx)
  amount_cents
  currency
  status (paid / refunded / partially_refunded)
  created_at

refund_log              โ† audit trail
  id (uuid, pk)
  order_id (fk)
  stripe_refund_id
  amount_cents
  reason
  refunded_by ("ai-assistant")
  created_at

โš™๏ธ Setup

1. Clone and install

git clone https://github.com/srik4442/Payment-Ops-mcp.git
cd Payment-Ops-mcp
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

2. Get a Stripe test key

  1. Create a free account at stripe.com (test mode, no card needed)

  2. Go to Developers โ†’ API keys โ†’ Create restricted key

  3. Grant Write access to: Charges and Refunds, Customers, Payment Intents, Payment Links, Products

  4. Copy the rk_test_... key

3. Configure environment

cp .env.example .env
# Edit .env and add your key:
# STRIPE_API_KEY=rk_test_your_key_here
# DATABASE_URL=sqlite:///./payments.db

4. Seed test data

python scripts/seed.py

Creates 5 test customers in both your local DB and Stripe, each with 2โ€“3 test charges.

5. Connect Claude Desktop

Add to ~/Library/Application Support/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "payment-ops": {
      "command": "/absolute/path/to/.venv/bin/python",
      "args": ["/absolute/path/to/src/mcp_server.py"],
      "env": {
        "STRIPE_API_KEY": "rk_test_your_key_here",
        "DATABASE_URL": "sqlite:////absolute/path/to/payments.db"
      }
    }
  }
}

Restart Claude Desktop โ€” you should see payment-ops connected.

6. Run the demo

Type into Claude Desktop:

"A customer, sarah@example.com, emailed about a double charge. Look into it and refund the most recent payment."


๐Ÿงช Tests

python -m pytest tests/ --cov=src -v

24 tests ยท 99% coverage on business logic ยท Stripe client fully mocked (no real API calls in tests)

tests/test_payment_service.py::TestLookupCustomer::test_lookup_customer_merges_db_and_stripe PASSED
tests/test_payment_service.py::TestIssueRefund::test_issue_refund_preview_when_not_confirmed PASSED
tests/test_payment_service.py::TestIssueRefund::test_issue_refund_writes_refund_log_when_confirmed PASSED
tests/test_payment_service.py::TestIssueRefund::test_issue_refund_rejected_for_charge_older_than_90_days PASSED
...

๐Ÿค” Tech Decisions

Why MCP instead of a backend script? A script automates one fixed workflow. MCP exposes capabilities to any AI client so a human can drive novel, multi-step support tasks in natural language โ€” without pre-coding every path.

Why SQLite? Zero-config for a portfolio project. The ORM (SQLAlchemy) makes swapping to Postgres a one-line config change.

Why test mode only? Test mode uses the identical Stripe API and code path as live โ€” the only difference is the key prefix. Switching to production is one environment variable.

Why a restricted Stripe key? Least-privilege: the key can only touch Charges, Customers, and Refunds. Even if the key were compromised, the blast radius is contained.

Why store pi_xxx IDs (PaymentIntent) instead of ch_xxx (Charge)? Modern Stripe workflows are built around PaymentIntents. Refunds via payment_intent work reliably across all payment methods.


๐ŸŽฌ Demo

Prompt typed into Claude Desktop: "A customer, sarah@example.com, emailed about a double charge. Look into it and refund the most recent payment."

STEP 1  Claude calls lookup_customer("sarah@example.com")
        โ†’ queries Orders DB โ†’ finds Sarah + Stripe customer ID

STEP 2  Claude calls list_payments(customer="cus_xxx")
        โ†’ queries Stripe (test mode) โ†’ returns recent charges

STEP 3  Claude PAUSES:
        "I found a duplicate $49.99 charge. Refund the most recent one? Please confirm."
        โ†’ human-in-the-loop gate before any money moves

STEP 4  You reply "yes"
        โ†’ Claude calls issue_refund(confirmed=True)
        โ†’ refund issued in Stripe
        โ†’ order status updated + audit row written to DB

STEP 5  Claude confirms:
        "Refunded $49.99 to Sarah (re_3To18wCLuf9LMqJ11xb4XutS).
         Logged in orders DB as duplicate-charge reversal."

Demo


๐Ÿ“„ Resume Bullet

AI Payment Operations Assistant (MCP Server) ยท Python, MCP SDK, Stripe API, SQLAlchemy, pytest

  • Built a Model Context Protocol server that lets AI assistants perform payment support operations by orchestrating between the Stripe API and an internal order database, exposing 7 tools, resources, and prompt templates

  • Implemented human-in-the-loop confirmation and server-side business rules to gate money-moving actions, with a full refund audit log written to the database for every transaction

  • Applied production payment patterns โ€” least-privilege restricted API keys, idempotency, and prompt-injection mitigation โ€” and validated orchestration logic with a mocked-Stripe pytest suite at 99% coverage

footer

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    Not graded
    maintenance
    Enables AI agents to interact with multiple payment providers (Stripe, Paystack) through a unified API. Supports payment initialization, verification, refunds, customer management, and invoicing without requiring knowledge of specific provider implementations.
    2
    -
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables natural language interaction with Stripe accounts to query customers, revenue, invoices, subscriptions, disputes, and issue refunds.
    28 npm
    1
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables integration with Stripe APIs through function calling, supporting operations on customers, products, invoices, subscriptions, and more.
    8,607 npm
    MIT