Releases: fastrevmd-lab/rustsdcmcp
Release list
v0.0.1
The first full release. Everything before it was published as a prerelease.
The version number moves down, from the 0.1.0 the earlier archives declared
to 0.0.1, because that number described a release that was never cut. Nothing
was removed from the tool surface to justify it.
What this is
54 MCP tools — 40 bounded read, 14 change-control — over HPE Juniper Security
Director Cloud. Every mutation is reachable only through
prepare → independent approval → apply; there is no direct deploy tool and no
unauthenticated write path. A wildcard token scope grants no write tool.
Built with Rust 1.98.0 (MSRV 1.88), with the six shared mecmcp crates pinned
at v0.21.0.
Assets
rustsdcmcp_0.0.1.20260828.c5438e80471b_amd64.tar.gz is commit-addressed to
c5438e80471bec552a26e13aa3397e013c1cbe79 and built by CI, not locally. Verify
it against the sibling .sha256, taking the expected value from this page:
edae795543b3abe4753238fa5ec99edf9d3d49c12d813b23213d9aa1caaa57e2 rustsdcmcp_0.0.1.20260828.c5438e80471b_amd64.tar.gz
A linux/amd64 container image is published for this tag to
ghcr.io/fastrevmd-lab/rustsdcmcp:0.0.1.
Upgrading from a v0.1.0-lab.N archive
The packaging assertions are exact-match, so this is not a drop-in swap:
- the archive drops its
-lab.infix —rustsdcmcp_0.0.1.<date>.<sha12>_amd64.tar.gz BUILD-INFOcarriesversion=0.0.1andrelease_status=release, and
packaging/lxc/install.shasserts both. An older archive will not install
under this installer, and this archive will not install under an older one.scripts/build-lab-package.shis nowscripts/build-package.sh, with no shim
--lab-mode is a security control, not release framing, and is unchanged.
Full detail in CHANGELOG.md.
v0.1.0-lab.10
Measured against the preceding v0.1.0-lab.9 tag, not against intermediate
untagged commits. The Unreleased block below is byte-identical to lab.9 and
therefore describes surface that tag already carried — it does not cover
anything in this release.
Added — action required for existing tokens
-
prepare_sdc_device_inventory_syncandapply_sdc_device_inventory_sync
(#21) takeKNOWN_TOOLSfrom 52 to 54. Both are write tools, gated by a
change set. They reconcile inventory, not configuration.Existing bearer tokens will not see them until re-minted. Tokens carry
explicit tool scopes, so a token issued against lab.9 keeps working for the
52 tools it names and silently omits the two new ones — which presents as a
broken upgrade rather than as a scoping decision. -
Templates as a read family (#33).
-
Three residual read families that fit the generic resource pair, with the
four that do not documented rather than forced (#83). -
SSDF evidence pipeline (mecmcp#292), flushed even when serving ends in an
error.
Fixed
get_sdc_change_set_detailsworks at all (#81). It was created keyed by
tenant and looked up by endpoint, so it could never find a change set.- License write-path before-state is projected to callers (#55), and an
unprojectable before-state is now withheld rather than failing the call.
Previously the raw upstream before-state bypassed the read-path allowlist. - A
2xxwhose body could not be read is treated as the sync having landed,
and the job id and per-device results survive acceptance (#21). - The installer
chmods only files that actually exist during a legacy
upgrade. - The packaging SBOM check rejects duplicate members instead of chasing
encodings, closing a duplicate-key bypass.
Security
-
Tier-2 hardening.
tokens.jsonmoves to/var/lib/rustsdcmcp, the unit
gains--audit-log-file $STATE_DIRECTORY/audit.jsonl, stale secrets are
scanned for, and the container image is built and checked in CI.The systemd unit's sandboxing is unchanged:
NoNewPrivileges,
ProtectSystem,PrivateDevices, namespace restrictions and syscall filters
were already present at lab.9. Named here only so this entry is not read as
claiming they arrived in lab.10. -
The legacy token store is no longer shadowed by an empty one. An upgrade
that found an empty primary could mask a populated legacy store, which reads
as "every credential was rejected" rather than as a packaging fault.
Changed
mecmcp0.11.0 -> 0.19.0. That is the jump from the lab.9 baseline;
0.17.0 was an intermediate untagged step. This repo pins mecmcp at an exact
version in ten files —Cargo.toml,Cargo.lock,
crates/rustsdcmcp/tests/mecmcp_dependency_contract.rs,
crates/rustsdcmcp/tests/sbom_validation.rs,
crates/rustsdcmcp/src/main.rs,.github/workflows/ci.yml,
scripts/verify-packaging.sh,
scripts/build-lab-package.sh,packaging/lxc/install.sh, and
packaging/tests/package-smoke.sh(which hard-codes the BUILD-INFO and SBOM
versions on top of the package set). The guards are exact, so a partial bump
fails the build rather than shipping a mixed package.rmcp3.1.1 -> 3.1.4.- Pinned toolchain moved to 1.98.0, with a CI toolchain-pin guard and a PR
image build. The Docker builder must match the pin rather than drift ahead. - Dependabot now watches the Dockerfile.
Documentation
- Recorded what this server is not for (#34), and why the distroless image has
noHEALTHCHECK.
Published retroactively from CHANGELOG.md; tagged 2026-08-25.
v0.1.0-lab.9
- build(deps): take h2 0.4.17 for RUSTSEC-2026-0258
- fix(server): carry the cache descriptor on tools/list
- docs: document audit forwarding and where the trail goes
- chore: re-pin the SBOM guard to 0.11.0 as well
- chore: re-pin mecmcp to v0.11.0
- fix: wire --allow-insecure-bind into the transport config
- chore(packaging): move the pinned mecmcp component versions to 0.10.0
- chore(packaging): move the pinned mecmcp ref to v0.10.0
- chore(deps): mecmcp v0.10.0
Changes since v0.1.0-lab.8: 13 files changed, 361 insertions(+), 90 deletions(-)
Published retroactively; summarised from the commit range, as this repository keeps no CHANGELOG. Tagged 2026-08-19.
v0.1.0-lab.8
- chore: point the SBOM provenance checks at mecmcp 0.9.1
- test: point the mecmcp dependency contract at v0.9.1
- chore: converge on mecmcp v0.9.1 and unbreak the packaging policy gate
- fix: restore and migrate http_boundary tests to real HTTP
- chore: migrate to mecmcp v0.9.0
- docs: correct three stale family counts in the Phase B design spec
- fix: add missing IpsSignatures family and correct family counts
- docs: record Phase B coverage and its verification limits
- fix(catalog): remove duplicate doc comment line in list_resource
- feat(catalog): project the generic resource list with fields
- feat(catalog): add 23 read-only resource families
- refactor(catalog): split the resource catalog by capability
- docs: Phase B implementation plan
- docs: Phase B design — split the resource catalog by capability
- docs: out-of-band resolution exists, but not on this API (#73)
- feat: add list_sdc_config_versions read tool (#21) (#72)
Changes since v0.1.0-lab.7: 24 files changed, 2455 insertions(+), 315 deletions(-)
Published retroactively; summarised from the commit range, as this repository keeps no CHANGELOG. Tagged 2026-08-13.
v0.1.0-lab.7
Lab-only prerelease. Not a public release.
Built from 1e88893774eabc7489cbe4c736512d5e5e47b89a with all six shared mecmcp crates pinned to v0.8.0.
sha256 0949a744a1f86ff8c56ea778cb3a61f3798a6e9e23dadb25e463fc351d3b2cb3
Phase A of the completion plan: the four change-control defects. Closes #61, #63, #66.
Two things to do when upgrading
Re-mint your tokens. The surface is now 51 tools. A token's tool scope is an explicit allowlist, so a token minted against the previous 50 will not see discard_sdc_operation. This presents as a failed deployment and is not one — check the token's scope length before suspecting the build.
Expect previews to look different. They are XML now, not CLI set lines. That is the fix rather than a side effect; see below.
Previews now cover the whole change (#66)
A deploy was observed committing a deletion its preview never disclosed. The cause turned out to be entirely on this side: GET /api/v1/policies/preview/{id}/devices/{id} accepts a format parameter — CLI (the default) or XML — and this client never passed it.
The same preview, fetched both ways:
format=CLI |
format=XML |
|
|---|---|---|
| bytes | 273 | 570 |
parent feed-server named |
no | yes |
| delete markers | 1 | 4 |
Since the preview digest is computed over that artifact, an approver could be shown less than the change. SDC was never concealing anything — its XML answer was always complete, and the deploy did exactly what that answer said. This client digested the lossy rendering of it.
Verified live before release: the parent object now appears in the digest-bound artifact.
Wedged operations can be cleared (#63)
A failed deploy left an operation that refused every later apply on the tenant, with no supported remedy short of editing changeset-state.json on a running deployment. Observed twice in production on 2026-08-12.
discard_sdc_operation clears it. Owner-only, fingerprint-bound so a stale caller cannot clear an operation it has not read, and registered in WRITE_TOOLS so a wildcard token scope cannot reach it. The failed operation stays visible — this unblocks applies, it does not erase the failure.
Exposing the upstream call alone would have made things worse: it invokes transaction.rollback, which returned an error the caller converts to Indeterminate — a state that can never be discarded. So SdcTransaction::rollback reports truthfully first, since SDC reverts the device itself on a failed deploy.
Unsupported deploy targets are refused locally (#61)
DEVICE_GROUP is marked "Not supported, future support" in the pinned spec, but requests naming one were built, sent, and rejected — after spending a preview job on the management plane. Now refused before any request, with a message quoting the spec. One guard and one call site, deletable when SDC supports it.
Known gaps
- #55 — certificate and licence write tools return the raw before-state, bypassing the read-path allowlist. No exposure today.
- #21, #31, #33, #34 — coverage epics. #31's ~110 operations and 36 of the 61 API groups share one five-operation shape the existing resource catalog can absorb; that is Phase B.
v0.1.0-lab.6
Lab-only prerelease. Not a public release.
Built from 9d4048fb971365da56fa4c642782b2929704bfdf with all six shared mecmcp crates pinned to v0.8.0.
sha256 b7d68e4c1df58422121ba9e68e4db8f2b2ab013a83bc42ec14415cfe6606e229
Upgrading from lab.5 requires re-minting your tokens
The tool surface is now 50 — 39 reads and 11 change-control tools, up from 48.
A token's tool scope is an explicit allowlist of names, so a token minted against the previous 48 keeps seeing exactly those 48 after the upgrade. The two new tools simply do not appear. This presents as a failed deployment and is not one; check the token's scope length before suspecting the build.
Re-mint with the full list:
rustsdcmcp token add --tokens-file /etc/rustsdcmcp/tokens.json \
-f /etc/rustsdcmcp/sdc.json --name <name> --devices <tenant> \
--tools '<all 50 names>'A wildcard --tools '*' still grants no write tool — that is deliberate, enforced in mecmcp-auth, and unchanged.
Added
- Device group reads (#34):
list_sdc_device_groupsandget_sdc_device_group. Reads only; group create, update, and delete remain unimplemented pending the gating decision in #24. The list accepts the API'sfieldsprojection, which matters becausesizebounds the number of groups and not the size of each — a group embeds its membership.
Documentation
- How to enable
--lab-mode. lab.5 documented what it means without ever showing the invocation. Also corrected:--lab-modeis CLI-only, with nosdc.jsonfallback, unlike--state-fileand--approval-timeout-secs. - A policy deploy deletes template-placed configuration that no imported policy references (#33), confirmed by a committed apply against a live device. Template origin confers no protection, so #23's co-management boundary stands and templates are not a remedy for it.
- A deploy can commit more than its preview disclosed (#66). In the observed case the preview named one object and the commit removed two, with the omitted object absent from the digest-bound artifact entirely. The change-set binding behaved correctly; what it bound did not describe the whole change. Treat a preview as a lower bound until the conditions are understood.
- The undocumented custom-template upload schema, derived from the endpoint's own errors, and an edge WAF that rejects a template body containing
http://plus an RFC1918 address. DEVICE_GROUPis not a supported deploy target — the pinned spec marks it "not supported, future support" — correcting a claim in #34 (#61).
Known gaps
- #66 — preview under-reporting, above. The most consequential open item.
- #63 — a failed deploy wedges every later apply on the tenant, and
discard_operationis unexposed. Recovery currently means hand-editing the state file. - #61 — a
DEVICE_GROUPtarget is accepted locally and fails downstream instead of being refused. - #55 — certificate and licence write tools return the raw before-state, bypassing the read-path allowlist. No exposure today.
v0.1.0-lab.5
Lab-only prerelease. Not a public release.
Built from 0b3a661c3c680bae1f03356e999731828db63b3d with all six shared mecmcp crates pinned to v0.8.0.
sha256 e46e17473728b8b056fd2aba2bcc7749e4061b3fca908940f6ee3b3c8662275c
The compatibility blocker is cleared
Previous release notes said v0.1.0 was blocked until one coherent upstream release replaced 59 compatibility ledger entries. That happened. The compat/ layer was deleted in #36 on the move to mecmcp 0.7.2, and the ledger in 369f9bb on the move to 0.8.0.
What still gates a public v0.1.0 is operational rather than upstream: container image support, remote audit-journal forwarding, broader live validation, and a tool surface that covers a minority of the SDC API.
Three questions answered against a live tenant
Each had blocked an issue on "needs a live tenant", and each is now recorded in docs/sdc-api/README.md.
- Certificates and licences carry no key material (#50). Six readers return metadata only. Four traps recorded, including two date formats inside one object and public-key metadata a naive
keydenylist would wrongly redact. - Device sync imports, and does not fix drift (#21). The device's commit log was unchanged across a sync, so nothing is pushed — but it reconciles inventory only and leaves
device_config_state: OUT_OF_BAND_CHANGED. It is not the remedy that issue wanted, andGetSyncStatusdoes not share the deploy job's status vocabulary. - Templates are arbitrary Junos CLI (#33). The 17 built-ins cover system config only — no general interface or routing template, and no
security dynamic-address. Custom definitions can express those, but whether template-placed config survives a policy deploy is a different pipeline and remains untested.
Added
- Certificate and licence tools: six reads, eight writes under change control (#32).
- IPsec profile and tunnel reads (#28); firewall and NAT rule reads (#25); NAT pool reads (#30).
- NAT policy authoring (#27), firewall policy writes (#24, partial), object authoring (#29) — all under change control.
get_sdc_change_set_details, recovering a preview digest that prepare returns only once (#22).- An allowlist projection over certificate and licence reads, applied at the MCP boundary so change-control drift detection keeps full-fidelity state (#50).
Changed
- Adopt the shared
mecmcpchange-set CLI standard:--lab-mode,--state-file,--approval-timeout-secs(#54). Explicit CLI beats product configuration, and adoption cannot silently relocate an existing deployment's state file or change its approval TTL. --versionnow answers. It previously failed as an unknown argument, which broke the package-identity check a deployment runs.--lab-modewaives the second principal for single-operator use. Wiring the flag alone was not enough — nothing called the waiver, so a plan could not move pastPlanned. The waiver is applied at change-set creation, recordsapprover: nullwithapproval_waiver: "lab-mode", and never fabricates an approver.- Adopt mecmcp 0.8.0 and delete the local compatibility transport copies (#36).
Fixed
- Refuse
--approval-timeout-secs 0, which expired every change set at creation and disabled the entire write surface. - Attribute an
expected_preview_digestmismatch to that argument by name.
Documentation
- Corrected release claims across README, CHANGELOG, and the operations guide. Five places asserted the cleared blocker, and the README denied a live policy deploy that had in fact happened — the deploy that #23 exists because of.
- Documented that a wildcard token scope grants no write tool:
--tools '*'yields the read surface only.
Known gaps
rustsdcmcp v0.1.0 lab prerelease 4
Lab-only prerelease. Not a public release — v0.1.0 remains blocked until
one coherent upstream mecmcp release replaces all 59 compatibility ledger
entries.
Built from ee38d0891151cfc61f5f9c6ec6deb4559e45400d with all five shared
mecmcp crates pinned to changeset-v0.3.7.
Why this re-cuts lab.3
The lab.3 archive shipped a README telling its reader to download lab.1.
The packager copied the repository README verbatim, and that file pins whichever
prerelease was current when it was written. A release is always built from a
commit that predates the documentation describing it, so every archive
carried download instructions for the previous release. lab.1 and lab.2 have the
same defect.
The package now carries a generated README describing itself — archive name,
source commit, mecmcp pin, prerequisites, verification, install, contents. It
references no release tag, so it cannot go stale. Two values are single-sourced
to keep it that way: the GLIBC floor shares BUILD-INFO's variable, and the
mecmcp reference is derived from the Cargo.toml pin rather than repeated,
with verify-packaging.sh asserting the manifest holds exactly one approved
tag.
The binary is unchanged from lab.3. If you have lab.3 installed and working,
this changes only the documentation inside the archive.
Verify the archive
Take the checksum from this release page over an authenticated connection — a
.sha256 obtained through the same channel as the archive only proves the two
agree.
gh release download v0.1.0-lab.4 \
--repo fastrevmd-lab/rustsdcmcp \
--pattern 'rustsdcmcp_0.1.0-lab.20260805.ee38d0891151_amd64.tar.gz*'
sha256sum -c rustsdcmcp_0.1.0-lab.20260805.ee38d0891151_amd64.tar.gz.sha256BUILD-INFO records release_status=lab-only, the source commit, toolchain and
GLIBC floor; SBOM.cdx.json carries the CycloneDX dependency set. Both are
metadata — they confirm what the package claims to be, and do not detect a
modified binary.
Known limitation, unchanged from lab.3
The systemd IPAddress* egress directives are defence in depth only and are
likely inert on an unprivileged LXC: systemd implements them with cgroup eBPF
and fails open when it cannot attach. The installer probes and reports
ENFORCED / NO POLICY / NOT ENFORCED / UNKNOWN; docs/operations.md
covers where to enforce instead.
systemctl show rustsdcmcp.service -p IPEgressBytes --value # [no data] = inertrustsdcmcp v0.1.0 lab prerelease 3
Lab-only prerelease. Not a public release — v0.1.0 remains blocked until
one coherent upstream mecmcp release replaces all 59 compatibility ledger
entries.
Built from 37db74a5a0b959444d335793297d3f7b07224009 with all five shared
mecmcp crates pinned to changeset-v0.3.7.
What changed since lab.2
Nine PRs from a review pass over the initial implementation. Three were
security-relevant:
- Listener authentication hole (#5) —
--tokens-filetogether with
--allow-no-authfell through to a_ => Nonearm that dropped the token
store, producing an unauthenticated MCP listener on any bind address with no
warning.mecmcp_runtime::cli_validatedoes not catch the pair. - Cancellation was inert (#5) — every tool built a fresh
CancellationToken::new(), so the plumbing threaded through twenty client
methods was connected to nothing. Now takes rmcp'sRequestContext::ct. - No shutdown handling (#5, #12) — SIGTERM had no handler. Both listeners
now drain behind one deadline, sessions end on the process token, and the SDC
client abandons in-flight work rather than holding a drain open for the
job-poll deadline.
Also:
KNOWN_TOOLScould drift from the registered router without any test failing,
silently breaking token minting (#5)- Unknown SDC job statuses no longer fail the whole read; the vendor's string is
preserved verbatim into the audit artifact (#7) - The prepared-change envelope is digested once per apply instead of four times
(#7) - Two test assertions that could never fail were replaced (#6), and a third that
replaced them badly was fixed (#11) - systemd egress policy, with an installer probe that reports whether it is
actually enforced rather than assuming (#8, #12)
Known limitation in this build
The systemd IPAddress* egress directives are defence in depth only and are
likely inert on an unprivileged LXC: systemd implements them with cgroup eBPF
and fails open when it cannot attach. The installer now probes and reports
ENFORCED / NO POLICY / NOT ENFORCED / UNKNOWN, and
docs/operations.md covers where to enforce instead. Check with:
systemctl show rustsdcmcp.service -p IPEgressBytes --value # [no data] = inertVerify the archive
gh release download v0.1.0-lab.3 \
--repo fastrevmd-lab/rustsdcmcp \
--pattern 'rustsdcmcp_0.1.0-lab.20260805.37db74a5a0b9_amd64.tar.gz*'
sha256sum -c rustsdcmcp_0.1.0-lab.20260805.37db74a5a0b9_amd64.tar.gz.sha256BUILD-INFO in the archive records release_status=lab-only, the source commit,
and mecmcp_ref=changeset-v0.3.7; SBOM.cdx.json carries the CycloneDX
dependency set.
rustsdcmcp v0.1.0 lab prerelease 2
Private collaborator lab prerelease built from the successful main CI artifact.
- Source commit:
190dab9a4e8ff546b06403999afbaaacfe96633c - mecmcp reference:
changeset-v0.3.7 - Archive SHA-256:
7ce2b10c27d422aebb18488f08b7a12419e543ad33ff95b1ec16e2cf014a06d5 - Packaged binary SHA-256:
dc839d43cff890d69a9fe572518c4981a4f23a5db7fada3d8cdbf4d46746ccf0 - Target:
x86_64-unknown-linux-gnu
The archive includes BUILD-INFO and a CycloneDX SBOM. It is intended only for the private lab and collaborators; it is not a stable or public-production release. The remaining 59 explicitly tracked compatibility implementations still block stable promotion.
No credential, bearer token, HMAC key, tenant data, or live change-set state is included. No LXC dump is required for this upgrade; VMID 606 is protected by a Proxmox snapshot. State compatibility from mecmcp 0.3.6 to 0.3.7 has been verified.