{
  "markdown": "# MCP SAT 69 / 69-B · WATR\n\nServidor **MCP (Model Context Protocol)** en **Python / FastMCP** para consultar las listas públicas del SAT:\n\n- **Artículo 69-B del CFF (EFOS)** — operaciones simuladas: *Presunto, Desvirtuado, Definitivo, Sentencia Favorable*.\n- **Artículo 69 del CFF** — situación fiscal firme: *firmes, exigibles, no localizados, cancelados, condonados*.\n\nMisma arquitectura que el MCP de movilizaciones de la SSC-CDMX: FastMCP con transporte **stdio + Streamable HTTP**, **OAuth 2.1 (WorkOS AuthKit)** con fallback a **bearer estático**, persistencia en **Turso (libSQL)**, **proxy de descarga opcional**, y despliegue en **Render** con cron externo (GitHub Actions). Sin OCR: los CSV del SAT ya vienen estructurados.\n\n## Tools\n\n| Tool | Qué hace |\n|------|----------|\n| `verificar_rfc` | Verifica un RFC → **veredicto de riesgo** (`CRITICO`…`LIMPIO`). |\n| `verificar_lote` | Valida hasta 500 RFCs; devuelve sólo hallazgos por severidad. |\n| `buscar_nombre` | Búsqueda por nombre/razón social (FTS5, insensible a acentos). |\n| `estado_datos` | Vigencia declarada por el SAT, conteos y última importación. |\n| `actualizar_datos` | Descarga + sincroniza los listados (idempotente por hash). |\n\n**Riesgo:** `CRITICO` (EFOS definitivo) · `ALTO` (EFOS presunto) · `MEDIO` (69 firme/exigible/no localizado) · `BAJO` (desvirtuado/sentencia favorable) · `INFORMATIVO` (69 cancelado/condonado) · `LIMPIO`.\n\n> Los resultados reflejan la última importación de los archivos públicos del SAT. No constituyen asesoría fiscal ni legal.\n\n## Arquitectura\n\n```\nCSV del SAT (Latin-1)                     ┌────────── FastMCP ──────────┐\n   │  fetcher (httpx + proxy opcional)    │ verificar_rfc / _lote        │\n   ▼                                      │ buscar_nombre / estado_datos │\n pipeline (parse 69 / 69b)  ──►  SQLite ──┤ actualizar_datos             │\n   │   (FTS5 unicode61, triggers)  ▲  │   └──────────┬──────────────────┘\n   ▼                               │  │   stdio (server.py) + HTTP (web.py)\n Turso (libSQL, durable) ◄── push  │  └► pull al arranque      │\n                                   └─────────────────── /health /refresh /reload\n```\n\n- **`server.py`** — FastMCP (stdio) + tools + AuthKit.\n- **`web.py`** — Starlette/uvicorn (HTTP): `/health` (abierto), `/refresh` y `/reload` (bearer M2M), `/mcp` (OAuth o bearer).\n- **`pipeline.py`** — descarga → SHA-256 (omite si no cambió) → parse Latin-1 → reemplazo en SQLite → push a Turso.\n- **`database.py`** — SQLite + FTS5 con triggers; RFC por índice B-tree (camino caliente).\n- **`turso.py`** — sync durable Turso ↔ local.\n- **`risk.py`** — normalización de RFC + árbol de veredicto (69-B manda sobre 69).\n\n## Instalación local\n\n```bash\ncd sat69-mcp\npython -m venv .venv && source .venv/bin/activate\npip install -e \".[dev]\"\n\n# Primera ingesta (descarga ~22 MB; el 69 son ~½ millón de filas)\npython -c \"from sat69 import database as db, config; db.init_db(config.settings.db_path)\"\npython -c \"from sat69.pipeline import process_import; print(process_import())\"\n\n# Probar\npytest -q\n```\n\n### Conectar en Claude Desktop / Cowork (stdio)\n\n```json\n{\n  \"mcpServers\": {\n    \"sat69\": { \"command\": \"sat69-mcp\" }\n  }\n}\n```\n\n(o `\"command\": \"python\", \"args\": [\"-m\", \"sat69\"]` con el venv activo.)\n\n## Despliegue en Render\n\n`render.yaml` provisiona el servicio web con runtime Python nativo (sin Docker):\n\n1. Sube el repo a GitHub y crea un Blueprint en Render apuntando a `render.yaml`.\n2. Variables (marcadas `sync:false`): `MCP_API_KEY` (bearer), opcional `AUTHKIT_DOMAIN`+`BASE_URL` (OAuth), `TURSO_DATABASE_URL`+`TURSO_AUTH_TOKEN`.\n3. Endpoint MCP: `POST https://<servicio>.onrender.com/mcp`.\n\n### Auth (dos modos, igual que movilizaciones)\n\n- **Bearer estático** (`MCP_API_KEY`): simple, protege `/mcp`, `/refresh`, `/reload`.\n- **OAuth 2.1 (WorkOS AuthKit)**: define `AUTHKIT_DOMAIN` + `BASE_URL` y `/mcp` pasa a OAuth con Dynamic Client Registration; el bearer sigue protegiendo los endpoints M2M.\n\n### Refresco automático\n\n`.github/workflows/refresh.yml` hace `POST /refresh` diario (11:30 UTC ≈ 05:30 CDMX). Secrets del repo: `RENDER_BASE_URL`, `MCP_API_KEY`. Manual: `workflow_dispatch` (con `force`).\n\n## Persistencia (Turso)\n\nSQLite local (efímero en Render, `/tmp`) atiende las consultas; Turso es el almacén durable que sobrevive redeploys. Al arrancar se hace pull de Turso; tras cada `actualizar_datos`/`/refresh` se hace push. Sin `TURSO_*`, corre en modo local puro.\n\n## Proxy de descarga (opcional)\n\n`FETCH_PROXY` o `DATAIMPULSE_*` enrutan **sólo** la descarga de los CSV. En pruebas el SAT **no** bloqueó IPs de datacenter (descargas 200 directas), así que normalmente no hace falta; se conserva por paridad y resiliencia.\n\n## Fuentes de datos (SAT · Datos Abiertos)\n\n- 69-B: `Listado_Completo_69-B.csv` (~14 k registros, 20 columnas, header en línea 3).\n- 69: `Firmes.csv`, `Cancelados.csv`, `NoLocalizados.csv`, `Exigibles.csv`, `Sentencias.csv`, `Condonados.csv` (~½ millón de registros, 6 columnas). URLs en `config.py`.\n",
  "bytes": 5003,
  "sha": "594889455b79e4d90e379f7330a55ef0c2edecc035d26eb91b0b344ab150f476",
  "repo_slug": "edbror/sat69-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_edbror_sat69_mcp_734bb825/readme"
}