SONiC MCP Server
SONiC MCP Server — gNMI variant / pyGNMI
An MCP server that exposes a SONiC switch's gNMI interface as tools, so any MCP-capable LLM can read switch state and change configuration. It speaks gNMI natively in Python via pyGNMI — no external binary required — and serves MCP over Streamable HTTP, packaged as a portable Docker container you can run on any Docker host.
┌─────────────┐ Streamable HTTP ┌──────────────────────┐ gNMI/gRPC ┌──────────────┐
│ LLM client │ ─────────────────► │ sonic-mcp container │ ────────────► │ SONiC switch │
│ (Claude, │ POST /mcp │ FastMCP + pyGNMI │ :8080 │ gnmi/telemetry│
│ IDE, etc.) │ ◄───────────────── │ │ ◄──────────── │ container │
└─────────────┘ └──────────┬───────────┘ └──────────────┘
devices.yaml (bind-mounted)Security posture is lab-grade by default. gNMI runs insecure (plaintext), MCP auth is off, and writes execute live. An LLM with a write-enabled connection to your switches can disrupt the network
Quick start (Docker)
cp devices.example.yaml devices.yaml # then edit for your fabric
cp .env.example .env # set passwords referenced as ${VAR}
docker compose up --buildThe MCP endpoint comes up at http://127.0.0.1:8000/mcp and a health probe at
http://127.0.0.1:8000/health. The image carries no site data — devices.yaml
is bind-mounted and everything else is an env var, so the same image runs in any
environment.
Configure
devices.yaml is a validated list of switches. Credentials may be inlined (lab)
or referenced from the environment as ${VAR} so secrets stay out of the file:
devices:
- name: leaf-01
host: 10.0.0.11
port: 8080 # SONiC gnmi/telemetry default; build-specific
insecure: true # plaintext gRPC (no TLS)
username: admin
password: ${LEAF01_PASSWORD}
tags: [lab, leaf]
- name: spine-01
host: 10.0.0.1
read_only: true # refuses config changes regardless of server setting
tags: [prod, spine]Every tool takes a device argument that must match a name here — an LLM never
addresses a raw IP, so the read_only guard can't be sidestepped. Mark production
switches read_only: true.
Set the gNMI port. It's build-specific — commonly 8080, but 50051 / 6030 / 57400 are seen. The probe (below) confirms it.
Probe first — this is the gate
SONiC's OpenConfig coverage varies by branch, Set support is a compile-time
flag, and path semantics are discovered empirically. Before trusting any tool
against real hardware, run the standalone probe (needs only pip install pygnmi pyyaml):
python scripts/probe.py --inventory devices.yaml
python scripts/probe.py --inventory devices.yaml --test-write # LAB switches ONLYIt writes probe-results.json enumerating, per device: reachability, gNMI
version, supported models/encodings, which candidate paths return data, whether
Subscribe works, and (with --test-write) whether a reversible Set is accepted.
Local development (without Docker)
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]" # runtime + pytest/ruff
SONIC_MCP_INVENTORY_PATH=./devices.yaml python -m sonic_mcp # HTTP on :8000
SONIC_MCP_TRANSPORT=stdio SONIC_MCP_INVENTORY_PATH=./devices.yaml python -m sonic_mcp
pytest # 53 tests, no switch required
ruff check src testsConnect an LLM
Model-agnostic — anything that speaks MCP works.
HTTP client / orchestrator / automation — point your MCP client at
http://<host>:8000/mcp.
Claude Desktop (stdio) — edit
~/Library/Application Support/Claude/claude_desktop_config.json. Use the venv
Python that has the deps, and an absolute inventory path (Claude Desktop
launches with a minimal PATH):
{
"mcpServers": {
"sonic-gnmi": {
"command": "/abs/path/.venv/bin/python",
"args": ["-m", "sonic_mcp"],
"env": {
"SONIC_MCP_TRANSPORT": "stdio",
"SONIC_MCP_INVENTORY_PATH": "/abs/path/devices.yaml"
}
}
}
}Tools (19)
Call list_devices first to learn the valid device names (credential-free).
Group | Tools |
Discovery |
|
Read |
|
Telemetry |
|
Write |
|
Ops |
|
Escape hatches |
|
Safety model
Three independent rails stand between an LLM and a live switch:
Read-only refusal.
SONIC_MCP_READ_ONLY=truedisables every write server-wide; any device flaggedread_onlyrefuses writes regardless. The guard trips before a SetRequest is built.Dry-run. Every write tool accepts
dry_run; a dry run returns the exact SetRequest without touching the Set RPC.SONIC_MCP_DEFAULT_DRY_RUN=truemakes preview the default.Prior-state + backup. The client captures the targeted subtree before each write (inline undo); with
SONIC_MCP_AUTO_BACKUP=truea full-config snapshot is also written to the backup dir first.
Every mutation attempt — refused, dry-run, applied, or errored — is recorded to
the audit log (SONIC_MCP_AUDIT_LOG_PATH for a durable JSON-lines trail).
gNMI notes & caveats
Paths are OpenConfig xpaths. List keys use
[key=value]; the leadingmodule:prefix is the gNMI origin. OpenConfig (interfaces, platform) is gNMI's sweet spot.SONiC-native models may need an origin.
get_device_identity(DEVICE_METADATA) andcreate_vlan(VLAN) use SONiC YANG paths whose resolution is build-specific. Validate withget_capabilities/gnmi_get.No standard save. gNMI has no commit/save RPC and SONiC generally maps none — writes hit running config only. Persist with the CLI (
config save), or setSONIC_MCP_SAVE_PATHif your build maps one.Rollback is experimental. A
get --type configsnapshot is a list of notifications, not a set-ready tree, so replaying it as a Setreplacemay not round-trip. Prefer targetedgnmi_setreverts.
Environment variables
Every field in src/sonic_mcp/settings.py maps to a SONIC_MCP_-prefixed var;
the full annotated list is in .env.example. The most-used:
Var | Default | Purpose |
|
| inventory file path |
|
|
|
|
| MCP bind address |
|
| disable all write tools |
|
| make writes preview by default |
|
| snapshot running config before each write |
|
| snapshot directory |
| (unset) | JSON-lines change trail |
|
| per-RPC seconds |
|
| gNMI value encoding |
| (unset) | gNMI Set path that persists running→startup |
| (unset) | require |
Layout
src/sonic_mcp/ package (settings, inventory, gnmi, paths, safety, backup, context, server, tools/)
scripts/probe.py standalone pre-flight probe (the Phase 2 gate)
tests/ pytest suite (fake gNMI; no switch needed)
Dockerfile multi-stage, non-root
docker-compose.yml reference deployment
legacy/ the original single-file server.py (superseded; kept for reference)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/owroc/sonic-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server