netmiko-mcp-server
Allows operating Cisco IOS network devices over SSH or Telnet, including running show commands and applying configuration changes through an explicit allowlist.
Click on "Deploy 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., "@netmiko-mcp-servershow ip interface brief on switch_ssh"
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.
English | 日本語
netmiko-mcp-server
An MCP server for operating network devices through chat. It uses netmiko for SSH / Telnet connectivity and supports enable passwords.
Warning: This tool sends commands directly to network devices. Configuration-change tools are disabled by default, and even show-style commands are all denied unless a commands file is supplied. Always verify behavior in a lab environment before connecting to production devices.
Features
SSH / Telnet support
enablepassword (secret) supportSSH public-key authentication (
use_keys,key_file) supportMCP stdio / SSE modes
Commands are denied by default; only commands explicitly allowed by the allow/deny lists in
commands.tomlcan runThe configuration-change tool (
set_config_commands_and_commit_or_save) is disabled by default; enable it explicitly with--enable-configSSE mode requires Bearer token authentication (can be explicitly disabled with
--no-http-auth, not recommended)Every command attempt and connection result is recorded to a JSON audit log (fail-closed: the operation itself fails if the log write fails)
Large output is automatically saved to a file and can be read back with paging (keeps the LLM's context from being overwhelmed)
Parallel command execution across multiple devices grouped via
[groups]Structured (JSON) output via ntc-templates with
use_textfsm=TrueInventory
password/secretvalues can be stored encrypted (Fernet symmetric encryption)import_inventory.pylets you build or append to the inventory interactively (no LLM tokens needed, with per-field validation)
Related MCP server: Network MCP Lab
Usage
1. Define devices (TOML)
Edit network_devices.toml to define [default] and individual devices.
[default]
username = "netops"
password = "password"
secret = "enablepassword"
[router_telnet]
hostname = "192.0.2.10"
device_type = "cisco_ios_telnet"
[switch_ssh]
hostname = "192.0.2.11"
device_type = "cisco_ios"
use_keys = true
key_file = "/home/user/.ssh/id_rsa"
[c1200coreSW]
hostname = "192.0.2.12"
device_type = "cisco_ios"
pre_commands = ["terminal datadump"]
ansi_escape_codes = trueBuild the inventory interactively (import_inventory.py)
Instead of editing the TOML by hand, you can build or append to the inventory with an interactive script.
uv run python import_inventory.py # default: network_devices.toml
uv run python import_inventory.py -f my_devices.toml
uv run python import_inventory.py --metadata # also prompt for public search metadataPrompts for each field (device name, hostname/IP with IPv4/IPv6/FQDN support, device_type from netmiko's platform list, etc.) with validation and re-prompting on invalid input. A wrong
device_typeshows partial-match suggestions. Enterqat the device-name prompt to finish and move to the save/confirm step.Passwords and the
enablesecret are always entered twice and must match, since neither is echoed.If the target file already exists, choose append / overwrite / abort. Append mode preserves existing comments, key order, and already-encrypted (
enc:) values untouched. Whenever the target file already exists — append as well as overwrite — a<filename>.bakbackup is written first (*.toml.bakis gitignored because it can hold credentials).Entering group names (comma-separated) per device automatically updates the
[groups]table. A group may not share a name with a device: the inventory resolves device names first, so a same-named group could never be selected.If
NETMIKO_MCP_SERVER_INVENTORY_KEYis set,password/secretare encrypted automatically before saving (see Encrypting credentials). If it isn't set, you can choose to save in plaintext or abort. This is asked before any device is entered, so aborting never discards typed input.The file is written atomically with owner-only permissions (0600), and the saved file is round-trip verified through the inventory loader after writing.
Out of scope: editing/deleting existing devices, creating the
[default]section, and prompting forpre_commands/ansi_escape_codes/ timeout fields (edit these by hand). Note that in append mode, if an existing[groups]table is at the end of the file, new device tables are appended after it — this is still valid TOML and loads correctly.
2. Command allowlist (TOML)
Create commands.toml to explicitly list the commands the LLM is allowed to run. If this file is not provided, every command is denied.
allowed_commands = [
"show version",
"show ip interface brief",
"show ip interface*", # trailing glob: matches "show ip interface" plus anything after it
"show ip route *", # space + glob: "show ip route" alone is not allowed; an argument is required
]
denied_commands = [
"show running-config", # deny always wins, even if it also matches allowed_commands
]Allowlist for configuration-change commands (when using --enable-config)
Adding config_allowed_commands/config_denied_commands to the same commands.toml applies allow/deny checks to each line passed to set_config_commands_and_commit_or_save (denied entirely if unset). If even one command in the batch is denied, nothing is sent to the device.
All four command lists must be arrays of non-empty strings; malformed lists stop server startup. An empty configuration batch is rejected and audited before connecting to a device.
config_allowed_commands = [
"interface *",
"description *",
"ip address *",
"no shutdown",
]
config_denied_commands = [
"no ip address*",
]In addition, shutdown (bringing an interface down) and clear* are always denied regardless of the above configuration (a hardcoded baseline protection — see BASELINE_CONFIG_DENIED_COMMANDS in security.py). Listing them in config_allowed_commands cannot override this. no shutdown (bringing an interface back up) is not on the dangerous side of the operation, so it is not included in the baseline deny list.
3. Device metadata and groups (optional)
Metadata
Device tables can include optional public search metadata:
[tokyo_core]
hostname = "192.0.2.20"
device_type = "cisco_ios"
site = "Tokyo"
role = "core-switch"
environment = "production"
description = "Tokyo core switch"
tags = ["bgp", "critical"]Metadata is exposed to MCP clients, so it must not contain passwords or other secrets. It is not sent to Netmiko, and a device's name cannot override its TOML table name.
Groups
Add a [groups] table to network_devices.toml to run commands in parallel across a set of devices with send_command_to_group.
[groups]
core_switches = ["switch_ssh", "c1200coreSW"]Group members must be arrays of device-name strings. Duplicate members are executed once.
Use
allinstead of a group name to target every device.allis reserved, and a group may not share a name with a device (rejected during discovery).Each group command reads devices and groups together once, so inventory changes take effect on the next call without mixing versions during execution.
Discovery tools
get_network_device_listreturns public metadata and group memberships.get_network_group_listlists groups and deduplicated member names.find_network_devices(query="", site=None, role=None, environment=None, group=None, tag=None, limit=20)returns candidates without connecting or executing. Free text uses case-insensitive substrings; other filters use case-insensitive exact matching, and all conditions combine with AND.limitis 1-100.The response includes
matches,total,truncated,requires_selection, andexecuted=false.Ambiguity is calculated before truncation: present the choices to the user and execute with an exact registered name.
4. Encrypting credentials (optional)
If plaintext passwords in the TOML file are a concern, password/secret can be encrypted.
# 1. Generate a key and set it as an environment variable (the server needs the same key at startup)
export NETMIKO_MCP_SERVER_INVENTORY_KEY=$(uv run --frozen python main.py --generate-key)
# 2. Encrypt the password and paste the result into the TOML file
uv run --frozen python main.py --encrypt-value "mypassword"
# => enc:gAAAAA...[router1]
hostname = "192.0.2.10"
device_type = "cisco_ios"
password = "enc:gAAAAA..."If NETMIKO_MCP_SERVER_INVENTORY_KEY is not set while an encrypted value is being loaded, the server fails at startup. Keep the key out of the TOML file and manage it only through the environment variable.
5. Starting the server
Checking setup (--doctor)
Check setup before starting the server, using the same options and environment as the intended startup:
uv run --frozen python main.py network_devices.toml --commands-file commands.toml --doctorValidates inventory and group references, decrypts encrypted credentials for validation, and checks SSH key paths, command policies, storage accessibility, and numeric limits. With
--sse, it also checks bind/subnet/port settings and bearer-token presence.Does not connect to devices, bind a port, start MCP, or write files. Passing these offline checks does not verify connectivity or guarantee later filesystem writes.
Add
--doctor-jsonfor machine-readable checks and remedies.Exit status is 1 for errors and 0 otherwise; warnings such as deny-all are reported without failing the check.
stdio (local)
uv run --frozen python main.py /path/to/devices.toml \
--commands-file /path/to/commands.tomlAdd --enable-config if you also want to use the configuration-change tool (disabled by default).
SSE (for remote connections)
SSE mode requires Bearer token authentication. First set the token as an environment variable.
export NETMIKO_MCP_SERVER_BEARER_TOKEN="$(openssl rand -hex 32)"uv run --frozen python main.py /path/to/devices.toml \
--commands-file /path/to/commands.toml \
--sse --bind 10.70.72.1 --port 10000Example SSE URL: http://<server-ip>:10000/sse (the client must send an Authorization: Bearer <token> header)
Starting --sse without NETMIKO_MCP_SERVER_BEARER_TOKEN set stops the server with a startup error. Only pass --no-http-auth explicitly if you want to run without authentication (not recommended).
Restricting access by subnet
In SSE mode, --allowed-subnet lets you specify allowed subnets (comma-separated, e.g. 10.70.72.0/24). The default is 0.0.0.0/0. Combining this with Bearer token authentication provides defense in depth.
uv run --frozen python main.py /path/to/devices.toml \
--commands-file /path/to/commands.toml \
--sse --bind 10.70.72.1 --allowed-subnet 10.70.72.0/24,127.0.0.1/32 --port 10000Audit log
By default, entries are recorded in JSON Lines format at ~/.netmiko_mcp_server_audit.log. Use --audit-log-file /path/to/audit.log to change the path.
Handling large output
By default, output exceeding 1000 lines is automatically saved under ~/.netmiko_mcp_server_outputs/<device>/ and can be read back with paging via the list_device_outputs/read_device_output tools. The threshold is configurable with --output-save-threshold, and the save location with --output-dir.
Saved files are created with owner-only permissions (0600) and unique filenames. Saving, listing, and reading reject symlinks that resolve outside the output directory. For paging, offset must be non-negative and limit must be positive.
Parallelism for group execution
send_command_to_group defaults to 10 concurrent connections. Change this with --max-workers.
Running with Docker
Use the published image (recommended)
GitHub Actions automatically builds and publishes an image to the GitHub Container Registry (GHCR) on every push to main or on v*.*.* tag pushes (the publish job in .github/workflows/ci.yaml). The image is only published once lint, type-check, and tests all pass.
docker pull ghcr.io/nagayon-935/netmiko_mcp_server:latestAvailable tags:
Tag | Meaning |
| latest commit on the |
| build for a specific commit (for traceability) |
| semver tags, generated only when a |
Supported architectures: linux/amd64, linux/arm64 (also works with Docker/Podman on a Raspberry Pi or Apple Silicon).
Note for first-time publishing: GHCR packages can default to Private even when the repository is public. If
docker pullfails with a 403, go to the repository's GitHub page → Packages → the package's Package settings, and change Visibility to Public. Also check that Actions haspackages: writepermission under Settings → Actions → General → Workflow permissions ("Read and write permissions" must be enabled).
Building it yourself
docker build -t netmiko-mcp-server .Then mount the device config file and the command allowlist and start the container (replace netmiko-mcp-server with ghcr.io/nagayon-935/netmiko_mcp_server:latest if you're using the published image).
docker run -d -p 10000:10000 \
-v $(pwd)/network_devices.toml:/app/config.toml \
-v $(pwd)/commands.toml:/app/commands.toml \
-e NETMIKO_MCP_SERVER_BEARER_TOKEN="$(openssl rand -hex 32)" \
--name netmiko-mcp netmiko-mcp-server \
--sse --port 10000 --commands-file /app/commands.tomlMCP tools
MCP calls return structured results in structuredContent and the same JSON as text. Success is {"ok": true, "data": ...}; failure is {"ok": false, "error": {"code": ..., "message": ..., "next_action": ..., "retryable": false, "execution_state": ...}} with MCP isError=true. This changes the MCP response shape; clients should read data instead of assuming raw output. Python helper calls retain their text/parsed-output interface.
Errors distinguish policy denial, missing inventory/devices, authentication failure, connection timeout, output storage, and audit-write failure. Raw connection exceptions are not returned. Failed configuration operations report execution_state="unknown" and retryable=false: inspect device state before repeating them. Output-save failures report execution_state="completed" because the command already ran. Group results contain a data map of per-device envelopes plus a summary of total/succeeded/failed; partial failure sets ok=false without discarding successful output. No automatic retries are performed.
Tool | Description |
| Returns the list of all devices in the inventory (no credentials included) |
| Lists groups and exact registered member names |
| Searches public metadata and returns candidates without executing |
| Sends a command to a single device, with |
| Runs a command in parallel across a device name, group name, or |
| Lists saved output files |
| Reads a saved output file with paging |
| Sends configuration-change commands (requires |
Using it from the Gemini CLI (example)
Register the server in the Gemini CLI's MCP configuration.
{
"mcpServers": {
"netmiko server": {
"url": "http://<server-ip>:10000/sse"
}
}
}If Bearer token authentication is enabled (the default), the client also needs to be configured to send an Authorization: Bearer <token> header. How to set headers varies by AI client, so check your client's MCP server configuration documentation. This is not needed if authentication was disabled with --no-http-auth (not recommended).
Notes
Set
device_typeto a name supported bynetmiko.If
secretis set,enable()is attempted automatically.Show-style tools (
send_command_and_get_output,send_command_to_group) useallowed_commands/denied_commands;set_config_commands_and_commit_or_saveusesconfig_allowed_commands/config_denied_commandsand also requires--enable-config. Everything is denied when the relevant list is unset (see Command allowlist).Only trusted operators should use configuration commands, and only within the scope of
config_allowed_commands.
Migrating from older versions
The --secured and --disable-config flags from earlier versions have been removed.
--secured(prefix-based blocklist) → replaced by the allow/deny list in--commands-file--disable-config(enabled by default, opt-out) → replaced by--enable-config(disabled by default, opt-in)
License
MIT License. See LICENSE for details.
This server cannot be deployed
Maintenance
Related MCP Connectors
MCP server for network documentation, generated by doc2mcp.
An MCP server for deep research or task groups
Nifty's MCP server — exposes tasks, projects, messages, and files as tools for AI agents.
An MCP server that let you interact with Cycloid.io Internal Development Portal and Platform
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceSecure network administration MCP server that enables AI assistants to safely execute networking commands like SSH, ping, and DNS lookups inside a Docker container sandbox.16MIT
- FlicenseNot gradedqualityDmaintenanceMCP server for executing show commands and managing Cisco IOS-XE network devices via async SSH using Scrapli.6-
- FlicenseAqualityFmaintenanceAn MCP server that integrates Nornir with NAPALM and Netmiko, enabling LLMs to orchestrate multi-vendor network infrastructure through natural language.52-
- AlicenseNot gradedqualityDmaintenanceMCP server for network operations that lets AI assistants interact with Cisco/Juniper network devices through safe, well-defined tools like compliance audits and configuration backups.MIT