# MCP Security The MCP server runs against your live OneHazel operator: tool calls are real workflow executions against your real connections. This page covers the auth model, how to scope an MCP key down to least-privilege, and what gets audited. ## Auth model MCP uses the same `oh_live_*` API keys as the rest of the OneHazel API. There is **no separate "MCP token"** type. Two extra checks on top of standard key validation gate every MCP request: 1. **`mcp_enabled` flag** (per-key, default OFF) — only keys that have been explicitly opted in via **Configure MCP** can reach `/mcp-server`. A normal `oh_live_*` key that hasn't been MCP-enabled returns `MCP_NOT_ENABLED`. 2. **Operator-match check (legacy URLs only)** — the canonical URL `/mcp-server` derives the operator from the bearer token alone. If a client uses the legacy `/mcp-server/` form, the server still enforces that the segment matches the key's `operator_id` and returns `AUTH_OPERATOR_MISMATCH` if not — so a leaked key cannot be used against another tenant's URL. Combined with the standard SHA-256 hash + revocation check, the gate is fail-closed: any failure short-circuits with a JSON-RPC `-32001` error and an HTTP 401 / 403. ## Workflow allowlist (least-privilege) Each MCP-enabled key carries an `mcp_workflow_allowlist`: | Value | Behaviour | |---|---| | `null` | Every workflow with `mcp_exposed=true` is callable | | `[]` (empty array) | Nothing is callable — key is MCP-enabled in name only | | `["wf_abc", "wf_xyz"]` | Only the listed workflows | The allowlist is enforced at two layers: - **`tools/list`** — only allowlisted, exposed workflows are returned - **`tools/call`** — even if a client tries an out-of-allowlist tool name, the call is rejected before the workflow engine runs ::: tip Per-assistant keys Create one key per assistant or per workflow set rather than reusing a single MCP key everywhere. If a key leaks, you revoke just that scope — not your whole MCP surface. ::: ## What's NOT auto-protected A few sharp edges worth calling out: - **No dry-run** — `tools/call` always runs the workflow for real. If the workflow emails customers, charges a card, or writes to a downstream system, it'll do so. Use a narrow allowlist for write-heavy workflows. - **Trigger-schema fields are advisory at the client** — the LLM uses `inputSchema` to fill arguments but might hallucinate values. The workflow engine re-validates against the trigger schema before running (`MISSING_TRIGGER_FIELD` on miss), but it can't tell you "Claude made up a plausible-looking customer ID." Keep MCP-exposed workflows defensive: validate IDs against your own data, fail fast, return useful error messages. - **Quota is shared with the rest of the API** — MCP calls bill against your operator's monthly Actions / AI Actions counters. A chatty client can burn through quota; size your plan with that in mind. ## Rate limits ::: info Coming with OH-441 Dedicated per-key MCP rate limits ship with [OH-441](https://joinelevateai.atlassian.net/browse/OH-441). Today, MCP calls share the operator's overall quota: Grow ~500 ev/s, Scale ~2,500 ev/s, Enterprise negotiated (see [Authentication → Rate limits](/authentication)). The OH-441 plan introduces a separate `mcp:tools/call` budget per key (per minute and per day), so a runaway assistant can't drain the rest of your workflow capacity. ::: ## Audit log ::: info Coming with OH-440 Every MCP `tools/call` will be recorded in the operator audit log with the calling key ID, tool name, argument hash, execution ID, and outcome — built on [OH-440](https://joinelevateai.atlassian.net/browse/OH-440). Today, every MCP-triggered workflow run is persisted in `workflow_executions` with `metadata.source='mcp'`, so you can already filter executions by source and trace what an assistant has done. The dedicated audit-log surface lands with OH-440. ::: ## Operational hygiene - **Rotate MCP keys quarterly**, sooner if a laptop running Claude Desktop / Cursor goes missing - **Never paste your bearer into a chat window** with an AI you don't fully trust — the key shouldn't leave the config file - **Per-project Cursor configs** should be gitignored — see the [Cursor guide](/mcp/connecting-cursor) - **Revocation is instant** — `DELETE /api-keys/:id` invalidates the key in-memory across all functions; no cache wait For incident response (suspected key leak, anomalous tool calls), revoke the key first and ask questions after — there's no risk in over-revoking. ## What's next - [API Reference](/mcp/api-reference) — full JSON-RPC method docs - [Connecting Claude Desktop](/mcp/connecting-claude-desktop) / [Connecting Cursor](/mcp/connecting-cursor) — setup walkthroughs