feat(mcp): mutating tools + consent + audit (VA-27) #23

Merged
David merged 1 commit from feat/mcp-mutating-va-27 into main 2026-05-20 11:57:42 +02:00
Owner

Summary

  • 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; verify_read_only still enforces the denylist on that path.
  • 9 mutating tools registered with ToolPolicy::ConsentRequired: exec, file_write, file_delete, services start/stop/restart, power_reboot, packages_install, process_kill. Each carries destructive=true / readOnly=false / requiresConsent-equivalent annotations so MCP clients render warnings without out-of-band knowledge.
  • meshagent-mcp grows pluggable ConsentGate and AuditSink traits plus a ToolPolicy registry. Registry::register_with_policy carries the policy at registration time; v1 callers using register keep Allow by default. serve_with(opts) is the new v2 entrypoint; serve stays as the no-gate v1 shim.
  • HostConsentGate (in host::mcp) wraps host::consent::ask_consent (zenity / kdialog) and adds argument-aware scoping: file_write to a path under FILE_WRITE_ALLOWED_PREFIXES (default: /tmp/) auto-approves and skips the dialog. NoUi outcomes become Deny so headless agents fail closed.
  • TracingAudit emits one structured tracing::info event 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.
  • Adds host primitives fileops::write_bytes, services::{start,stop,restart}, packages::install (non-interactive apt-get / dnf / apk). Existing exec::run, power::execute, fileops::delete, process::kill reused as-is.

Acceptance criteria status

  • mutating-tools cargo feature gates the mutating registry; default-on, off-able for hardened builds.
  • At least 8 mutating tools exposed: shipped 9 (exec, file_write, file_delete, services start/stop/restart, power_reboot, packages_install, process_kill).
  • Every mutating tool call passes through host/consent.rs via HostConsentGate. Denied / timed-out consent returns an MCP isError result with a recognizable reason string.
  • file_write demonstrates argument-aware scoping via FILE_WRITE_ALLOWED_PREFIXES (path-prefix predicate).
  • MCP Tool descriptors carry destructive / read-only annotations; verified by an integration test that introspects tools/list.
  • Stub-gate tests cover Allow, Deny, Timeout, and no-gate-configured paths; each maps to the right MCP response shape.
  • Audit hook emits structured events with decision + latency + redacted args digest (TracingAudit).

Deviations / notes

  • Per-call audit lives in the agent as a tracing event for v1, not a persistent local store. VS-23 owns the centralised audit log; pinning local audit to disk has no current consumer.
  • Default consent prompt timeout reuses host::consent::DEFAULT_PROMPT_TIMEOUT (30 s). HostConsentGate::new accepts a custom timeout if a deployment wants longer.
  • Cancellation notifications (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.
  • Manual MCP round-trip against a live agent with the consent dialog (lands with VS-23 once the gateway flow is wired).
## Summary - 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`; `verify_read_only` still enforces the denylist on that path. - 9 mutating tools registered with `ToolPolicy::ConsentRequired`: `exec`, `file_write`, `file_delete`, services `start`/`stop`/`restart`, `power_reboot`, `packages_install`, `process_kill`. Each carries `destructive=true` / `readOnly=false` / `requiresConsent`-equivalent annotations so MCP clients render warnings without out-of-band knowledge. - `meshagent-mcp` grows pluggable `ConsentGate` and `AuditSink` traits plus a `ToolPolicy` registry. `Registry::register_with_policy` carries the policy at registration time; v1 callers using `register` keep `Allow` by default. `serve_with(opts)` is the new v2 entrypoint; `serve` stays as the no-gate v1 shim. - `HostConsentGate` (in `host::mcp`) wraps `host::consent::ask_consent` (zenity / kdialog) and adds argument-aware scoping: `file_write` to a path under `FILE_WRITE_ALLOWED_PREFIXES` (default: `/tmp/`) auto-approves and skips the dialog. NoUi outcomes become `Deny` so headless agents fail closed. - `TracingAudit` emits one structured `tracing::info` event 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. - Adds host primitives `fileops::write_bytes`, `services::{start,stop,restart}`, `packages::install` (non-interactive apt-get / dnf / apk). Existing `exec::run`, `power::execute`, `fileops::delete`, `process::kill` reused as-is. ## Acceptance criteria status - [x] `mutating-tools` cargo feature gates the mutating registry; default-on, off-able for hardened builds. - [x] At least 8 mutating tools exposed: shipped 9 (`exec`, `file_write`, `file_delete`, services start/stop/restart, `power_reboot`, `packages_install`, `process_kill`). - [x] Every mutating tool call passes through `host/consent.rs` via `HostConsentGate`. Denied / timed-out consent returns an MCP `isError` result with a recognizable reason string. - [x] `file_write` demonstrates argument-aware scoping via `FILE_WRITE_ALLOWED_PREFIXES` (path-prefix predicate). - [x] MCP `Tool` descriptors carry destructive / read-only annotations; verified by an integration test that introspects `tools/list`. - [x] Stub-gate tests cover Allow, Deny, Timeout, and no-gate-configured paths; each maps to the right MCP response shape. - [x] Audit hook emits structured events with decision + latency + redacted args digest (`TracingAudit`). ## Deviations / notes - Per-call audit lives in the agent as a tracing event for v1, not a persistent local store. VS-23 owns the centralised audit log; pinning local audit to disk has no current consumer. - Default consent prompt timeout reuses `host::consent::DEFAULT_PROMPT_TIMEOUT` (30 s). `HostConsentGate::new` accepts a custom timeout if a deployment wants longer. - Cancellation notifications (`notifications/cancelled`) still log-only in v2; in-flight consent cancellation lands with the gateway-side flow. ## Test plan - [x] `cargo build --workspace` (mutating-tools on, default). - [x] `cargo build --package meshagent --no-default-features --features meshcore` (mutating-tools off). - [x] `cargo test --workspace` (298 lib + 7 handshake + 2 mcp_tunnel + 4 mcp_mutating + 19 wsproxy + 24 tcpforward + 17 mcp lib + ...; all green). - [x] `cargo clippy --workspace --all-targets -- --deny warnings` (mutating-tools on and off). - [x] `cargo fmt --all --check`. - [ ] Manual MCP round-trip against a live agent with the consent dialog (lands with VS-23 once the gateway flow is wired).
feat(mcp): mutating tools + consent + audit (VA-27)
All checks were successful
Check / clippy + fmt + tests (pull_request) Successful in 4m36s
95634d1fa2
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>
David merged commit 5dbe307ff5 into main 2026-05-20 11:57:42 +02:00
David deleted branch feat/mcp-mutating-va-27 2026-05-20 11:57:42 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/vervain-agent!23
No description provided.