feat(mcp): consent broker + per-call audit + coarse ACL (VS-23) #33
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/mcp-consent-broker-acl-audit-VS-23"
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?
What
Implements VS-23: turns the per-agent MCP gateway (VS-22) into the operator-facing consent point for mutating MCP tool calls. New
mcp_consentmodule + gateway wiring; routes registered inbuild_router.Read-only v1 traffic is unchanged: it never emits a control frame and falls straight through to the response path.
Acceptance criteria
consent_requestfrom the agent, renders an operator-facing prompt, returns the decision (Allow / Allow-for-session / Deny / Deny-forever).Design decisions
{"vervain":"consent_request","call_id","tool","args"}; the"vervain"envelope key is namespaced so it can never collide with a JSON-RPC response. Gateway replies{"vervain":"consent_response","decision":"allow"|"deny"}.POST /agents/:agent_id/mcp/consent/:consent_idrather than threading through the 512KB SPA control-channel handler. The prompt is still pushed to the operator's live session(s) via the user-session registry so the UI can render a modal; the gateway POST blocks on a oneshot until the decision endpoint resolves it (same process, sharedAppState). Keyed by a gateway-mintedconsent_idbecause the agent'scall_idis operator-controlled and could collide across concurrent operators.deny>pre_approve> prompt. A matchingdenyshort-circuits atools/callbefore a tunnel is opened.allowis permissive but non-decisive (still prompts).args_digestis a SHA-256 over canonical args plus a redacted, length-capped (512B) summary; sensitive keys (password/token/secret/...) are masked. Raw args are never persisted.AllowForSessioncaches an in-memory grant (1h TTL);DenyForeverpersists adenyACL row.New routes
POST /agents/:agent_id/mcp/consent/:consent_id- operator posts a decision.GET|POST|DELETE /agents/:agent_id/mcp/acl- coarse ACL admin surface (admin only).GET /agents/:agent_id/mcp/audit- per-call audit query (admin only).Data model (doc store)
mcpacldocs:{operator, agent, tool_glob, effect}.mcpauditdocs:{operator, agent, meshid, tool, args_digest, args_summary, decision, latency_ms, time}.Tests
cargo test -p meshcentral-web(mcp_consent unit + mcp_gateway integration): 9 unit + 10 integration tests pass.cargo clippy --all-targets -- -D warningsandcargo fmt --checkclean.Follow-ups (out of scope here)
mcpconsentpromptframe (server contract is in place; frame shape documented).consent_request/consent_responseemission (companion VS-VA / mcp-agent-tunnel-v2 issue). This PR is exercised end-to-end by a stub agent.Mcp-Session-Idand server-to-client SSE remain deferred.🤖 Generated with Claude Code
Turn the per-agent MCP gateway (VS-22) into the operator-facing consent point for mutating tool calls. Adds a new `mcp_consent` module plus gateway wiring; existing read-only v1 traffic is unchanged because it never emits a control frame and falls straight through to the response path. Consent broker: the agent emits a dedicated `{"vervain":"consent_request",...}` control frame before running a mutating tool. The `"vervain"` envelope key is namespaced so it cannot collide with a JSON-RPC response. The gateway intercepts the frame, decides (pre-approval / session cache / interactive prompt), pushes a prompt to the operator's live session(s), and relays `{"vervain":"consent_response","decision":"allow"|"deny"}` back to the agent. The interactive decision arrives out-of-band on POST /agents/:agent/mcp/consent/:consent_id, keyed by a gateway-minted consent id (the agent's call id is operator-controlled and may collide across operators). Fails closed to deny on a 120s timeout. Coarse server-side ACL: per (operator, agent, tool-glob) `mcpacl` docs with effect deny|allow|pre_approve. Precedence deny > pre_approve > prompt. A matching deny short-circuits a tools/call before a tunnel is opened. Admin-only CRUD at /agents/:agent_id/mcp/acl (full site-admin or MeshRights::ADMIN on the agent's mesh). Per-call audit: every mutating tool call (including coarse-denied and pre-approved) writes an `mcpaudit` doc {operator, agent, tool, args_digest, decision, latency_ms, ts}. args_digest is a SHA-256 plus a redacted, length-capped summary (sensitive keys masked) so raw args / secrets are never persisted. Queryable by operator, agent, and time range at /agents/:agent_id/mcp/audit. Decision side effects: AllowForSession caches an in-memory grant (1h TTL) to suppress re-prompting; DenyForever persists a deny ACL row so future calls are coarse-filtered. Tests: end-to-end stub-agent consent round-trip (prompt routed, operator decision flows back, audit lands), pre-approval skips prompt but still audits, coarse-deny blocks before tunnel, ACL admin CRUD round-trip + non-admin rejection, plus mcp_consent unit tests (glob, precedence, redaction, control-frame parse, registry). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>