savia-mcp
MCP server that exposes SAVIA, a legacy travel-industry ERP with no public API, as tools an AI assistant can use. It uses Playwright to operate the browser, enabling read-only extraction of information like screenshots, text, and tables.
README
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 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_escribirysavia_presionar_enterson 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)
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:
{
"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
savia_loginsavia_capturapara ver dónde quedó la sesión tras el loginsavia_elementos_clicablespara ver qué se puede tocar en el menúsavia_clickcon el texto del menú deseado, despuéssavia_capturade nuevo para confirmarsavia_extraer_tablaosavia_texto_paginapara 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_navegadorahora 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_duplicadaslo 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.
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
Neon Database
MCP server for interacting with Neon Management API and databases
E2B
Using MCP to run code via e2b.
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.