MCP Adapter
The latchgate-mcp binary bridges MCP-speaking agents to a running LatchGate gate. It translates between the MCP tool-call protocol and LatchGate’s authenticated execution pipeline, so every tool call goes through authentication, policy, WASM sandbox, and signed receipt — transparently.
Architecture
Section titled “Architecture”Agent (Claude Code, Cursor, Codex, …) ↕ MCP stdio JSON-RPClatchgate-mcp serve (agent session) ↕ DPoP-authenticated HTTP (UDS or TCP)LatchGate gate ↕ WASM sandbox + host I/OExternal systemsThe adapter runs as a local process spawned by the agent. Communication between agent and adapter is MCP-over-stdio. Communication between adapter and gate is the standard LatchGate Action API with DPoP sender-binding.
Separation of duties
Section titled “Separation of duties”The adapter has two distinct operating modes, each its own subcommand:
-
latchgate-mcp serve— the agent session. Exposes protected actions as MCP tools. Has no operator credential and no code path that advertises or handles approval tools. A requesting agent therefore cannot approve its own held actions. -
latchgate-mcp operator— the operator approval session. Exposes approval, denial, and review tools authenticated by an operator DPoP credential. Must run as a separate adapter instance fromserveso the agent can never reach these tools.
This is a security design decision, not a configuration option — the approval tools physically don’t exist in the agent session’s code path.
How actions become MCP tools
Section titled “How actions become MCP tools”On startup, latchgate-mcp serve calls GET /v1/actions on the gate and registers each action as an MCP tool:
- Tool name — the action’s
action_id(e.g.http_fetch,github_create_issue). - Description — from the action manifest’s
descriptionfield. - Input schema — the manifest’s
request_schema, exposed as the MCPinputSchema. The agent sees it as a standard JSON Schema and fills in the parameters.
When the agent invokes a tool, the adapter calls POST /v1/actions/{id}/execute with a fresh DPoP proof. The full kernel pipeline runs: lease validation, policy check, WASM sandbox dispatch, verification, signed receipt. The adapter maps the result back to MCP content.
If new actions are registered while the adapter is running, the agent discovers them on the next MCP tools/list call. No adapter restart needed. The adapter emits notifications/tools/list_changed when the tool set changes.
Agent session tools
Section titled “Agent session tools”The agent session (latchgate-mcp serve) exposes all registered gate actions as MCP tools, plus one built-in tool:
| MCP tool | Purpose |
|---|---|
| (gate actions) | One tool per registered action — the agent’s primary interface |
latchgate_my_pending | List this agent’s own pending approvals (read-only, scoped by agent_id and session_id). Useful after reconnect to discover in-flight approvals from a previous session. |
The latchgate_my_pending tool returns only this agent’s pending approvals — there is no cross-agent visibility and no mutation capability.
Operator session tools
Section titled “Operator session tools”The operator session (latchgate-mcp operator) exposes five tools for managing approvals:
| MCP tool | Purpose |
|---|---|
latchgate_list_pending | List all pending approvals across all agents with summary (risk level, expiry) |
latchgate_get_approval | Fetch full detail for a specific approval by ID |
latchgate_approve | Approve a pending action by approval ID |
latchgate_deny | Deny a pending action by approval ID with a reason |
latchgate_allowlist | Add a domain to an action’s egress allowlist (only when --enable-allowlist-tool is set) |
All operator tools are authenticated via DPoP proof-of-possession against the admin socket. The operator identity is integrity-bound into the approval grant.
The latchgate_allowlist tool permanently modifies security policy and is off by default — the operator must opt in explicitly with --enable-allowlist-tool.
MCP resources
Section titled “MCP resources”The adapter implements resources/list and resources/read for structured read access to gate state:
| Resource URI | Session | Content |
|---|---|---|
latchgate://actions | Agent + Operator | All registered actions with schemas, risk levels, and annotations |
latchgate://status | Agent + Operator | Gate health, session metadata, adapter version |
latchgate://approvals/pending | Operator only | Current approval queue with risk levels and expiry |
latchgate://audit/recent | Operator only | Last 25 audit ledger entries |
Resources are read-only and always return application/json.
MCP prompts
Section titled “MCP prompts”The adapter implements prompts/list and prompts/get on the operator session. The agent session returns an empty prompt list — agents must not introspect denial reasons (that would leak policy internals).
| Prompt | Session | Arguments | Purpose |
|---|---|---|---|
explain_denial | Operator only | trace_id (required) | Fetches the audit ledger entry for a trace_id and formats it for LLM analysis of why an action was denied |
review_pending | Operator only | (none) | Fetches all pending approvals and formats as a prioritized review list with risk levels, time remaining, and context |
DPoP authentication
Section titled “DPoP authentication”The adapter generates an ephemeral P-256 key pair at startup and obtains a sender-bound lease from the gate. Every request carries a DPoP proof (RFC 9449) binding the HTTP method, URI, and body hash. The lease auto-renews before expiry.
The agent never sees the DPoP key, the lease token, or any gate credentials. From the agent’s perspective, it calls an MCP tool and gets a result.
Approval flow over MCP
Section titled “Approval flow over MCP”When policy requires human approval for an action, the adapter returns an MCP text content block explaining that approval is pending, including the approval_id. The agent can present this to the user or decide how to proceed.
User: "Create a GitHub issue titled 'Fix auth bug'"
Agent → MCP tools/call → latchgate-mcp serve → POST /v1/actions/github_create_issue/execute ← HTTP 202 (pending_approval) ← MCP content: "⏳ Approval required (ID: appr_01J…). Waiting for operator."
Operator: latchgate approvals approve appr_01J… (via TUI / CLI / operator session)
Agent → MCP tools/call → latchgate-mcp serve → POST /v1/actions/github_create_issue/execute ← HTTP 200 + signed receipt ← MCP content: result + receipt_idThe agent can poll by re-invoking the same tool, or call latchgate_my_pending to check status. The operator can approve from the TUI (screen 2), CLI (latchgate approvals approve), or a separate latchgate-mcp operator session.
Transport
Section titled “Transport”UDS (default, recommended for production). The adapter connects to the gate via Unix domain socket at the standard path ($XDG_RUNTIME_DIR/latchgate/gate.sock or /tmp/latchgate-<uid>/gate.sock). No TCP exposure. The --public-base-url flag tells the adapter which URL to use in DPoP htu claims — it must match the gate’s public_base_url.
HTTP (development only). With --gate-url http://localhost:3000, the adapter connects via TCP. This requires the gate to be started with --expose-http.
CLI reference
Section titled “CLI reference”latchgate-mcp serve
Section titled “latchgate-mcp serve”Run the agent MCP stdio server.
| Flag | Env var | Default | Description |
|---|---|---|---|
--gate-socket | LATCHGATE_SOCKET | Auto-detected UDS path | Path to the gate Unix domain socket. Mutually exclusive with --gate-url. |
--gate-url | LATCHGATE_URL | — | HTTP base URL (e.g. http://localhost:3000). Mutually exclusive with --gate-socket. Dev only. |
--public-base-url | LATCHGATE_PUBLIC_URL | Same as --gate-url for TCP; required for UDS | Canonical URL for DPoP htu claims. Must match gate’s public_base_url. |
--agent-id | LATCHGATE_AGENT_ID | latchgate-mcp | Agent identifier in audit trail and budget tracking. |
--session-id | LATCHGATE_SESSION_ID | Fresh UUID v7 per process | Session identifier for budget tracking and audit correlation. |
--log-level | RUST_LOG | warn | Log level. Logs go to stderr (stdout is MCP protocol). |
latchgate-mcp operator
Section titled “latchgate-mcp operator”Run the operator approval MCP stdio server. All credential flags are required.
| Flag | Env var | Default | Description |
|---|---|---|---|
--admin-socket | LATCHGATE_ADMIN_SOCKET | — | Path to the admin Unix domain socket. |
--operator-key | LATCHGATE_OPERATOR_KEY | — | Path to operator private key file (P-256 PEM). Read at startup, handle dropped immediately. |
--operator-token | LATCHGATE_OPERATOR_TOKEN | — | Operator API token. Must match an api_key in the gate’s operator_credentials. |
--operator-id | LATCHGATE_OPERATOR_ID | operator | Operator principal name for audit attribution. |
--public-base-url | LATCHGATE_PUBLIC_URL | http://localhost:3000 | Canonical URL for DPoP htu claims. |
--agent-id | LATCHGATE_AGENT_ID | latchgate-mcp | Default agent principal for latchgate_allowlist when the tool call omits agent_id. |
--enable-allowlist-tool | LATCHGATE_ENABLE_ALLOWLIST_TOOL | false | Enable the latchgate_allowlist tool. Off by default — permanently modifies policy. |
--log-level | RUST_LOG | warn | Log level. |
The credential is verified against the admin API at startup. An invalid credential aborts the process immediately rather than surfacing a runtime 401 on the first approval.
latchgate-mcp install
Section titled “latchgate-mcp install”Write MCP server configuration for a target IDE.
latchgate-mcp install --ide cursorlatchgate-mcp install --ide claude-codelatchgate-mcp install --ide opencodelatchgate-mcp install --ide claude # Claude Desktoplatchgate-mcp install --ide clinelatchgate-mcp install --ide windsurflatchgate-mcp install --ide codexlatchgate-mcp install --ide copilot # GitHub Copilot (VS Code)latchgate-mcp install --ide open-claw # OpenClaw (MCPorter)latchgate-mcp install --ide hermes-agent # Hermes Agent (NousResearch)latchgate-mcp install --ide antigravity # Antigravity (Google)Each command writes the MCP server entry to the target IDE’s config file. Use --dry-run to preview without writing. Pass --gate-url or --gate-socket to override the transport (auto-detected from the active up session by default).
For manual setup and per-IDE details, see the individual integration pages: Cursor · Claude Code · OpenCode · Claude Desktop · Codex CLI · Cline · Windsurf
Troubleshooting
Section titled “Troubleshooting”Tools don’t appear in the agent.
The adapter discovers actions at startup. Verify the gate is running (latchgate status) and that actions are registered (latchgate actions list). Check adapter stderr for connection errors — MCP logs go to stderr, protocol to stdout.
Every tool call returns 401.
The adapter’s public_base_url must match the gate’s public_base_url exactly. This is the URL used in DPoP htu claims. A mismatch means the gate rejects every proof. For UDS transport, pass --public-base-url explicitly.
Tool call returns “approval required” but no one can approve.
Operator approval tools require a separate latchgate-mcp operator session with --admin-socket, --operator-key, and --operator-token. Without a running operator session, approvals must be handled via the TUI (latchgate tui, screen 2) or CLI (latchgate approvals approve <id>).
“Schema validation failed” on tool call.
The agent sent parameters that don’t match the action’s request_schema. Check the manifest with latchgate actions show <action_id>. Common causes: missing required fields, wrong types, extra properties when additionalProperties: false.
Adapter exits immediately. The adapter reads MCP JSON-RPC from stdin. If stdin is closed (e.g. the agent terminated), the adapter exits. This is correct behavior — the adapter’s lifecycle is tied to the agent session.
Connection refused / socket not found.
For UDS: verify the socket exists (ls -la /run/latchgate/gate.sock). For HTTP: verify the gate is listening (curl http://localhost:3000/healthz). If neither, start the gate first: latchgate up.
Operator credential verification failed.
The operator subcommand verifies credentials at startup. Check that --operator-key, --operator-token, and --operator-id match an entry in the gate’s operator_credentials configuration. The error message includes the admin socket path for debugging.