Junos MCP

io.github.shigechikaPublished by GitHub account shigechikav0.23.0Updated Nov 1, 2026

MCP server for Juniper Networks device operations via junos-ops

Overview

AI-generated overview

MCP server that lets an AI assistant inspect, configure, upgrade and diagnose Juniper Junos network devices through junos-ops over NETCONF.

What you can do
  • Read device facts, versions, router lists and health status
  • Run single, multiple or batched CLI show commands across hosts, with tag filters and regex output filtering
  • Read configuration, show config diffs against rollback versions, and push config with commit confirmed and health checks
  • Check upgrade readiness, compare versions, copy and install firmware packages, roll back packages and schedule reboots
  • Collect RSI/SCF diagnostics and run pre-flight reachability, inventory and package checks
  • Produce a daily brief of alarms, interface state, syslog patterns and route counts across devices
When to use it
Useful for network engineers who already manage Juniper devices with junos-ops and want an assistant to run show commands, gather diagnostics, do morning health sweeps or stage configuration and firmware changes across a fleet of routers.
Requirements
Runs locally as a Python package (Python 3.12+, MCP Python SDK 1.0+) with junos-ops installed. Needs a working config.ini inventory with device credentials, found via JUNOS_OPS_CONFIG, ./config.ini or ~/.config/junos-ops/config.ini. Network reachability and NETCONF access to the devices are required; optional JUNOS_MCP_POOL and JUNOS_MCP_POOL_IDLE tune the connection pool.
Before you install
Five tools change device state: push_config, copy_package, install_package, rollback_package and schedule_reboot. They default to dry_run=True, but setting dry_run=False commits real changes, installs firmware or reboots hardware. Device credentials live in config.ini and the account's Junos login class decides what can be changed. Pooled connections are long-lived SSH sessions that may conflict with session-duration policies.

Installation

In SourceWeft

  1. Open Junos MCP in the dashboard and add it to a workspace.
  2. Enable the server for the chats that should use its tools.

Desktop only via STDIO. STDIO servers start a local process, so they need the SourceWeft desktop host.

Other MCP clients

Follow the launch instructions in the repository.

README

junos-mcp

English | 日本語

MCP (Model Context Protocol) server for junos-ops.

Exposes Juniper Networks device operations to MCP-compatible AI assistants (Claude Desktop, Claude Code, OpenAI Codex, etc.) via STDIO transport. While junos-ops is the CLI tool for humans, junos-mcp is the AI-facing interface to the same powerful engine.

Features

Device Information

ToolDescriptionConnection
get_device_factsGet basic device information (model, hostname, serial, version)Yes
get_versionGet JUNOS version with upgrade statusYes
get_router_listList routers from config.ini (optionally filtered by tags)No
health_checkReport server version + config status (router count, distinct tags). Lightweight; does NOT connect to any deviceNo

CLI Command Execution

ToolDescriptionConnection
run_show_commandRun a single CLI show command (output_format: text/json/xml)Yes
run_show_commandsRun multiple CLI commands in a single session (output_format: text/json/xml)Yes
run_show_command_batchRun a command on multiple devices in parallel (supports tag filter and grep_pattern)Yes

Configuration Management

ToolDescriptionConnection
get_configGet device configuration (text/set/xml format)Yes
get_config_diffShow config diff against a rollback versionYes
push_configPush config with commit confirmed + health checkYes
push_config_start / push_config_resultThe same push as a background job, for hosted clients that cut a call at about 60 s and for slow-committing models (SRX branch): push_config_start returns the diff and a job_id without waiting for the commit, which runs on the same locked session; poll push_config_result until it is no longer runningYes

Upgrade Operations

ToolDescriptionConnection
check_upgrade_readinessCheck if device is ready for upgradeYes
compare_versionCompare two JUNOS version stringsNo
get_package_infoGet model-specific package file and hashNo
list_remote_filesList files on remote device pathYes
copy_packageCopy firmware package via SCP with checksumYes
install_packageInstall firmware with pre-flight checks (unlink flag for EX2300/EX3400)Yes
rollback_packageRollback to previous package versionYes
schedule_rebootSchedule device reboot at specified timeYes

Diagnostics

ToolDescriptionConnection
collect_rsiCollect RSI/SCF with model-specific timeoutsYes
collect_rsi_batchCollect RSI/SCF from multiple devices in parallel (supports tag filter)Yes

Pre-flight Checks

Equivalent to the junos-ops check subcommand modes. All three reuse the junos-ops display layer for table rendering.

ToolDescriptionConnection
check_reachabilityProbe NETCONF reachability + available disk space per host (fast: no facts, 5s TCP probe)Yes
check_local_inventoryVerify local firmware checksums against config.ini inventoryNo
check_remote_packagesVerify staged firmware checksum + available disk space on devices (post-SCP verification)Yes

Daily Operations

ToolDescriptionConnection
daily_briefMorning health check across multiple devices in parallel — alarms, interface up/down, syslog alert patterns within a look-back window (since_hours, default 18 h), dual-RE faults ([RE_FAULT]; skipped on SRX chassis clusters, whose facts misreport RE status — a failed cluster node surfaces via chassis alarms instead), and an optional inet.0 route-count baseline (route_baseline, e.g. tags=["main"], route_baseline=152). Returns a CRITICAL/WARNING/OK Markdown summary.Yes
daily_brief_start / daily_brief_resultRun the same sweep as a background job for fleets too large for one call (a hosted client's per-call limit is about 60 s): daily_brief_start returns a job_id at once, poll daily_brief_result until done. The synchronous daily_brief now stops after JUNOS_DEADLINE seconds (default 45; 0 disables) and lists unfinished hosts under NOT CHECKED.Yes

Safety by Design

All destructive operations (push_config, copy_package, install_package, rollback_package, schedule_reboot) default to dry-run mode (dry_run=True). The AI assistant must explicitly set dry_run=False to make changes.

push_config provides additional safety features not found in other Junos MCP servers:

  • commit confirmed with configurable timeout (auto-rollback if not confirmed)
  • Fallback health check after commit (ping, NETCONF uptime probe, or any CLI command)
  • Automatic rollback if health check fails (commit is not confirmed, timer expires)
  • no_commit=True — issues commit confirmed but intentionally skips the final commit. JUNOS auto-rolls back after confirm_timeout minutes. Useful for restarting services that lack a request ...restart command (e.g. syslog daemon on EX3400 post-upgrade).

RPC timeout and the per-call deadline. Every load/commit RPC uses timeout from the host's config.ini section, else the junos-ops model default: 180 s on SRX branch models (SRX300/320/340/345 and the like, whose commits can exceed 30 s), 120 s otherwise. It is never shortened. With JUNOS_DEADLINE set (default 45 s; 0 disables), push_config runs the push as a background job and waits for it until shortly before the deadline: if it finishes, the answer is the same as without a deadline; if not, the answer says Still running with a job_id to poll with push_config_result. push_config_start returns the diff and the job_id without waiting for the commit; the job loads and diffs the change and then commits on the same locked session, so what is committed is exactly the diff returned. A failure after commit confirmed has been sent is reported as a possibly pending commit that rolls back on the device after confirm_timeout minutes. Only one push per host runs at a time; a second one is refused, and other tools on that host wait at most until the deadline and then answer host busy.

Requirements

Installation

bash
pip install junos-mcp

Or for development:

bash
git clone https://github.com/shigechika/junos-mcp.gitcd junos-mcppython3 -m venv .venv. .venv/bin/activatepip install -e ".[test]"

CLI options

bash
python -m junos_mcp --help
OptionDescription
-V, --versionPrint version and exit
--checkLoad config.ini, list routers, and exit (exit code 1 on error)
--check-host HOSTNAMEWith --check, also open a NETCONF session to verify reachability/auth
--transport {stdio,streamable-http}Transport protocol (default: stdio)
--host HOST / --port PORTstreamable-http only: address and port to listen on (default: 127.0.0.1 / 8000). Use these rather than FASTMCP_HOST / FASTMCP_PORT, which MCP Python SDK 2.x no longer reads

--check is handy to verify JUNOS_OPS_CONFIG and config.ini are reachable before registering the server with an AI assistant. Combine with --check-host rt1 to also confirm that credentials actually authenticate against a real device.

Tag-based host filtering

run_show_command_batch, collect_rsi_batch, and get_router_list accept an optional tags argument. The grammar matches the junos-ops --tags CLI flag (since junos-mcp 0.9.0 / junos-ops 0.16.6):

  • Each list element is one tag group. Comma-separated tags inside a group AND together.
  • Multiple list elements OR together across groups.
  • When combined with hostnames on batch tools, the result is the intersection (tags filter further narrowed by names). An empty intersection returns an error.
python
# 1 group, 1 tag — hosts tagged "main"run_show_command_batch(command="show route summary", tags=["main"])
# 1 group, 2 tags — AND within the group: tokyo AND edgecollect_rsi_batch(tags=["tokyo,edge"])
# 2 groups — OR across groups: main OR backupget_router_list(tags=["main", "backup"])
# Mixed: (tokyo AND core) OR backuprun_show_command_batch(command="show version", tags=["tokyo,core", "backup"])
# Intersection: among backup-tagged hosts, only rt1/rt2run_show_command_batch(    command="show version",    hostnames=["rt1.example.jp", "rt2.example.jp"],    tags=["backup"],)

See the junos-ops tag documentation for how to tag sections in config.ini and for the matching CLI grammar.

Structured output format

run_show_command and run_show_commands accept an optional output_format parameter:

ValueDescription
"text"Default. Plain-text CLI output (same as typing the command)
"json"NETCONF JSON output — device returns a structured dict
"xml"NETCONF XML output — device returns pretty-printed XML

Note: CLI pipe stages (| match, | last, | count, etc.) are silently dropped regardless of output_format. PyEZ's Device.cli() sends the command over NETCONF RPC, which JunOS does not pipe-process. Run the command without pipes and filter client-side instead. For a single command, run_show_command_batch's grep_pattern argument (see below) offers server-side-style filtering — even against a single host, by passing a one-element hostnames list — but it always fetches plain-text output internally (it cannot be combined with output_format="json"/"xml"), and it only accepts one command at a time, so it isn't a drop-in workaround for run_show_commands' multi-command case.

python
# Get structured BGP summary datarun_show_command("router-a", "show bgp summary", output_format="json")

Server-side output filtering

run_show_command_batch accepts an optional grep_pattern argument (Python re pattern). When set, only lines matching the pattern are kept from each host's output. Header lines (starting with #) are always preserved. Hosts with no matching lines show (no match).

This reduces large batch results — for example, 93 routers × show route summary — from hundreds of KB to a few hundred bytes by extracting just the relevant lines:

python
# Extract only the inet.0 destination count from 93 routersrun_show_command_batch(    command="show route summary",    tags=["main"],    grep_pattern=r"inet\.0:\s+\d+ destinations",)

Connection pool

junos-mcp maintains a per-host NETCONF connection pool. Reusing an idle Device avoids the TCP/NETCONF handshake on every tool call; the pool serialises concurrent operations on the same host through a per-host lock.

Environment variableDefaultDescription
JUNOS_MCP_POOL1 (enabled)Set to 0 to disable the pool and open a fresh connection per call
JUNOS_MCP_POOL_IDLE60Idle timeout in seconds. Connections unused longer than this are closed on the next call. Set to 0 to disable eviction

Security note: pooled connections are long-lived SSH sessions. In environments where session duration is restricted by policy, set JUNOS_MCP_POOL_IDLE to a value shorter than the inactivity limit, or set JUNOS_MCP_POOL=0 to disable the pool entirely.

Configuration

This server uses the same config.ini as junos-ops. See junos-ops README for details.

Each tool accepts an optional config_path parameter. If omitted, the default search order is used:

  1. Environment variable JUNOS_OPS_CONFIG
  2. ./config.ini
  3. ~/.config/junos-ops/config.ini

config.ini is not optional in practice: every tool — including get_router_list and health_check, which never open a device connection — reads from it at startup, and there is no degrade-gracefully path if it can't be found. Put a working config.ini in one of the three locations above before registering the server with any MCP client.

Write operations

Five tools change device state. Everything else only reads. These are the same five that default to dry_run=True — see Safety by Design for the dry-run and commit-confirmed mechanics; this table is about what each one calls and the device-side privilege that gates it.

ToolAPI callPermission gate
push_config (and its background form push_config_start)jnpr.junos.utils.config.Config: lock → load(format="set") → diff → commit_check → commit(confirm=confirm_timeout) → health check → final commit → unlockThe config.ini account for the target host needs a JUNOS login class permitting configuration mode and commit — not a read-only/operator class. The exact class name is whatever was provisioned per device in config.ini.
copy_packagejunos_ops.upgrade.copy() — SCPs the firmware package to the device with checksum verification and pre-copy storage cleanupSame account needs file-copy / storage-write access (SCP to device flash).
install_packagejunos_ops.upgrade.install() — version check, pending-rollback check, copy + checksum, clear reboot schedule, rescue-config save, then PyEZ SW.install() (or request system software add via the unlink CLI path on low-flash EX2300/EX3400)Requires software-installation privilege — JUNOS maintenance-class or superuser login class.
rollback_packagejunos_ops.upgrade.rollback() — equivalent of request system software rollback, only after confirming a pending version existsSame elevated software-maintenance privilege as install_package.
schedule_rebootSchedules request system reboot at <time>Requires reboot/maintenance privilege on the device.

Provision the config.ini account for a host with a read-only/operator login class and these five tools fail against that host with a permission error; every other tool — show commands, config reads, diagnostics, daily_brief — keeps working. There is no separate plugin-level switch for this: the privilege boundary is entirely in the JUNOS login class assigned to the account in config.ini.

Usage

Claude Code (plugin)

This repository doubles as a single-plugin marketplace, so Claude Code can install the server for you:

/plugin marketplace add shigechika/junos-mcp/plugin install junos-mcp@junos-mcp

The plugin launches uvx junos-mcp and reads the same environment variables described in Configuration; export JUNOS_OPS_CONFIG (or drop config.ini at ./config.ini or ~/.config/junos-ops/config.ini) before starting Claude Code.

uvx must be on the PATH of the process that runs Claude Code — a login shell usually has it, but a GUI-launched app may not; install uv system-wide if the plugin fails to start.

Claude Code (manual)

Register the MCP server with claude mcp add:

bash
claude mcp add junos-mcp \  -e JUNOS_OPS_CONFIG=~/.config/junos-ops/config.ini \  -- python -m junos_mcp

The --scope (-s) option controls where the configuration is stored:

ScopeDescriptionConfig location
local (default)Current project, current user only~/.claude.json
projectCurrent project, shared with team.mcp.json in project root
userAll projects, current user only~/.claude.json

Claude Desktop

Add to Claude Desktop config file:

OSConfig file
macOS~/Library/Application Support/Claude/claude_desktop_config.json
Windows%APPDATA%\Claude\claude_desktop_config.json
Linux~/.config/Claude/claude_desktop_config.json
json
{  "mcpServers": {    "junos-mcp": {      "command": "python",      "args": ["-m", "junos_mcp"],      "env": {        "JUNOS_OPS_CONFIG": "/path/to/config.ini"      }    }  }}

Restart Claude Desktop after editing.

Codex

Register the MCP server with codex mcp add:

bash
codex mcp add junos-mcp \  --env JUNOS_OPS_CONFIG=~/.config/junos-ops/config.ini \  -- python -m junos_mcp

Any other MCP client that launches stdio servers works the same way: give it python -m junos_mcp as the command and set JUNOS_OPS_CONFIG in its environment.

Remote Access with OAuth (via mcp-stdio)

junos-mcp supports Streamable HTTP transport, enabling remote access from any MCP client (Claude Desktop, Claude Code, Codex, …) through mcp-stdio as an OAuth proxy.

mermaid
graph TB    A[junos-mcp<br/>remote server] <-- "OAuth 2.1 + HTTPS" --> B[mcp-stdio<br/>proxy]    B <-- "STDIO" --> C[MCP client<br/>Claude / Codex / …]

Step 1: Start junos-mcp with Streamable HTTP on the remote server

bash
JUNOS_OPS_CONFIG=~/.config/junos-ops/config.ini \  python -m junos_mcp --transport streamable-http

The server listens on http://localhost:8000/mcp by default.

Step 2: Register mcp-stdio as the MCP server on your local machine

bash
# Claude Codeclaude mcp add junos-mcp -- mcp-stdio https://your-server:8000/mcp
# Codexcodex mcp add junos-mcp -- mcp-stdio https://your-server:8000/mcp

mcp-stdio handles OAuth 2.1 authentication (RFC 8414 discovery, RFC 7591 dynamic client registration, PKCE) and relays STDIO ↔ Streamable HTTP.

See mcp-stdio README for detailed configuration including OAuth provider setup.

When junos-mcp is launched by mcp-stdio serve (the gateway mode) and runs on MCP SDK 2.x, use mcp-stdio 0.43.7 or later. Older gateways forward a request envelope that a 2.x server rejects; on those, install with mcp<2.

MCP Inspector (development)

bash
mcp dev junos_mcp/server.py

Testing

bash
pytest tests/ -v

133 tests covering all 23 tools, the connection pool, helper functions, and edge cases.

Live smoke test

Those tests mock PyEZ, which is what makes them fast — and also what makes them blind to a tool that has stopped returning real data. scripts/smoke_test.py runs every registered tool against the configured devices and fails on empty, malformed or error answers:

bash
# uses the same inventory file as the server (JUNOS_OPS_CONFIG)uv run python scripts/smoke_test.pyuv run python scripts/smoke_test.py --only facts --traceback
  • Read-only. push_config, push_config_start, copy_package, install_package, rollback_package and schedule_reboot are skipped by name, and a test enforces that. collect_rsi / collect_rsi_batch are skipped too — they change nothing, but they are minutes of RE CPU and a file per device for an answer no assertion would read. The command-running tools are exercised with show system uptime: they accept operational commands in general, and a smoke test must not be the thing that types one that matters.
  • No payloads in the report. Tool names and statuses only; error text is redacted too, since these tools quote the device they were asked about and the payloads are configuration.
  • Nothing estate-specific in the specs. The device the per-host tools need is discovered at run time from the configured inventory, and the hardware model get_package_info needs comes from that device's own facts. Two tests keep it that way: one refuses those parameters as literals, the other bans anything address-shaped anywhere in the file, because this repository is public.
  • Every probe refuses the Error: ... / Connection error: ... lines these tools return in place of raising — otherwise an unreachable device would read as a successful call.
  • CI enforces the cheap half: a tool registered without a probe spec fails the build (tests/test_smoke_probes.py), so adding a tool forces the question "how would we know it works?".
  • scripts/smoke_harness.py is the engine and holds no JUNOS knowledge: it is kept identical across the servers that share it, so fix engine bugs once and sync the file rather than patching this copy.

Architecture

Stdout-safe by construction

Since junos-ops 0.14.1, core functions return structured dict values and never print to stdout; MCP tools render output via junos_ops.display.format_*(). No contextlib.redirect_stdout is needed, so the MCP STDIO JSON-RPC channel stays clean.

Global State Initialization

junos-ops uses common.args and common.config as global variables. The MCP server initializes these using the same pattern as the test fixtures in junos-ops (conftest.py).

Parallel Execution

Batch tools (run_show_command_batch, collect_rsi_batch) use ThreadPoolExecutor via junos-ops common.run_parallel() with configurable max_workers.

License

Apache License 2.0

Source: README.md at commit 9f2b393

Tools

0
SourceWeft has not started this package in its sandbox yet, so its tools are not known.

Version history

4
  1. v0.23.0LatestOct 18, 2026
  2. v0.22.1Oct 8, 2026
  3. v0.22.0Sep 29, 2026
  4. v0.18.0Sep 16, 2026

Score

50% of the applicable checks met

How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.

  • Reached in the last 30 days25 pts

    Keep the endpoint answering the MCP handshake; SourceWeft probes it every 30 days.

  • Auth declared10 pts

    Declare the credential headers in the registry entry, or keep the endpoint answering the probe so its auth can be classified.

  • Tools listed15 pts

    Answer tools/list without a credential, or ship a package that starts in a sandbox without one.

  • DescriptionRequired15 pts
  • Logo10 pts
  • Documentation15 pts
  • License10 pts