Skip to content

MultiClientManager ​

The MultiClientManager class provides the base functionality for managing multiple MCP (Model Context Protocol) server connections and tool registration.

Overview ​

MultiClientManager handles the complexity of connecting to multiple MCP servers, discovering their tools, resolving naming conflicts through automatic remapping, and registering all tools into a centralized ToolsManager. It serves as the foundation for ClientManager's singleton implementation.

Properties ​

  • clients (list[MCPClient]): List of all registered MCP clients
  • script_to_clients (dict[str, MCPClient]): Mapping from server script paths to client instances
  • name_to_clients (dict[str, MCPClient]): Mapping from tool names to their owning clients
  • tools_remapping (dict[str, str]): Tool name remapping dictionary (original_name → remapped_name)
  • reversed_remappings (dict[str, str]): Reverse remapping dictionary (remapped_name → original_name)
  • tools_manager (MultiToolsManager): The tool manager where MCP tools are registered
  • _is_initialized (bool): Whether all clients have been initialized
  • _lock (asyncio.Lock): Async lock for thread-safe operations

Methods ​

__init__() -> None ​

Initializes a new MultiClientManager instance.

Example:

python
from amrita_core.tools.mcp import MultiClientManager

manager = MultiClientManager()

get_client_by_script(server_script: str | Path) -> MCPClient ​

Creates a new MCP client for a specific server script without registering it.

Parameters:

  • server_script: Path to the MCP server script or URI

Returns:

  • MCPClient: A new, unconnected client instance

Example:

python
client = manager.get_client_by_script("/path/to/server.mcp")

async get_client_by_tool_name(tool_name: str) -> MCPClient ​

Finds the MCP client that owns a specific tool by its name.

Parameters:

  • tool_name: Name of the tool (automatically handles remapped names)

Returns:

  • MCPClient: The client instance that manages this tool

Raises:

  • RuntimeError: If the tool is not found in any registered client

Example:

python
client = await manager.get_client_by_tool_name("get_weather")
print(f"Tool owner: {client.server_script}")

register_only(*, client: MCPClient) -> Self ​

Registers an MCP client without initializing it.

Parameters:

  • client: Pre-created MCP client instance

Returns:

  • Self: For method chaining

Example:

python
custom_client = MCPClient("/special/server.mcp")
manager.register_only(client=custom_client)

register_only(*, server_script: str | Path) -> Self ​

Registers an MCP server by script path without initializing it.

Parameters:

  • server_script: Path to the MCP server script

Returns:

  • Self: For method chaining

Example:

python
manager.register_only(server_script="/path/to/server.mcp")

async initialize_this(server_script: str | Path, fail_then_raise: bool = False) -> Self ​

Registers and initializes a single MCP server.

Parameters:

  • server_script: Path to the MCP server script
  • fail_then_raise: If True, raises exceptions on initialization failure

Returns:

  • Self: For method chaining

Example:

python
await manager.initialize_this("/path/to/weather.mcp")

async initialize_scripts_all(scripts: Iterable[str | Path]) -> Self ​

Initializes multiple MCP servers from an iterable of script paths.

Parameters:

  • scripts: Iterable of server script paths

Returns:

  • Self: For method chaining

Example:

python
scripts = ["/path/to/weather.mcp", "/path/to/database.mcp"]
await manager.initialize_scripts_all(scripts)

async initialize_all(lock: bool = True) -> Self ​

Connects to all registered MCP servers and registers their tools.

Parameters:

  • lock: If True, acquires internal lock before initialization

Returns:

  • Self: Sets _is_initialized to True upon completion

Example:

python
# Register servers first
manager.register_only(server_script="/server1.mcp")
manager.register_only(server_script="/server2.mcp")

# Then initialize all at once
await manager.initialize_all()

async update_tools(client: MCPClient) -> Self ​

Updates tools from a specific client, re-registering them with conflict resolution.

Parameters:

  • client: The client whose tools should be updated

Returns:

  • Self: For method chaining

Example:

python
await manager.update_tools(existing_client)

async unregister_client(script_name: str | Path, lock: bool = True) -> None ​

Unregisters an MCP server and removes all its tools from the tools manager.

Parameters:

  • script_name: Path to the server script to remove
  • lock: If True, acquires internal lock during operation

Example:

python
await manager.unregister_client("/path/to/remove.mcp")

async reinitialize_all() -> None ​

Reinitializes all registered clients (useful for refreshing connections after failures).

Example:

python
await manager.reinitialize_all()

_tools_wrapper(tool_name: str) -> Callable[[dict[str, Any]], Awaitable[str]] ​

Creates a wrapper function for tool execution that can be registered as a tool handler.

Parameters:

  • tool_name: Name of the tool to wrap

Returns:

  • Callable: Async function that accepts tool arguments and returns results

Note: Internal method used for tool registration.

_load_this(client: MCPClient, fail_then_raise: bool = True) -> None ​

Internal method to load tools from a client and register them with conflict resolution.

Parameters:

  • client: The client whose tools should be loaded
  • fail_then_raise: If True, raises exceptions on tool loading failure

Note: This is an internal method called during initialization.

Key Features ​

Automatic Tool Registration ​

All tools from registered MCP servers are automatically discovered and added to tools_manager, making them immediately available to agents.

Tool Name Conflict Resolution ​

When multiple servers provide tools with identical names:

  • First registration retains the original name
  • Subsequent registrations are automatically remapped (e.g., search → referred_42_search)
  • Warning logs are generated for each conflict detected
  • Remapping information is stored in tools_remapping and reversed_remappings dictionaries

Intelligent Routing ​

The get_client_by_tool_name() method automatically resolves which client owns a tool, handling both original and remapped names transparently.

Thread Safety ​

All critical operations are protected by an async lock (_lock) to ensure thread-safe access to shared state when managing concurrent operations.

Lifecycle Management ​

Handles the complete lifecycle of multiple MCP connections:

  • Connection establishment
  • Tool discovery and format conversion
  • Tool registration with conflict handling
  • Connection cleanup and reinitialization

Complete Usage Example ​

python
import asyncio
from amrita_core.tools.mcp import MultiClientManager


async def main():
    # Create manager instance
    manager = MultiClientManager()

    # Method 1: Programmatic setup
    scripts = ["/path/to/weather.mcp", "/path/to/database.mcp", "/path/to/calendar.mcp"]

    # Register and initialize all servers
    await manager.initialize_scripts_all(scripts)

    # Check available tools
    available_tools = manager.tools_manager.get_tools()
    print(f"Available tools: {list(available_tools.keys())}")

    # Find which client owns a specific tool
    weather_client = await manager.get_client_by_tool_name("get_weather")
    print(f"Weather tool provided by: {weather_client.server_script}")

    # Handle duplicate tool names (automatic remapping)
    # If two servers both have a "search" tool:
    # - First server keeps "search"
    # - Second server becomes "referred_42_search"

    # Dynamically add a new server at runtime
    await manager.initialize_this("/dynamic/new-server.mcp")

    # Remove a server and its tools
    await manager.unregister_client("/path/to/old-server.mcp")

    # Refresh all connections (e.g., after network issues)
    await manager.reinitialize_all()

    # Manual client management
    custom_client = manager.get_client_by_script("/special/server.mcp")
    manager.register_only(client=custom_client)
    await manager.initialize_all()


asyncio.run(main())

Error Handling ​

MultiClientManager includes robust error handling:

  • Server initialization failure: Logs error and continues with other servers (unless fail_then_raise=True)
  • Tool execution errors: Handled by individual MCPClient instances, returns structured error JSON
  • Duplicate tool names: Automatically remapped with warning logs
  • Connection loss: Automatic retry on next tool call via reinitialize_all()
  • Thread safety violations: Prevented by async lock mechanism

Relationship with ClientManager ​

ClientManager extends MultiClientManager and adds:

  • Singleton pattern: Ensures only one instance exists per application
  • Global accessibility: Can be accessed from anywhere via ClientManager()
  • Configuration integration: Works seamlessly with AmritaConfig's MCP settings

For most use cases, prefer using ClientManager over directly instantiating MultiClientManager.

Apache 2.0 License