Skip to main content
Glama
owroc

SONiC MCP Server

by owroc

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 --build

The 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 ONLY

It 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 tests

Connect 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

list_devices, get_capabilities

Read

get_interface_status, get_interface_counters, get_lldp_neighbors, get_bgp_neighbors, get_environment, get_device_identity, get_config

Telemetry

subscribe_telemetry (bounded by sample count and wall-clock)

Write

set_interface_admin, configure_interface_ip, create_vlan

Ops

backup_config, diff_config, rollback_config, save_config

Escape hatches

gnmi_get, gnmi_set

Safety model

Three independent rails stand between an LLM and a live switch:

  1. Read-only refusal. SONIC_MCP_READ_ONLY=true disables every write server-wide; any device flagged read_only refuses writes regardless. The guard trips before a SetRequest is built.

  2. 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=true makes preview the default.

  3. Prior-state + backup. The client captures the targeted subtree before each write (inline undo); with SONIC_MCP_AUTO_BACKUP=true a 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 leading module: 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) and create_vlan (VLAN) use SONiC YANG paths whose resolution is build-specific. Validate with get_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 set SONIC_MCP_SAVE_PATH if your build maps one.

  • Rollback is experimental. A get --type config snapshot is a list of notifications, not a set-ready tree, so replaying it as a Set replace may not round-trip. Prefer targeted gnmi_set reverts.

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

SONIC_MCP_INVENTORY_PATH

/config/devices.yaml

inventory file path

SONIC_MCP_TRANSPORT

http

http or stdio

SONIC_MCP_HOST / SONIC_MCP_PORT

0.0.0.0 / 8000

MCP bind address

SONIC_MCP_READ_ONLY

false

disable all write tools

SONIC_MCP_DEFAULT_DRY_RUN

false

make writes preview by default

SONIC_MCP_AUTO_BACKUP

true

snapshot running config before each write

SONIC_MCP_BACKUP_DIR

/config/backups

snapshot directory

SONIC_MCP_AUDIT_LOG_PATH

(unset)

JSON-lines change trail

SONIC_MCP_GNMI_TIMEOUT

15

per-RPC seconds

SONIC_MCP_ENCODING

json_ietf

gNMI value encoding

SONIC_MCP_SAVE_PATH

(unset)

gNMI Set path that persists running→startup

SONIC_MCP_AUTH_TOKEN

(unset)

require Authorization: Bearer <token>

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

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