---
title: 'ADR 0009: MCP architecture'
description: An MCP server is an untrusted tool source. Discovery proposes; a human authorizes.
label: '0009 MCP architecture'
---

## Context

Model Context Protocol servers expose tools to agents. The protocol is designed
for an agent to connect, discover what is available, and start calling things.

That is the right model for a local development tool and the wrong one for
production authorization. The server controls its own tool list, so connecting
to a server for one capability means accepting whatever it chooses to expose
later.

## Decision

**An MCP server is an untrusted tool source.**

```text
register    →  PENDING, unusable
discover    →  tools appear, all UNAUTHORIZED
authorize   →  per tool, by a human
invoke      →  through the gateway, full pipeline
```

**Discovery and authorization are separate steps, and this is the decision.**

A newly discovered tool is unauthorized. Running discovery again on a server
whose other tools you have already authorized produces newly discovered tools
that are **also** unauthorized.

**Invocation goes through the gateway.** `POST /gateway/v1/mcp/call` runs the
same twelve-stage pipeline: same decisions, same action record, same audit
trail. An MCP tool is not a side door.

**An authorized MCP tool is a tool.** It requires a capability, it is subject
to policy, and `tool.kind` and `tool.protocol` are both `MCP` — so rules can
apply to MCP specifically.

**The server gets a credential from the broker**, not from the agent.

## Alternatives

**Let discovery grant access.** What the protocol's own model implies, and the
reason this ADR exists.

Under it, an MCP server could widen its own reach by adding a tool. You connect
for `get_ticket` and later the server also offers `run_query`, and your agents
can call it. The server proposes *and* decides.

**Authorize at the server level rather than per tool.** Less administrative
work: trust the server, trust everything it offers. Same problem, deferred — the
grant covers tools that do not exist yet.

**Let agents connect to MCP servers directly, outside Veefore.** Simplest, and
it puts the credential in the agent and takes the calls out of the pipeline. No
policy, no action record, no audit. Which means MCP becomes the thing you use to
avoid enforcement.

**Proxy MCP transparently without registering tools.** Pass calls through,
enforce on the destination host. Loses the ability to write policy about a
specific tool, and loses the per-tool authorization that is the point.

**Automatically authorize tools that match an allow-list pattern.**
Convenient — `get_*` is probably safe. It also means the server chooses its own
tool names, so the allow-list is a convention the untrusted party controls.

## Consequences

**Administrative work per tool.** Someone reads the discovered list and decides.
That is the intended friction: a tool named `run_query` with free-text SQL is a
different proposition from `get_ticket`, and the names are chosen by the server.

**Re-discovery has to be a routine you actually look at.** A tool appearing in
the list is the server asking for more reach. Making it implicit would recreate
the problem.

**A pending server is invisible to agents** until authorized, and a server left
`PENDING` for a long time is someone who started and did not finish. `doctor`
reports it.

**Transport auditing matters.** `doctor` checks for a server reachable over
plaintext HTTP to a non-loopback host, tools authorized but never used, and
servers with no credential calling an endpoint that presumably wants one.

**Policy on `tool.kind = MCP` is the practical default** for a server you do not
operate. Requiring approval for all MCP tools in production, then relaxing
per-tool once you have watched specific ones behave, is the pattern — and it is
expressible as one rule rather than as a list of tool keys that goes stale.

**MCP tools inherit the egress guards.** Destination allowlist, HTTPS unless
loopback, no redirects, 15-second timeout, per-tool circuit breaker. Nothing
about MCP bypasses the egress boundary.

**The protocol's own ergonomics are deliberately not followed.** An MCP client
built for convenience connects and starts calling. Veefore inserts a human. That
is a worse developer experience and the correct authorization model, and it is
worth saying plainly rather than presenting the friction as an accident.

## Related

- [MCP](/build/mcp)
- [Register a tool](/build/register-a-tool)
- [MCP API](/reference/api/mcp)
- [A2A architecture](/architecture/adr/0010-a2a-architecture)
