Gateways and composition
One gateway in front of many servers: tool names that collide, a prefix that keeps them apart, an allow-list that cuts the tool list's token cost, and a call routed to the right server or the wrong one.
Loading the animation…
Concept
A host with ten MCP servers holds ten connections, ten sets of credentials and ten tool lists. A gateway collapses them into one: it is an MCP server to the host and an MCP client to each server behind it. It lists their tools, merges them, and routes each tools/call to the server that owns the tool. Step through the animation: one tools/list from the host fans out to the 10 tools of four servers, and comes back as one list.
Names collide. Tool names are only unique within a server. Here files, github and web all offer search, and files and github both offer read_file. The spec's tools page (accessed 2026-10-08) says it plainly: aggregators "MAY encounter naming collisions" and "SHOULD implement a disambiguation strategy such as prefixing tool names with a server identifier", and the server's own serverInfo.name "SHOULD NOT be relied upon" for it, since it need not be unique.
- flat: the first server registered wins each name. The host sees 7 tools; three are shadowed, silently. When the model asks for
searchmeaning the web, the gateway sends it to files, and the model gets "no matches in the project" back: a wrong answer, not an error. - prefix: every tool is exposed as
alias.toolwith an alias the gateway chooses (the MCP series writesgithub__search; any separator among the characters the spec allows for tool names, letters, digits,_,-and., will do). All 10 are reachable andweb.searchgoes to web. - filtered: prefixed, and only an allow-list for the task is exposed: 3 tools.
The tokens are the bill. The model reads every exposed definition in its prompt on every turn. The four servers' lists cost 651 tokens together (Qwen2.5 tokenizer); prefixing every name costs a little more (655), while flat shadowing "saves" tokens only by losing tools (472). Filtering to what the task needs cuts the list to 178. The same cost seen from the harness's side, and what it does to the context window, is Agent Harnesses Explained, the context budget.
What else a gateway does. It is also where a deployment puts authentication to the servers, audit logs and policy: the OAuth_for_MCP deck walks through a real one (Docker's MCP Gateway), and the two playgrounds below run an aggregating gateway and a per-server bridge in Docker Compose.
Maths
With servers each exposing tools of about tokens each, the list costs tokens on every model call, so over a session of calls the tool list alone costs input tokens (before any prompt caching). Prefixing changes only by the extra name tokens; filtering changes it by the fraction kept, here 3 of 10 tools.
Shadowing is a probability problem for the model as well as a routing one: if the model picks among same-named tools by name alone, a flat merge maps all of them to one server, so the call is right only when the model meant the first-registered server.
Code
The merge, as the vendored engine does it:
const exposed = policy === "flat" ? (t.name as string) : s.alias + "." + t.name;
let status = "exposed";
if (policy === "flat" && exposed in owner) status = "shadowed";
else if (policy === "filtered" && !ALLOW.includes(exposed)) status = "filtered";
The host's call under prefix, as it reaches the gateway:
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "web.search",
"arguments": { "query": "MCP gateway tool name collisions" },
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": { "name": "example-host", "version": "1.0.0" },
"io.modelcontextprotocol/clientCapabilities": {}
}
}
}