Metadata-Version: 2.4
Name: stripe-blade-mcp
Version: 0.1.0a1
Summary: Stripe MCP server for Stallari — billing-v1 (subscriptions, products, prices, invoices) + payments-v1 (PaymentIntents, refunds, disputes, webhooks). Token-efficient pipe-delimited output, mandatory env isolation, Restricted-Key-only, per-resource write-gating.
Project-URL: Homepage, https://github.com/groupthink-dev/stripe-blade-mcp
Project-URL: Issues, https://github.com/groupthink-dev/stripe-blade-mcp/issues
Project-URL: Changelog, https://github.com/groupthink-dev/stripe-blade-mcp/blob/main/CHANGELOG.md
Author: Groupthink Dev
License: MIT
License-File: LICENSE
Keywords: billing,mcp,payments,stallari,stripe,subscriptions
Classifier: Development Status :: 1 - Planning
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business :: Financial
Requires-Python: >=3.12
Requires-Dist: fastmcp>=2.0.0
Requires-Dist: httpx>=0.28.1
Requires-Dist: pydantic>=2.0.0
Requires-Dist: stripe>=11.0.0
Description-Content-Type: text/markdown

# stripe-blade-mcp

> ⚠️ **Phase 0 scaffold — pre-alpha (`0.1.0a1`).** All 45 tools currently raise
> `NotImplementedError`. The scaffold exists to validate the
> [`/build-blade-mcp`](https://github.com/groupthink-dev/stallari-doc) skill
> conventions (security, privacy, token-efficiency, devops baseline) end-to-end.
> Production implementation of Phases A–E is gated on a real Stallari build
> trigger per `BUILD_PLAN.md`. **Do not install for production use.** Use
> [`@stripe/mcp`](https://docs.stripe.com/mcp) for direct Claude Desktop / Cursor
> Stripe access in the meantime.

> Stripe MCP server for Stallari workloads. Token-efficient, Restricted-Key-only, per-resource write-gated. Closes the gaps in Stripe's own `@stripe/mcp` for Stallari pack-workload integration.

[![PyPI](https://img.shields.io/pypi/v/stripe-blade-mcp)](https://pypi.org/project/stripe-blade-mcp/)
[![Tests](https://github.com/groupthink-dev/stripe-blade-mcp/actions/workflows/test.yml/badge.svg)](https://github.com/groupthink-dev/stripe-blade-mcp/actions/workflows/test.yml)
[![License](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
[![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)

Dual-contract blade implementing both `billing-v1` (subscriptions, products, prices, invoices) and `payments-v1` (PaymentIntents, refunds, disputes, webhooks). First blade where one repo cleanly implements both Stallari payment contracts end-to-end.

## Why another Stripe MCP?

Stripe ships [`@stripe/mcp`](https://docs.stripe.com/mcp). It covers 23 tools across 11 resource categories and works well for direct Claude Desktop / Cursor use. This blade exists because:

- **Missing `payments-v1` Required ops.** `@stripe/mcp` ships `list_payment_intents` + `create_refund` only — missing `payment_intent_create/capture/cancel/get`, `refund_get/list`, and the full `webhook_verify` + `webhook_subscription_*` block that Stallari's `payments-v1` contract requires.
- **HTTP-only transport** — `@stripe/mcp` is HTTP only; DD-242 requires stdio when launched by Stallari.
- **Raw JSON responses** — `@stripe/mcp` returns standard MCP JSON-RPC payloads. This blade emits pipe-delimited summaries on list ops (~16× token reduction measured on equivalent paddle-blade ops), with field selection and null-omit on detail views.
- **Advisory-only write-gating** — `@stripe/mcp` recommends human confirmation but doesn't enforce. This blade enforces per-resource `STRIPE_WRITE_*` env gates AND a `confirm=true` parameter on destructive ops.

If you only need basic Stripe access in Claude Desktop, use [`@stripe/mcp`](https://docs.stripe.com/mcp). If you're running Stallari pack workloads, use this blade.

## Comparison

| Capability | `stripe-blade-mcp` | `@stripe/mcp` |
|---|---|---|
| Tool count | ~45 (full `billing-v1` + `payments-v1`) | **23** (partial coverage) |
| `payments-v1` Required ops | 8/8 | **2/8** |
| `billing-v1` Required ops | 6/6 | **3/6** |
| Webhook HMAC verification | Built-in `stripe_webhook_verify` | **Not present** |
| Webhook subscription management | 4-op block (list/create/delete/replay) | Not present |
| Token-efficient responses | Pipe-delimited, field selection, null-omit, money-formatted | Raw JSON |
| Write gating | Per-op env var, fail-closed | **Advisory only** |
| Destructive op confirmation | `confirm=true` enforced | None enforced |
| Sandbox/live isolation | Mandatory `STRIPE_ENV`, fail-closed | Yes (per-env access) |
| API key security | Env var only, credential scrubbing | Env var OR `--api-key` CLI arg (ps leak) |
| Restricted-Key enforcement | Startup check + scope coherence warn | None |
| Stdio when harness-launched | **Yes** (DD-242) | **No** — HTTP only |
| Money formatting | Human-readable (`$29.00 USD`) | Raw cents |
| Field selection | `fields=` parameter | Not available |
| Pagination hints | Cursor + "N more" | Standard `has_more` only |
| Idempotency-key auto-attach | Yes (`stallari-...` prefix) | Unclear |
| Connect (multi-tenant) | Out of scope v1 — explicit refuse | Possibly available |
| Runtime | Python (uv) | TypeScript (npm) |

## Token efficiency: before and after

**Raw Stripe API (≈ `@stripe/mcp` shape)** — 1 page of 25 subscriptions:
```json
{
  "object": "list",
  "url": "/v1/subscriptions",
  "has_more": true,
  "data": [
    {
      "id": "sub_1NkVJ8...",
      "object": "subscription",
      "application": null,
      "application_fee_percent": null,
      "automatic_tax": {"enabled": false, "liability": null},
      "billing_cycle_anchor": 1715000000,
      "billing_thresholds": null,
      "cancel_at": null,
      "cancel_at_period_end": false,
      "canceled_at": null,
      "cancellation_details": {"comment": null, "feedback": null, "reason": null},
      "collection_method": "charge_automatically",
      "created": 1715000000,
      ... ~50 more fields per subscription
    },
    ... 24 more
  ]
}
```
**~12,000 tokens for the page.**

**`stripe-blade-mcp` pipe-delimited:**
```
[env=test]
sub_1NkVJ8 | acme inc. | active | $29.00 USD / month | 3 days ago
sub_1NkVK2 | beta corp  | active | $99.00 USD / month | 7 days ago
sub_1NkVL5 | trial co.  | trialing | $0.00 USD / month | 1 day ago
... 22 more (pass starting_after=sub_1NkVL5 to continue)
```
**~280 tokens.** ≈ **40× reduction**.

Need a single field? Call `stripe_subscription_get` with `fields=["id", "status", "current_period_end"]` to project exactly what you need.

## Installation

### As a Stallari blade

`stripe-blade-mcp` installs as a Stallari pack. The blade resolves credentials
through the Stallari CredentialStore (DD-186) at launch — never commit secrets.

```bash
stallari pack install groupthink-dev/stripe-blade-mcp
```

### Standalone (Claude Desktop, Cursor, etc.)

```bash
uv tool install stripe-blade-mcp
```

Claude Desktop MCP config:

```json
{
  "mcpServers": {
    "stripe": {
      "command": "stripe-blade-mcp",
      "env": {
        "STRIPE_ENV": "test",
        "STRIPE_API_KEY": "rk_test_..."
      }
    }
  }
}
```

## Quickstart

1. Create a Restricted Key at [https://dashboard.stripe.com/apikeys/create](https://dashboard.stripe.com/apikeys/create) — Standard Keys are rejected
2. Enable the resources your workload needs (Payments / Subscriptions / Refunds)
3. Configure the env:

```bash
export STRIPE_ENV=test                    # or 'live' for production
export STRIPE_API_KEY=rk_test_...         # restricted key only
export STRIPE_WRITE_PAYMENTS=1            # only enable writes you need
```

4. Validate: `stripe-blade-mcp` then invoke `stripe_account_info`

## Usage examples

```python
# List recent subscriptions (pipe-delimited)
await stripe_subscription_list(after=None, limit=10)

# Get one with field selection
await stripe_subscription_get(id="sub_1NkVJ8", fields=["id", "status", "current_period_end"])

# Create a PaymentIntent (gated — requires STRIPE_WRITE_PAYMENTS=1)
await stripe_payment_intent_create(
    payload={"amount": 2900, "currency": "usd", "customer": "cus_QABCDEF", "confirm": True}
)

# Verify a Stripe webhook
await stripe_webhook_verify(
    body=raw_request_body,
    signature_header=req.headers["Stripe-Signature"],
    secret=os.environ["STRIPE_WEBHOOK_SECRET"]
)
# → {"verified": True, "event_type": "payment_intent.succeeded", "event_id": "evt_...", "age_seconds": 2}
```

## Security posture

- **Mandatory sandbox/live env isolation.** Refuses to start without `STRIPE_ENV=test|live`.
- **Restricted Keys only.** `sk_live_*` Standard Keys rejected at startup; only `rk_*` accepted in live mode.
- **Per-resource write gates.** Read is always permitted; write requires opt-in per-resource (`STRIPE_WRITE_PAYMENTS=1`, etc.). Defaults to all off.
- **Confirm gates on destructive ops.** `cancel`, `void`, `delete` require `confirm=true` parameter.
- **Credential scrubbing.** Every error path (incl. exception cause chain) is scrubbed of `rk_*`, `sk_*`, `whsec_*`, and generic `api_key`-named fields.
- **PCI allowlist.** Card data projected to `last_4` + `brand` + `exp_*` + `fingerprint` only (DD-179). Raw `payment_method` / `card` objects never reach the response.
- **Idempotency.** Auto-attached `stallari-<date>T<run_id>-<uuid>` key on every mutation; caller override always wins.
- **Stdio transport when harness-launched** (DD-242). HTTP transport opt-in is loopback-only with mandatory bearer token.
- **No telemetry, no phone-home** (convention #19).
- **Mesh exposure denied** — blade tools never advertised on daemon `:9847/mcp` (DD-240 invariant #8).

## Sandbox vs production

| Env | `STRIPE_ENV` | Key prefix | Notes |
|---|---|---|---|
| Test | `test` | `rk_test_*` or `sk_test_*` | Stripe test mode — no real money. Use freely. |
| Live | `live` | `rk_live_*` (only) | Real-money production. `sk_live_*` rejected. |

The blade echoes `_env: test` or `_env: live` on every response so misconfig is visible immediately.

## Tool inventory

### Required (`billing-v1` + `payments-v1` conformance)

| Tool | Wraps | Contract op |
|---|---|---|
| `stripe_product_list` / `stripe_product_get` | Products | `billing-v1 products / product` |
| `stripe_price_list` | Prices | `billing-v1 prices` |
| `stripe_customer_list` / `stripe_customer_get` | Customers | `billing-v1 customers + payments-v1 customer_*` |
| `stripe_subscription_list` / `stripe_subscription_get` | Subscriptions | `billing-v1 subscriptions / subscription` |
| `stripe_transaction_list` | Invoices | `billing-v1 transactions` |
| `stripe_payment_intent_create / _capture / _cancel / _get / _list` | PaymentIntents | `payments-v1 payment_*` |
| `stripe_refund_create / _get / _list` | Refunds | `payments-v1 refund_*` |
| `stripe_webhook_verify` | (local HMAC) | `payments-v1 webhook_verify` |

### Recommended (~14 tools)

`stripe_customer_create / _update` · `stripe_subscription_update / _cancel` · `stripe_invoice_list / _get` · `stripe_adjustment_list / _create` · `stripe_discount_list / _apply` · `stripe_event_list` · `stripe_credit_balance_get` · `stripe_payment_method_list` · `stripe_dispute_list`

### Optional / target-specific

`stripe_preview_transaction` · `stripe_notification_list` · `stripe_simulate` · `stripe_payout_list / _get` · `stripe_balance_get` · `stripe_dispute_get / _evidence_submit` · `stripe_webhook_endpoint_list / _create / _delete` · `stripe_replay_notification` · `stripe_webhook_event_types` · `stripe_account_info`

### Out of scope for v1

Stripe Connect · Stripe Issuing · Stripe Treasury · Stripe Climate · Stripe Identity. Each refused upfront at the server boundary with a pointer to a future DD.

## Webhook verification

```python
from stripe_blade_mcp.tools import stripe_webhook_verify

result = await stripe_webhook_verify(
    config,
    body=raw_request_body,           # bytes or str
    signature_header=req.headers["Stripe-Signature"],
    secret=webhook_endpoint_secret,
    tolerance_seconds=300,           # default 5min replay window
)
# → {
#     "verified": True,
#     "event_type": "payment_intent.succeeded",
#     "event_id": "evt_1NkVJ8...",
#     "created_at": "2026-05-11T15:32:00+10:00",
#     "age_seconds": 4,
#     "data": {...}                  # parsed event payload
#   }
```

The HMAC scheme follows Stripe's canonical format: `t={timestamp},v1={hmac}`. The blade enforces the timestamp tolerance to defeat replay attacks.

## Rate limits + retry policy

| Mode | Global | Per endpoint |
|---|---|---|
| Live | 100 req/s | 25 req/s |
| Test | 25 req/s | 25 req/s |

Plus: PaymentIntent updates ≤ 1000/hour, Search ≤ 20 req/s.

On 429, the blade surfaces `RateLimited` verbatim with the `Stripe-Rate-Limited-Reason` header (`global-rate` / `endpoint-rate` / `global-concurrency` / `resource-specific`). **No internal retry beyond 2 attempts** — configure token-bucket throttling at the Stallari workload layer if you need it.

## Development

```bash
git clone https://github.com/groupthink-dev/stripe-blade-mcp
cd stripe-blade-mcp
uv sync --group test --group dev
make test
make lint
make typecheck
```

The build plan lives in `BUILD_PLAN.md`. Each phase is a fresh PR.

## Contributing

This is a first-party Groupthink blade. Issues + PRs welcome via [github.com/groupthink-dev/stripe-blade-mcp/issues](https://github.com/groupthink-dev/stripe-blade-mcp/issues).

For Stallari pack development conventions, see the Stallari developer docs.

## License

MIT. See [LICENSE](LICENSE).
