packet-coders-mcp
Provides tools to manage Cisco IOS, IOS-XE, and NX-OS network devices via SSH, including sending show commands, running health checks, retrieving OSPF and BGP neighbor states, and pushing configuration changes.
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., "@packet-coders-mcprun a health check on the lab"
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.
From Chat to MCP: A Network Engineer's Path Through AI
This repo is a compact webinar demo for building a local MCP server that can talk to a network lab. It uses FastMCP and exposes a small, useful set of tools:
Tool | Purpose |
| Show the inventory the MCP server can reach. |
| Run a read-only show command on one device. |
| Run a small health bundle against one device or the whole lab. |
| Read OSPF neighbor state with the right command per platform. |
| Read BGP summary state with the right command per platform. |
| Push config lines, dry-run by default, with confirmation required. |
The demo can run in mock mode with no lab, then switch to a real EVE-NG lab by changing the inventory file. The same server works from Claude Code, Claude Desktop, or a fully local LLM stack (Ollama / vLLM) — see Connecting a client.
Architecture
flowchart LR
subgraph Clients
CC["Claude Code"]
CD["Claude Desktop"]
LOCAL["Local LLM host<br/>(Open WebUI + mcpo)"]
end
Clients -->|stdio or HTTP MCP| B["FastMCP server"]
B --> C["LabService"]
C --> D{"Driver"}
D -->|mock| E["Built-in sample routers"]
D -->|ssh/netmiko| F["EVE-NG lab nodes"]FastMCP is deliberately thin here. The important teaching point is that MCP tools are just typed Python functions with useful docstrings, while the actual networking code stays in a normal service layer. The model never speaks MCP directly — an MCP host runs the tool-calling loop and connects the tools to whatever model you point it at.
Related MCP server: Packet Tracer MCP
Current Lab State (reference topology)
The live demo runs against four Arista EOS switches in a full mesh, all in a single
OSPF area. Every switch holds a FULL adjacency with the other three.
Note on addresses: the management/SSH addresses are environment-specific and live only in your git-ignored
inventory.local.yaml(see Keeping the lab private). The addresses below are the in-fabric OSPF/loopback addresses (RFC 1918) and are safe to share. Do not put your real management subnet in this file.
SW1 (10.255.0.1)
/ | \
10.12.12/24 10.13.13/24 10.14.14/24
/ | \
SW2 ------10.23.23/24------ SW3
(10.255.0.2) \ / (10.255.0.3)
\ 10.24.24/24 10.34.34/24
\____________|_______/
SW4 (10.255.0.4)Routers / router IDs (each switch's Loopback0 doubles as its OSPF router ID):
Switch | Platform | Loopback0 / Router ID | OSPF process / area |
SW1 |
|
| 1 / area 0 |
SW2 |
|
| 1 / area 0 |
SW3 |
|
| 1 / area 0 |
SW4 |
|
| 1 / area 0 |
Point-to-point links (six /24 transit segments, full mesh):
Link | Subnet | A-side | B-side |
SW1–SW2 |
| SW1 | SW2 |
SW1–SW3 |
| SW1 | SW3 |
SW1–SW4 |
| SW1 | SW4 |
SW2–SW3 |
| SW2 | SW3 |
SW2–SW4 |
| SW2 | SW4 |
SW3–SW4 |
| SW3 | SW4 |
Each switch advertises its Loopback0/32 into area 0, so every loopback is reachable from
every switch. Adding a demo loopback (e.g. Loopback11 10.255.0.11/32 advertised into
area 0) and watching it appear in the other switches' routing tables is a clean way to show
configure_device driving a real change end-to-end.
Quick Start
Install the project:
uv venv
uv pip install -e ".[dev]"Run the server in mock mode (no lab required):
PACKET_CODERS_INVENTORY=configs/inventory.mock.yaml uv run packet-coders-mcpOr run it through the FastMCP CLI:
PACKET_CODERS_INVENTORY=configs/inventory.mock.yaml \
uv run fastmcp run src/packet_coders_mcp/server.py:mcpRun it over HTTP for clients that prefer a URL:
PACKET_CODERS_INVENTORY=configs/inventory.mock.yaml \
uv run fastmcp run src/packet_coders_mcp/server.py:mcp --transport http --port 8000HTTP clients connect to:
http://localhost:8000/mcpConnecting a client
All clients use the same stdio launch shape. Replace <ABSOLUTE_PATH_TO_REPO> with the
absolute path to your local clone, and point PACKET_CODERS_INVENTORY at the inventory you
want (start with configs/inventory.mock.yaml, switch to your git-ignored
inventory.local.yaml for the real lab). A ready-to-edit template lives at
examples/mcp.json.
Claude Code
Register the server from the repo root with the CLI:
claude mcp add packet-coders-lab \
--env PACKET_CODERS_INVENTORY=<ABSOLUTE_PATH_TO_REPO>/configs/inventory.mock.yaml \
-- uv run --project <ABSOLUTE_PATH_TO_REPO> packet-coders-mcpOr commit a project-scoped .mcp.json at the repo root:
{
"mcpServers": {
"packet-coders-lab": {
"command": "uv",
"args": [
"run",
"--project",
"<ABSOLUTE_PATH_TO_REPO>",
"packet-coders-mcp"
],
"env": {
"PACKET_CODERS_INVENTORY": "<ABSOLUTE_PATH_TO_REPO>/configs/inventory.mock.yaml"
}
}
}
}Verify with /mcp inside Claude Code, then call list_lab_devices.
Claude Desktop
Add the same server block to Claude Desktop's config file, then restart the app.
macOS:
~/Library/Application Support/Claude/claude_desktop_config.jsonWindows:
%APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"packet-coders-lab": {
"command": "uv",
"args": [
"run",
"--project",
"<ABSOLUTE_PATH_TO_REPO>",
"packet-coders-mcp"
],
"env": {
"PACKET_CODERS_INVENTORY": "<ABSOLUTE_PATH_TO_REPO>/configs/inventory.mock.yaml"
}
}
}
}uv must be on the PATH that the desktop app inherits. If the server does not appear, use
an absolute path to the uv binary (which uv) as the command.
Local LLMs (Ollama / vLLM)
You can drive this server entirely with a local model — no Anthropic API involved. The key idea is the split between the MCP host (runs the tool-calling loop) and the inference backend (serves chat completions). The model itself does not speak MCP; the host does.
This guide uses Open WebUI + mcpo.
mcpo wraps a stdio MCP server as an OpenAPI tool server that Open WebUI can call, and Open
WebUI connects to either backend below.
Open WebUI (MCP host)
├── mcpo ──► packet-coders-mcp (this server)
└── chat completions ──► Ollama @ Mac Mini (Qwen3 ~30B-class)
vLLM @ GPU box (smaller models)1. Expose the MCP server through mcpo:
PACKET_CODERS_INVENTORY=<ABSOLUTE_PATH_TO_REPO>/configs/inventory.mock.yaml \
uvx mcpo --port 8000 -- uv run --project <ABSOLUTE_PATH_TO_REPO> packet-coders-mcpIn Open WebUI, add http://localhost:8000 as a Tool server (Settings → Tools). The six
lab tools then show up to the model.
2a. Backend — Ollama (e.g. on a Mac Mini, reached over Tailscale):
Point Open WebUI's Ollama connection at the tailnet host:
http://<your-tailnet-host>:11434.Use a tool-calling-capable model (Qwen3 is a strong pick; it selects tools reliably).
Raise the context window. Ollama defaults to a small context (~4K), and the tool schemas plus verbose
showoutput overflow it — which looks like "the model ignored the tools." Set it larger, e.g.OLLAMA_CONTEXT_LENGTH=32768 ollama serve, or bakenum_ctxinto a Modelfile.
2b. Backend — vLLM (e.g. on a GPU box, OpenAI-compatible):
Tool calling is off by default in vLLM. Launch with auto tool choice and a parser that matches your model:
vllm serve <your-qwen3-model> --enable-auto-tool-choice --tool-call-parser hermesThe correct
--tool-call-parseris model-dependent (Qwen-family commonly uses thehermesparser) — verify against the current vLLM docs for your exact model. With the wrong parser, the model emits tool calls as plain text and nothing fires.Add it to Open WebUI as an OpenAI connection:
http://<your-tailnet-host>:8000/v1.
Model-strength guidance: lead with the larger Qwen3 model as the "driver" — it picks
the right tool and emits clean JSON. Keep smaller models (and Gemma variants, which are less
consistent at function calling) on the read-only tools, or on configure_device with
dry_run=True only. A weak model in an auto-confirming agent loop is exactly where you do
not want unattended writes — keep a human in the loop for any real change (see
Safety model).
Inventory Model
Inventory is YAML:
defaults:
username: admin
password: admin
port: 22
platform: cisco_ios
transport: ssh
devices:
r1:
host: 192.0.2.11
role: edge
r2:
host: 192.0.2.12
role: edgeSupported transport values:
Transport | Meaning |
| Uses built-in demo outputs. No lab required. |
| Uses Netmiko to connect to the device. |
Common platform values:
Platform | Notes |
| IOS or IOS-XE style commands. |
| NX-OS style commands. |
| Arista EOS style commands. |
| Junos style commands. |
| FRR through |
EVE-NG Setup
Put your lab devices on a management network reachable from the machine running this server.
Enable SSH on the nodes.
Copy
configs/inventory.eve-ng.example.yamltoinventory.local.yaml.Replace the
host,username,password, andplatformvalues.Start the server with:
PACKET_CODERS_INVENTORY=inventory.local.yaml uv run packet-coders-mcpKeeping the lab private
This README and the committed configs deliberately contain no real credentials, no real management IPs, and no machine-specific paths:
Real credentials and management addresses live only in
inventory.local.yaml, which is git-ignored. The committedconfigs/*.yamluseadmin/adminplaceholders and the RFC 5737 documentation range (192.0.2.0/24).Client configs that hold your absolute paths or tailnet hostnames go in
mcp.local.json(also git-ignored). Keep<ABSOLUTE_PATH_TO_REPO>/<your-tailnet-host>as placeholders in anything you commit or share.Keep your inference backends (Ollama/vLLM) on the tailnet, never exposed to the public internet — an open Ollama/vLLM port is an unauthenticated model and tool surface.
In-fabric OSPF/loopback addresses (
10.x) are safe to share; your real management subnet is not — don't paste it into docs.
Safety Model
This is a demo server, not a production change platform. It still has a few useful guardrails:
send_commandblocks obvious config and destructive commands.configure_devicedefaults todry_run=True.Real config requires both
dry_run=Falseandconfirm=True.Dangerous config lines such as
reload,erase,delete, andwrite eraseare blocked.High-impact writes should stay human-confirmed — do not let an agent loop auto-approve
configure_deviceagainst a live device.Do not point this at production networks.
Suggested Webinar Flow
Start with
configs/inventory.mock.yamland list devices.Show
server.pyand how@mcp.toolturns Python functions into MCP tools.Run
get_ospf_neighborsandget_bgp_summaryagainst the mock lab.Switch
PACKET_CODERS_INVENTORYto the EVE-NG inventory (the four-switch mesh above).Run the same tools against the real lab and show the full-mesh adjacencies.
Demonstrate
configure_devicefirst as a dry run, then withconfirm=True— add and then remove a demo loopback, verifying it appears in OSPF.Optional: repeat the demo driven by a local Qwen3 model to show the same tools with no cloud API.
Development Checks
uv run --extra dev pytest
uv run --extra dev ruff check .Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
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/E-Conners-Lab/packet-coders-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server