Releases: fastrevmd-lab/RustJunosMCP
Release list
v0.7.0 — candidate-safety tools + rmcp 2.0 / quick-xml 0.41
First tagged rust-junosmcp release since v0.6.3. Tool surface 15 → 17.
Added
commit_check_config(#95) — non-destructivecommit check: loads a candidate, returns{success, diff, error?}, then discards it. Never activates config. Own token scope (least-privilege). 15 → 16.discard_candidate(#107) — discard uncommitted candidate changes (rollback 0) to recover a candidate left dirty ("configuration database modified"). Never changes the running config. Own token scope. 16 → 17.junos_config_diff(#108) — when the on-box config won't parse for the current mode (e.g. after a chassis-cluster change), the raw parse error now carries an actionable hint instead of leaving the caller blind.
Security
rmcp0.8.5 → 2.0.0, closing RUSTSEC-2026-0189 (DNS rebinding in the Streamable HTTP transport). The transport now enforces aHostallowlist (default: loopback only). New flags--allowed-host <HOST>(repeatable) and--disable-host-check. Off-loopback deployments MUST pass--allowed-hostfor their LAN authority or clients receive HTTP 403.quick-xml0.36 → 0.41 (+rustez0.12.1 /rustnetconf0.12.3), closing RUSTSEC-2026-0194 / RUSTSEC-2026-0195 (quick-xml DoS). JTAC-bundle redaction now suppresses quick-xml 0.41GeneralRefentity events inside redacted elements — a bare version bump would have leaked entity fragments of secrets.
Full changelog: https://github.com/fastrevmd-lab/RustJunosMCP/blob/main/CHANGELOG.md
Diff: v0.6.3...v0.7.0
srxmcp-v0.3.0 — Phase 3 cluster-health + JTAC support-bundle
srxmcp-v0.3.0 — Phase 3
Two new MCP tools (surface 8 → 10):
-
validate_chassis_cluster_health— runs 8 cluster-scoped RPCs, applies 7 ordered checks (red_led,disabled_secondary,control_link_failure,major_alarm,minor_alarm,recent_reboot,version_skew), rolls up to a singlepass | warn | failverdict. Standalone devices short-circuit toSrxState::NotConfigured. -
collect_jtac_support_bundle— per-router serialized via in-processSemaphorekeyed on(router, "support_bundle").problem_type=genericproduces an on-device tarball (BundleLocation::Device); per-type paths capture baseline + per-type RPCs (deduped) and tar them on LXC (BundleLocation::LxcStaging) with SHA-256.
Six new typed errors with the locked [code=<snake>] router=<name>: <detail> convention.
Live-lab smokes (LXC 601:30032)
- ✅
validate_chassis_cluster_healthagainstvSRX-test19-20→state=active,verdict=fail, surfaced real lab issues (Red LED, disabled RG members, control-link transitions). - ✅
validate_chassis_cluster_healthagainstvSRX-test10→state=not_configured(standalone short-circuit). - ✅
collect_jtac_support_bundleproblem_type=genericagainstvSRX-test10→BundleLocation::Devicewithnext_stephint. - ✅
collect_jtac_support_bundleproblem_type=[routing]againstvSRX-test10→BundleLocation::LxcStaging, 6.8 KB tarball, sha256 verified, baseline + routing RPCs captured.
Deploy footnote
/etc/systemd/system/rust-srxmcp.service ReadWritePaths= widened to include /var/lib/rust-srxmcp for the LXC staging dir (/var/lib/rust-srxmcp/staging/bundles/).
Known follow-ups (cosmetic / v0.3.1)
- Tarball filename double-prefixes
srxmcp-(request_id already has it):srxmcp-srxmcp-<uuid>.tgz. - Logs path stubbed in v0.3.0 (manifest records "not implemented in v0.3.0").
- v0.3.1 plan: consolidate per-type path onto on-device
request support informationonce per-RPC scoping is verified.
Verification
- 644 workspace tests green (cargo fmt / build / clippy
-D warnings/ test) - Fixture-based tests caught and fixed a real parser bug in
check_control_link_failure(<transition-reason>vs<redundancy-group-transition-reason>)
rust-srxmcp v0.2.1 — Phase 2 AppID signature-package lifecycle
What's new
manage_appid_signature_package — full Application Identification signature-package lifecycle on Juniper SRX, sibling of manage_idp_security_package shipped in v0.2.0:
check_server— query installed + latest application-package version fromsignatures.juniper.netdownload_and_install— confirmation-gated, supports explicit version pinning, downloads and installs the latest (or pinned) AppID package + protocol bundleuninstall— confirmation-gated removal of the currently-installed application package- Cluster-aware: synchronizes both nodes on chassis-cluster devices
- Two-call confirmation protocol + per-router transfer locks (reused from v0.2.0 IDP primitives)
RPC contract (live-captured against vSRX-test3, Junos 24.4R1)
The Phase 2 design doc's AppID RPC shapes were a best-guess from CLI namespaces; v0.2.1 corrects them against the live wire format:
- All AppID RPCs are flat single-element (no composite
parent + childlike IDP). - Names use the
request-appid-application-package-*prefix (NOTrequest-services-application-identification-*, which does not exist as an RPC). - Check-server envelope is
<apppack-server-status>with<apppack-server-status-detail>, distinct from the<apppack-download-status>envelope used by the download workflow. - Async-status responses use plain-English tokens (
Downloaded/Installed/Uninstalledfor success; substringfailedfor failure) — NOT IDP'sDone;/Failed;markers. get-appid-package-versionreports<version-detail>0</...>post-uninstall on Junos 24.4R1 —normalize_version_texttreats"0","", and"N/A"as equivalent absence markers.
Validation
5/5 destructive live smokes pass on vSRX-test3 against LXC 601:30032:
appid_check_server_returns_latest_version✅appid_download_and_install_call1_returns_plan✅appid_uninstall_call1_returns_plan✅appid_uninstall_call2_succeeds✅ (real destructive uninstall — confirmed package 3910 removed)appid_cluster_install_syncs_both_nodes✅ (graceful-degrade — acceptslicense_inactiveuntil lab heals)
Lab gaps (documented, not blocking)
vSRX-test3cannot reachsignatures.juniper.netfrom the homelab;check_serverand the destructive download path emitsignatures_server_unreachableuntil egress is fixed. Smokes graceful-degrade to accept that error.- The cluster smoke (
vSRX-test19-20) requires a clustered+AppID-licensed pair the lab does not currently have; the smoke accepts alicense_inactiveor transport error in the interim.
Tool surface
8 srxmcp tools total (up from 7 in v0.2.0):
srxmcp_status,check_srx_feature_license,vpn_lifecycle_report,get_chassis_cluster_status,get_srx_security_services_statusmanage_idp_security_package(v0.2.0)manage_appid_signature_package(new in this release)
Deployed to LXC 601:30032.
rust-srxmcp v0.2.0 — Phase 2 IDP signature-package lifecycle
What's new
manage_idp_security_package — full IDP signature-package lifecycle on Juniper SRX:
check_server— query latest available signature version fromsignatures.juniper.netdownload_and_install— confirmation-gated, idempotent (already_at_targetshort-circuit), supports explicit version pinningrollback— restores previously installed signature version- Cluster-aware: synchronizes both nodes on chassis-cluster devices
Fixes shipped with this release (#73)
- IDP composite RPC shapes —
check-server,download-status, andinstall-statusare composite XML (parent + empty child), not flat hyphenated names. Same failure mode as Phase 1B #68. - Junos 24.4R0 license-schema tolerance — parser now accepts both legacy (
<licenses-installed>/<licenses-used>/<licenses-needed>/<license-type>) and 24.4R0+ live (<licensed>/<used-licensed>/<needed>/<validity-type>) element names. Previously, counts read as zero on 24.4R0 devices, tripping the preflight defence-in-depth check on devices that clearly had the license installed.
Validation
5/7 destructive live smokes pass on vSRX-test3 against LXC 601:30032:
idp_check_server_returns_latest_version✅idp_download_and_install_call1_returns_plan✅idp_download_and_install_call2_succeeds✅ (237s — real ~300 MB pull fromsignatures.juniper.net)idp_already_at_target_short_circuits✅idp_version_pin_accepts_explicit✅idp_rollback_after_install_restores_previous— lab precondition (no prior IDP package on test3)idp_cluster_install_syncs_both_nodes— known lab gap (no IDP-licensed cluster pair)
Tool surface
7 srxmcp tools total (up from 6 in v0.1.2):
srxmcp_status,check_srx_feature_license,vpn_lifecycle_report,get_chassis_cluster_status,get_srx_security_services_statusmanage_idp_security_package(new in this release)
Deployed to LXC 601:30032.
v0.5.8 — upgrade_junos self-deadlock fix
What changed
Pre-v0.5.8, every real call to upgrade_junos with confirm=true hung indefinitely at Phase 2 (transfer) the moment preflight returned Proceed. Root cause: run() acquired the per-router transfer permit, then transfer_file::handle() tried to re-acquire the same permit on the same task — a self-deadlock.
The fix removes the redundant early acquire in run(). transfer_file::handle() is the sole owner of the per-router transfer-lock for the actual SCP critical section.
A same_task_reacquire_deadlocks regression test was added to lock in the expected (deadlock-producing) shape of TransferLocks.
Verification
- Live-tested on vSRX-test16 (2026-05-19):
lock_acquire_pre→lock_acquire_postcompletes in microseconds; full Phase 2 transfer flow proceeds normally. cargo test --workspace --release: 444 passed, 0 failed.cargo audit: 0 vulnerabilities.
Compatibility
- No API surface changes vs v0.5.7.
- No new env vars or config fields.
- Wire-protocol compatible.
v0.4.1 — security & hardening
Security + hardening release. No tool API changes; one server-side response-header change for unauthenticated requests, plus two new response fields on list_staged_files.
Security
- RFC 6750 bearer challenges on every 401 — the streamable-HTTP endpoint now always returns a
WWW-Authenticate: Bearer ...header on401 Unauthorized. Wrong-token rejections includeerror=\"invalid_token\"per RFC 6750 §3.1 so clients can distinguish bearer rejection from an OAuth-discovery prompt. (#27, #28) transfer_filesource-path allowlist tightened — now restricts to[A-Za-z0-9._-], rejecting NUL bytes, ASCII control chars, shell metacharacters, RTL overrides, and homoglyph scripts that the prior `..` + slash + 255-byte checks missed. (#26 L2, #30)scpstderr scrubbed inScpFailederrors — absolute paths and IPv4 addresses are redacted to `` / `` before reaching the MCP caller. (#26 L1, #31)
Reliability
list_staged_filescapped at 256 entries — caps at `STAGING_DIR_MAX_ENTRIES = 256`, deterministic name-sorted truncation, skips sha256 on excess files. Response gains `staged_files_truncated: bool` and `staged_files_total_found: usize`. (#26 L5, #32)- Per-router serialization for
transfer_file— new `TransferLocks` process-wide map of `Arc<Semaphore(1)>` keyed by router. Different routers proceed in parallel; same router serializes. (#26 L4, #33)
Operability
- Actionable EACCES message on
tokens.json— surfaces file owner uid + mode and running process uid plus a `sudo -u ` / `chown` hint when the daemon can't read the tokens file. README gained a service-user note in the "Mint a token" section. (#22, #23, #29)
Compatibility
- No tool name / argument changes.
- `list_staged_files` response gains two new fields (`staged_files_truncated`, `staged_files_total_found`). Existing fields unchanged.
- `401 Unauthorized` responses now always include `WWW-Authenticate: Bearer ...`. Clients that ignore this header are unaffected.
See CHANGELOG.md for the full entry.
v0.2.2 — add_device + reload_devices (full upstream parity)
Sub-project #4 PR #7 / 2 — closes the upstream parity gap. Tool surface now matches Juniper/junos-mcp-server exactly (11 tools).
Highlights
add_device— add a Junos device to the in-memory inventory and persist todevices.json. Atomic write (tempfile::NamedTempFile::persist+fsync), preserves_blocklist_defaults, per-deviceblocklist, and any other top-level keys viaserde_json::Valueround-trip withpreserve_order. SHA-256 TOCTOU guard rejects calls that race with external edits.reload_devices— re-read the current--device-mapping(no args) or swap to a new inventory file (file_name). Reportsadded/removed/changeddevice names plusprevious_router_count,new_router_count, andinventory_path.- rmcp elicitation pass-through with structured args fallback. Missing required fields surface as
JmcpError::MissingArguments.
Architecture notes
DeviceManagerswitched toArc<ArcSwap<Inventory>>for lock-free hot-swap. Per-request reads still snapshot at handler entry. Writes serialized through atokio::sync::MutexonDeviceManager.inventory_write_lock.JmcpHandlerno longer holds a staleArc<Inventory>snapshot field;get_router_listreads live fromDeviceManager.inventory()so post-mutation router list reflects reality immediately.- SIGHUP now reloads inventory in addition to the token store. Inventory reload runs first; the token-store reload then refreshes its
knownrouter list fromDeviceManager.inventory().names(). KNOWN_TOOLS(rust-junosmcp-auth) extended with"add_device"and"reload_devices"— 11 entries.
New CLI flags
--inventory-readonly— rejectadd_deviceandreload_devicesunconditionally (independent of token scopes).--allow-password-auth-add— permitauth.type=passwordinadd_device. Off by default. Mutually exclusive with--inventory-readonly.
Documented sharp edge
add_device does not modify the token store. If a token has --routers 'edge-*' and you add core-3, the existing token will not see the new router. Mint a new token or rotate scopes after add_device.
Gating order (unchanged)
transport → AuthLayer → CallerCtx → tool scope → router scope → blocklist. The two new tools are tool-scope-gated only — they don't take a router_name argument.
Install
cargo install --git https://github.com/fastrevmd-lab/RustJunosMCP --tag v0.2.2Or build from source — see README.md. LXC tarball is rust-junosmcp_0.2.2_amd64.tar.gz.
Plan & spec
- Spec:
docs/superpowers/specs/2026-05-05-templates-inventory-design.md - Plan:
docs/superpowers/plans/2026-05-05-inventory-mutation.md(16 tasks, all complete) - PR: #7
Diff
v0.2.1...v0.2.2. 244 tests passing / 9 ignored (real-device, gated on JMCP_TEST_HOST/USER/PASS).
v0.2.1 — PFE + batch tools
Sub-project #3 follow-up to v0.2.0. Two new MCP tools and an independent PFE blocklist list. Stdio + remote transport + auth paths unchanged from v0.2.0.
Highlights
execute_junos_pfe_command— single PFE-shell call against an explicit FPC target. Wraps asrequest pfe execute target <fpc> command "<cmd>". Rejects literal"in the input (JmcpError::BadPfeCommand).execute_junos_command_batch— N routers x M operational CLI commands. Parallel across routers (cap =max_concurrent_routers, default 16) viatokio::sync::Semaphore+tokio::task::JoinSet. Sequential per router. Per-commandcommand_timeout(default 360s) and optional whole-batchbatch_timeout. Continue-on-error after pre-flight; per-router error rows preservecommands.len()invariant. Result order matches input.- Independent
pfe_commandsblocklist list — under_blocklist_defaultsand per-deviceblocklist. A deny oncommandsdoes NOT gate PFE and vice versa. Seedevices-template.json.
Architecture notes
BatchRunner/RouterSessiontraits (viaasync-trait) provide a testing seam — production runner wrapsrustez::Device; tests use a stub.batch_timeoutwraps the JoinSet collect future and synthesizes"batch timeout"rows for routers that didn't report.KNOWN_TOOLS(rust-junosmcp-auth) extended with the two new names. Token files referencing them load only on a 0.2.1+ binary; older binaries reject the file at parse time.
Gating order (unchanged from v0.2.0)
transport → AuthLayer → CallerCtx → tool scope → router scope → blocklist. For batch, router-scope is enforced in a for loop with first-failure short-circuit BEFORE any device I/O.
Install
cargo install --git https://github.com/fastrevmd-lab/RustJunosMCP --tag v0.2.1Or build from source — see README.md. Docker tag is now 0.2; LXC tarball is rust-junosmcp_0.2.1_amd64.tar.gz.
Plan & spec
- Spec:
docs/superpowers/specs/2026-05-05-pfe-batch-design.md - Plan:
docs/superpowers/plans/2026-05-05-pfe-batch.md(17 tasks, all complete) - PR: #5
Diff
v0.2.0...v0.2.1. 177 tests passing / 7 ignored (real-device, gated on JMCP_TEST_HOST/USER/PASS and JMCP_TEST_FPC).
v0.2.0 — remote transport + bearer-token auth
First public release with remote MCP transport. v0.1 features remain unchanged on the stdio path.
Highlights
- Streamable-HTTP transport (rmcp 0.8.5) —
--transport streamable-http -H <host> -p <port> - Bearer-token auth with per-token router/tool scopes —
token add/list/revoke/rotatesubcommand - Optional rustls TLS (default-on
tlsfeature, ring crypto provider, no aws-lc-rs) - SIGHUP hot-reload of the token store via
Arc<ArcSwap<TokenStore>> - CLI refusal matrix — refuses
--allow-no-authoff-loopback; refuses plain HTTP off-loopback without--allow-insecure-bind - Stdio path is bit-for-bit unchanged from v0.1
Security
subtle::ConstantTimeEqfor hash verify- Token plaintext shown exactly once on
token add/rotate; never logged, noDebugimpl onSecret - Atomic
TokenStoreFile::saveviaNamedTempFile::persist— crash during rotate cannot lose tokens - Blocklist guardrails (v0.1.x) still gate every tool call regardless of token scope
Install
cargo install --git https://github.com/fastrevmd-lab/RustJunosMCP --tag v0.2.0Or build from source — see README.md. Docker image and LXC tarball follow the same workflow as v0.1, with version tags now 0.2.
Operator quick-start
# Mint a token
cargo run -- token add --tokens-file tokens.json --name ops \
--routers '*' --tools execute_junos_command,gather_device_facts
# Run with auth on loopback
cargo run -- --device-mapping devices.json --transport streamable-http \
-H 127.0.0.1 -p 8765 --tokens-file tokens.json
# Hot-reload after rotate (sends SIGHUP automatically)
cargo run -- token rotate --tokens-file tokens.json --name ops --server-pid <pid>See README's "Remote transport + auth (v0.2)" section for the full refusal matrix and TLS setup.
Plan & spec
- Spec:
docs/superpowers/specs/2026-05-05-remote-transport-auth-design.md - Plan:
docs/superpowers/plans/2026-05-05-remote-transport-auth.md(16 tasks, all complete) - PR: #4
Diff
v0.1.0...v0.2.0: 27 files changed, +7227 / -67. 145 tests passing.