Skip to content

MCP tools bypass the approval gate (trust: approval_gated has no effect on consent) #132

Description

@Ramyazzam

Summary

Tools provided by MCP servers never reach the approval gate. Built-in tools (os.shell.run, os.fs.write, browser.navigate, …) prompt as expected, but an MCP tool that does the equivalent thing runs silently.

This is easy to miss because McpServerConfig.trust defaults to the value "approval_gated", which reads like a consent setting. It is not one: src/agent/tool-resource-class.ts documents that class as a scheduling constraint.

approval_gated — Virtual class. Tools that may invoke requireApproval synchronously inside run(). Forbidden inside a batch […]

"May invoke" is the operative part. Nothing in the MCP subsystem ever does.

Verification

On main @ 7073a61 (v0.2.1):

$ git grep -n "requireApproval" origin/main -- src/mcp
$ echo $?
1                     # no matches

$ git grep -ln "requireApproval" origin/main -- src/tools | wc -l
7                     # built-in tools do call it

src/mcp/mcp-client.ts stamps resourceClass: resourceClassFor(qualifiedName) on each proxied tool, and src/mcp/mcp-resource-class.ts maps trust to that class, but the resulting class only affects batching. The tool's run() proxies straight to the server with no gate.

Reproduction

  1. Register any MCP server that can mutate state. Example used here, CursorTouch/Windows-MCP:
{
  "mcp": {
    "servers": [
      {
        "name": "windows",
        "enabled": true,
        "transport": { "kind": "stdio", "command": "uvx", "args": ["windows-mcp", "serve"] }
      }
    ]
  }
}
  1. Leave agent.approvalRequired: true and set no trust (so it defaults to approval_gated).
  2. Ask the agent to launch an application.

Expected: an approval prompt, as with os.shell.run.
Actual: mcp.windows.App executes immediately. No approval request is emitted on GET /api/events, and no prompt appears in the TUI or over the HTTP API.

Contrast: in the same session and config, os.fs.write prompts normally. The difference is only whether the tool is built in or proxied from MCP.

Impact

Whatever the server exposes runs unattended. With the example above that includes PowerShell, Registry, FileSystem, plus synthetic mouse and keyboard input (Click, Type, Shortcut). With @playwright/mcp it includes browser_run_code_unsafe.

Two things make it sharper:

  • McpServerConfig has no per-tool allowlist (name, description, enabled, transport, trust, env), so an operator who wants approvals on one risky tool can only disable the whole server.
  • The naming actively suggests the opposite is happening. An operator reading trust: "approval_gated" would reasonably conclude the calls are gated.

This seems worth treating as a security issue rather than a docs one: the safe-by-default posture the README describes ("Approval gates", "Everything atomic-agent does is inspectable and interruptible") does not hold for MCP tools.

Suggested fix

Any of these would close it, roughly in order of preference:

  1. Have the MCP tool adapter call requireApproval when the resolved trust is approval_gated, so the existing gate applies uniformly. This makes the current default mean what it says.
  2. Route MCP tools through dangerousTool() (or the v0.2 permission ladder) so they inherit the same policy as built-ins, including session-scoped grants.
  3. If gating every MCP call is too coarse, honour the MCP annotations.readOnlyHint that servers already advertise, and gate anything not marked read-only.

A per-server allowTools / denyTools list would also help independently, since it lets an operator adopt a useful server without accepting its whole surface.

Environment

  • atomic-agent v0.1.72 (observed), re-verified against main @ 7073a61 (v0.2.1)
  • Windows 11, agent.approvalRequired: true
  • Servers: CursorTouch/Windows-MCP via uvx, @playwright/mcp via npx

Happy to open a PR for option 1 if that direction is welcome.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions