feat(mcp): mutating tools + consent + audit (VA-27) #23
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/mcp-mutating-va-27"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
mutating-toolscargo feature onmeshagent. Strict read-only deployments build with--no-default-features --features meshcore;verify_read_onlystill enforces the denylist on that path.ToolPolicy::ConsentRequired:exec,file_write,file_delete, servicesstart/stop/restart,power_reboot,packages_install,process_kill. Each carriesdestructive=true/readOnly=false/requiresConsent-equivalent annotations so MCP clients render warnings without out-of-band knowledge.meshagent-mcpgrows pluggableConsentGateandAuditSinktraits plus aToolPolicyregistry.Registry::register_with_policycarries the policy at registration time; v1 callers usingregisterkeepAllowby default.serve_with(opts)is the new v2 entrypoint;servestays as the no-gate v1 shim.HostConsentGate(inhost::mcp) wrapshost::consent::ask_consent(zenity / kdialog) and adds argument-aware scoping:file_writeto a path underFILE_WRITE_ALLOWED_PREFIXES(default:/tmp/) auto-approves and skips the dialog. NoUi outcomes becomeDenyso headless agents fail closed.TracingAuditemits one structuredtracing::infoevent per mutating call (tool, decision, latency, redacted args digest). The gateway-side audit log (VS-23) is the system of record; this sink leaves a local trail.fileops::write_bytes,services::{start,stop,restart},packages::install(non-interactive apt-get / dnf / apk). Existingexec::run,power::execute,fileops::delete,process::killreused as-is.Acceptance criteria status
mutating-toolscargo feature gates the mutating registry; default-on, off-able for hardened builds.exec,file_write,file_delete, services start/stop/restart,power_reboot,packages_install,process_kill).host/consent.rsviaHostConsentGate. Denied / timed-out consent returns an MCPisErrorresult with a recognizable reason string.file_writedemonstrates argument-aware scoping viaFILE_WRITE_ALLOWED_PREFIXES(path-prefix predicate).Tooldescriptors carry destructive / read-only annotations; verified by an integration test that introspectstools/list.TracingAudit).Deviations / notes
host::consent::DEFAULT_PROMPT_TIMEOUT(30 s).HostConsentGate::newaccepts a custom timeout if a deployment wants longer.notifications/cancelled) still log-only in v2; in-flight consent cancellation lands with the gateway-side flow.Test plan
cargo build --workspace(mutating-tools on, default).cargo build --package meshagent --no-default-features --features meshcore(mutating-tools off).cargo test --workspace(298 lib + 7 handshake + 2 mcp_tunnel + 4 mcp_mutating + 19 wsproxy + 24 tcpforward + 17 mcp lib + ...; all green).cargo clippy --workspace --all-targets -- --deny warnings(mutating-tools on and off).cargo fmt --all --check.Add the v2 mutating-tool surface to the MCP tunnel mode (USAGE_MCP=8), gated by a new default-on `mutating-tools` cargo feature on `meshagent`. Strict read-only deployments build with `--no-default-features --features meshcore` and keep the v1 surface intact (verify_read_only still enforces the denylist on that path). Adds 9 mutating tools, each registered with `ToolPolicy::ConsentRequired`: `exec`, `file_write`, `file_delete`, services `start`/`stop`/`restart`, `power_reboot`, `packages_install`, and `process_kill`. Every mutating tool carries the destructive/read-only/idempotent annotation set so MCP clients can render warnings without out-of-band knowledge. `meshagent-mcp` grows three pluggable surfaces. `ToolPolicy { Allow, ConsentRequired, Deny }` is attached at registration time via the new `Registry::register_with_policy`; v1 callers that use `register` get `Allow` by default. `ConsentGate` is asked before any `ConsentRequired` tool runs and returns `Allow` / `Deny { reason }` / `Timeout`. `AuditSink` receives a structured event per call (tool, args digest with sensitive-key redaction + 256-byte cap, policy, decision, latency, is_error). The v1 entry point (`serve`) stays as the no-gate shim; v2 callers use `serve_with(opts)`. `HostConsentGate` (in `host::mcp`) is the agent's default gate. It wraps `host::consent::ask_consent` (zenity / kdialog) for the dialog path and adds per-tool argument scoping: `file_write` to a path under `FILE_WRITE_ALLOWED_PREFIXES` (default: `/tmp/`) is auto-approved and skips the dialog. NoUi outcomes become `Deny` so a headless agent without an operator fails closed rather than open. `TracingAudit` emits one structured `tracing::info` event per mutating tool call as the local audit trail; the gateway-side audit log (VS-23) is the system of record. Adds the host primitives the new tools need: `fileops::write_bytes(path, bytes, append, create)`, `services::{start,stop,restart}(unit)`, and `packages::install(package)` (non-interactive apt-get / dnf / apk). Existing `exec::run`, `power::execute(Reboot)`, `fileops::delete`, and `process::kill` are reused as-is. Tests: 7 new unit tests in `meshagent-mcp` cover the gate dispatch (Allow / Deny / Timeout / no-gate-configured), policy short-circuit, args digest redaction + truncation; 4 new tests in `host::mcp` cover the v2 annotation invariants and the host gate's auto-approve / headless-deny paths; 4 new integration tests in `tests/mcp_mutating.rs` drive the full WebSocket path. The v1 `mcp_tunnel.rs` integration test is updated to only assert read-only annotations on the v1 tool set so the default v2 build still passes. Build verified clippy-clean + fmt-clean with the feature both on and off. #VA-27 State Done Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>