{
  "markdown": "<!-- mcp-name: io.github.shigechika/junos-mcp -->\n\n# junos-mcp\n\nEnglish | [日本語](README.ja.md)\n\nMCP (Model Context Protocol) server for [junos-ops](https://github.com/shigechika/junos-ops).\n\nExposes Juniper Networks device operations to MCP-compatible AI assistants\n(Claude Desktop, Claude Code, etc.) via STDIO transport.\nWhile [junos-ops](https://github.com/shigechika/junos-ops) is the CLI tool for humans,\n**junos-mcp** is the AI-facing interface to the same powerful engine.\n\n## Features\n\n### Device Information\n\n| Tool | Description | Connection |\n|------|-------------|:----------:|\n| `get_device_facts` | Get basic device information (model, hostname, serial, version) | Yes |\n| `get_version` | Get JUNOS version with upgrade status | Yes |\n| `get_router_list` | List routers from config.ini (optionally filtered by tags) | No |\n| `health_check` | Report server version + config status (router count, distinct tags). Lightweight; does NOT connect to any device | No |\n\n### CLI Command Execution\n\n| Tool | Description | Connection |\n|------|-------------|:----------:|\n| `run_show_command` | Run a single CLI show command (`output_format`: text/json/xml) | Yes |\n| `run_show_commands` | Run multiple CLI commands in a single session (`output_format`: text/json/xml) | Yes |\n| `run_show_command_batch` | Run a command on multiple devices in parallel (supports tag filter and `grep_pattern`) | Yes |\n\n### Configuration Management\n\n| Tool | Description | Connection |\n|------|-------------|:----------:|\n| `get_config` | Get device configuration (text/set/xml format) | Yes |\n| `get_config_diff` | Show config diff against a rollback version | Yes |\n| `push_config` | Push config with commit confirmed + health check | Yes |\n\n### Upgrade Operations\n\n| Tool | Description | Connection |\n|------|-------------|:----------:|\n| `check_upgrade_readiness` | Check if device is ready for upgrade | Yes |\n| `compare_version` | Compare two JUNOS version strings | No |\n| `get_package_info` | Get model-specific package file and hash | No |\n| `list_remote_files` | List files on remote device path | Yes |\n| `copy_package` | Copy firmware package via SCP with checksum | Yes |\n| `install_package` | Install firmware with pre-flight checks (`unlink` flag for EX2300/EX3400) | Yes |\n| `rollback_package` | Rollback to previous package version | Yes |\n| `schedule_reboot` | Schedule device reboot at specified time | Yes |\n\n### Diagnostics\n\n| Tool | Description | Connection |\n|------|-------------|:----------:|\n| `collect_rsi` | Collect RSI/SCF with model-specific timeouts | Yes |\n| `collect_rsi_batch` | Collect RSI/SCF from multiple devices in parallel (supports tag filter) | Yes |\n\n### Pre-flight Checks\n\nEquivalent to the `junos-ops check` subcommand modes. All three reuse the\njunos-ops display layer for table rendering.\n\n| Tool | Description | Connection |\n|------|-------------|:----------:|\n| `check_reachability` | Probe NETCONF reachability + available disk space per host (fast: no facts, 5s TCP probe) | Yes |\n| `check_local_inventory` | Verify local firmware checksums against config.ini inventory | No |\n| `check_remote_packages` | Verify staged firmware checksum + available disk space on devices (post-SCP verification) | Yes |\n\n### Daily Operations\n\n| Tool | Description | Connection |\n|------|-------------|:----------:|\n| `daily_brief` | Morning 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 |\n\n### Safety by Design\n\nAll destructive operations (`push_config`, `copy_package`, `install_package`,\n`rollback_package`, `schedule_reboot`) default to **dry-run mode** (`dry_run=True`).\nThe AI assistant must explicitly set `dry_run=False` to make changes.\n\n`push_config` provides additional safety features not found in other Junos MCP servers:\n\n- **commit confirmed** with configurable timeout (auto-rollback if not confirmed)\n- **Fallback health check** after commit (ping, NETCONF uptime probe, or any CLI command)\n- **Automatic rollback** if health check fails (commit is not confirmed, timer expires)\n- **`no_commit=True`** — issues `commit confirmed` but intentionally skips the final commit.\n  JUNOS auto-rolls back after `confirm_timeout` minutes. Useful for restarting services that\n  lack a `request ...restart` command (e.g. syslog daemon on EX3400 post-upgrade).\n\n## Requirements\n\n- Python 3.12+\n- [junos-ops](https://github.com/shigechika/junos-ops) with a valid `config.ini`\n- [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk) >= 1.0\n\n## Installation\n\n```bash\npip install junos-mcp\n```\n\nOr for development:\n\n```bash\ngit clone https://github.com/shigechika/junos-mcp.git\ncd junos-mcp\npython3 -m venv .venv\n. .venv/bin/activate\npip install -e \".[test]\"\n```\n\n## CLI options\n\n```bash\npython -m junos_mcp --help\n```\n\n| Option | Description |\n|--------|-------------|\n| `-V`, `--version` | Print version and exit |\n| `--check` | Load config.ini, list routers, and exit (exit code 1 on error) |\n| `--check-host HOSTNAME` | With `--check`, also open a NETCONF session to verify reachability/auth |\n| `--transport {stdio,streamable-http}` | Transport protocol (default: `stdio`) |\n\n`--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.\n\n## Tag-based host filtering\n\n`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):\n\n- Each list element is **one tag group**. Comma-separated tags inside a group **AND** together.\n- Multiple list elements **OR** together across groups.\n- When combined with `hostnames` on batch tools, the result is the **intersection** (tags filter further narrowed by names). An empty intersection returns an error.\n\n```python\n# 1 group, 1 tag — hosts tagged \"main\"\nrun_show_command_batch(command=\"show route summary\", tags=[\"main\"])\n\n# 1 group, 2 tags — AND within the group: tokyo AND edge\ncollect_rsi_batch(tags=[\"tokyo,edge\"])\n\n# 2 groups — OR across groups: main OR backup\nget_router_list(tags=[\"main\", \"backup\"])\n\n# Mixed: (tokyo AND core) OR backup\nrun_show_command_batch(command=\"show version\", tags=[\"tokyo,core\", \"backup\"])\n\n# Intersection: among backup-tagged hosts, only rt1/rt2\nrun_show_command_batch(\n    command=\"show version\",\n    hostnames=[\"rt1.example.jp\", \"rt2.example.jp\"],\n    tags=[\"backup\"],\n)\n```\n\nSee the [junos-ops tag documentation](https://github.com/shigechika/junos-ops#tag-based-host-filtering) for how to tag sections in `config.ini` and for the matching CLI grammar.\n\n## Structured output format\n\n`run_show_command` and `run_show_commands` accept an optional `output_format` parameter:\n\n| Value | Description |\n|-------|-------------|\n| `\"text\"` | Default. Plain-text CLI output (same as typing the command) |\n| `\"json\"` | NETCONF JSON output — device returns a structured dict |\n| `\"xml\"` | NETCONF XML output — device returns pretty-printed XML |\n\n**Note:** CLI pipe stages (`| match`, `| last`, `| count`, etc.) are silently dropped\nregardless of `output_format`. PyEZ's `Device.cli()` sends the command over NETCONF\nRPC, which JunOS does not pipe-process. Run the command without pipes and filter\nclient-side instead. For a single command, `run_show_command_batch`'s `grep_pattern`\nargument (see below) offers server-side-style filtering — even against a single\nhost, by passing a one-element `hostnames` list — but it always fetches plain-text\noutput internally (it cannot be combined with `output_format=\"json\"`/`\"xml\"`), and\nit only accepts one command at a time, so it isn't a drop-in workaround for\n`run_show_commands`' multi-command case.\n\n```python\n# Get structured BGP summary data\nrun_show_command(\"router-a\", \"show bgp summary\", output_format=\"json\")\n```\n\n## Server-side output filtering\n\n`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)`.\n\nThis 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:\n\n```python\n# Extract only the inet.0 destination count from 93 routers\nrun_show_command_batch(\n    command=\"show route summary\",\n    tags=[\"main\"],\n    grep_pattern=r\"inet\\.0:\\s+\\d+ destinations\",\n)\n```\n\n## Connection pool\n\njunos-mcp maintains a per-host NETCONF connection pool.  Reusing an idle\n`Device` avoids the TCP/NETCONF handshake on every tool call; the pool\nserialises concurrent operations on the same host through a per-host lock.\n\n| Environment variable | Default | Description |\n|----------------------|---------|-------------|\n| `JUNOS_MCP_POOL` | `1` (enabled) | Set to `0` to disable the pool and open a fresh connection per call |\n| `JUNOS_MCP_POOL_IDLE` | `60` | Idle timeout in seconds. Connections unused longer than this are closed on the next call. Set to `0` to disable eviction |\n\n**Security note:** pooled connections are long-lived SSH sessions.  In\nenvironments where session duration is restricted by policy, set\n`JUNOS_MCP_POOL_IDLE` to a value shorter than the inactivity limit, or set\n`JUNOS_MCP_POOL=0` to disable the pool entirely.\n\n## Configuration\n\nThis server uses the same `config.ini` as junos-ops. See [junos-ops README](https://github.com/shigechika/junos-ops) for details.\n\nEach tool accepts an optional `config_path` parameter. If omitted, the default search order is used:\n1. Environment variable `JUNOS_OPS_CONFIG`\n2. `./config.ini`\n3. `~/.config/junos-ops/config.ini`\n\n`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.\n\n### Write operations\n\nFive tools change device state. Everything else only reads. These are the same five that default to `dry_run=True` — see [Safety by Design](#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.\n\n| Tool | API call | Permission gate |\n|---|---|---|\n| `push_config` | `jnpr.junos.utils.config.Config`: `lock` → `load(format=\"set\")` → `diff` → `commit_check` → `commit(confirm=confirm_timeout)` → health check → final `commit` → `unlock` | The `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`. |\n| `copy_package` | `junos_ops.upgrade.copy()` — SCPs the firmware package to the device with checksum verification and pre-copy storage cleanup | Same account needs file-copy / storage-write access (SCP to device flash). |\n| `install_package` | `junos_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. |\n| `rollback_package` | `junos_ops.upgrade.rollback()` — equivalent of `request system software rollback`, only after confirming a pending version exists | Same elevated software-maintenance privilege as `install_package`. |\n| `schedule_reboot` | Schedules `request system reboot at <time>` | Requires reboot/maintenance privilege on the device. |\n\nProvision 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`.\n\n## Usage\n\n### Claude Code (plugin)\n\nThis repository doubles as a single-plugin marketplace, so Claude Code can install the server for you:\n\n```\n/plugin marketplace add shigechika/junos-mcp\n/plugin install junos-mcp@junos-mcp\n```\n\nThe plugin launches `uvx junos-mcp` and reads the same environment variables described in [Configuration](#configuration); export `JUNOS_OPS_CONFIG` (or drop `config.ini` at `./config.ini` or `~/.config/junos-ops/config.ini`) before starting Claude Code.\n\n`uvx` must be on the `PATH` of the process that runs Claude Code — a login\nshell usually has it, but a GUI-launched app may not; install\n[uv](https://docs.astral.sh/uv/) system-wide if the plugin fails to start.\n\n### Claude Code (manual)\n\nRegister the MCP server with `claude mcp add`:\n\n```bash\nclaude mcp add junos-mcp \\\n  -e JUNOS_OPS_CONFIG=~/.config/junos-ops/config.ini \\\n  -- python -m junos_mcp\n```\n\nThe `--scope` (`-s`) option controls where the configuration is stored:\n\n| Scope | Description | Config location |\n|-------|-------------|-----------------|\n| `local` (default) | Current project, current user only | `~/.claude.json` |\n| `project` | Current project, shared with team | `.mcp.json` in project root |\n| `user` | All projects, current user only | `~/.claude.json` |\n\n### Claude Desktop\n\nAdd to Claude Desktop config file:\n\n| OS | Config file |\n|----|-------------|\n| macOS | `~/Library/Application Support/Claude/claude_desktop_config.json` |\n| Windows | `%APPDATA%\\Claude\\claude_desktop_config.json` |\n| Linux | `~/.config/Claude/claude_desktop_config.json` |\n\n```json\n{\n  \"mcpServers\": {\n    \"junos-mcp\": {\n      \"command\": \"python\",\n      \"args\": [\"-m\", \"junos_mcp\"],\n      \"env\": {\n        \"JUNOS_OPS_CONFIG\": \"/path/to/config.ini\"\n      }\n    }\n  }\n}\n```\n\nRestart Claude Desktop after editing.\n\n### Remote Access with OAuth (via mcp-stdio)\n\njunos-mcp supports Streamable HTTP transport, enabling remote access\nfrom Claude Desktop or Claude Code through\n[mcp-stdio](https://github.com/shigechika/mcp-stdio) as an OAuth proxy.\n\n```mermaid\ngraph TB\n    A[junos-mcp<br/>remote server] <-- \"OAuth 2.1 + HTTPS\" --> B[mcp-stdio<br/>proxy]\n    B <-- \"STDIO\" --> C[Claude Desktop<br/>Claude Code]\n```\n\n**Step 1: Start junos-mcp with Streamable HTTP on the remote server**\n\n```bash\nJUNOS_OPS_CONFIG=~/.config/junos-ops/config.ini \\\n  python -m junos_mcp --transport streamable-http\n```\n\nThe server listens on `http://localhost:8000/mcp` by default.\n\n**Step 2: Register mcp-stdio as the MCP server on your local machine**\n\n```bash\nclaude mcp add junos-mcp -- mcp-stdio https://your-server:8000/mcp\n```\n\nmcp-stdio handles OAuth 2.1 authentication (RFC 8414 discovery, RFC 7591\ndynamic client registration, PKCE) and relays STDIO ↔ Streamable HTTP.\n\nSee [mcp-stdio README](https://github.com/shigechika/mcp-stdio) for\ndetailed configuration including OAuth provider setup.\n\n### MCP Inspector (development)\n\n```bash\nmcp dev junos_mcp/server.py\n```\n\n## Testing\n\n```bash\npytest tests/ -v\n```\n\n133 tests covering all 23 tools, the connection pool, helper functions, and edge cases.\n\n### Live smoke test\n\nThose tests mock PyEZ, which is what makes them fast — and also what makes them\nblind to a tool that has stopped returning real data.\n`scripts/smoke_test.py` runs **every registered tool** against the configured\ndevices and fails on empty, malformed or error answers:\n\n```bash\n# uses the same inventory file as the server (JUNOS_OPS_CONFIG)\nuv run python scripts/smoke_test.py\nuv run python scripts/smoke_test.py --only facts --traceback\n```\n\n- **Read-only.** `push_config`, `copy_package`, `install_package`,\n  `rollback_package` and `schedule_reboot` are skipped by name, and a test\n  enforces that. `collect_rsi` / `collect_rsi_batch` are skipped too — they\n  change nothing, but they are minutes of RE CPU and a file per device for an\n  answer no assertion would read. The command-running tools are exercised with\n  `show system uptime`: they accept operational commands in general, and a\n  smoke test must not be the thing that types one that matters.\n- **No payloads in the report.** Tool names and statuses only; error text is\n  redacted too, since these tools quote the device they were asked about and\n  the payloads are configuration.\n- **Nothing estate-specific in the specs.** The device the per-host tools need\n  is discovered at run time from the configured inventory, and the hardware\n  model `get_package_info` needs comes from that device's own facts. Two tests\n  keep it that way: one refuses those parameters as literals, the other bans\n  anything address-shaped anywhere in the file, because this repository is\n  public.\n- Every probe refuses the `Error: ...` / `Connection error: ...` lines these\n  tools return in place of raising — otherwise an unreachable device would read\n  as a successful call.\n- CI enforces the cheap half: a tool registered without a probe spec fails the\n  build (`tests/test_smoke_probes.py`), so adding a tool forces the question\n  \"how would we know it works?\".\n- `scripts/smoke_harness.py` is the engine and holds no JUNOS knowledge: it is\n  kept identical across the servers that share it, so fix engine bugs once and\n  sync the file rather than patching this copy.\n\n## Architecture\n\n### Stdout-safe by construction\n\nSince 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.\n\n### Global State Initialization\n\njunos-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`).\n\n### Parallel Execution\n\nBatch tools (`run_show_command_batch`, `collect_rsi_batch`) use `ThreadPoolExecutor` via junos-ops `common.run_parallel()` with configurable `max_workers`.\n\n## License\n\nApache License 2.0\n",
  "bytes": 18606,
  "sha": "6027ac743590f0c00d13b6dacaae7e725ee7dee5b6a4acfbc6eedbfefd76d784",
  "repo_slug": "shigechika/junos-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_shigechika_junos_mcp_e10147cf/readme"
}