Network engineers spend a large share of every change window and every P1 doing the
same three things: log in to a device, run a set of show commands, and read the
output. Automating that normally means writing and maintaining Python — a barrier
for the many engineers who are excellent at networking but do not script daily.
This project removes that barrier. It is a Model Context Protocol (MCP) server
that exposes Cisco pyATS operations as tools an AI assistant can call. With it
running, an engineer types "run a detailed health check on iosv-0" in GitHub
Copilot Chat, and the assistant calls a real, audited pyATS function that connects
over SSH, executes vetted commands, and returns the device output — no script
written, no credentials in the chat window.
The hard part of connecting an LLM to production-adjacent infrastructure is not the
plumbing, it is the blast radius. A model that can invent commands can invent
write erase. This server is therefore built around a deny-by-default guardrail
layer: every string the model produces is validated before it reaches a device,
configuration changes are disabled unless explicitly turned on, and a deny-list
blocks service-impacting commands outright. Those guardrails are unit tested and run
in CI without needing a device.
flowchart LR
A["Engineer<br/>natural language"] --> B["VS Code<br/>GitHub Copilot Chat"]
B -- "MCP stdio" --> C["mcp_server<br/>FastMCP tools"]
C --> D["Guardrails<br/>allow-list + deny-list"]
D --> E["pyATS / Genie<br/>Unicon SSH"]
E --> F["Cisco CML<br/>IOSv / IOS XE"]
F -- "device output" --> B
Problem. Day-2 network operations are repetitive but not trivial. Gathering a
consistent health snapshot across devices, checking interface state after a change,
or capturing a running-config backup all require CLI access plus the discipline to
run the same commands the same way every time. Teams that solve this with scripts
end up with a library only a handful of people can maintain, and engineers who
cannot script are left waiting on those people.
Solution. Expose a small, deliberately narrow set of pyATS operations through
MCP, and let an AI assistant map plain English onto them. The assistant chooses
which tool to call; the server decides what is allowed to run. That split is the
point: the model provides the natural-language interface, the server provides the
determinism and the safety.
Outcomes.
| Outcome | Before | With this project |
|---|---|---|
| Health snapshot across a device | Manual CLI, inconsistent command set | One prompt, six standard commands, identical every time |
| Skill required | Python plus pyATS API knowledge | Plain English |
| Command safety | Whatever the operator types | Allow-list for reads, deny-list for writes, writes off by default |
| Auditability | Terminal scrollback | Structured tool calls logged to stderr |
| Onboarding a new engineer | Learn the script library first | Ask a question on day one |
Benefits to the organisation. Consistent pre- and post-change validation, fewer
outages caused by ad-hoc CLI work, faster onboarding, and an automation surface that
scales by adding devices to a testbed rather than by writing more scripts.
Challenges solved during development.
print() or Unicon log corrupts the session. All connections uselog_stdout=False and every log line goes to stderr.%ENV{} markup, so no password is ever written into the repository,Ideas that would extend this. Per-user role mapping so apply_config is limited
to specific engineers; Genie learn/diff snapshots for automatic pre/post change
comparison; a dry-run mode that returns the intended configuration for approval
before applying it.
| Requirement | Notes |
|---|---|
| Python 3.10 or later | python3 --version. Download Python |
| Git | Download Git |
| An SSH-reachable Cisco device | A Cisco Modeling Labs node, or a DevNet Sandbox |
| VS Code | Download VS Code |
| GitHub Copilot + Copilot Chat extensions | Installed from the VS Code Marketplace, with an active Copilot subscription |
pyATS is fully supported on Linux and macOS. On Windows, use
WSL 2 — the Windows-native
commands below cover the repository tooling and tests, but device connections
should be made from WSL.
git clone https://github.com/CiscoDevNet/cisco-pyats-mcp-network-automation.git
cd cisco-pyats-mcp-network-automationLinux / macOS / WSL
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip setuptools wheelWindows (PowerShell)
python -m venv .venv .venv\Scripts\Activate.ps1 python -m pip install --upgrade pip setuptools wheel
pip install -r requirements.txt
This installs the MCP SDK and pyats[library], which includes the Genie parsers and
the Unicon connection library. Verify pyATS:
pyats version
cp testbed/testbed.example.yaml testbed/testbed.yaml
Edit testbed/testbed.yaml and set the device name and os to match your lab. The
file contains no credentials — it references environment variables through the
pyATS %ENV{} markup. testbed/testbed.yaml is git-ignored.
Validate it:
pyats validate testbed testbed/testbed.yaml
cp .env.example .env
Edit .env with your lab values, then load it:
Linux / macOS / WSL
set -a; source .env; set +a
Windows (PowerShell)
Get-Content .env | Where-Object { $_ -notmatch '^\s*#|^\s*$' } | ForEach-Object { $k,$v = $_ -split '=',2; [Environment]::SetEnvironmentVariable($k,$v) }
.env is git-ignored. Enter the management IP address on its own — no https://
prefix and no trailing slash. For example 192.0.2.10.
python scripts/check_env.py
This confirms that the dependencies import, the testbed file resolves, and the
credential variables are set. Fix anything it reports before continuing.
All configuration is supplied through environment variables, so the same code runs
in a lab, in CI, or under an MCP client without edits.
| Variable | Default | Purpose |
|---|---|---|
PYATS_TESTBED |
testbed/testbed.yaml |
Path to the pyATS testbed file. Use an absolute path when launching from an MCP client. |
PYATS_MCP_ALLOW_CONFIG |
false |
Master switch for apply_config. While false, the server is strictly read-only. |
PYATS_MCP_ALLOW_SAVE |
false |
Additionally required before write memory is permitted. |
PYATS_MCP_ALLOWED_DEVICES |
(empty) | Comma-separated allow-list of device names. Empty means every device in the testbed. |
PYATS_MCP_CONNECT_TIMEOUT |
60 |
SSH connection timeout in seconds. |
PYATS_MCP_COMMAND_TIMEOUT |
120 |
Per-command execution timeout in seconds. |
PYATS_MCP_MAX_OUTPUT_CHARS |
60000 |
Caps a single tool response so long output cannot exhaust the model context. |
PYATS_MCP_INSECURE_SSH |
false |
Set to true to disable SSH host key verification. Throwaway labs only. |
PYATS_MCP_KNOWN_HOSTS |
(system default) | Custom known_hosts file used when host key verification is enabled. |
Device credentials are separate and are consumed by the testbed:
CML_DEVICE_IP, CML_DEVICE_USERNAME, CML_DEVICE_PASSWORD and
CML_DEVICE_ENABLE_PASSWORD.
cp .vscode/mcp.json.example .vscode/mcp.json
Edit .vscode/mcp.json and set command to the Python interpreter inside your
virtual environment:
${workspaceFolder}/.venv/bin/python${workspaceFolder}\\.venv\\Scripts\\python.exeThe template uses a VS Code promptString input, so the device password is typed
into a masked prompt at start-up rather than being stored on disk. .vscode/mcp.json
is git-ignored.
Reload VS Code, open Copilot Chat, switch to Agent mode, and confirm that
cisco-pyats-mcp appears in the tools picker. VS Code will ask you to trust the
server because it executes local code — only trust servers you have reviewed.
python -m mcp_server
The server speaks MCP over stdio and produces no stdout output of its own; start-up
information is written to stderr. Normally you let VS Code launch it rather than
running it by hand.
Run a command against a device using the same code path the server uses. This is the
fastest way to separate a connectivity problem from an MCP problem:
python scripts/run_show.py --device iosv-0 --command "show ip interface brief"Both scripts print full usage with --help, and with no arguments at all:
python scripts/run_show.py --help python scripts/check_env.py --help
| Tool | Changes device state | Description |
|---|---|---|
list_devices |
No | Lists the testbed devices this server may reach. |
run_show_command |
No | Runs one validated show command. |
health_check |
No | basic: version and interface state. detailed: adds CPU, memory, routing summary and recent logs. |
backup_running_config |
No | Returns show running-config for offline backup or diffing. |
apply_config |
Yes | Applies configuration lines. Refuses to run unless PYATS_MCP_ALLOW_CONFIG=true. |
Open Copilot Chat in Agent mode and ask:
show ip interface brief on iosv-0 and tell me which interfaces are down."Copilot calls the matching tool, VS Code shows you the tool invocation for approval,
and the device output is returned into the conversation for analysis.
Configuration is disabled by default. Enable it only against a lab device you own:
export PYATS_MCP_ALLOW_CONFIG=true export PYATS_MCP_ALLOWED_DEVICES=iosv-0
Restart the server, then ask Copilot to apply this loopback to iosv-0:
interface loopback123
description created-by-copilot-mcp
ip address 10.123.123.1 255.255.255.255
Verify the result with a read-only call:
show ip interface brief | include Loopback123 on iosv-0."Commands such as reload, write erase, erase, format, delete,
boot system, config-register, crypto key zeroize, local username changes and
no ip ssh are rejected by the server even when configuration is enabled.
The guardrail tests need no device and no pyATS installation:
pip install -r requirements-dev.txt
pytest -q
ruff check .
bandit -r mcp_server scripts -qcisco-pyats-mcp-network-automation/
├── mcp_server/
│ ├── __main__.py # python -m mcp_server
│ ├── config.py # environment-driven settings, safe defaults
│ ├── devices.py # testbed loading and short-lived connections
│ ├── server.py # FastMCP tool definitions
│ └── validation.py # allow-list, deny-list, injection guards
├── scripts/
│ ├── check_env.py # pre-flight environment validator
│ └── run_show.py # run a show command without MCP
├── testbed/
│ └── testbed.example.yaml # credential-free template
├── tests/ # guardrail and settings tests, no device needed
├── docs/
│ ├── lab-guide.md # end-to-end lab build
│ ├── server-code-explained.md # the server code, line by line
│ ├── mcp-config-explained.md # the MCP client configuration explained
│ └── pyats-cli-cheatsheet.md # everyday pyATS and Genie commands
├── .vscode/mcp.json.example
├── .env.example
└── .github/workflows/ci.yml
mcp.json does and how to troubleshoot it.No hardware is required. Both of these give you SSH-reachable Cisco devices:
.env and point testbed/testbed.yaml at it.When using a shared sandbox, keep PYATS_MCP_ALLOW_CONFIG=false so the server stays
read-only.
run_show_command deliberately accepts a single showgenie parse output is on the roadmap.Track and report issues at
GitHub Issues.
python scripts/check_env.py — it diagnoses most setup problems.python scripts/run_show.py --device <name> --command "show version"..vscode/mcp.json iscommand points at the interpreter inside .venv, and that allContributions are welcome. The areas that would benefit most right now:
genie learn and genie diff so pre- andapply_config that returns the intended change forRead CONTRIBUTING.md for the development setup, the checks to run
before opening a pull request, and the rules every new MCP tool must follow. All
participants are expected to follow the Code of Conduct.
Author: Ranil Fernando
Built on
Inspired by the Cisco DevNet NetDevOps community and the
Code Exchange repository template.
This code is licensed under the Apache License, Version 2.0. See
LICENSE for the full text and NOTICE for the copyright notice
and third-party attributions.
Copyright 2026 Cisco Systems, Inc. and its affiliates.
This is an independent community project. It is not an official Cisco product,
is not supported by Cisco TAC, and carries no Cisco warranty. It is intended for
lab, demonstration and enablement use. Review SECURITY.md before
pointing it at any device you care about.
Cisco, Cisco Modeling Labs, pyATS, Genie and IOS are trademarks or registered
trademarks of Cisco Systems, Inc.
Owner
Contributors
Categories
ToolsSecurityObservabilityNetworkingProducts
Catalyst RoutersCatalyst SwitchesIOS XEIOS XRIOxNX-OSpyATSLicense
Code Exchange Community
Get help, share code, and collaborate with other developers in the Code Exchange community.View Community