feat(mcp): per-agent MCP gateway endpoint (VS-22) #30

Merged
David merged 1 commit from feat/mcp-gateway-vs-22 into main 2026-05-20 11:00:10 +02:00
Owner

Summary

Implements the per-agent MCP gateway endpoint (VS-22).

  • POST /agents/{agent_id}/mcp - authenticates the operator session (same cookie as /control.ashx), gates on a new MeshRights::MCP_ACCESS bit, opens a TunnelMode::Mcp tunnel to the connected agent via msg/tunnel, and round-trips a JSON-RPC request over the tunnel as MCP Streamable HTTP. GET / DELETE return 405 (stable shape for future SSE + session teardown).
  • New /mcptunnel.ashx agent-side rendezvous - separate from /meshrelay.ashx so MCP framing flows without the legacy 'c' rendezvous byte or per-tunnel protocol byte. Stashes use a dedicated mcp_relays registry on AppState.
  • Tunnel mode constant tunnel_mode::MCP = 7 lives in meshcentral-agent-proto so the companion agent-side issue lands against a shared value. The relevant lockstep note is in tunnel_mode.rs.
  • Audit: one mcpsessionopen + one mcpsessionclose event per request via the existing event_log::record path. Outcome string (ok, timeout, agent-closed, agent-disconnected) lands in the message field so admins can grep.

v1 scope decisions

  • Stateless: one tunnel per POST. Mcp-Session-Id header is returned but not yet used to correlate calls. Persistent sessions + SSE + per-call audit + consent broker all ride VS-23.
  • Streamable HTTP only, current MCP spec. No SSE in v1.
  • Agent-side TunnelMode::Mcp handler is pending. The integration test stubs it by dialing /mcptunnel.ashx directly with a mock agent so the bridge is exercised end-to-end.

Test plan

  • cargo build --workspace
  • cargo clippy --workspace --all-targets clean
  • cargo test --workspace all pass (incl. the 5 new gateway tests)
  • Stub-agent round-trip: gateway forwards JSON-RPC tools/list, agent's canned response lands back as application/json, Mcp-Session-Id header set, audit log carries open + close events, mcp_relays.len() == 0 on completion
  • 401 without session cookie
  • 403 when the operator's mesh rights lack MCP_ACCESS
  • 503 when the agent is offline
  • 405 on GET / DELETE
  • End-to-end against a real agent - blocked on the companion VS-VA TunnelMode::Mcp issue
## Summary Implements the per-agent MCP gateway endpoint (VS-22). - `POST /agents/{agent_id}/mcp` - authenticates the operator session (same cookie as `/control.ashx`), gates on a new `MeshRights::MCP_ACCESS` bit, opens a `TunnelMode::Mcp` tunnel to the connected agent via `msg/tunnel`, and round-trips a JSON-RPC request over the tunnel as MCP Streamable HTTP. GET / DELETE return 405 (stable shape for future SSE + session teardown). - New `/mcptunnel.ashx` agent-side rendezvous - separate from `/meshrelay.ashx` so MCP framing flows without the legacy `'c'` rendezvous byte or per-tunnel protocol byte. Stashes use a dedicated `mcp_relays` registry on AppState. - Tunnel mode constant `tunnel_mode::MCP = 7` lives in `meshcentral-agent-proto` so the companion agent-side issue lands against a shared value. The relevant lockstep note is in `tunnel_mode.rs`. - Audit: one `mcpsessionopen` + one `mcpsessionclose` event per request via the existing `event_log::record` path. Outcome string (`ok`, `timeout`, `agent-closed`, `agent-disconnected`) lands in the message field so admins can grep. ## v1 scope decisions - Stateless: one tunnel per POST. `Mcp-Session-Id` header is returned but not yet used to correlate calls. Persistent sessions + SSE + per-call audit + consent broker all ride VS-23. - Streamable HTTP only, current MCP spec. No SSE in v1. - Agent-side `TunnelMode::Mcp` handler is pending. The integration test stubs it by dialing `/mcptunnel.ashx` directly with a mock agent so the bridge is exercised end-to-end. ## Test plan - [x] `cargo build --workspace` - [x] `cargo clippy --workspace --all-targets` clean - [x] `cargo test --workspace` all pass (incl. the 5 new gateway tests) - [x] Stub-agent round-trip: gateway forwards JSON-RPC `tools/list`, agent's canned response lands back as `application/json`, `Mcp-Session-Id` header set, audit log carries open + close events, `mcp_relays.len() == 0` on completion - [x] 401 without session cookie - [x] 403 when the operator's mesh rights lack `MCP_ACCESS` - [x] 503 when the agent is offline - [x] 405 on GET / DELETE - [ ] End-to-end against a real agent - blocked on the companion VS-VA `TunnelMode::Mcp` issue
feat(mcp): per-agent MCP gateway endpoint (VS-22)
Some checks failed
Check / fmt + clippy + build + tests (pull_request) Failing after 8s
Create release / Create release from merged PR (pull_request) Has been skipped
a16ac1f7c7
Adds `/agents/{agent_id}/mcp` which authenticates the operator against the existing session store, gates on a new `MeshRights::MCP_ACCESS` bit, asks the connected agent to open a `TunnelMode::Mcp` tunnel via `msg/tunnel`, and bridges Streamable-HTTP JSON-RPC frames over a dedicated `/mcptunnel.ashx` rendezvous. v1 is stateless (one tunnel per POST) and audits open + close via the existing event log. The companion agent-side `TunnelMode::Mcp` handler is still pending; the integration test stubs it by dialing the rendezvous endpoint directly with a mock agent so the bridge is exercised end-to-end.

Consent broker, persistent Mcp-Session-Id, server-to-client SSE notifications, and per-call audit ride the VS-23 follow-up.

#VS-22 State Done

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
David merged commit e6d3b8141f into main 2026-05-20 11:00:10 +02:00
David deleted branch feat/mcp-gateway-vs-22 2026-05-20 11:00:10 +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-server!30
No description provided.