- The block - five keys, and auth
- The file is the block, and nothing else - what an mcp file refuses
- Choosing which tools to offer - a filter, not a contract
- Where the source can be listed - agent or task
- What each target needs - the SDK, and the dependency
- Advanced - what the compile writes
- Troubleshooting - the refusals, and a server that is down
tools/web_search.yaml
agent.yaml
auth: added to show the
field. The real one ships in
examples/hotel-concierge:
its tools/web_search.yaml names the same server and the same two tools, and
carries no url_env, transport or auth:. The platform reads the server by
name from its own registration, so those fields matter only for a code target.
That package deploys to slng only, agent name hotel-concierge, alongside four
other tools, so there was nothing for auth: to reach.
The block
string
The server’s registered platform name. Omit to use this tool file’s name.
string
An UPPER_SNAKE environment variable name holding the server URL. Required on LiveKit and
Pipecat. SLNG uses its registered server and does not read this field.
string
Accepts
sse or streamable_http. If omitted, use the runtime’s URL-based transport
selection; a URL ending in /mcp selects streamable HTTP.object
type, token_env, and optional header, as described below. Omit for no authored
authentication. SLNG uses its registered credentials and does not read this block.list of strings
Unique, non-empty server tool names. Required on SLNG. If omitted on a code target,
expose every tool the server offers.
url_env, transport or
auth. SLNG already has the server registered, with its own connection
settings and credential; those three fields exist for LiveKit and Pipecat,
which dial the server themselves. Selecting a code target still requires
them, with their existing rules and refusals.
server exists because the two names live in different namespaces. A tool file
name is lowercase snake_case, and a server’s name on the platform is whatever
somebody typed in a dashboard: real ones carry dashes and spaces. Without
server, a server called firecrawl-mcp-2 could not be named at all. Read the
names your organisation has with voiceai mcp list.
transport is optional because both platforms already have a rule for guessing
it: a URL whose path ends in /mcp is streamable HTTP, anything else is SSE.
Write it when you want the choice visible instead of inferred.
auth is the same shape webhook tools use, so there is nothing new to learn and
no new code in the generated project. Its own three keys:
string
required
Accepts
bearer or api_key. Required when auth is present; no scheme is inferred.string
required
An UPPER_SNAKE environment variable name holding the token, never the token itself.
There is no default.
string
An HTTP header name, legal only with
type: api_key. Omitted means X-API-Key. Bearer
authentication uses Authorization: Bearer.The file is the block, and nothing else
Seven fields that describe one tool to the model are illegal on anmcp: file.
Each is refused on its own, with the file, the line, and the reason:
output, inject, interruption,
effect, and announce. In one place, so you can see the whole rule at once:
This is the whole point of the shape. One tool file describes one tool the model
can call. An
mcp: file is not that: it is a source of tools, and how many
there are, what they take, and what they return is only known once the server is
running.
Choosing which tools to offer
tools out and the agent gets everything the server exposes.
An empty list entry or a name repeated twice is still an authoring error and is
refused before the server is contacted.
On SLNG, unmute deploy checks every selected name against the registered
server’s discovery snapshot. A missing tool blocks deployment. A real deploy
can refresh an unusable snapshot once; a dry run never refreshes it.
url_env, transport, and auth reach no further than that same offline
compile on SLNG. The platform already has the server registered, under
server, with whatever credential it needs. Nothing in this block reaches it
at run time except the tool list. Those three fields matter for the code
targets only.
Where the source can be listed
The reason is the framework’s: a Pipecat Flows node builds its advertised tool set
out of its own function schemas, and Pipecat’s MCP client offers no per-tool
handler to put in one. So the source goes on the agent, where it is offered
whenever that agent is active.
Two files may name the same
url_env. They are two independent sources, each with
its own selection and its own assignment, which is how one server can offer a
narrow set to one agent and a wider set to another.
What each target needs
LiveKit needs the Python SDK. Its Node SDK has no MCP support at all, so the gate is a refusal rather than a warning:livekit-agents 1.8.1. A target that
names any other version fails the global version check rather than having its
version quietly changed:
llm_tool_call spans carrying function.name, tool_call_id, and function.arguments.
Pipecat refuses to start when an agent tool, task function, or MCP source on the same agent exposes the same name.
Either way the dependency the project declares picks up an mcp extra for
you, alongside whatever your other models already need. Compiling the worked
example above, with its other roles filled in, writes
livekit-agents[cartesia,deepgram,mcp,openai]==1.8.1 and
pipecat-ai[cartesia,deepgram,mcp,openai,runner,silero,webrtc]==1.10.0.
Advanced
What the compile does with it
Every name the block holds becomes part of the generated project. Compiling a package that names the worked example’s tool and also targets livekit adds, among the namesbuild/livekit/.env.example collects from the rest of the
package:
build/livekit/agent.py’s startup check, REQUIRED_ENV,
and in build/livekit/compile-report.json, which says where each one came from:
AgentSession.start, LiveKit creates temporary clients from this same
factory and checks all distinct sources concurrently. Runtime agents and tasks
then receive fresh clients; preflight clients are never reused.
Leave a field out of the block and the generated call leaves out its argument,
rather than passing a guess: no transport means no transport_type, no tools
means no allowed_tools, no auth means no headers.
Troubleshooting
The server is down
Both targets treat a listed MCP source as required:- LiveKit connects and fetches the tools before
AgentSession.start. A failure stops the session before its greeting. Closing every created preflight client is always attempted, and a close failure also stops startup. The agent or task receives a fresh client from the same factory. - Pipecat raises on startup and the bot exits loudly.
url_env holds a URL instead of a name
url_env is a name, never an address. Writing the URL there is refused:
transport has a value the client does not know
Any other value is refused with both legal ones:
sse or streamable_http, or leave the key out and let the SDK
infer it from the URL.
Pipecat refuses an MCP source scoped to a task
The Pipecat refusal names the fix:tools: list, as in
Where the source can be listed.
The slng target asks for an explicit tool list
SLNG has no “whole server” attachment, sotools: is required there.
LiveKit and Pipecat pass the server through and let it decide what it offers.
SLNG writes one reference per tool up front, and an offline compile cannot ask
the server what those are:
mcp.tools. Read what the server offers
with voiceai mcp list.
Where to go next
Prebuilt tools
The last way a tool can run: the ones the runtime already has.
Secrets
Where
url_env and token_env values come from.