savia-mcp
README.md
# savia-mcp
MCP Server en Python para integrar la app web **SAVIA** (`savia4.com.ar`) con
Claude Desktop, usando Playwright para operar el navegador.
> **In English** — An [MCP](https://modelcontextprotocol.io) server that exposes a
> legacy travel-industry ERP as tools an AI assistant can use. The ERP has no
> public API: network analysis showed all traffic goes through the framework's
> internal AJAX protocol with server-regenerated widget UUIDs, so a browser is
> the only viable integration path. The interesting parts are documented below:
> why HTTP was ruled out, how interactive elements are detected in a DOM with no
> semantic elements, and how server-side sessions are handled. Written as
> interoperability tooling for an account I own — read-only by design.
> ⚠️ **SOLO LECTURA.** Este MCP es exclusivamente para extraer información
> (capturas, texto, tablas). Aunque `savia_click`, `savia_escribir` y
> `savia_presionar_enter` son genéricos y técnicamente podrían usarse para
> crear o modificar expedientes, órdenes, comprobantes, etc., **eso está
> prohibido** por decisión explícita del dueño de la cuenta. Cualquier tarea
> que requiera crear/modificar algo en SAVIA se hace manualmente, no con
> este MCP.
## Qué es SAVIA (importante para entender el diseño)
SAVIA corre sobre **ZK Framework 6.5** (Java, AJAX, ~2012). Esto tiene dos
consecuencias:
- Los IDs del DOM se regeneran en cada carga de página y **no son estables**
entre sesiones (ej. `rH8Qk0`). Por eso el login se ubica por *placeholder*
del campo (fijo en el servidor), no por ID ni CSS frágil.
- Las pantallas internas (post-login: reservas, facturación, etc.) **no
vienen mapeadas de antemano** en este proyecto — no había forma de
inspeccionarlas sin credenciales válidas. Por eso los tools no son
scrapers fijos por pantalla, sino un set de herramientas **genéricas**
(captura de pantalla, texto visible, listar elementos clicables, click por
texto, extracción de grillas ZK) para que Claude pueda explorar y operar
cualquier pantalla de SAVIA de forma interactiva, viendo lo mismo que ve el
usuario.
El flujo de login (usuario + ID de empresa → luego contraseña, en dos pasos)
**está probado en vivo y funciona** contra una cuenta propia. El popup de
error de SAVIA usa la clase ZK
`.z-window-modal` (confirmado forzando un error real) — es genérico, sirve
para detectar cualquier alerta en cualquier pantalla, no solo el login.
El dashboard post-login (`index.zul`) ya se conoce: 8 menús superiores
(Página Principal, Perfil Empresarial, Tarifarios y Cupos, Proveedores y
Egresos, Clientes e Ingresos, Caja y Otros Medios, Contabilidad y Reportes,
Soporte y Mesa de Ayuda) y accesos directos como Cotizaciones, Expedientes,
Clientes, Órdenes de Ingreso/Egreso, Cronogramas de Salidas, Cajas,
Comisiones y Comprobantes. Lo que falta mapear es el detalle de cada
pantalla puntual (columnas de cada listado, formularios), por eso los tools
siguen siendo genéricos hasta que se elija una pantalla concreta para
automatizar.
**¿Por qué no hablarle directo por HTTP en vez de un navegador?** Se
inspeccionó el tráfico real (Network) durante login y navegación: todo pasa
por `POST /savia/zkau` con el protocolo interno de ZK ("AU requests"), cuyo
payload referencia UUIDs de widgets que el servidor regenera en cada sesión
(`uuid_0=c5yPr0`, etc.), atados al árbol de componentes vivo en el servidor.
No hay ningún endpoint REST/JSON paralelo (se confirmó navegando por varias
secciones: 0 requests XHR/fetch fuera de `/zkau`, un solo dominio). Replicar
esto sin navegador implicaría reimplementar buena parte del protocolo
cliente de ZK a mano — fragil y se rompe con cualquier update de SAVIA. Por
eso el diseño usa Playwright: deja que el navegador real haga esa
sincronización.
## Estructura
```
savia-mcp/
├── savia_mcp/
│ ├── __init__.py
│ ├── browser.py # Sesión de Playwright (login, screenshot, ciclo de vida)
│ ├── extractors.py # Scraping genérico: texto, clicables, grillas ZK
│ └── server.py # Servidor MCP (stdio) y definición de tools
├── .env.example
├── .env # (git-ignored) tus credenciales reales
├── pyproject.toml
├── setup.ps1
└── README.md
```
## Instalación (Windows)
```powershell
cd "savia-mcp"
.\setup.ps1
```
Esto crea un entorno virtual `.venv`, instala dependencias, descarga
Chromium para Playwright y genera `.env` a partir de `.env.example`.
Después completá tus credenciales en `.env`:
```
SAVIA_URL=https://www.savia4.com.ar:8443/savia
SAVIA_USUARIO=tu_usuario
SAVIA_EMPRESA=tu_id_de_empresa
SAVIA_PASSWORD=tu_contraseña
SAVIA_HEADLESS=false
```
`SAVIA_HEADLESS=false` deja la ventana de Chrome visible — recomendado
mientras se explora la app por primera vez con Claude.
## Configurar Claude Desktop
`setup.ps1` imprime el snippet listo para copiar. Es de esta forma, en
`%APPDATA%\Claude\claude_desktop_config.json`:
```json
{
"mcpServers": {
"savia": {
"command": "C:\\ruta\\a\\savia-mcp\\.venv\\Scripts\\python.exe",
"args": ["C:\\ruta\\a\\savia-mcp\\savia_mcp\\server.py"]
}
}
}
```
Reiniciá Claude Desktop después de guardar.
## Tools disponibles
| Tool | Qué hace |
|---|---|
| `savia_login` | Login en SAVIA (usa `.env` salvo que se pasen overrides). Maneja los dos flujos reales: formulario completo (usuario+empresa→password) y el rápido "Hola <nombre>..." que aparece si el navegador ya tiene una sesión previa en el mismo proceso |
| `savia_cerrar_sesiones_duplicadas` | Si `savia_login` falla por límite de sesiones simultáneas de la empresa, cierra las sesiones listadas y completa el login. Desloguea a cualquiera que esté usando ese usuario en otro lado — confirmar antes con esa persona |
| `savia_logout` | Cierra la sesión en el **servidor** (libera el cupo de sesiones de la empresa), no solo el navegador local |
| `savia_cerrar_navegador` | Hace logout real y además apaga Chromium, liberando los recursos de Playwright |
| `savia_estado` | Sesión activa + URL actual |
| `savia_captura` | Screenshot de la pantalla actual (se ve inline en el chat) |
| `savia_texto_pagina` | Texto visible de la pantalla (o de un selector) |
| `savia_elementos_clicables` | Lista elementos clickeables visibles con su texto (SAVIA no usa `<a>`/`<button>`: detecta por `cursor:pointer`, la única señal confiable en ZK) |
| `savia_click` | Click por texto visible o por selector CSS |
| `savia_escribir` | Escribe en un campo (por placeholder o selector) |
| `savia_presionar_enter` | Enter en un campo — muchas acciones de ZK usan `onOK` (Enter) en vez de botón |
| `savia_leer_modal` | Chequea si hay un popup/alerta de SAVIA visible y devuelve su texto |
| `savia_extraer_tabla` | Extrae la primera grilla/listado ZK visible como filas/columnas |
| `savia_navegar` | Navega a una ruta relativa dentro de `/savia/` (ej. `index.zul`) |
## Flujo de uso típico
1. `savia_login`
2. `savia_captura` para ver dónde quedó la sesión tras el login
3. `savia_elementos_clicables` para ver qué se puede tocar en el menú
4. `savia_click` con el texto del menú deseado, después `savia_captura`
de nuevo para confirmar
5. `savia_extraer_tabla` o `savia_texto_pagina` para sacar los datos de esa
pantalla
Con ese ciclo (captura → clicables → click → captura) se puede mapear
cualquier sección de SAVIA sin haber tenido acceso previo, y una vez que se
identifica una pantalla puntual que se use seguido (ej. reservas del día),
se le puede agregar una función dedicada a `extractors.py`.
## Notas
- La sesión de SAVIA expira tras **60 minutos de inactividad** (mensaje
propio de la app). Si un tool falla con error de sesión, volvé a llamar
`savia_login`.
- **SAVIA limita cuántas sesiones simultáneas puede tener la empresa a la vez**
(confirmado en vivo: 8). Cerrar el navegador sin hacer logout deja la
sesión "colgada" del lado del servidor y come ese cupo — por eso
`savia_logout`/`savia_cerrar_navegador` ahora clickean el botón real de
salir en vez de solo cerrar la pestaña. Si igual se llega al límite,
`savia_cerrar_sesiones_duplicadas` lo resuelve (pide confirmación primero
porque puede desloguear a alguien más).
- Las credenciales nunca se hardcodean: viven solo en `.env` (git-ignored).
- El navegador persiste entre llamadas a tools dentro del mismo proceso del
servidor MCP — no se relanza en cada tool call.
- No hay API REST/JSON alternativa (confirmado inspeccionando el tráfico de
red real): todo pasa por el protocolo interno de ZK. Playwright es la única
vía razonable para automatizar SAVIA.
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues