trackmcp
Back to directory
sydasif

nornir-napalm-mcp

View on GitHub

FastMCP server exposing Nornir + NAPALM network device state to AI assistants

2 stars PythonOthers Updated Sep 4, 2026
mcpnapalmnetworkingnornirpython

Documentation

Nornir-NAPALM FastMCP Server

A FastMCP server that exposes live network device state to AI assistants via NAPALM getters and CLI commands. Nornir handles inventory loading and concurrent device connections over SSH, eAPI, and NETCONF.

Reads are free; writes are gated. `nornir_apply_config` (dry-run by default) and `nornir_save_config` are the only tools that touch device state, and every write is policy-screened, pre-change-backed up (fail-closed), transcript-parsed, and audit-logged. Every response is a structured envelope with an explicit success flag (spec §21).


Features

ToolDescription
`nornir_list_inventory`List all devices with hostname, platform, and group membership
`nornir_get_facts`System facts: vendor, model, OS version, serial number
`nornir_run_getter`Run any NAPALM getter by name (`arp_table`, `bgp_neighbors`, `vlans`, etc.)
`nornir_get_config`Retrieve running and/or startup configuration from a device
`nornir_list_getters`Introspect available NAPALM getters for each platform in the inventory
`nornir_reload_inventory`Re-read YAML inventory from disk
`nornir_run_command`Run one read-only CLI command (READ_ONLY/SAFE_OPERATIONAL only, per device)
`nornir_run_commands`Run a batch of read-only CLI commands; rejected commands fail only themselves
`nornir_backup_config`Capture and store the running config as an immutable backup (rollback substrate)
`nornir_list_backups`List stored backups for a device, oldest first
`nornir_apply_config`Plan (dry-run by default) and apply config lines; pre-change backups are mandatory and fail-closed
`nornir_save_config`Persist running config to startup/NVRAM — explicit-only, never called implicitly by apply (spec §11)
  • Writes are gated. `nornir_apply_config` and `nornir_save_config` are the only write tools. Apply dry-runs by default, rejects DANGEROUS/BLOCKED lines per device, always captures a pre-change backup (fail-closed: a failed backup means the device is never touched), and reports transcript errors honestly. Saving to NVRAM is a separate, explicit, audited step.
  • Lazy initialization — server starts even with a broken inventory, exposing the tool catalogue for inspection.
  • Singleton caching — Nornir instance is initialized once and reused across requests. Failed-device quarantine (`failed_hosts`) is reset before every call so dropped devices are available again on the next request.
  • Flexible filtering — filter by device name, group, or platform on any tool.
  • HTTP and STDIO transport — run locally for Claude Desktop or expose over HTTP.

Safety model

Every command is classified into one of six categories per platform (`ios` / `eos` rulesets — anything else defaults to UNKNOWN and is denied):

CategoryRead tools (`nornir_run_command*`)Apply (`nornir_apply_config`)
`READ_ONLY` (`show …`)✅ allowedallowed
`SAFE_OPERATIONAL` (`ping`, `traceroute`)✅ allowedallowed
`CONFIGURATION` (`interface`, `ip route`, …)❌ rejected✅ allowed
`UNKNOWN`❌ rejected (deny by default)✅ allowed (fails on-device if bad)
`DANGEROUS` (`reload`)❌ rejected❌ rejected
`BLOCKED` (`write erase`, `wr e`, …)❌ rejected❌ rejected
  • Abbreviated forms (`wr e`, `conf t`, `rel`) are expanded before classification — abbreviated and full forms behave identically.
  • Newline/control-character injection is structurally impossible: multi-line input is rejected before any device is touched.
  • Every change gets an immutable pre-change backup (0600 perms, sha256 sidecar) — if the backup fails, the device is never touched. Backups are the rollback substrate for a future rollback tool.
  • Applied configs are transcript-parsed heuristically; a detected device error is never reported as success, and `device_state` stays honestly "unknown" (no read-back in v1).
  • All writes are appended to an audit log with change ids and sha256 hashes — never config text.

Prerequisites

RequirementVersionNotes
Python3.12+Required for type hint syntax and pathlib improvements
uvlatestRecommended package manager (install)
NAPALM-supported devicesVendor-specificSSH, eAPI, or NETCONF access to target devices

Setup

Nornir configuration

The server requires a Nornir configuration file, provided via the `NORNIR_CONFIG` environment variable.

Configuration Setup

  • Copy the included example config to the project root (or any path you prefer):
bash
cp config.example.yaml config.yaml
  • Edit `config.yaml` to point at your inventory files. A minimal config looks like:
yaml
---
inventory:
  plugin: SimpleInventory
  options:
    host_file: "inventory/hosts.yaml"
    group_file: "inventory/groups.yaml"
    defaults_file: "inventory/defaults.yaml"

runner:
  plugin: threaded
  options:
    num_workers: 10

logging:
  enabled: false

_Note: The inventory files referenced must exist relative to this config file._


MCP client configuration

Register this server with any MCP client (Claude Desktop, VS Code, etc.) by adding the following to your project's `.mcp.json`:

json
{
  "mcpServers": {
    "nornir": {
      "command": "uvx",
      "args": [
        "--from",
        "git+https://github.com/sydasif/nornir-napalm-mcp",
        "nornir-mcp"
      ],
      "env": {
        "NORNIR_CONFIG": "/absolute/path/to/config.yaml"
      }
    }
  }
}

Environment variables

VariableDefaultDescription
`NORNIR_CONFIG`— (required)Path to the Nornir bootstrap config
`NORNIR_MCP_BACKUP_DIR``./backups`Root directory for immutable backups
`NORNIR_MCP_AUDIT_DIR``./audit`Root directory for the append-only audit log
`NORNIR_MCP_MAX_OUTPUT_BYTES``65536`Per-output truncation budget (spec §21.1)

NAPALM getters

Use `nornir_run_getter` with any of these:

GetterDescription
`arp_table`ARP table
`bgp_config`BGP running configuration
`bgp_neighbors`BGP neighbors summary
`bgp_neighbors_detail`BGP neighbors detailed
`config`Running/startup/candidate configuration
`facts`System facts (vendor, model, OS, serial, uptime)
`interfaces`Interface status and details
`interfaces_ip`IP addresses on interfaces
`lldp_neighbors`LLDP neighbors summary
`lldp_neighbors_detail`LLDP neighbors detailed
`mac_address_table`MAC address table
`ntp_servers`NTP server configuration
`snmp_information`SNMP configuration
`vlans`VLAN information

Usage

CLI help

bash
uv run nornir-mcp --help

_Note: there is no CLI flag for listing inventory — use the `nornir_list_inventory` MCP tool instead._

Run as MCP server (STDIO)

bash
NORNIR_CONFIG=/path/to/config.yaml uv run nornir-mcp

Run as HTTP server

bash
NORNIR_CONFIG=/path/to/config.yaml uv run nornir-mcp --transport http --host 0.0.0.0 --port 8000

Run as Python module

bash
NORNIR_CONFIG=/path/to/config.yaml uv run python -m nornir_mcp

Project Structure

code
nornir-mcp/
├── nornir_mcp/
│   ├── __init__.py       # Package version
│   ├── __main__.py       # python -m support
│   ├── main.py           # CLI entry point (argparse, transport selection)
│   ├── models.py         # Pydantic data models (InventoryDevice, GetterInfo)
│   ├── errors.py         # Categorized exceptions (McpError, ErrorType) with retryable policy
│   ├── responses.py      # ToolEnvelope / HostOutcome response models and truncation
│   ├── policy.py         # Command canonicalization + classification (READ/SAFE/CONFIG/DANGEROUS/BLOCKED/UNKNOWN)
│   ├── capability.py     # Platform capability gate (netmiko device-type mapping)
│   ├── storage.py        # Immutable backup storage (0600 files, sha256 sidecars, traversal-safe)
│   ├── audit.py          # Append-only JSONL audit logger (hashes only, never config text)
│   ├── changes.py        # Write-path orchestration: capture, plan, fail-closed backups, dry-run, transcript parse
│   ├── runner.py         # Nornir init, config loading, singleton caching, execution lock, NornirLike protocol
│   ├── tasks.py          # Task helpers: device filtering, execution, outcome normalization
│   ├── introspection.py  # NAPALM getter discovery per platform
│   ├── server.py         # FastMCP server instance and the 12 tool definitions
│   └── py.typed          # PEP 561 marker for downstream type checking
├── tests/
│   ├── conftest.py       # Fake Nornir stubs, fake netmiko tasks, pytest fixtures
│   ├── test_server.py    # Unit tests for the 12 MCP tools, envelope contract, tool-surface pin
│   ├── test_policy.py    # Canonicalization, classification, config-line gating
│   ├── test_capability.py# Capability gate + netmiko fakes
│   ├── test_storage.py   # Immutable backup storage
│   ├── test_audit.py     # Audit logger
│   ├── test_tasks.py     # Device filtering and task execution
│   ├── test_runner.py    # Config loading and path expansion
│   ├── test_locking.py   # Global execution lock
│   ├── test_changes.py   # Planning, pre-change backups, dry-run, transcript parsing
│   ├── test_e2e.py       # Full-stack tests through the MCP protocol layer
│   ├── test_cli.py       # CLI entry points
├── config.example.yaml   # Example Nornir configuration
├── pyproject.toml        # Build config, dependencies, and tool settings
├── uv.lock               # Locked dependencies
└── README.md

Contributing

Development workflow

bash
# Install dependencies
uv sync

# Run tests
uv run pytest

# Run tests with coverage
uv run pytest --cov=nornir_mcp --cov-branch

# Lint
uv run ruff check .

# Auto-fix lint issues
uv run ruff check --fix .

# Format
uv run ruff format .

# Type check (strict mode)
uv run mypy .

Code standards

  • Python 3.12+ — use modern syntax (f-strings, `match`, `str.removeprefix`)
  • Type hints — required on all function signatures (`mypy --strict`)
  • Docstrings — Google-style with `Args:`, `Returns:`, `Raises:`
  • Tests — AAA pattern, one assertion per logical check, use `pytest` fixtures
  • Linting — `ruff` with `E`, `F`, `I`, `UP` rules

Commit conventions

Use Conventional Commits:

code
feat(server): add nornir_ping tool
fix(runner): handle missing NORNIR_CONFIG gracefully
refactor: extract _run_nornir_task helper
test: add runner config expansion tests

Companion Lab

Test against real devices using the netlab-demo test lab with Cisco devices via Containerlab.


License

MIT

Frequently asked questions

What is nornir-napalm-mcp?

nornir-napalm-mcp is FastMCP server exposing Nornir + NAPALM network device state to AI assistants

How do I install nornir-napalm-mcp?

Open the GitHub repository and follow its README. Most MCP servers are added to your client's MCP config, then called by your agent.

Is nornir-napalm-mcp open source?

Yes — it is hosted on GitHub at https://github.com/sydasif/nornir-napalm-mcp and has 2 stars.

Related MCP tools

Run your own MCP server? See who uses it and what to fix.

Measure it with TrackMCP