Control Tower v0.2.1

MCP tool servers

Tool calls are where agents act — create a ticket, merge a pull request, delete a contact. Register your MCP servers with Control Tower and every tool call is mapped, gated, held for approval or inspected like a model call.

Register a server

MCP servers → Add server: a name, a slug (the tool prefix), the server's Streamable HTTP endpoint, and its credentials if it needs them (stored encrypted; agents never see them).

Registering an MCP server

Control Tower connects, lists the tools and classifies each one: read (from readOnlyHint or a name like get_, list_, search_), write, or destructive (from destructiveHint or a name like delete_, merge_, transfer_). Gates can target those classes.

A registered server and its tools

Servers are health-checked, and their tool lists are refreshed so new tools appear on the map. The transport is Streamable HTTP. For a stdio-only server, run it behind a small bridge such as supergateway and register the bridge's URL — Control Tower does not start local commands.

Each agent gets its own session with each upstream server, so a stateful server never shows one agent what another left in its session (a login, an open document, a cursor). Keys that share an agent ID share a session. An unused session is closed after 30 minutes, and at most 2,000 are kept open, the least recently used closing first.

Connect clients

Agents connect to one address with their own key:

  • http://<host>:4000/mcp — every server, tools named server__tool (github__merge_pr).
  • http://<host>:4000/mcp/<slug> — one server, original tool names.

Client setup for Claude Code, Cursor and the OpenAI Agents SDK is in Connect your agents.

Who sees which tools

tools/list is filtered per key before the agent sees anything:

  1. the key's allowed_mcp globs (e.g. salesforce__search_*),
  2. then gates that block a tool for this agent regardless of arguments.

A tool an agent can't see is one it can't call and doesn't need approval for — this is the strongest control Control Tower has, stronger than blocking calls after the fact. Gates that depend on arguments leave the tool visible and decide at call time.

Gates on tools

Everything in the Airspace applies: drag from an agent to a server or a single tool row to block it, require approval or inspect it. On a tool call:

  • Blocked — the agent gets a tool result with isError: true and the reason, so the model can explain it instead of retrying blindly.
  • Held — the call waits for a human; the card shows the tool's actual arguments. Unanswered, the result's JSON carries ct_status: "pending" and a ticket. Once someone approves, repeat the same call with the ticket in params._meta.ct_approval (or the x-ct-approval HTTP header); it goes through once. A retry before anyone decides gets pending again, with retry_after_ms. See Retrying with a ticket.
  • Inspected — arguments and results are scanned. Scanning tool results is where indirect prompt injection and data leaks are caught before the model reads them: mask an email address in a CRM record, block instructions hidden in a web page.

Resources and prompts

On a single server's endpoint (/mcp/<slug>), agents can also list and read the server's resources and get its prompts. Reading a resource and getting a prompt are flights like tool calls — recorded, gated and inspected — named <slug>__resources/read and <slug>__prompts/get, with the URI or prompt name and arguments as their arguments:

  • A key's allowed_mcp must cover them (docs__* does; docs__search alone doesn't): otherwise the lists come back empty and reads are refused.
  • A gate on docs__resources/read blocks, holds or inspects reads — an inspect gate on the output is where instructions hidden in documents are caught. Match a single resource with an argument constraint on uri.
  • A refused read or prompt comes back as a JSON-RPC error with the reason.
  • A server that fronts an agent is sent a delegation token with them too.

In the config file

mcp_servers entries in a config file (URL, auth_type, auth_value, static_headers) become MCP servers, both with Import config and with --config. See Config file.