Skip to content

Guest MCP Endpoint

Capsem has two MCP entry points: a host-side server (capsem-mcp) that exposes sandbox management tools to AI agents via stdio, and a guest-side relay (capsem-mcp-server) that carries tool calls from inside the VM to the host MITM MCP endpoint over framed vsock.

graph TB
    subgraph "AI Agent (Claude Code, Gemini CLI)"
        AGENT["MCP Client<br/>(stdio)"]
    end

    subgraph "Host"
        HOST_MCP["capsem-mcp<br/>(stdio MCP server, rmcp)"]
        SVC["capsem-service<br/>(HTTP/UDS)"]
        GW["MITM MCP Endpoint<br/>(framed vsock:5002)"]
        AGG["capsem-mcp-aggregator<br/>(isolated subprocess)"]
        BUILTIN["capsem-mcp-builtin<br/>(isolated subprocess)"]
        EXT["External MCP servers<br/>(HTTP/SSE)"]
    end

    subgraph "Guest VM"
        GUEST_MCP["capsem-mcp-server<br/>(stdio-to-vsock relay)"]
        GUEST_AGENT["AI agent process"]
    end

    AGENT -->|stdio JSON-RPC| HOST_MCP
    HOST_MCP -->|HTTP/UDS| SVC

    GUEST_AGENT -->|stdio| GUEST_MCP
    GUEST_MCP -->|"framed MCP<br/>vsock:5002"| GW
    GW -->|"policy + telemetry"| AGG
    AGG -->|"stdio MCP"| BUILTIN
    AGG -->|"HTTP/SSE"| EXT

The host MCP server manages VMs. The guest relay provides MCP tools to code running inside the VM while the host endpoint owns parsing, policy, telemetry, and dispatch.

The host MCP server runs as a stdio process, typically spawned by an AI agent (Claude Code, Gemini CLI). It uses the rmcp crate for JSON-RPC handling.

sequenceDiagram
    participant Agent as AI Agent
    participant MCP as capsem-mcp
    participant Svc as capsem-service

    Agent->>MCP: tools/call (capsem_exec)
    MCP->>Svc: POST /vms/{id}/exec (HTTP/UDS)
    Svc-->>MCP: {stdout, stderr, exit_code}
    MCP-->>Agent: tool result

26 tools for full sandbox lifecycle management, telemetry, host diagnostics, and guest MCP routing:

ToolDescriptionService endpoint
capsem_createCreate a new VM (name, RAM, CPUs, env, image)POST /vms/create
capsem_listList all VMs with status and configGET /vms/list
capsem_infoVM details (ID, PID, profile, status)GET /vms/{id}/info
capsem_execRun shell command inside VM (timeout param)POST /vms/{id}/exec
capsem_runOne-shot: provision + exec + destroyPOST /run
capsem_read_fileRead file from guest filesystemPOST /vms/{id}/files/read
capsem_write_fileWrite file to guest filesystemPOST /vms/{id}/files/write
capsem_stopStop VMPOST /vms/{id}/stop
capsem_suspendSuspend VM (save RAM/CPU state)POST /vms/{id}/pause
capsem_resumeResume stopped or paused VMPOST /vms/{id}/resume
capsem_saveSave current VM statePOST /vms/{id}/save
capsem_deletePermanently destroy VM and all stateDELETE /vms/{id}/delete
capsem_purgeClean up disposable sessions; all=true includes retained sessionsPOST /purge
capsem_forkFork VM into reusable imagePOST /vms/{id}/fork
capsem_vm_logsGet serial/process logs (grep + tail params)GET /vms/{id}/logs
capsem_service_logsGet service logs (grep + tail params)Service log file
capsem_host_logsGet an allowlisted host log by symbolic nameGET /host-logs/{name}
capsem_panicsExtract structured panics and backtraces from host logsGET /panics
capsem_triageSummarize recent panics, IPC drops, server errors, and slow opsGET /triage
capsem_timelineRender a time-ordered session timeline by event layer and trace IDGET /vms/{id}/timeline
capsem_versionMCP server version and service connectivityLocal + service
capsem_mcp_serversList configured guest MCP serversService MCP IPC
capsem_mcp_toolsList discovered guest MCP toolsService MCP IPC
capsem_mcp_callCall a namespaced guest MCP toolService MCP IPC

If the service is not running when the MCP server starts, it attempts to launch capsem-service from the same bin/ directory. It polls the UDS socket for up to 5 seconds before giving up.

The guest MCP relay is a minimal stdio-to-framed-vsock bridge. It does not route or execute tools; the host MITM MCP endpoint owns parsing, policy, telemetry, and dispatch.

sequenceDiagram
    participant Agent as Guest AI process
    participant Relay as capsem-mcp-server
    participant EP as Host MITM MCP Endpoint

    Relay->>EP: \0CAPSEM_META:claude\n (metadata)
    Agent->>Relay: {"jsonrpc":"2.0","method":"tools/list"}\n (stdin)
    Relay->>EP: MCP frame stream_id=1 process=claude (vsock:5002)
    EP-->>Relay: MCP frame stream_id=1 payload={"jsonrpc":"2.0","result":{...}}
    Relay-->>Agent: {"jsonrpc":"2.0","result":{...}}\n (stdout)
StepDataDirection
1. Connectvsock:5002 (VSOCK_PORT_SNI_PROXY)Guest -> Host
2. Metadata\0CAPSEM_META:<process_name>\nGuest -> Host
3. RelayLength-prefixed MCP frames containing JSON-RPC payloadsBidirectional
4. EOFstdin closes -> half-close vsock writeGuest -> Host

The \0 prefix distinguishes connection metadata from framed content. Process names are sanitized: control characters and spaces replaced with underscores, truncated to 128 characters. The frame envelope also carries the authoritative per-request process name.

Two threads handle the relay:

  • Main thread: stdin -> vsock (reads from AI agent, writes to host)
  • Reader thread: vsock -> stdout (reads from host, writes back to AI agent)

The MITM MCP endpoint receives framed JSON-RPC over vsock:5002, normalizes the frame into the shared SecurityEvent rule rail, records protocol evidence, and routes allowed requests through the aggregator:

graph TD
    REQ["tools/call request"] --> PARSE["Extract tool name"]
    PARSE --> CHECK{"Tool category?"}
    CHECK -->|"local__fetch_http,<br/>local__grep_http,<br/>local__http_headers"| BUILTIN["capsem-mcp-builtin<br/>(HTTP tools)"]
    CHECK -->|"snapshots_*, file_*,<br/>dir_*"| FILE["capsem-mcp-builtin<br/>(VirtioFS file tools)"]
    CHECK -->|"server__tool<br/>(contains '__')"| EXT["capsem-mcp-aggregator<br/>(isolated subprocess)"]
    CHECK -->|"Unknown"| ERR["Error: tool not found"]
CategoryCriteriaHandlerExamples
Builtin HTTPlocal__fetch_http, local__grep_http, local__http_headerscapsem-mcp-builtinlocal__fetch_http, local__grep_http, local__http_headers
File toolsName starts with snapshots_, file_, dir_capsem-mcp-builtin (VirtioFS only)file_read, dir_list, snapshots_create
ExternalContains __ separator (server namespace)AggregatorClient routes to isolated subprocessgithub__list_repos, slack__send_message

External tool calls are routed through the MCP Aggregator — an isolated subprocess that manages all external MCP server connections with privilege separation.

Every tools/call request is normalized into a first-party SecurityEvent at the framed MITM boundary before the aggregator sees it. Rules use the shared security rule rail described in Policy, so MCP matches use fields such as mcp.method, mcp.server.name, mcp.tool_call.name, and mcp.tool_list.

rule actionBoundary behavior
allowTool call proceeds.
askRequest waits for an approval or denial row before dispatch.
blockReturns a policy JSON-RPC error. The request is not dispatched.
preprocess / postprocessRuns the configured plugin against the same SecurityEvent object.

The MCP gateway does not own a separate decision provider. Its job is to parse MCP, attach typed MCP fields to SecurityEvent, call the shared security engine, and log transport evidence plus any security_rule_events matches.

The product/security tool ledger is tool_calls. Every model-native, built-in/local, or MCP-origin tool invocation must appear there with an origin such as native, builtin, local, mcp, or mcp_proxy. Visible MCP protocol facts such as initialize/list/resource frames are represented as typed security events and matching security_rule_events, not as a second tool-call ledger. An MCP tools/call without a matching tool_calls row is a serious telemetry bug.

See Session Telemetry for the full tool_calls, tool_responses, and security-rule ledger joins.

FieldTypePurpose
aggregatorAggregatorClientClient handle for the isolated MCP aggregator subprocess
dbArc<DbWriter>Async telemetry writer
security_rulesRwLock<Arc<SecurityRuleSet>>Hot-reloadable security-event rules
plugin_policyRwLock<Arc<SecurityPluginPolicy>>Hot-reloadable plugin modes for security-event preprocessing/postprocessing

The AggregatorClient is cloneable (Arc-wrapped mpsc channel) and shared across endpoint sessions for a given VM. The rule set uses double-Arc style atomic swap through the endpoint state. New frames read the current rules, so reloads affect already-open guest MCP connections.

MCP server definitions are profile-owned. The profile points at mcp.json, and semantic routes mutate MCP server/tool posture through backend-owned profile rules instead of exposing raw rule text to the UI.

{
"servers": [
{
"id": "capsem",
"name": "Capsem",
"description": "Built-in Capsem MCP server for file and snapshot tools",
"transport": "stdio",
"command": "/run/capsem-mcp-server",
"builtin": true,
"enabled": true
}
]
}

Profile MCP config and corp constraints are validated by the service and passed to the MCP Aggregator subprocess at spawn time. Credentials are broker-owned references, not raw tokens in MCP config.

FilePurpose
capsem-mcp/src/main.rsHost MCP server: 26 tools, rmcp handler, service bridge
capsem-agent/src/mcp_server.rsGuest relay: stdin/stdout <-> framed MCP over vsock:5002
capsem-core/src/net/mitm_proxy/mcp_frame.rsFramed transport parser, stream lifecycle, and disconnect metrics
capsem-core/src/net/mitm_proxy/mcp_endpoint.rsHost endpoint: JSON-RPC dispatch, policy, telemetry
capsem-core/src/mcp/aggregator.rsAggregator protocol types and AggregatorClient
capsem-core/src/mcp/builtin_tools.rsBuiltin HTTP tools (fetch_http, grep_http, http_headers)
capsem-core/src/mcp/file_tools.rsFile and snapshot tools (VirtioFS workspace)
capsem-core/src/mcp/server_manager.rsExternal MCP server lifecycle and tool catalog
capsem-core/src/net/policy_config/security_rule_profile.rsSecurity-event rule schema, validation, Sigma import, and compiled rule set
capsem-core/src/security_engine/SecurityEvent construction, rule evaluation, plugin actions, and rule-ledger emission
capsem-mcp-aggregator/src/main.rsIsolated subprocess: NDJSON loop, server connections
capsem-process/src/main.rsspawn_mcp_aggregator(): launch and driver tasks
config/profiles/<id>/mcp.jsonProfile MCP server definitions

See MCP Aggregator for the full subprocess architecture.