pyATS
Interactúa con dispositivos de red utilizando las bibliotecas pyATS y Genie de Cisco para automatización basada en modelos.
Documentación
Servidor MCP pyATS
Cisco pyATS y Genie ya saben cómo hablar con una red — parseando comandos show, aplicando configuración, aprendiendo el estado de las funciones, ejecutando pruebas declarativas. Lo que no tenían era una forma para que un agente de IA pudiera manejar cualquiera de eso directamente. Este servidor cierra esa brecha: envuelve pyATS/Genie como un conjunto de herramientas MCP estructuradas y protegidas que un agente como Claude puede invocar contra un testbed real, a través del transporte Streamable HTTP actual del Model Context Protocol.
Apúntale un agente y podrá buscar un dispositivo, ejecutar y parsear un comando show, aplicar configuración con un punto de rollback, aprender y comparar el estado de una función antes y después de un cambio, distribuir un comando a través de una flota — un pool de hilos o un proceso por dispositivo — ejecutar una prueba declarativa Blitz o Robot Framework, o llamar a la API REST/RESTCONF de un dispositivo directamente. Cada ruta riesgosa está protegida antes de llegar a un dispositivo, y cada llamada aterriza en un registro de auditoría en memoria que el agente puede revisar a mitad de sesión.
De un vistazo
- Transporte — Streamable HTTP (
mcp>=2.0.0), con estado o sin estado, elegido con una variable de entorno. STDIO ya no existe. - 26 herramientas en descubrimiento, comandos show, configuración, Genie learn/diff, Genie Clean, pruebas declarativas (Blitz, Robot Framework, AEtest), REST/RESTCONF genérico y Cisco XPresso.
- Dos formas de distribuir un comando a través de muchos dispositivos — un pool de hilos compartido para uso diario, o un proceso de SO por dispositivo (
pyats.async_.pcall) cuando quieres aislamiento real a escala. - Protecciones, no sistemas de honor — los comandos peligrosos se bloquean antes de llegar a un dispositivo, Genie Clean nunca puede ejecutar una etapa que reinicie o reimage un dispositivo, y las acciones destructivas requieren una frase de confirmación exacta.
- Nada codificado — cada credencial y detalle de dispositivo vive en
.env, extraído atestbed.yamlen tiempo de ejecución mediante sustitución de%ENV{}.
Requisitos previos
- Python 3.10+
- Un
testbed.yamlde pyATS apuntando a dispositivos de red reales o virtuales — un laboratorio físico, Cisco Modeling Labs / VIRL / GNS3, o cualquier otra cosa que Unicon pueda alcanzar por SSH/Telnet. pyATS MCP no simula una red; maneja una real. - Un cliente compatible con MCP para comunicarse — ver Conecta tu agente abajo.
Inicio rápido
# 1. Clone and install
git clone https://github.com/automateyournetwork/pyATS_MCP
cd pyATS_MCP
pip install -r requirements.txt
# 2. Configure your environment
cp .env.example .env
# Edit .env — see Configuration below
# 3. Run — starts a Streamable HTTP server on 0.0.0.0:8080 by default
python3 pyats_mcp_server.py
El endpoint MCP es entonces accesible en http://<host>:<port>/mcp.
Configuración
Todos los detalles de dispositivos y credenciales viven en un archivo .env — nada está codificado en el repositorio.
1. Copia la plantilla
cp .env.example .env
2. Establece las variables del servidor
PYATS_TESTBED_PATH=/absolute/path/to/your/testbed.yaml
PYATS_MCP_ARTIFACTS_DIR= # default: ~/.pyats-mcp/artifacts
PYATS_MCP_KEEP_ARTIFACTS=1 # 1 = keep, 0 = delete after each run
PYATS_MCP_TESTBED_CACHE_TTL=30 # seconds before testbed reloads from disk
PYATS_MCP_CONN_CACHE_TTL=0 # seconds to keep connections alive (0 = off)
PYATS_MCP_OP_LOG_MAX=500 # max entries in the in-memory operation log
# Transport (Streamable HTTP only — STDIO is not supported)
PYATS_MCP_TRANSPORT_MODE=stateful # stateful (default) | stateless
PYATS_MCP_HTTP_HOST=0.0.0.0
PYATS_MCP_HTTP_PORT=8080
# Optional — only needed for pyats_xpresso_request
XPRESSO_URL=
XPRESSO_API_TOKEN=
XPRESSO_GROUP=
PYATS_MCP_TRANSPORT_MODE=stateless establece stateless_http=True en el transporte Streamable HTTP, por lo que no se retiene estado de sesión en el servidor entre solicitudes de clientes que aún negocian el protocolo más antiguo basado en handshake. Los clientes que hablan el protocolo MCP actual (2026-07-28, SEP-2575) no requieren handshake por defecto independientemente de esta configuración — eso proviene del SDK mcp>=2.0.0 en sí, no de nada configurado aquí.
3. Añade un bloque para cada dispositivo
Cada dispositivo en tu testbed.yaml usa sustitución de %ENV{VAR}, por lo que las credenciales y detalles de conexión se leen de .env en tiempo de ejecución.
Usa la convención de nombres {DEVICENAME}_{FIELD}:
# Supported os values: iosxe | iosxr | nxos | ios | eos | junos | panos | linux | windows
# Set os=generic and platform="" to let Unicon autodetect on first connect.
CORE1_IP=10.1.1.1
CORE1_PORT=22
CORE1_OS=iosxe
CORE1_PLATFORM=cat9k
CORE1_USERNAME=admin
CORE1_PASSWORD=s3cr3t
CORE1_ENABLE_PASSWORD=s3cr3t
FW1_IP=10.1.1.2
FW1_PORT=22
FW1_OS=panos
FW1_PLATFORM=
FW1_USERNAME=admin
FW1_PASSWORD=s3cr3t
# (no enable password for Palo Alto)
LINUX1_IP=10.1.1.3
LINUX1_PORT=22
LINUX1_OS=linux
LINUX1_PLATFORM=ubuntu
LINUX1_USERNAME=admin
LINUX1_PASSWORD=s3cr3t
# (no enable password for Linux)
Si un grupo de dispositivos comparte credenciales, define variables a nivel de grupo y refiérelas entre dispositivos:
SITE_A_USERNAME=netops
SITE_A_PASSWORD=s3cr3t
SITE_A_ENABLE_PASSWORD=s3cr3t
4. Referencia las variables en testbed.yaml
devices:
CORE1:
alias: "Core Switch 1"
type: "switch"
os: "%ENV{CORE1_OS}"
platform: "%ENV{CORE1_PLATFORM}"
credentials:
default:
username: "%ENV{CORE1_USERNAME}"
password: "%ENV{CORE1_PASSWORD}"
enable:
password: "%ENV{CORE1_ENABLE_PASSWORD}"
connections:
cli:
protocol: ssh
ip: "%ENV{CORE1_IP}"
port: "%ENV{CORE1_PORT}"
arguments:
connection_timeout: 360
Para dispositivos con SO desconocido, establece
os: "%ENV{DEVICE_OS}"conDEVICE_OS=genericen.envy opcionalmente añadelearn_os: truebajoarguments:— Unicon detectará y almacenará en caché el SO después de la primera conexión.
Docker
Construir
docker build -t pyats-mcp-server .
Ejecutar (pasa .env directamente)
docker run -p 8080:8080 --rm \
--env-file /absolute/path/to/.env \
-v /absolute/path/to/testbed.yaml:/app/testbed.yaml \
pyats-mcp-server
De cualquier manera, el servidor es un proceso de larga duración que inicias una vez y apuntas clientes hacia él — no es algo que un agente genere por sesión. Ver abajo exactamente cómo cada cliente se conecta a él.
Conecta tu agente
El servidor expone una cosa: un endpoint MCP en http://<host>:<port>/mcp (Streamable HTTP). Cada cliente abajo solo necesita esa URL — sin command/args, sin proceso local que el cliente gestione.
Claude Code
claude mcp add --transport http pyats http://localhost:8080/mcp
# Behind auth (e.g. a reverse proxy in front of the server)
claude mcp add --transport http pyats http://localhost:8080/mcp \
--header "Authorization: Bearer your-token"
O colócalo directamente en .mcp.json (a nivel de proyecto, confirmado en el repositorio) o ~/.claude.json (a nivel de usuario):
{
"mcpServers": {
"pyats": { "type": "http", "url": "http://localhost:8080/mcp" }
}
}
VS Code (GitHub Copilot Chat)
Añade un .vscode/mcp.json en el espacio de trabajo (o ejecuta MCP: Add Server desde la Paleta de Comandos):
{
"servers": {
"pyats": { "type": "http", "url": "http://localhost:8080/mcp" }
}
}
OpenAI Codex CLI
codex mcp add pyats --url http://localhost:8080/mcp
O en ~/.codex/config.toml:
[mcp_servers.pyats]
url = "http://localhost:8080/mcp"
Claude Desktop
El claude_desktop_config.json de Claude Desktop es solo stdio — poner un campo url en él no funciona (es un problema conocido, no una ruta compatible). Los servidores remotos/HTTP se añaden en su lugar como un Custom Connector en Configuración → Conectores, y Desktop se conecta a ellos desde la nube de Anthropic, no desde tu máquina local — por lo que necesita una URL HTTPS real y públicamente accesible, no localhost.
Para apuntar Desktop a un servidor que se ejecuta en tu propia máquina de todos modos, conéctalo a través de mcp-remote como un proxy stdio local:
{
"mcpServers": {
"pyats": {
"command": "npx",
"args": ["-y", "mcp-remote", "http://localhost:8080/mcp", "--transport", "http-only"]
}
}
}
Python puro (LangGraph, agentes personalizados, cualquier otra cosa)
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
async def main():
async with streamablehttp_client("http://localhost:8080/mcp") as (read, write, _session_id):
async with ClientSession(read, write) as session:
await session.initialize()
tools = await session.list_tools()
result = await session.call_tool(
"pyats_run_show_command",
arguments={"device_name": "CORE1", "command": "show version"},
)
Qué preguntarle
Una vez conectado, háblale como le hablarías a alguien que ya conoce la red:
- "¿Qué dispositivos hay en el testbed?" →
pyats_list_devices - "Muéstrame el resumen BGP en CORE1" →
pyats_run_show_command, parseado en JSON estructurado - "Captura el estado OSPF de CORE1, luego aplica esta configuración y muéstrame qué cambió" →
pyats_learn_feature(antes) →pyats_configure_with_diff→pyats_learn_feature(después) →pyats_diff_learned_snapshots - "Ejecuta
show ip interface briefen cada switch" →pyats_run_show_command_multi(opyats_pcall_show_commandpara aislamiento de proceso por dispositivo a escala real) - "Si ese cambio de configuración rompe algo, reviértelo" →
pyats_rollback_config - "Ejecuta esta prueba Blitz contra R1 y R2" / "Ejecuta esta suite de Robot Framework" →
pyats_run_blitz/pyats_run_robot
El agente encadena esto por sí mismo — describes el resultado, él elige las herramientas.
Herramientas disponibles
26 herramientas, agrupadas por lo que hacen.
Descubrimiento
| Herramienta | Descripción |
|---|---|
pyats_list_devices | Lista todos los dispositivos en el testbed |
pyats_search_devices | Búsqueda difusa de dispositivos por nombre o alias |
Comandos show
| Herramienta | Descripción |
|---|---|
pyats_run_show_command | Ejecuta un comando show validado; devuelve JSON parseado o salida cruda |
pyats_run_show_command_multi | Ejecuta un comando show en múltiples dispositivos concurrentemente (pool de hilos) |
pyats_pcall_show_command | Igual, pero un proceso de SO por dispositivo (pyats.async_.pcall) en lugar de un pool de hilos compartido |
pyats_show_running_config | Recupera la configuración en ejecución completa (texto crudo) |
pyats_show_logging | Recupera los registros del sistema del dispositivo vía show logging |
pyats_ping_from_network_device | Ejecuta un ping desde un dispositivo de red |
pyats_run_linux_command | Ejecuta un comando en un host Linux |
Configuración
| Herramienta | Descripción |
|---|---|
pyats_configure_device | Aplica comandos de configuración con protecciones de seguridad |
pyats_configure_devices_multi | Aplica configuración en múltiples dispositivos concurrentemente (pool de hilos) |
pyats_pcall_configure_devices | Igual, pero un proceso de SO por dispositivo |
pyats_configure_with_diff | Aplica configuración y devuelve un diff antes/después |
pyats_rollback_config | Revierte a la última instantánea de configuración guardada |
Estado y diagnóstico
| Herramienta | Descripción |
|---|---|
pyats_device_health | Captura CPU, memoria, interfaces y estado de enrutamiento |
pyats_get_neighbors | Recupera vecinos CDP/LLDP |
pyats_find_interface_by_ip | Encuentra qué interfaz posee una dirección IP dada |
pyats_learn_feature | Genie device.learn() para una función completa (interface, ospf, bgp, …), opcionalmente guardado como instantánea nombrada |
pyats_diff_learned_snapshots | Compara dos instantáneas guardadas por pyats_learn_feature |
Pruebas y automatización
| Herramienta | Descripción |
|---|---|
pyats_clean_device | Genie Clean (Kleenex), restringido a etapas no destructivas connect+execute_command; dry_run=True por defecto |
pyats_run_blitz | Ejecuta una prueba declarativa pyATS Blitz YAML |
pyats_run_robot | Ejecuta una suite de Robot Framework usando las bibliotecas de palabras clave pyats.robot/genie.libs.robot |
pyats_run_dynamic_test | Ejecuta un script pyATS AEtest en sandbox |
APIs
| Herramienta | Descripción |
|---|---|
pyats_rest_request | Llamada REST/RESTCONF/NX-API genérica vía rest.connector de pyATS (un tipo de conexión separado de CLI/SSH) |
pyats_xpresso_request | Llamada autenticada a la API REST v2 de Cisco XPresso (solicitudes de prueba, trabajos, testbeds, imágenes, …) |
Sesión
| Herramienta | Descripción |
|---|---|
pyats_get_operation_log | Recupera el registro de operaciones en memoria |
Seguridad
- Los comandos show se validan — se bloquean tuberías, redirecciones y palabras clave peligrosas.
- Los cambios de configuración se verifican para
reload,erase,write erase,delete,format— la misma verificación se ejecuta dentro depyats_clean_device,pyats_run_blitzypyats_run_robot. - Los scripts de prueba dinámicos se ejecutan en un sandbox restringido (importaciones prohibidas:
os,sys,subprocess, etc.). pyats_clean_devicenunca ejecuta una etapa real de Genie Clean que reinicie, borre o reimage un dispositivo — solo se generanconnect+execute_command— y por defecto esdry_run=True; ejecutar de verdad también requiere una frase de confirmación exacta.- Cada caché global de proceso (caché de conexión, caché de testbed, instantáneas de configuración/learn, registro de operaciones) está protegida por un bloqueo, para que clientes HTTP concurrentes no puedan corromper estado compartido.
- Todas las credenciales provienen de
.env— nunca almacenadas en el archivo de testbed o en el código fuente.
Estructura del proyecto
.
├── pyats_mcp_server.py # MCP server
├── test_pyats_mcp_server.py # Unit tests (119 tests)
├── benchmark/ # Pre/post, stateful/stateless transport benchmark
├── Dockerfile # Container definition
├── requirements.txt # Pinned runtime dependencies
├── requirements-dev.txt # Dev/test dependencies
├── pyproject.toml # Tool config (black, isort, pytest, mypy)
├── .env.example # Configuration template — copy to .env
├── .gitignore
├── LICENSE
└── CONTRIBUTING.md
Desarrollo
# Install dev dependencies with uv
uv venv .venv && uv pip install -r requirements-dev.txt
# Run tests
.venv/bin/python -m pytest
# Lint and format
.venv/bin/black .
.venv/bin/isort .
.venv/bin/flake8 . --max-line-length=100
Ver CONTRIBUTING.md para la configuración completa y el flujo de trabajo de PR.
Benchmark
benchmark/ compara STDIO (legado) contra Streamable HTTP en modo con estado y sin estado, contra un testbed real. Ver benchmark/scenarios.py para la lista de escenarios y benchmark/aggregate.py para construir el informe de comparación; benchmark/results/summary.md tiene los números de la ejecución más reciente.