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

Trust Score

Available on CodeGuilds

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 a testbed.yaml en tiempo de ejecución mediante sustitución de %ENV{}.

Requisitos previos

  • Python 3.10+
  • Un testbed.yaml de 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}" con DEVICE_OS=generic en .env y opcionalmente añade learn_os: true bajo arguments: — 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 brief en cada switch" → pyats_run_show_command_multi (o pyats_pcall_show_command para 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

HerramientaDescripción
pyats_list_devicesLista todos los dispositivos en el testbed
pyats_search_devicesBúsqueda difusa de dispositivos por nombre o alias

Comandos show

HerramientaDescripción
pyats_run_show_commandEjecuta un comando show validado; devuelve JSON parseado o salida cruda
pyats_run_show_command_multiEjecuta un comando show en múltiples dispositivos concurrentemente (pool de hilos)
pyats_pcall_show_commandIgual, pero un proceso de SO por dispositivo (pyats.async_.pcall) en lugar de un pool de hilos compartido
pyats_show_running_configRecupera la configuración en ejecución completa (texto crudo)
pyats_show_loggingRecupera los registros del sistema del dispositivo vía show logging
pyats_ping_from_network_deviceEjecuta un ping desde un dispositivo de red
pyats_run_linux_commandEjecuta un comando en un host Linux

Configuración

HerramientaDescripción
pyats_configure_deviceAplica comandos de configuración con protecciones de seguridad
pyats_configure_devices_multiAplica configuración en múltiples dispositivos concurrentemente (pool de hilos)
pyats_pcall_configure_devicesIgual, pero un proceso de SO por dispositivo
pyats_configure_with_diffAplica configuración y devuelve un diff antes/después
pyats_rollback_configRevierte a la última instantánea de configuración guardada

Estado y diagnóstico

HerramientaDescripción
pyats_device_healthCaptura CPU, memoria, interfaces y estado de enrutamiento
pyats_get_neighborsRecupera vecinos CDP/LLDP
pyats_find_interface_by_ipEncuentra qué interfaz posee una dirección IP dada
pyats_learn_featureGenie device.learn() para una función completa (interface, ospf, bgp, …), opcionalmente guardado como instantánea nombrada
pyats_diff_learned_snapshotsCompara dos instantáneas guardadas por pyats_learn_feature

Pruebas y automatización

HerramientaDescripción
pyats_clean_deviceGenie Clean (Kleenex), restringido a etapas no destructivas connect+execute_command; dry_run=True por defecto
pyats_run_blitzEjecuta una prueba declarativa pyATS Blitz YAML
pyats_run_robotEjecuta una suite de Robot Framework usando las bibliotecas de palabras clave pyats.robot/genie.libs.robot
pyats_run_dynamic_testEjecuta un script pyATS AEtest en sandbox

APIs

HerramientaDescripción
pyats_rest_requestLlamada REST/RESTCONF/NX-API genérica vía rest.connector de pyATS (un tipo de conexión separado de CLI/SSH)
pyats_xpresso_requestLlamada autenticada a la API REST v2 de Cisco XPresso (solicitudes de prueba, trabajos, testbeds, imágenes, …)

Sesión

HerramientaDescripción
pyats_get_operation_logRecupera 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 de pyats_clean_device, pyats_run_blitz y pyats_run_robot.
  • Los scripts de prueba dinámicos se ejecutan en un sandbox restringido (importaciones prohibidas: os, sys, subprocess, etc.).
  • pyats_clean_device nunca ejecuta una etapa real de Genie Clean que reinicie, borre o reimage un dispositivo — solo se generan connect+execute_command — y por defecto es dry_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.


Licencia

MIT