pyATS MCP Server
Enables an AI agent to interact with Cisco network devices using pyATS/Genie, providing tools for running show commands, managing configuration with rollback, learning and diffing feature states, and running declarative tests.
Allows running Robot Framework test suites against network devices, integrating declarative testing into the MCP server.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@pyATS MCP Serverlearn OSPF state on core1 and diff against baseline after the config change"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
pyATS MCP Server
Cisco pyATS and Genie already know how to talk to a network — parsing show commands, pushing configuration, learning feature state, running declarative tests. What they didn't have was a way for an AI agent to drive any of it directly. This server closes that gap: it wraps pyATS/Genie as a set of structured, guarded MCP tools that an agent like Claude can call against a real testbed, over the Model Context Protocol's current Streamable HTTP transport.
Point an agent at it and it can look up a device, run and parse a show command, apply configuration with a rollback point, learn and diff a feature's state before and after a change, fan a command out across a fleet — one thread pool or one process per device — run a declarative Blitz or Robot Framework test, or call a device's REST/RESTCONF API directly. Every risky path is guarded before it reaches a device, and every call lands in an in-memory audit log the agent can review mid-session.
At a glance
Transport — Streamable HTTP (
mcp>=2.0.0), stateful or stateless, chosen with one environment variable. STDIO is gone.26 tools across discovery, show commands, configuration, Genie learn/diff, Genie Clean, declarative testing (Blitz, Robot Framework, AEtest), generic REST/RESTCONF, and Cisco XPresso.
Two ways to fan out a command across many devices — a shared thread pool for everyday use, or one OS process per device (
pyats.async_.pcall) when you want real isolation at scale.Guardrails, not honor systems — dangerous commands are blocked before they reach a device, Genie Clean can never run a stage that reboots or reimages one, and destructive actions require an exact confirmation phrase.
Nothing hard-coded — every credential and device detail lives in
.env, pulled intotestbed.yamlat runtime via%ENV{}substitution.
Related MCP server: network-mcp
Prerequisites
Python 3.10+
A pyATS
testbed.yamlpointed at real or virtual network devices — a physical lab, Cisco Modeling Labs / VIRL / GNS3, or anything else Unicon can reach over SSH/Telnet. pyATS MCP doesn't simulate a network; it drives one.An MCP-capable client to talk to it — see Connect Your Agent below.
Quick Start
# 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.pyThe MCP endpoint is then reachable at http://<host>:<port>/mcp.
Configuration
All device details and credentials live in a .env file — nothing is hard-coded in the repo.
1. Copy the template
cp .env.example .env2. Set the server variables
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 sets stateless_http=True on the Streamable HTTP transport, so no server-side session state is retained between requests from clients still negotiating the older, handshake-based protocol. Clients speaking the current MCP protocol (2026-07-28, SEP-2575) are handshake-free by default regardless of this setting — that comes from the mcp>=2.0.0 SDK itself, not anything configured here.
3. Add a block for each device
Every device in your testbed.yaml uses %ENV{VAR} substitution, so credentials and connection details are read from .env at runtime.
Use the {DEVICENAME}_{FIELD} naming convention:
# 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)If a group of devices shares credentials, define group-level vars and reference them across devices:
SITE_A_USERNAME=netops
SITE_A_PASSWORD=s3cr3t
SITE_A_ENABLE_PASSWORD=s3cr3t4. Reference the variables in 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: 360For devices with unknown OS, set
os: "%ENV{DEVICE_OS}"withDEVICE_OS=genericin.envand optionally addlearn_os: trueunderarguments:— Unicon will detect and cache the OS after the first connection.
Docker
Build
docker build -t pyats-mcp-server .Run (pass .env directly)
docker run -p 8080:8080 --rm \
--env-file /absolute/path/to/.env \
-v /absolute/path/to/testbed.yaml:/app/testbed.yaml \
pyats-mcp-serverEither way, the server is a long-running process you start once and point clients at — it isn't something an agent spawns per session. See below for exactly how each client connects to it.
Connect Your Agent
The server exposes one thing: an MCP endpoint at http://<host>:<port>/mcp (Streamable HTTP). Every client below just needs that URL — no command/args, no local process for the client to manage.
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"Or drop it straight into .mcp.json (project-scoped, committed to the repo) or ~/.claude.json (user-scoped):
{
"mcpServers": {
"pyats": { "type": "http", "url": "http://localhost:8080/mcp" }
}
}VS Code (GitHub Copilot Chat)
Add a .vscode/mcp.json in the workspace (or run MCP: Add Server from the Command Palette):
{
"servers": {
"pyats": { "type": "http", "url": "http://localhost:8080/mcp" }
}
}OpenAI Codex CLI
codex mcp add pyats --url http://localhost:8080/mcpOr in ~/.codex/config.toml:
[mcp_servers.pyats]
url = "http://localhost:8080/mcp"Claude Desktop
Claude Desktop's claude_desktop_config.json is stdio-only — putting a url field in it doesn't work (it's a known issue, not a supported path). Remote/HTTP servers are added instead as a Custom Connector under Settings → Connectors, and Desktop connects to it from Anthropic's cloud, not your local machine — so it needs a real, publicly-reachable HTTPS URL, not localhost.
To point Desktop at a server running on your own machine anyway, bridge it through mcp-remote as a local stdio proxy:
{
"mcpServers": {
"pyats": {
"command": "npx",
"args": ["-y", "mcp-remote", "http://localhost:8080/mcp", "--transport", "http-only"]
}
}
}Raw Python (LangGraph, custom agents, anything else)
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"},
)What To Ask It
Once connected, talk to it like you'd talk to someone who already knows the network:
"What devices are in the testbed?" →
pyats_list_devices"Show me the BGP summary on CORE1" →
pyats_run_show_command, parsed into structured JSON"Snapshot CORE1's OSPF state, then apply this config and show me what changed" →
pyats_learn_feature(before) →pyats_configure_with_diff→pyats_learn_feature(after) →pyats_diff_learned_snapshots"Run
show ip interface briefacross every switch" →pyats_run_show_command_multi(orpyats_pcall_show_commandfor process-per-device isolation at real scale)"If that config change breaks anything, roll it back" →
pyats_rollback_config"Run this Blitz test against R1 and R2" / "Run this Robot Framework suite" →
pyats_run_blitz/pyats_run_robot
The agent chains these itself — you describe the outcome, it picks the tools.
Available Tools
26 tools, grouped by what they do.
Discovery
Tool | Description |
| List all devices in the testbed |
| Fuzzy-search devices by name or alias |
Show commands
Tool | Description |
| Run a validated show command; returns parsed JSON or raw output |
| Run a show command across multiple devices concurrently (thread pool) |
| Same, but one OS process per device ( |
| Retrieve the full running configuration (raw text) |
| Retrieve device system logs via |
| Execute a ping from a network device |
| Run a command on a Linux host |
Configuration
Tool | Description |
| Apply configuration commands with safety guardrails |
| Apply configuration across multiple devices concurrently (thread pool) |
| Same, but one OS process per device |
| Apply config and return a before/after diff |
| Roll back to the last saved configuration snapshot |
State & diagnostics
Tool | Description |
| Snapshot CPU, memory, interfaces, and routing state |
| Retrieve CDP/LLDP neighbors |
| Find which interface owns a given IP address |
| Genie |
| Diff two snapshots saved by |
Testing & automation
Tool | Description |
| Genie Clean (Kleenex), restricted to non-destructive |
| Run a declarative pyATS Blitz YAML test |
| Run a Robot Framework suite using the |
| Execute a sandboxed pyATS AEtest script |
APIs
Tool | Description |
| Generic REST/RESTCONF/NX-API call via pyATS's |
| Authenticated call to Cisco XPresso's REST API v2 (test requests, jobs, testbeds, images, …) |
Session
Tool | Description |
| Retrieve the in-memory operation log |
Security
Show commands are validated — pipes, redirects, and dangerous keywords are blocked.
Config changes are checked for
reload,erase,write erase,delete,format— the same check runs insidepyats_clean_device,pyats_run_blitz, andpyats_run_robot.Dynamic test scripts run in a restricted sandbox (banned imports:
os,sys,subprocess, etc.).pyats_clean_devicenever runs a real Genie Clean stage that reboots, erases, or reimages a device — onlyconnect+execute_commandare ever generated — and defaults todry_run=True; running for real also requires an exact confirmation phrase.Every process-global cache (connection cache, testbed cache, config/learn snapshots, operation log) is protected by a lock, so concurrent HTTP clients can't corrupt shared state.
All credentials come from
.env— never stored in the testbed file or source code.
Project Structure
.
├── 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.mdDevelopment
# 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=100See CONTRIBUTING.md for the full setup and PR workflow.
Benchmark
benchmark/ compares STDIO (legacy) against Streamable HTTP in both stateful and stateless mode, against a real testbed. See benchmark/scenarios.py for the scenario list and benchmark/aggregate.py for building the comparison report; benchmark/results/summary.md has the most recent run's numbers.
License
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceEnables structured interaction with Cisco network devices using pyATS and Genie. Supports executing show commands, ping tests, and configuration changes on IOS/NX-OS devices through secure STDIO communication.78MIT
- AlicenseNot gradedqualityDmaintenanceProvides AI assistants with direct access to multi-vendor network devices for tasks like configuration management, health checks, and topology discovery through 35 specialized tools. It enables natural language control over platforms including Cisco, Juniper, and Nokia using SSH, NETCONF, and SNMP protocols.11MIT
- FlicenseNot gradedqualityDmaintenanceEnables AI agents to interact with Cisco IOS-XE network devices over SSH using structured tools. Provides read and write capabilities for network management with built-in validation and security.
- AlicenseNot gradedqualityAmaintenanceEnables LLMs to interact with network devices via Cisco RADKit, supporting inventory discovery, device attribute inspection, CLI command execution, and SNMP queries.11Apache 2.0
Related MCP Connectors
Build, validate, and deploy multi-agent AI solutions from any AI environment.
Curated knowledge API for AI agents - skill packs, semantic search, validated patterns.
Deterministic reasoning stack for AI agents: simulate, decide & compute, plus cross-domain tools.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/sunayan22doli-bit/MCP'
If you have feedback or need assistance with the MCP directory API, please join our Discord server