|
| 1 | +# ClientManager |
| 2 | + |
| 3 | +The ClientManager class provides centralized management for multiple MCP (Model Context Protocol) server connections and tool registration. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +ClientManager is a **singleton class that extends [`MultiClientManager`](MultiClientManager.md)**, inheriting all of its functionality while adding singleton pattern support for global accessibility. It handles multiple MCP clients, resolves tool name conflicts through automatic remapping, routes tool calls to appropriate servers, and automatically registers all discovered tools into the ToolsManager. |
| 8 | + |
| 9 | +## Inheritance |
| 10 | + |
| 11 | +```text |
| 12 | +ClientManager → MultiClientManager |
| 13 | +``` |
| 14 | + |
| 15 | +ClientManager inherits all methods and properties from MultiClientManager and adds: |
| 16 | + |
| 17 | +- Singleton instance management via `__new__()` |
| 18 | +- Global single-instance access pattern |
| 19 | + |
| 20 | +## Properties |
| 21 | + |
| 22 | +_Inherited from [`MultiClientManager`](MultiClientManager.md):_ |
| 23 | + |
| 24 | +- `clients` (list[MCPClient]): List of all registered MCP clients |
| 25 | +- `script_to_clients` (dict[str, MCPClient]): Mapping from server script paths to clients |
| 26 | +- `name_to_clients` (dict[str, MCPClient]): Mapping from tool names to their owning clients |
| 27 | +- `tools_remapping` (dict[str, str]): Tool name remapping (original → remapped) |
| 28 | +- `reversed_remappings` (dict[str, str]): Reverse remapping (remapped → original) |
| 29 | +- `tools_manager` (MultiToolsManager): The tool manager where MCP tools are registered |
| 30 | +- `_is_initialized` (bool): Whether all clients have been initialized |
| 31 | + |
| 32 | +## Methods |
| 33 | + |
| 34 | +### `__new__() -> Self` |
| 35 | + |
| 36 | +Creates or returns the singleton instance of ClientManager. |
| 37 | + |
| 38 | +**Returns:** |
| 39 | + |
| 40 | +- `Self`: The singleton instance |
| 41 | + |
| 42 | +**Note:** ClientManager implements the singleton pattern - only one instance exists per application. Every call to `ClientManager()` returns the same instance. |
| 43 | + |
| 44 | +**Example:** |
| 45 | + |
| 46 | +```python |
| 47 | +from amrita_core.tools.mcp import ClientManager |
| 48 | + |
| 49 | +manager1 = ClientManager() |
| 50 | +manager2 = ClientManager() |
| 51 | +print(manager1 is manager2) # True - same instance |
| 52 | +``` |
| 53 | + |
| 54 | +### `__init__() -> None` |
| 55 | + |
| 56 | +Initializes the ClientManager (runs only once due to singleton pattern). |
| 57 | + |
| 58 | +**Note:** Initialization logic executes only on the first instantiation. |
| 59 | + |
| 60 | +--- |
| 61 | + |
| 62 | +_All other methods are inherited from [`MultiClientManager`](MultiClientManager.md):_ |
| 63 | + |
| 64 | +- `get_client_by_script(server_script)` - Get client by server script |
| 65 | +- `get_client_by_tool_name(tool_name)` - Find client owning a specific tool |
| 66 | +- `register_only(client)` / `register_only(server_script)` - Register without initializing |
| 67 | +- `initialize_this(server_script)` - Register and initialize single server |
| 68 | +- `initialize_scripts_all(scripts)` - Initialize multiple servers |
| 69 | +- `initialize_all()` - Connect to all registered servers |
| 70 | +- `update_tools(client)` - Update tools from a client |
| 71 | +- `unregister_client(script_name)` - Remove a server |
| 72 | +- `reinitalize_all()` - Refresh all connections |
| 73 | + |
| 74 | +See [`MultiClientManager`](MultiClientManager.md) documentation for detailed method descriptions. |
| 75 | + |
| 76 | +## Complete Usage Example |
| 77 | + |
| 78 | +```python |
| 79 | +import asyncio |
| 80 | +from amrita_core.tools.mcp import ClientManager |
| 81 | + |
| 82 | +async def main(): |
| 83 | + # Get the singleton instance |
| 84 | + manager = ClientManager() |
| 85 | + |
| 86 | + # Method 1: Configuration-based setup (recommended) |
| 87 | + # See AmritaConfig for declarative configuration |
| 88 | + |
| 89 | + # Method 2: Programmatic setup |
| 90 | + scripts = [ |
| 91 | + "/path/to/weather.mcp", |
| 92 | + "/path/to/database.mcp", |
| 93 | + "/path/to/calendar.mcp" |
| 94 | + ] |
| 95 | + |
| 96 | + # Register and initialize all servers |
| 97 | + await manager.initialize_scripts_all(scripts) |
| 98 | + |
| 99 | + # Check available tools |
| 100 | + available_tools = manager.tools_manager.get_tools() |
| 101 | + print(f"Available tools: {list(available_tools.keys())}") |
| 102 | + |
| 103 | + # Find which client owns a tool |
| 104 | + weather_client = await manager.get_client_by_tool_name("get_weather") |
| 105 | + print(f"Tool owner: {weather_client.server_script}") |
| 106 | + |
| 107 | + # Handle duplicate tool names (automatic remapping) |
| 108 | + # If two servers have "search" tool, second one becomes "referred_42_search" |
| 109 | + |
| 110 | + # Dynamically add a new server |
| 111 | + await manager.initialize_this("/dynamic/new-server.mcp") |
| 112 | + |
| 113 | + # Remove a server |
| 114 | + await manager.unregister_client("/path/to/old-server.mcp") |
| 115 | + |
| 116 | + # Reinitialize all (refresh connections) |
| 117 | + await manager.reinitalize_all() |
| 118 | + |
| 119 | +asyncio.run(main()) |
| 120 | +``` |
| 121 | + |
| 122 | +## Key Features |
| 123 | + |
| 124 | +### Automatic Tool Registration |
| 125 | + |
| 126 | +All tools from registered MCP servers are automatically added to `ToolsManager` and become available to agents. |
| 127 | + |
| 128 | +### Tool Name Conflict Resolution |
| 129 | + |
| 130 | +When multiple servers provide tools with the same name: |
| 131 | + |
| 132 | +- First registration keeps original name |
| 133 | +- Subsequent registrations are auto-remapped (e.g., `referred_42_search`) |
| 134 | +- Warning logs are generated for conflicts |
| 135 | + |
| 136 | +### Intelligent Routing |
| 137 | + |
| 138 | +When a tool is called, `ClientManager` automatically routes the request to the correct MCP server based on tool name mapping. |
| 139 | + |
| 140 | +### Thread Safety |
| 141 | + |
| 142 | +All operations are protected by an async lock (`_lock`) to ensure thread-safe access to shared state. |
| 143 | + |
| 144 | +### Lifecycle Management |
| 145 | + |
| 146 | +Handles connection establishment, tool discovery, registration, and cleanup for multiple servers simultaneously. |
| 147 | + |
| 148 | +## Error Handling |
| 149 | + |
| 150 | +- **Server initialization failure**: Logs error, continues with other servers (unless `fail_then_raise=True`) |
| 151 | +- **Tool execution errors**: Handled by individual `MCPClient`, returns structured error JSON |
| 152 | +- **Duplicate tools**: Auto-remapped with warning logs |
| 153 | +- **Connection loss**: Automatic retry on next tool call |
| 154 | + |
| 155 | +## Related Documentation |
| 156 | + |
| 157 | +- [MCPClient](MCPClient.md) - Individual client management |
| 158 | +- [ToolsManager](ToolsManager.md) - Tool registration system |
| 159 | +- [MCP Server Integration](../../guide/extensions-integration/mcp-server-integration.md) - Comprehensive integration guide |
| 160 | +- [AmritaConfig](AmritaConfig.md) - Configuration-based setup |
0 commit comments