nornir-napalm-mcp
FastMCP server exposing Nornir + NAPALM network device state to AI assistants
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
| Tool | Description |
|---|---|
| `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):
| Category | Read tools (`nornir_run_command*`) | Apply (`nornir_apply_config`) |
|---|---|---|
| `READ_ONLY` (`show …`) | ✅ allowed | allowed |
| `SAFE_OPERATIONAL` (`ping`, `traceroute`) | ✅ allowed | allowed |
| `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
| Requirement | Version | Notes |
|---|---|---|
| Python | 3.12+ | Required for type hint syntax and pathlib improvements |
| uv | latest | Recommended package manager (install) |
| NAPALM-supported devices | Vendor-specific | SSH, 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):
cp config.example.yaml config.yaml- Edit `config.yaml` to point at your inventory files. A minimal config looks like:
---
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`:
uvx from GitHub (recommended)
{
"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
| Variable | Default | Description |
|---|---|---|
| `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:
| Getter | Description |
|---|---|
| `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
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)
NORNIR_CONFIG=/path/to/config.yaml uv run nornir-mcpRun as HTTP server
NORNIR_CONFIG=/path/to/config.yaml uv run nornir-mcp --transport http --host 0.0.0.0 --port 8000Run as Python module
NORNIR_CONFIG=/path/to/config.yaml uv run python -m nornir_mcpProject Structure
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.mdContributing
Development workflow
# 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:
feat(server): add nornir_ping tool
fix(runner): handle missing NORNIR_CONFIG gracefully
refactor: extract _run_nornir_task helper
test: add runner config expansion testsCompanion 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
An AI Gateway, registry, and proxy that sits in front of any MCP, A2A, or REST/gRPC APIs, exposing a unified endpoint with centralized discovery, guardrails and management. Optimizes Agent & Tool calling, and supports plugins.
A super light-weight embedded code search engine CLI (AST based) that just works - improves speed and efficiency for coding agent 🌟 Star if you like it!
AI-powered OSINT agent with interactive REPL, MCP server, and CLI. 19 tools. Works with Claude, GPT-4, or local models. For authorized security research only.
Production-grade MCP server giving Claude 27 security intelligence tools across 21 APIs — CVE lookup, EPSS scoring, CISA KEV, MITRE ATT&CK, Shodan, VirusTotal, and more.
Open-source coding agent memory. Records issues, attempts, fixes and decisions, then warns your agent before it repeats an approach that already failed. Native MCP server for Claude Code, Cursor, Antigravity and Codex. 100% local, no cloud, no telemetry. MIT.
An LLM agent that conducts deep research (local and web) on any given topic and generates a long report with citations. Built for the Model Context Protocol to
Run your own MCP server? See who uses it and what to fix.
Measure it with TrackMCP