# Rustjunosmcp

> MCP server for Juniper Junos devices, built on rustEZ.

- **Type:** MCP server
- **Install:** `agentstack add mcp-fastrevmd-lab-rustjunosmcp`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [fastrevmd-lab](https://agentstack.voostack.com/s/fastrevmd-lab)
- **Installs:** 0
- **Category:** [Integrations](https://agentstack.voostack.com/c/integrations)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [fastrevmd-lab](https://github.com/fastrevmd-lab)
- **Source:** https://github.com/fastrevmd-lab/rustjunosmcp
- **Website:** https://mechub.org

## Install

```sh
agentstack add mcp-fastrevmd-lab-rustjunosmcp
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

rust-junosmcp

One MCP server for Juniper Junos and SRX devices, in Rust
a mechub project — sovereign network-security automation

> **Unofficial / community project.** This repository is an independent, community-driven project. It is not affiliated with, endorsed by, sponsored by, or supported by Hewlett Packard Enterprise or Juniper Networks. "HPE", "Juniper", "SRX", "JUNOS", "Security Director" and "Juniper Mist" are trademarks of their respective owners and are used here only to describe what this software interoperates with. Please direct support and licensing questions about those products to the respective vendors.

A [Model Context Protocol](https://modelcontextprotocol.io/) server for Juniper Junos
and SRX devices, written in Rust. The single `rust-junosmcp` process exposes
the core Junos tools and, by default, the SRX security workflows through one
tool registry and endpoint. It is drop-in compatible with
[Juniper/junos-mcp-server](https://github.com/Juniper/junos-mcp-server) on the
inventory format and core tool surface, but built on async Rust
([rustEZ](https://github.com/fastrevmd-lab/rustEZ) +
[rustnetconf](https://github.com/fastrevmd-lab/rustnetconf)) instead of PyEZ.

## Beyond Juniper/junos-mcp-server

Drop-in on `devices.json` and the core tools — plus a lot the Python/PyEZ server doesn't have:

- **Safer config** — `commit_check_config` (validate, never commit), confirmed commits with auto-rollback, and `discard_candidate` to unstick a dirty candidate.
- **Device lifecycle** — staged `upgrade_junos` (image → install → reboot → verify), SCP `transfer_file`/`fetch_file`, PFE commands.
- **Scale & UX** — parallel session-pooled batch (~1.7× faster), `| last N`/`| count` + `max_lines`/`max_bytes` output caps, `router`/`router_name` aliases, Jinja2 templates.
- **Transport & auth** — streamable-HTTP with per-token router/tool scopes, TLS, and a `Host` allowlist; upstream is stdio-only.
- **SRX tools** (enabled by the default `srx` feature) — IDP & Application-ID **signature-package updates** (check/download/install/rollback), chassis-cluster health, license & security-services status, JTAC bundle with secret redaction.

## Performance

Benchmarked against [Juniper/junos-mcp-server](https://github.com/Juniper/junos-mcp-server)
(Python/PyEZ) on the same vSRX lab devices, same network path.

| Test | rust-junosmcp (v0.3.0) | junos-mcp (Python) | Speedup |
|------|------------------------|--------------------|---------|
| 5 sequential commands | 30.4s (6.1s/cmd) | 52.2s (10.4s/cmd) | **1.7x** |
| 5 parallel commands | 8.1s (1.6s/cmd) | 11.1s (2.2s/cmd) | **1.4x** |
| 4 routers x 3 commands (batch) | 16.1s (1.3s/cmd) | N/A | Rust-only |

Session pooling (`PooledDevice`) eliminates SSH/NETCONF handshake overhead
on sequential commands to the same router. The batch tool runs routers in
parallel with a configurable concurrency cap.

> ## v0.7.0 released
>
> Two new non-destructive candidate-safety tools and a hardened HTTP
> transport. `commit_check_config` validates a candidate (`commit check`)
> and discards it without ever activating config; `discard_candidate`
> recovers a candidate left dirty via `rollback 0`. `junos_config_diff`
> now returns an actionable hint when the on-box config won't parse for
> the current mode. Security: `rmcp` 0.8.5 → 2.0.0 (closes
> RUSTSEC-2026-0189 DNS-rebinding; adds a `Host` allowlist — off-loopback
> deployments must pass `--allowed-host`) and `quick-xml` 0.36 → 0.41
> (closes RUSTSEC-2026-0194/-0195 DoS). Tool surface 15 → 17.
>
> See the [v0.7.0 release notes](https://github.com/fastrevmd-lab/rustjunosmcp/releases/tag/v0.7.0).

## Feature scope

### v0.1 (released)

- 6 tools: `get_router_list`, `gather_device_facts`, `execute_junos_command`,
  `get_junos_config`, `junos_config_diff`, `load_and_commit_config`.
- stdio transport only.
- `devices.json` drop-in compatible (`auth.type` ∈ {`password`, `ssh_key`}).
- Docker image (distroless) and LXC release tarball with systemd unit.

### v0.2 (released)

- streamable-http transport (with optional rustls TLS).
- bearer-token auth with per-token router/tool scopes.
- SIGHUP hot-reload of the token store.

### v0.2 follow-up: PFE + batch (released)

- `execute_junos_pfe_command` — single PFE-shell call against an explicit FPC target.
- `execute_junos_command_batch` — N routers x M operational CLI commands, parallel across routers, per-command and optional whole-batch timeouts. Pre-flight blocklist + unknown-router checks; continue-on-error after pre-flight.
- New `pfe_commands` rule list under `_blocklist_defaults` and per-device `blocklist`. Independent from `commands`.

### v0.2 follow-up: Templates (released)

- `render_and_apply_j2_template` — render a Jinja2 template (inline `template_content`) with a JSON `vars_content` object. Supports single (`router_name`) or multiple routers (`router_names`), dry-run, and full commit. Reuses the same blocklist + format gating as `load_and_commit_config`.
- Vars must be a top-level JSON object. **YAML is no longer accepted** as of v0.5.2 (RJMCP-SEC-002): the `serde_yml` / `libyml` advisory chain (RUSTSEC-2025-0067/-0068) was reachable from MCP input, so the YAML branch was removed.
- Size caps: `template_content` and `vars_content` are each bounded at 64 KiB.
- Strict-undefined: missing variables fail with the variable name rather than rendering empty.
- Auto-format detection: leading `` on a Junos device. Pre-flight free-space check on `/var` (`local_size + 32 MiB` headroom), SHA-256 verify, post-transfer checksum re-validation with delete-on-mismatch. SSH-key auth only — password-auth devices rejected with `[code=unsupported_auth]`.
- **`list_staged_files`** — lists host staging dir always, plus device `/var/tmp/` listing when `router_name` is supplied.
- **Stable error codes** — every transfer failure carries an LLM-readable `[code=...]` Display tag (`bad_source_path`, `insufficient_disk`, `unsupported_auth`, `dest_exists_differs`, `scp_failed`, `connect_timeout`, `verify_mismatch`, `outer_timeout`, `device_probe_failed`).
- **New CLI flags** — `--staging-dir` (default `/var/lib/jmcp/staging`) and `--known-hosts-file` (default `/etc/jmcp/known_hosts`).
- **Packaging** — `install.sh` provisions the new on-disk surface owned by `jmcp:jmcp`. See the File transfers section below for details.
- Tool count: 11 → 13.

### v0.5 (released)

- **`upgrade_junos`** — two-call (stage then confirm) Junos software upgrade. Uploads the package via `transfer_file` semantics, runs `request system software add`, and reboots. Standalone-only; rejected if a session pool entry exists for the target router.
- Tool count: 13 → 14.

### v0.6 (released)

- **`fetch_file`** — downloads a file from `:/var/tmp/` to the host staging dir. SHA-256-verified, idempotent skip if the local copy already matches, per-router serialization. Mirror of `transfer_file`.
- Tool count: 14 → 15.

### v0.7 (released)

- **`commit_check_config`** — validate a candidate config (`commit check`) without committing — loads, diffs, checks, then discards. Never activates config. Own token scope (least-privilege).
- **`discard_candidate`** — discard uncommitted candidate changes (`rollback 0`) to recover a candidate left dirty ("configuration database modified"). Never changes the running config. Own token scope (least-privilege).
- Tool count: 15 → 17.

## Blocklist guardrails (v0.2)

`devices.json` may carry an optional `_blocklist_defaults` block plus an
optional `blocklist` field on each device entry. Rules use simple globs
(`*`, `?`) and an `action` of `"deny"` or `"allow"`. Most-specific match
wins; per-device rules tiebreak top-level defaults. See
[`devices-template.json`](devices-template.json) for an example, and
[`docs/superpowers/specs/2026-05-04-blocklist-guardrails-design.md`](docs/superpowers/specs/2026-05-04-blocklist-guardrails-design.md)
for the full design.

The `pfe_commands` rule list is independent: a deny on `commands` does not gate `execute_junos_pfe_command` and vice versa. Use it to restrict PFE inputs (e.g. `set *`) without affecting the operational CLI.

The blocklist applies to `execute_junos_command` and `load_and_commit_config`.
For `load_and_commit_config`, `config_format` must be `set` whenever the
device has any effective config rules; `text` and `xml` payloads are
rejected pre-flight in that case.

> **Compat note:** files using `_blocklist_defaults` or per-device
> `blocklist` are not cross-compatible with Juniper/junos-mcp-server's
> inventory format. Files without these fields remain drop-in compatible.

## Confirmed commits (v0.3)

`load_and_commit_config` supports Junos `commit confirmed` via the
`confirm_timeout_mins` parameter. The router auto-rolls back after N
minutes unless a follow-up commit confirms the change — a critical safety
net for remote config pushes that might break management connectivity.

```json
{
  "router_name": "core-1",
  "config_text": "set interfaces ge-0/0/0 description test",
  "confirm_timeout_mins": 10,
  "commit_comment": "safe change with rollback window"
}
```

Response:
```json
{
  "success": true,
  "diff": "[edit interfaces ge-0/0/0]\n+   description test;",
  "confirmed": true,
  "rollback_in_minutes": 10,
  "message": "Commit confirmed: auto-rollback in 10 minutes unless confirmed. Send another commit to confirm."
}
```

To confirm (prevent rollback), send another `load_and_commit_config` with
the same config (or any valid config) without `confirm_timeout_mins`.

## File transfers (`transfer_file` / `fetch_file` / `list_staged_files`)

`transfer_file` pushes a host-staged file to `/var/tmp/` on a Junos
device using legacy SCP (`scp -O`, since Junos disables the OpenSSH SFTP
subsystem). It is **idempotent on SHA-256**: if the remote file already exists
with a matching digest the call returns `status: "skipped"`. Pass `force: true`
to overwrite when digests differ.

`fetch_file` is the mirror operation: it downloads `/var/tmp/` from a
Junos device to the host staging dir using the same legacy SCP path. It is
**idempotent on SHA-256** — if the local file already exists with a matching
digest the call returns `status: "skipped"`. Per-router serialization and
post-transfer SHA-256 re-verification apply identically to `transfer_file`.

**Auth:** SSH key only. Devices with `auth.type = "password"` are rejected with
`[code=unsupported_auth]`. Add an SSH key to the device and reference its path
via `auth.private_key_path` in `devices.json`.

**On-disk surface:**

| Path                          | Purpose                                       | Default mode | Owner       |
| ----------------------------- | --------------------------------------------- | ------------ | ----------- |
| `/var/lib/jmcp/staging/`      | Host-side stage for files awaiting transfer  | `0750`       | `jmcp:jmcp` |
| `/etc/jmcp/known_hosts`       | SSH `known_hosts` consulted for every push    | `0644`       | `jmcp:jmcp` |
| `/var/lib/jmcp/device-leases` | Shared Junos/SRX destructive-operation locks | `0700`       | `jmcp:jmcp` |

Override at startup with `--staging-dir `, `--known-hosts-file `,
and `--device-lease-dir `. Junos and SRX services must use the same
device lease directory.

**Host-key policy (v0.5.2+):** scp runs with `StrictHostKeyChecking=yes` by
default — unknown device host keys are refused. The `known_hosts` file must
exist before the first `transfer_file` / `upgrade_junos` call, otherwise the
tool errors with `[code=known_hosts_missing]`. Pre-populate it with the
bundled helper:

```bash
scripts/scan-known-hosts.sh --inventory /etc/jmcp/devices.json \
                            --known-hosts /etc/jmcp/known_hosts
```

For lab / first-contact use, pass `--ssh-accept-new-host-keys` to fall back
to OpenSSH's `accept-new` (TOFU) mode.

`list_staged_files` returns the contents of the host staging dir. If
`router_name` is supplied it also runs `file list /var/tmp/ detail` on the
device and includes those entries under `device_files`.

**Source path safety:** `source_path` must be a basename only (no `/`, no `\`,
no `..`, no leading dot, ≤ 255 bytes); it is resolved relative to
`--staging-dir` and never escapes it.

**Pre-flight checks:** before scp, `transfer_file` runs
`show system storage no-forwarding` and refuses to push when free space on
`/var` is below `local_size + 32 MiB`.

**Post-verify:** unless `verify: false` is passed, the device-side checksum is
re-computed via `file checksum sha-256 /var/tmp/` and the file is
deleted on mismatch.

## Long-running operational commands

Each MCP tool exposes a per-call `timeout` parameter (default 360 s). This is
the **sole user-visible bound** on operation duration; the underlying
`rustez::Device` is configured with a 1-hour internal RPC timeout at
connection time, so commands that legitimately take many minutes
(`request system software add`, `request support information`,
`request system snapshot`, etc.) will not be silently truncated.

If you need to run an operation that exceeds 1 hour, split it into
phases or invoke the work fire-and-forget on the device and poll for
completion separately.

**Caveat:** when a long-running RPC is followed by a device reboot, the
NETCONF session will of course die. The session pool reconnects cleanly
on the next call.

## Security warning

This server lets an LLM run commands and push configuration changes against
your Junos devices. Read [Juniper/junos-mcp-server's security notice](https://github.com/Juniper/junos-mcp-server#important-security-notice)
before deploying. The same warnings apply.

- Prefer SSH key authentication over passwords.
- Review configurations before allowing commit tools to run.
- Restrict network access to the MCP server.
- Don't deploy to untrusted networks.
- Set `devices.json` permissions to `0600` — it contains SSH credentials.
- `get_junos_config` returns the full config including `## SECRET-DATA`
  hashed password lines. Restrict this tool's scope to trusted tokens.
- `reload_devices` requires `file_name` to be a *relative* path resolving
  inside the original `--device-mapping` directory (since v0.5.2). Absolute
  paths, `..` traversal, and symlinks pointing outside the inventory
  directory are all rejected.
- Text input fields (`command`, `config_text`, `template_content`,
  `pfe_command`) are capped at 1 MB. Batch lists are capped at 100
  routers and 50 commands.

## Audit logging

`rust-junosmcp` emits structured audit events for every Junos and SRX tool invocation. Each event records the caller, tool, target routers, authorization decision, outcome, and duration. See [`docs/AUDIT.md`](docs/AUDIT.md) for the full schema and forwarding guidance.

| Flag | Environment Variable | Default | Description |
|------|---------------------|---------|-------------|
| `--audit-format` | `JMCP_AUDIT_FORMAT` | `text` | Output format: `text` or `json`. |
| `--audit-log-file` | `JMCP_AUDIT_LOG_FILE` | (none) | Optional file path to append JSON events to (in addition to stderr). |
| `--audit-journald` | `JMCP_AUDIT_JOURNALD` | `false` | Optional native journald fan-out for structured audit fields; fails startup when explicitly enabled but unavailable. |

## Quick start (local)

```bash
git clone https://github.com/fastrevmd-lab/rustjunosmcp.git
cd RustJunosMCP

# Build the default 26-tool Junos/SRX server with TLS.
cargo build --release

# Optional: build the 17-tool Junos-only server without TLS.
cargo build --release --no-default-features

# Optional: build Junos-only with TLS.
cargo build --release --no-default-features --features tls

# Configure devices.
cp devices-template.json devices.json
$EDITOR devices.json   # set ip / username / auth

# Run as MCP stdio server.
./target/release/rust-junosmcp -f devices.json
```

## Claude Desktop config

One registration exposes every tool enabled in the built binary (26 with the
default `srx` feature, or 17 in a Junos-only build):

```json
{
  "mcpServers": {
    "junos": {
      "command": "/path/to/rus

…

## Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [fastrevmd-lab](https://github.com/fastrevmd-lab)
- **Source:** [fastrevmd-lab/rustjunosmcp](https://github.com/fastrevmd-lab/rustjunosmcp)
- **License:** MIT
- **Homepage:** https://mechub.org

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/mcp-fastrevmd-lab-rustjunosmcp
- Seller: https://agentstack.voostack.com/s/fastrevmd-lab
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
