Tool routing across servers
Now let's build the actual multi-server client. The core pattern: connect to all servers, collect all tools, build a routing map, and let the LLM use them all transparently.
async def connect_to_server(self, server_name, server_config):
"""Connect to a single MCP server and register its tools."""
server_params = StdioServerParameters(
command=server_config["command"],
args=server_config.get("args", []),
env=None,
)
# AsyncExitStack manages multiple concurrent connections
stdio_transport = await self.exit_stack.enter_async_context(
stdio_client(server_params)
)
read, write = stdio_transport
session = await self.exit_stack.enter_async_context(
ClientSession(read, write)
)
await session.initialize()
# Discover tools and build routing map
tools_result = await session.list_tools()
for tool in tools_result.tools:
self.tool_to_session[tool.name] = session
self.tool_server_map[tool.name] = server_name
self.available_tools.append({
"type": "function",
"function": {
"name": tool.name,
"description": tool.description,
"parameters": tool.inputSchema,
}
})Connect to one server, discover its tools, register them in the routing map.
The key data structures: - tool_to_session: maps tool name to the session that owns it - tool_server_map: maps tool name to server name (for UI display) - available_tools: merged list of all tools in OpenAI format When the LLM calls a tool, we look up which session owns it and route the call.
async def execute_tool(self, tool_name, tool_args):
"""Route a tool call to the correct server."""
session = self.tool_to_session.get(tool_name)
if not session:
return f"Unknown tool: {tool_name}"
result = await session.call_tool(
tool_name, arguments=tool_args
)
return resultTool routing: look up the session, call the tool. Same call_tool() as before.
Ordering exercise: Multi-server connection flow
Loading practice…
Validation checklist: Multi-server validation
Loading practice…
Quiz: Quiz
Loading practice…