MCPAgent and MCPClientManager: how servers are connected, how tools are discovered, and how calls are routed.
End-to-end flow
MCPClientManager
MCPClientManager is the connection layer between MCPAgent and the outside world. It is initialised with either a path to mcp.json or an in-memory config dict.
Key attributes
| Attribute | Type | Description |
|---|---|---|
config_path | str | Path to mcp.json on disk. |
config | dict | In-memory config (takes precedence over file). |
sessions | Dict[str, ClientSession] | Active MCP sessions keyed by server name. |
server_tools_map | Dict[str, str] | Maps tool_name → server_name for routing. |
server_tasks | Dict[str, asyncio.Task] | Background tasks that keep connections alive. |
server_stop_events | Dict[str, asyncio.Event] | Signals used to cleanly shut down connections. |
Server connection
connect_to_servers() iterates every entry in mcpServers, skips servers whose name starts with "logicore" (to avoid recursion), and skips already-connected servers. For each remaining server it:
Reads command, args, and env from config
Environment variables from the config are merged on top of the current process environment.
Resolves the command path
Uses
shutil.which() to find the full path, falling back to the raw string if not found.Starts a background asyncio task
_run_server_connection() opens a stdio_client, creates a ClientSession, calls session.initialize(), and then waits on a stop event — keeping the connection alive for the lifetime of the agent.Waits up to 10 seconds for the connection
A
ready_event signals when session.initialize() completes. If initialization times out the task is left running (the server may be slow) but the manager proceeds without it.Transport type
The current implementation uses stdio transport exclusively (StdioServerParameters from the MCP SDK). Each server process is launched as a child process; the manager writes to its stdin and reads from its stdout.
HTTP and SSE transports are not yet wired into
MCPClientManager. Servers that only expose an HTTP endpoint must be wrapped in a stdio proxy for now.Tool schema conversion
get_tools() fetches the tool list from every active session and converts each MCP tool into the OpenAI function-calling schema that Logicore agents consume internally:
inputSchema from the MCP SDK is already JSON Schema, so no transformation is required.
Tool execution routing
When the model requests a tool call,MCPAgent._execute_tool() decides where to run it:
tool_search_regex— handled entirely insideMCPAgent(deferred mode discovery).- Any tool in
_tool_registry(deferred mode) — marks the tool as loaded, then delegates. - All other tools — delegated to the parent
Agent._execute_tool(), which checks whether the tool is local or in theserver_tools_map, and callsMCPClientManager.execute_tool()for external tools.
execute_tool() on the manager:
textitems are collected directly.imageitems produce[Image: <mimeType>].resourceitems produce[Resource: <uri>].
result.isError is True the return value is {"success": False, "error": "..."}. Otherwise {"success": True, "content": "..."}.
Session handling
MCPAgent adds lifecycle helpers on top of the base Agent session model:
| Method | Description |
|---|---|
create_session(session_id, system_message, metadata) | Creates an isolated context; fires on_session_created callback. |
destroy_session(session_id) | Deletes the session; fires on_session_destroyed callback. |
list_sessions() | Returns summaries of all active sessions including message count and timestamps. |
cleanup_stale_sessions() | Destroys sessions whose last_activity exceeds session_timeout; returns the number removed. |
Cleanup
Callawait manager.cleanup() to shut down all server connections gracefully. It sets every stop event, awaits the background tasks, and clears internal state.