Skip to main content
Glama
DanFrModa

Stripe MCP Server

by DanFrModa
README.md
# Stripe MCP Server

Servidor MCP (Model Context Protocol) que expone la API de Stripe a Claude.
Pensado para desplegarse en Railway en **modo solo lectura**.

👉 **Los pasos de despliegue están en [DEPLOY-RAILWAY.md](./DEPLOY-RAILWAY.md).**

---

## Qué hace

Da a Claude 17 herramientas para consultar Stripe: clientes, cobros, pagos,
suscripciones, facturas, catálogo, saldo, transacciones, payouts, eventos y
disputas — más una herramienta genérica que alcanza cualquier endpoint, otra que
resuelve cualquier objeto por su id, y un reporte agregado de ingresos.

| Herramienta | Endpoint |
|---|---|
| `stripe_whoami` | verifica la key, el modo y los permisos |
| `stripe_request` | cualquier endpoint, cualquier método |
| `stripe_get_object` | resuelve cualquier id (`cus_`, `ch_`, `sub_`...) |
| `stripe_search` | `GET /v1/{recurso}/search` |
| `stripe_list_customers` | `GET /v1/customers` |
| `stripe_list_charges` | `GET /v1/charges` |
| `stripe_list_payment_intents` | `GET /v1/payment_intents` |
| `stripe_list_subscriptions` | `GET /v1/subscriptions` |
| `stripe_list_invoices` | `GET /v1/invoices` |
| `stripe_list_products` | `GET /v1/products` |
| `stripe_list_prices` | `GET /v1/prices` |
| `stripe_get_balance` | `GET /v1/balance` |
| `stripe_list_balance_transactions` | `GET /v1/balance_transactions` |
| `stripe_list_payouts` | `GET /v1/payouts` |
| `stripe_list_events` | `GET /v1/events` |
| `stripe_list_disputes` | `GET /v1/disputes` |
| `stripe_revenue_summary` | pagina y suma bruto/comisiones/neto |

---

## Variables de entorno

| Variable | Requerida | Default | Descripción |
|---|---|---|---|
| `STRIPE_API_KEY` | sí | — | Restricted key (`rk_...`) o secret key (`sk_...`) |
| `STRIPE_MCP_TRANSPORT` | en Railway | `stdio` | `http` para servidor remoto |
| `MCP_AUTH_TOKEN` | si `http` | — | Secreto que protege el endpoint. Mínimo 32 caracteres |
| `STRIPE_READ_ONLY` | no | ver abajo | `1` bloquea toda escritura |
| `STRIPE_ALLOW_LIVE_WRITES` | no | `0` | Segunda reja: escrituras con key **live** |
| `STRIPE_API_BASE` | no | `https://api.stripe.com` | Para apuntar a un mock |
| `STRIPE_API_VERSION` | no | — | Fija el header `Stripe-Version` |
| `STRIPE_ACCOUNT` | no | — | Cuenta de Connect (`acct_...`) para todas las llamadas |
| `STRIPE_MAX_CHARS` | no | `20000` | Truncado de respuestas |
| `STRIPE_TIMEOUT` | no | `45` | Timeout en segundos |
| `PORT` | no | `8000` | Railway lo inyecta solo |

---

## Las tres rejas de escritura

Stripe mueve dinero real: un `POST` equivocado reembolsa a un cliente, cancela
una suscripción o borra un registro para siempre. Por eso hay tres candados
independientes, y una escritura tiene que pasar por los tres.

**1. `STRIPE_READ_ONLY` — el default depende del transporte**

- **`stdio` (local):** escrituras **permitidas** por default. Un solo usuario de
  confianza en su propia máquina.
- **`http` (remoto):** escrituras **bloqueadas** por default. Hay que poner
  `STRIPE_READ_ONLY=0` a propósito para habilitarlas.

Olvidar la variable en un despliegue público lo deja en solo lectura, no abierto.

**2. `STRIPE_ALLOW_LIVE_WRITES` — la reja de modo live**

Aunque apagues el read-only, una key **live** sigue rechazando escrituras hasta
que pongas `STRIPE_ALLOW_LIVE_WRITES=1`. Las keys de test (`sk_test_`, `rk_test_`)
no pasan por esta reja: experimentar contra datos de prueba no debe requerir
ceremonia.

**3. `confirm=true` — la reja por endpoint**

Los endpoints destructivos (`/v1/refunds`, `/v1/payouts`, `/v1/transfers`,
cancelaciones de suscripción, borrado de clientes, cierre de disputas) exigen
`confirm=true` en la llamada, además de todo lo anterior. Una herramienta
disparada por error no llega ahí sola.

Cada rechazo dice **cuál** de las tres rejas lo detuvo.

---

## Autenticación del endpoint

El protocolo MCP no trae autenticación propia. En modo `http`, este servidor
exige `Authorization: Bearer <MCP_AUTH_TOKEN>` en cada request, o el secreto
embebido en la ruta (`/s/<secreto>/mcp`) para los connectors de Claude.
`/healthz` es la única ruta pública.

El servidor **se niega a arrancar** si `MCP_AUTH_TOKEN` falta o tiene menos de
32 caracteres. Es a propósito: evita publicar un gateway abierto a tu cuenta de
Stripe por descuido.

---

## Correr en local

```bash
pip install -r requirements.txt

# stdio (para Claude Desktop)
STRIPE_API_KEY=rk_test_xxx python stripe_mcp.py

# http (como en Railway)
STRIPE_MCP_TRANSPORT=http \
STRIPE_API_KEY=rk_test_xxx \
MCP_AUTH_TOKEN=$(python3 -c "import secrets;print(secrets.token_urlsafe(48))") \
PORT=8000 python stripe_mcp.py
```

Al arrancar imprime en qué modo quedó:

```
[stripe-mcp] streamable-http on 0.0.0.0:8000  mode=test  read_only=True  allow_live_writes=False  writes_enabled=False
```

---

## Notas sobre la API de Stripe

Cosas que el servidor ya resuelve por ti, y que conviene saber al leer las
respuestas:

- **Stripe no acepta JSON en el body.** Todo va `application/x-www-form-urlencoded`
  con notación de corchetes: `metadata[order]=6735`, `expand[]=customer`,
  `items[0][price]=price_1`. El servidor traduce dicts y listas anidadas solo.
- **El dinero es un entero en la unidad mínima.** `2500` con `usd` es $25.00.
  Y hay monedas de cero decimales (JPY, KRW, CLP…) donde el entero *es* el monto
  — dividir entre 100 ahí sería un error. Las salidas formateadas lo respetan.
- **Test y live son universos separados.** Un id creado en test devuelve 404 con
  una key live y viceversa. Es la causa más común de un 404 con un id correcto.
- **La paginación es por cursor, no por página.** Se toma el último id y se pasa
  como `starting_after`. Cuando hay más, la respuesta incluye `next_page_hint`
  con el valor exacto.
- **No hay `PATCH` ni `PUT`.** Stripe usa `POST` tanto para crear como para
  actualizar. Solo `GET`, `POST` y `DELETE`.
- **Las listas no filtran por `status`** en charges ni payment_intents. Hay que
  filtrar sobre los registros devueltos o usar `stripe_search`.
- **`stripe_list_subscriptions` omite las canceladas por default.** Para
  preguntas de churn hay que pasar `status='all'` o la respuesta sale mal en
  silencio.
- **El índice de búsqueda tarda hasta un minuto** en ver un objeto recién creado.
  Para objetos nuevos, buscarlos por id.
- **Los eventos se guardan solo 30 días.** Más atrás hay que ir a los objetos.
- **No hay endpoint de agregación.** Por eso `stripe_revenue_summary` pagina y
  suma del lado del servidor MCP, y avisa con `complete=false` cuando el tope de
  páginas cortó el conteo — esos totales son parciales y no deben reportarse
  como finales.
- Cada `POST` sale con un `Idempotency-Key`, así que un reintento no cobra dos
  veces. Stripe los recuerda 24 horas.

---

## Seguridad

- Los secretos van en variables de entorno, nunca en el código. El `.gitignore`
  bloquea archivos `.env`.
- **Usa una restricted key (`rk_...`), no la secret key.** Se crean en el
  dashboard cuantas veces quieras y se les dan permisos por recurso. Para este
  despliegue: todo en *Read*, nada en *Write*. Una `sk_live_` da acceso total a
  la cuenta, incluido mover dinero.
- Un solo token compartido significa cero trazabilidad por persona. Todo lo que
  haga cualquiera queda registrado como la misma key.
- No hay rate limiting propio. Quien tenga el `MCP_AUTH_TOKEN` puede pegarle a
  Stripe hasta toparse con el límite de Stripe (~100 req/s de lectura en live).
- Para cortar el acceso de golpe: borra la restricted key en
  https://dashboard.stripe.com/apikeys — el servidor queda inútil al instante.