SpendGate
Servidor MCP/API que evalúa un gasto planificado como aprobar, consultar_humano o bloquear para que los agentes no quemen dinero por error. Gratis para probar, luego USDC vía x402.
Servidor MCP alojado
npx add-mcp 'https://spendgate.toolweather.workers.dev/mcp'Se instala en Claude Code, Codex, Cursor y más
Documentación
SpendGate
SpendGate ayuda a un agente autónomo a decidir si aprobar un gasto planificado antes de que pague.
Devuelve una puntuación de 0 a 100, un veredicto de approve, ask_human o block, razones breves, un recommended_action y checked_at. La puntuación es determinista. Reformular el propósito no servirá para obtener un mejor veredicto.
SpendGate no envía el pago al comercio y no guarda una clave de wallet. Las tarifas por exceso, cuando están configuradas, son pagos x402 USDC en Base a la dirección PAY_TO que establezcas.
Cuándo debe usarlo un agente
Llama a score_spend o POST /v1/score antes de que una herramienta transfiera criptomonedas, compre una tarjeta de regalo, inicie una suscripción o pague a una API o comercio.
- Pasa el monto en USD, la categoría, el comercio o la URL del endpoint, y cualquier presupuesto diario o mensual que aún tengas. Si el monto supera un presupuesto que proporcionas, el veredicto es
block. approvesignifica pagar.ask_humansignifica detenerse y preguntar a una persona.blocksignifica no pagar.- Los montos estrictamente por debajo del umbral micro gratuito (por defecto $1) devuelven
approve, a menos que superen un presupuesto que hayas pasado. - Omítelo para lecturas puras que no gastan. Si verificas una sonda de salud, usa la categoría
status_check. - Mantén tu propio límite máximo. SpendGate es una segunda revisión, no la única cerradura.
- No reintentes con un propósito más suave para cambiar la puntuación. v1 no almacena el cuerpo, y el cálculo no depende de la prosa.
Las alertas de gasto gratuitas no están en v1.
Puntuación v1
Comienza en 100 y luego resta. Versión de política 1. Constantes completas: GET /v1/policy.
| Entrada | Efecto |
|---|---|
| Monto | min(75, round(25 * log10(1 + usd))). Un gasto mayor reduce la puntuación. |
| Comercio | Host conocido o pista known_merchant: 0. Desconocido: 18. Faltante: 24. No-https, IP o URL local: +12. PayCheck suspicious: 30. PayCheck block: 42, veredicto block, puntuación máxima 24. |
| Categoría | Alta 30: crypto_transfer, irreversible, gift_card. Media 12: api_call, saas, infra. Baja 0: status_check. Cualquier otra: 18. |
| Moneda | USD y USDC: 0. USDT, DAI, USDP, PYUSD: 6. Otras: 12. Por defecto USDC. |
| Pistas de riesgo | Las pistas que aumentan suman 6 cada una, tope 18. Las pistas que disminuyen restan 6 cada una, tope 12. Las pistas son autoinformadas. |
Umbrales después de penalizaciones: 70 aprobar, 40 preguntar a un humano, por debajo bloquear.
Anulaciones estrictas, aplicadas después de la puntuación bruta:
- Monto mayor que
daily_budget_remainingomonthly_budget_remainingfuerzablocky limita la puntuación publicada a 24. Los presupuestos omitidos no se verifican. Igual al presupuesto restante está permitido. - De lo contrario, un monto estrictamente por debajo del umbral micro fuerza
approvey eleva la puntuación publicada a al menos 80.
agent_id se repite y no se puntúa.
PayCheck
Cuando el comercio es un dominio, una URL http(s) o una wallet 0x, y no es ya un host https público en la lista local de hosts conocidos, SpendGate llama a PayCheck POST /v1/check con { "target": "..." }. El Worker desplegado usa el enlace de servicio PAYCHECK (service = "paycheck"). Una búsqueda *.workers.dev pública desde otro worker en la misma cuenta falla, por lo que esa URL es solo el respaldo para pruebas y para un proceso sin enlace.
| Veredicto PayCheck | SpendGate |
|---|---|
trusted | Comercio conocido, igual que la lista de permitidos o known_merchant. |
unknown | Manejo actual de comercio desconocido. No se confía automáticamente. |
suspicious | Penalización de comercio 30 (más 12 si la URL es insegura). No conocido. |
block | Penalización de comercio 42 (más 12 si es insegura). Veredicto block, puntuación publicada máxima 24, incluso por debajo del umbral micro de $1. |
openai.com y los otros KNOWN_HOST_SUFFIXES permanecen conocidos sin llamada de red, por lo que una interrupción de PayCheck no los cambia. Cualquier otro fallo de PayCheck (tiempo de espera, no-OK, JSON malo) vuelve a las reglas locales de comercio y score_spend aún devuelve una puntuación. detail.paycheck_error es timeout, http_status:<code>, parse o network en ese respaldo, y el Worker registra paycheck lookup failed: …. Una IP cruda o URL http mantiene la penalización insegura en ese respaldo.
La búsqueda es una llamada interna de SpendGate, no un segundo cargo al llamante. No reenvía la clave sg_live_ del llamante ni x-tw-bundle-client. Autenticación, en orden: PAYCHECK_API_KEY opcional; si no, BUNDLE_PROXY_SECRET con el cliente de paquete propio de SpendGate 7370656e6467617465 (hex para spendgate, 1,000 verificaciones PayCheck por día UTC); si no, el nivel gratuito anónimo de PayCheck (100 por día UTC). No se requiere ningún secreto nuevo. PAYCHECK_URL por defecto es https://paycheck.toolweather.workers.dev.
Nivel gratuito y x402
| Nivel | Tope | Cómo |
|---|---|---|
| Anónimo | 100 puntuaciones / día UTC | Sin clave |
| Clave gratuita | 1,000 puntuaciones / día UTC | POST /v1/keys |
| Clave Toolweather (cliente hex) | 1,000 puntuaciones / día UTC | x-tw-bundle-secret coincidente más un x-tw-bundle-client hex |
| Llamante anónimo Toolweather | 100 puntuaciones / día UTC | Secreto coincidente más un id de cliente anon:. Mismo tope que anónimo. get_quota aún informa sujeto y nivel bundle. |
| Exceso | $0.001 USDC por puntuación | x402 en Base después del tope |
El Worker de Toolweather hace de proxy de score_spend, get_quota y spendgate_get_api_key a este servidor. Cuando envía x-tw-bundle-secret igual al BUNDLE_PROXY_SECRET de este Worker, la cuota se carga a x-tw-bundle-client. Un cliente hex (un hash de clave API de Toolweather, 16–128 caracteres hex; un valor más largo usa un prefijo estable de 128 caracteres) obtiene el tope de clave gratuita de 1,000 puntuaciones por día UTC. Un llamante anónimo de Toolweather (anon: más ese hash) obtiene las mismas 100 puntuaciones por día UTC que un llamante anónimo directo, por lo que una dirección de salida rotatoria no crea un nuevo depósito de 1,000 puntuaciones. get_quota informa sujeto y nivel bundle para ambos, con daily_limit establecido al tope que aplica. El id de cliente crudo no se almacena. Una clave sg_live_… presentada aún gana. Un secreto faltante, no establecido o incorrecto mantiene el depósito anónimo por IP y no devuelve un error solo porque los encabezados estén ausentes. La acuñación de claves a través del proxy usa el mismo id de cliente para el tope diario de acuñación.
- Red
eip155:8453(Base mainnet). - Activo USDC
0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913(nombre EIP-712USD Coin, versión2). - Facilitador https://facilitator.payai.network (
POST /verify, luegoPOST /settle). - Precio es 1000 unidades atómicas (6 decimales).
- Lecturas de cuota, acuñación de claves, documentos y descubrimiento MCP no se facturan.
- Establece
PAY_TOa la dirección 0x que debe recibir USDC. Si no está establecido, las puntuaciones por encima del tope devuelven402conpayment_not_configured. El servidor no inventará una wallet.
Un reintento pagado:
POST /v1/scoresin encabezado de pago.- Lee HTTP 402 y el encabezado
PAYMENT-REQUIREDen base64 (el cuerpo JSON lo refleja). - Firma EIP-3009
TransferWithAuthorizationpara el requisito aceptado. - Reintenta con
PAYMENT-SIGNATUREestablecido al payload de pago x402 v2 en base64. - La puntuación regresa con
PAYMENT-RESPONSEdespués del settle. El mismo nonce devuelve la puntuación original y no vuelve a hacer settle.
API
curl -s http://127.0.0.1:43123/v1/score \
-H 'content-type: application/json' \
-d '{
"amount": 12,
"currency": "USDC",
"category": "api_call",
"purpose": "embeddings for the planner",
"merchant": "https://api.openai.com/v1",
"agent_id": "agent_planner",
"daily_budget_remaining": 40,
"risk_hints": []
}'
curl -s http://127.0.0.1:43123/v1/keys \
-H 'content-type: application/json' \
-d '{"label":"planner"}'
curl -s http://127.0.0.1:43123/v1/quota \
-H "authorization: Bearer sg_live_..."
Otras rutas:
GET /healthGET /v1/policyGET /openapi.jsonGET /llms.txtyGET /llms-full.txtGET /.well-known/mcp(y/.well-known/mcp.json)GET /.well-known/x402POST /mcpHTTP streamable, protocolo2025-06-18(también acepta2025-03-26)
MCP
curl -s http://127.0.0.1:43123/mcp \
-H 'content-type: application/json' \
-H 'accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"example","version":"0"}}}'
curl -s http://127.0.0.1:43123/mcp \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"score_spend","arguments":{"amount":0.2,"category":"status_check","merchant":"https://www.cloudflare.com","agent_id":"agent_demo"}}}'
Herramientas: score_spend, get_quota, get_api_key.
Después del tope gratuito, tools/call en score_spend devuelve HTTP 402 con un error JSON-RPC (-32002) y el encabezado PAYMENT-REQUIRED. get_quota y get_api_key permanecen gratuitos.
Ejecutar localmente
Requiere Node 20+.
npm install
npx wrangler d1 migrations apply spendgate --local
npm run dev
El sitio y la API escuchan en http://127.0.0.1:43123.
Wallet receptora opcional, en .dev.vars (gitignored, copia desde .dev.vars.example):
PAY_TO=0xYourAddress
npm test
npm run typecheck
Desplegar
Plan gratuito de Cloudflare: un Worker, una base de datos D1, activos estáticos desde public/. Sin SaaS de pago y sin secretos en el repositorio.
npx wrangler d1 create spendgate
# Put the returned database_id in wrangler.toml (the committed id is a placeholder).
npx wrangler d1 migrations apply spendgate --remote
npx wrangler secret put PAY_TO
npx wrangler secret put BUNDLE_PROXY_SECRET
npx wrangler deploy
BUNDLE_PROXY_SECRET debe ser el mismo valor que en el Worker de Toolweather. Establécelo después del merge y luego vuelve a desplegar. No confirmes el valor. Hasta que ese secreto esté establecido, las llamadas proxy sin clave sg_live_… siguen compartiendo el depósito anónimo diario. El mismo secreto, cuando está establecido, también atribuye las búsquedas PayCheck de SpendGate al cliente de paquete interno de SpendGate. PAYCHECK_URL ya está en [vars]. PAYCHECK_API_KEY es opcional (npx wrangler secret put PAYCHECK_API_KEY) y no se requiere para el nivel anónimo de PayCheck.
npm run dry-run empaqueta el Worker como lo haría un despliegue, sin publicar.
Para alojar el HTML en Pages por sí solo: npx wrangler pages deploy public. La API aún necesita el Worker. Servir ambos desde el Worker es la configuración que usa wrangler.toml.
database_id en wrangler.toml es un UUID de marcador de posición. Reemplázalo antes de un despliegue remoto real. No pongas una wallet de producción en [vars].