An MCP server is a set of tools an external service hosts. Attaching one to an agent puts those tools in front of the model on every turn, next to the tools you built yourself. The agent decides when to call them; you decide which ones exist and what they are called. Three separate things control that, and they are worth keeping apart:

Attaching

mcps is a field of the agent config, alongside tools and kb_ids. Each entry names an integration:
The integration must be active and must be one that lends tools; Telegram is refused with is not MCP-capable and cannot be attached as a tool. Attaching the same integration twice in one config is refused too. Attaching takes a publish. Live traffic runs the last published version, so the tools are not in front of the model until you publish. Writes replace the whole config, so read it, add to mcps, and send the result back:
Agent tasks take the same field.

The inline shorthand

An entry can carry a server definition instead of an id:
On an agent write that is created as a custom_mcp integration and the entry is replaced by {"integration_id": "…"} in the stored config. It is a convenience for building an agent and its servers in one request, not a second kind of attachment — read the config back and you will find the id. The integration it made is an ordinary one, editable on the Integrations page, which is where its tool approval lives. Inline servers are API-key-shaped only; an OAuth provider has nowhere to keep a refresh token and must be a real integration. Details in custom MCP servers.

Seeing what a server offers

GET /v1/integrations/{integration_id}/mcp-tools connects to the server and lists everything it exposes. It is a live call, so it also proves the connection works.
For a server whose namespace is crm, that comes back as:
name is the server’s own name — that is what approval is keyed on. exposed_name is what the model is shown — that is what a prompt has to say. The two are the same string only when the namespace is blank, or when the server’s names already start with it. The listing is deliberately unfiltered: it is what you choose an approval from, so it has to include the tools nobody has approved. An integration that is not active answers 400; a server that cannot be reached answers 502 with the underlying failure.

Approving tools

Which of a server’s tools an agent may call is allowed_tools on the integration. It is approved once and shared by every agent attached to it.
  • null means all of them. A new connection starts there.
  • A PATCH replaces the list wholesale. It is a set, not an accumulation: send the full list you want, every time.
  • It can never be empty. “Approve nothing” is not a state; that is what disabling the integration means, and an empty list would read as “no filter” to the MCP client — the exact opposite.
  • It is live. Narrowing the list reaches every already-published agent on its next turn. There is nothing to republish.
There is no value that means “go back to everything” — send the full list from mcp-tools instead.

Why narrowing is worth the trouble

Every approved tool is described to the model on every turn. A voice agent choosing from ninety tool descriptions is slower and less accurate than one choosing from six, and it pays for those descriptions in input tokens on each turn of every call. Some vendor servers expose a very large surface. Approve the handful the agent’s job actually needs.

Approval matches the server’s own name

allowed_tools is filtered first, on the tool’s own name, and the namespace is applied afterwards. So approve search, not crm_search — and changing the namespace later never invalidates an approval.

Namespacing

The model does not see a server’s tool names raw. Each is prefixed with the integration’s tools_namespace:
The default is the provider key for a hosted provider (asana, hubspot, google_calendar) and nothing for a custom server, which has no name to derive one from. The rules, all of them:
  • A blank namespace leaves names untouched. "" is a real value meaning “the server’s own names”, not an unset field.
  • A tool that already starts with the prefix is not prefixed again. Tavily’s own tools are tavily_search and tavily_extract, so they stay that way rather than becoming tavily_tavily_search.
  • The value must match ^$|^[a-z][a-z0-9_]{0,23}$ — empty, or lowercase starting with a letter, up to 24 characters.
  • On a PATCH, omitting the field keeps the current namespace and sending "" clears the prefix. Those are different requests. Read the field back after any change you are not sure of.
Write prompts against the exposed name. allowed_tools approves search; your prompt has to say crm_search. Getting this backwards produces an agent that describes a tool the model cannot find and quietly stops calling it. exposed_name on the tool listing is the string to copy.

Two servers cannot share a namespace

If two attached servers present their tools under the same prefix, every name they share reaches the model twice and the session dies before the agent speaks. So the config is refused when you save it:
This is the ordinary case, not an edge one: two connections to one provider both default to the provider key. See connecting the same provider twice. Two servers with blank namespaces are not checked, because a blank namespace is the author asking for the server’s own names in both cases. If those names do collide the call ends at startup, with the duplicate named on the call detail page.

What happens on a call

The connection is made when the session starts, in parallel with everything else the agent needs. Each request to an MCP server — the tool listing at connect, and every tool call after it — has a five-second budget. A server that is down or slow to connect does not fail the call. The agent starts without that server’s tools and the failure is logged. From the caller’s side nothing is wrong; from the model’s side the tools simply are not in its list, and it will improvise around a prompt that still talks about them. This is the strongest argument for keeping the prompt honest about what the agent does when a lookup fails. A tool call that exceeds the budget comes back to the model as a failed tool call, and the model decides what to say. The native Google Calendar tools are the exception to all of this: they run against Google directly, with their own retry and error handling. For an OAuth provider, the access token is minted per request. If the provider answers 401, the token is refreshed once and the request retried; if that fails too, the integration is moved to needs_reconnect and stays broken until someone reconnects it.

In the dashboard

Integrations lists every connection with its status, the account it is signed in as, its namespace and how many of its tools are approved. Clicking a row — or Tools in its menu — opens the approval modal, which reads the live tool list, shows each tool under the name the model will see with the server’s own name beneath it, and saves the ticked names and the namespace together. The namespace field previews the exposed names as you type it. The modal will not save an empty selection. It says so: “Approve at least one tool, or disable the integration instead.”

Field reference

Next

Custom MCP servers

Point an agent at a server you run yourself.

Google Calendar

The nine native calendar tools, and how to prompt an agent to book.

Prompting

Telling the model when to reach for a tool, and what to say while it runs.