Releases: fastrevmd-lab/rustjunosmcp
Release list
v0.8.0 — unified Junos/SRX server and operational controls
v0.8.0 combines Junos and SRX into one server and adds the operational controls needed to run that endpoint safely. Default builds expose the complete 26-tool Junos/SRX surface; Junos-only builds retain the 17-tool surface with --no-default-features.
Highlights
- One Junos/SRX server — one binary, endpoint, process, inventory, device manager, destructive-operation lease manager, auth surface, and SIGHUP reload path.
- Resource controls — per-router concurrency limits, per-token session caps, per-token RPS limits, and strict global session admission.
- Operations and observability — bounded Prometheus metrics and optional native journald audit fan-out.
- Security and packaging — scoped router-list results, cross-process destructive-operation leases, non-root container SCP support, process healthchecks, and expanded package/container security gates.
Breaking deployment change
The standalone rust-srxmcp executable, rust-srxmcp.service, and 127.0.0.1:30032/mcp endpoint are retired. Package upgrades remove the old binary, unit, and enabled-service link while preserving support bundles under /var/lib/jmcp/srx-staging/bundles.
Use the unified rust-junosmcp endpoint at the configured listener (packaged default 127.0.0.1:30030/mcp). Existing JMCP_SRX_* names are deprecated aliases for v0.8.0 only. JMCP_SRX_HTTP_PORT is ignored because there is no second listener.
Artifacts
rust-junosmcp_0.8.0_amd64.tar.gzrust-junosmcp_0.8.0_amd64.tar.gz.sha256ghcr.io/fastrevmd-lab/rust-junosmcp:0.8.0ghcr.io/fastrevmd-lab/rust-junosmcp:0.8ghcr.io/fastrevmd-lab/rust-junosmcp:latest
Known tracked dependency risk
Issue #110 remains open for prerelease RustCrypto dependencies inherited through russh 0.61.2. The currently available stable russh 0.62.2 still carries RC crypto dependencies, so this release retains the audited and previously exercised dependency set.
Full changelog: https://github.com/fastrevmd-lab/rustjunosmcp/blob/v0.8.0/CHANGELOG.md
Diff: v0.7.0...v0.8.0
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.