MCP Governance
AIControls intercepts every MCP tool call from Claude Code and other MCP-enabled agents — applying policies, logging arguments and results, and routing high-risk operations for human approval.
How MCP proxying works
When Claude Code (or another MCP client) connects through the AIControls proxy, MCP tool calls are intercepted at the protocol layer before being forwarded to the upstream MCP server. The proxy evaluates policies against the tool name and arguments, then either allows, denies, or holds the call for approval.
No changes are needed on the MCP server side — the proxy is transparent to both the agent and the server.
What gets logged
Every MCP tool call produces an audit event containing:
| Field | Description |
|---|---|
| Tool name | e.g. bash, read_file, write_file, kubectl |
| Arguments | Full argument map passed to the tool |
| Result | Tool output (configurable — can be redacted for sensitive tools) |
| Decision | Allow, Deny, Approve, Ask, Warn, or Audit — and which policy triggered |
| Duration | Time from call to response |
| Resource | File path, URL, or resource identifier extracted from arguments |
Built-in SSRF protection
Any tool that accepts a URL argument (web fetch, browser automation, HTTP clients) can be directed by an adversarial prompt toward internal endpoints — cloud metadata services, internal APIs, or loopback addresses. AIControls blocks this by default: every tool-call argument that is a URL is resolved via DNS at call time and checked against private, loopback, link-local, and other internal ranges, across every tool and every argument — no per-tool configuration needed.
Resolving DNS at call time (rather than only pattern-matching the URL string) also catches DNS rebinding, where a hostname is deliberately made to resolve to an internal address.
If an agent legitimately needs to reach an internal address (e.g. an internal search API), add it to your workspace's SSRF allowlist rather than disabling the protection. See Policy Context for the object.mcp.ssrf.* fields available if you want to layer custom policy rules on top of the default protection.
Policy-based tool authorization
MCP tool calls are governed the same way as any other request: a policy declares a matchCondition that scopes it to specific tools or servers, and one or more validation rules that inspect the tool name and arguments to decide whether the call is allowed, denied, audited, or held for approval.
From the MCP Servers page, you can create these governance policies scoped to a specific server directly from the server list. Click the action menu (⋮) on any server to see two options:
| Action | What it does |
|---|---|
| Create a policy | Opens the policy editor pre-populated with a matchCondition scoped to this server's tool namespace. Add your validation logic and save — the policy applies immediately. |
| Add to existing policy | Adds a new rule to an existing policy, pre-scoped to this server. Use this to consolidate related rules into a single policy rather than creating many single-rule policies. |
This is the recommended starting point for governing a newly registered server. A typical workflow:
- Register the MCP server and confirm it appears in the server list.
- Use Create a policy to add an Audit rule that logs all tool calls from this server. Observe the audit log for a few days to understand usage patterns.
- Use Add to existing policy to layer in Deny or Approve (
warn-approve) rules for specific high-risk tools, using the argument patterns you observed.
Policies generated this way are full CEL ValidatingPolicy documents — you can edit them freely in Policies after creation. The server-scoped match condition is a starting point, not a constraint.
MCP resource subscriptions
Newer MCP clients (speaking protocol version 2026-07-28 or later) can subscribe to a resource on an MCP server and receive push notifications whenever it changes, instead of polling with repeated reads. AIControls governs subscriptions the same way it governs everything else: establishing one runs through the same identity, policy, content-safety, and enforcement checks as a tool call or resource read, and every pushed update is itself evaluated and audited.
Two limits protect against a runaway or misbehaving subscriber:
| Limit | Default | Effect when exceeded |
|---|---|---|
| Concurrent subscriptions per agent | 10 | The next resources/subscribe call is denied (subscription-concurrency-cap, see Decision Types) until the agent releases an existing subscription. |
| Update events per second, per subscription | 50 | Events over the limit are dropped silently (no error returned to the client) and audited (subscription_rate_exceeded); the subscription itself stays open. |
Each pushed update is evaluated against your policies independently — a policy can deny specific update content (e.g. a change to a sensitive file) without denying the subscription as a whole. Denying an individual update drops just that one push; the subscription itself stays open.
The MCP Servers page shows the MCP protocol version each server actually negotiated (e.g. "MCP 2026-07-28") next to its health status — useful for confirming which of your upstream servers support the newer subscription-capable protocol versus the classic request/response-only one.
Tool-definition pinning (rug-pull defense)
MCP servers can change behavior after you've already reviewed them — a tool's description or input schema can be silently rewritten between one tools/list response and the next. Because tool descriptions are fed directly to the model, a changed description can steer an agent into unintended behavior without anyone touching a config file. This is sometimes called a "rug-pull."
AIControls defends against it by treating a server's tool set as a snapshot to compare against, not just a reachable endpoint:
- At scan time, AIControls hashes every tool's name, description, and input schema from the server's
tools/listresponse and stores the snapshot. - On every later scan, the current
tools/listresponse is compared against the pinned snapshot, tool by tool.
Each tool carries a drift state:
| State | Meaning |
|---|---|
| Pinned | Matches the approved snapshot — unchanged. |
| Drifted | The tool existed in the snapshot, but its description, name, or input schema changed since it was approved. |
| New | The tool wasn't in the snapshot — the server started advertising it after pinning. |
Drift states are sticky: once a tool is drifted or new it stays that way — through any number of later scans — until an admin explicitly re-pins it. A drift alert fires once, when the state changes, not on every scan that re-observes it.
Drift and new-tool states appear as badges on a server's Tools tab — reachable by any role from Inventory → MCP Servers → Observed → (click a server), or by an admin from the Registration tab's own server details — and every detected change is written to the audit log so you can see exactly what changed and when. The current snapshot and hash for a server are also available read-only at GET /api/v1/mcp-servers/{name}/tool-schemas if you want to check it from a script.
Scheduled re-scan
Scans don't only happen when a server is added or manually refreshed — AIControls re-scans every connected server's tools/list on a schedule, so a rug-pull between manual refreshes is caught within the interval. The interval defaults to 60 minutes (0 disables) and is set under Inventory → MCP Servers → Registration tab → Details → Governance, or via PUT /api/v1/settings/mcp-rescan-interval. Servers using per-developer credentials are skipped — their inventory updates when a developer next uses them or an admin refreshes with a token.
Drift response mode
Each server has a drift response mode, defaulting to a workspace-wide setting (Alert unless changed) and overridable per server:
| Mode | Effect |
|---|---|
| Alert | A drifted or new tool raises an admin alert but stays callable — the default. |
| Block | Drifted and new tools are hidden from tools/list — clients can't see or call them — until an admin re-pins them. |
Re-pinning accepts a tool's current definition as the new approved baseline: its state returns to pinned (and, under Block mode, it becomes visible again). Re-pin from the tool list in Inventory → MCP Servers → Registration tab, or via POST /api/v1/mcp-servers/{name}/tool-schemas/{tool}/repin. Re-pinning is an admin action — reviewing the schema diff before accepting it is the point.
Block mode hides a drifted tool at the catalog (tools/list) layer. A per-call RequireApproval-on-drift flow — parking each individual call to a drifted tool for approval rather than hiding the tool outright — is planned but not yet available.
Shadow MCP traffic & quarantine
Every MCP tool call targets a server. If that server isn't one you've configured as a datasource, AIControls still sees the traffic — the proxy sits inline — and treats it as a signal instead of silently ignoring it.
The first time a tool call reaches a server that is in neither your configured datasources nor the server catalog, AIControls:
- Automatically creates a catalog entry for that server in the quarantined state.
- Raises a one-time admin alert — you're notified once per newly discovered server, not once per call.
A quarantined entry is an audit-only placeholder: traffic to it keeps being logged, but the server has no owning team, no bindings, and no policy of its own — it exists so the discovery isn't lost while an admin decides what to do with it.
State (quarantine_state) | Meaning |
|---|---|
| (empty) | Normal — a known, admin-managed server. |
| quarantined | Traffic was observed to an unregistered server; pending admin resolution. |
| denied | An admin reviewed the quarantined server and rejected it — further traffic is blocked by policy. |
An admin resolves a quarantined entry from Inventory → MCP Servers → Registration tab by either promoting it into a normal governed server (eligible for policies and Access Bindings) or denying it. See Resolve a quarantined server for the exact steps.
Observed server inventory
Inventory → MCP Servers opens on the Observed tab: every server that has actually received traffic through the proxy, built from audit data rather than the registration list, so it reflects what's really being called instead of what's been configured. Each row shows first/last-seen timestamps, total call count, and distinct tool count, and carries one of three statuses:
| Status | Meaning |
|---|---|
| Governed | The server has been observed in traffic and is registered — policies apply to it. |
| Ungoverned | The server has been observed in traffic but was never registered — no policy scope covers it. Raises an ungoverned_mcp_server security event. |
| Stale | The server is registered but has never been observed in traffic. |
Clicking a row opens its Tools tab — the same per-tool drift/inventory view described above — so any role can see what a server declares and who it's granted to, not just admins.
An Ungoverned row is your cue to register the server (see Register the server) so it comes under policy. The Registration tab, visible to admins only, holds the server list, connection details, and the drift/quarantine tooling described above.