Manage MCP server lifecycles and expose only connected servers. Use this helper to keep MCP connect/cleanup on the same task and avoid run failures when a server is unavailable. The manager will attempt to connect each server and then expose the connected subset via `active_servers`
| 106 | |
| 107 | |
| 108 | class MCPServerManager(AbstractAsyncContextManager["MCPServerManager"]): |
| 109 | """Manage MCP server lifecycles and expose only connected servers. |
| 110 | |
| 111 | Use this helper to keep MCP connect/cleanup on the same task and avoid |
| 112 | run failures when a server is unavailable. The manager will attempt to |
| 113 | connect each server and then expose the connected subset via |
| 114 | `active_servers`. |
| 115 | |
| 116 | Basic usage: |
| 117 | async with MCPServerManager([server_a, server_b]) as manager: |
| 118 | agent = Agent( |
| 119 | name="Assistant", |
| 120 | instructions="...", |
| 121 | mcp_servers=manager.active_servers, |
| 122 | ) |
| 123 | |
| 124 | FastAPI lifespan example: |
| 125 | @asynccontextmanager |
| 126 | async def lifespan(app: FastAPI): |
| 127 | async with MCPServerManager([server_a, server_b]) as manager: |
| 128 | app.state.mcp_manager = manager |
| 129 | yield |
| 130 | |
| 131 | app = FastAPI(lifespan=lifespan) |
| 132 | |
| 133 | Important behaviors: |
| 134 | - `active_servers` only includes servers that connected successfully. |
| 135 | `failed_servers` holds the failures and `errors` maps servers to errors. |
| 136 | - `drop_failed_servers=True` removes failed servers from `active_servers` |
| 137 | (recommended). If False, `active_servers` will still include all servers. |
| 138 | - `strict=True` raises on the first connection failure. If False, failures |
| 139 | are recorded and the run can proceed with the remaining servers. |
| 140 | - `reconnect(failed_only=True)` retries failed servers and refreshes |
| 141 | `active_servers`. |
| 142 | - `connect_in_parallel=True` uses a dedicated worker task per server to |
| 143 | allow concurrent connects while preserving task affinity for cleanup. |
| 144 | """ |
| 145 | |
| 146 | def __init__( |
| 147 | self, |
| 148 | servers: Iterable[MCPServer], |
| 149 | *, |
| 150 | connect_timeout_seconds: float | None = 10.0, |
| 151 | cleanup_timeout_seconds: float | None = 10.0, |
| 152 | drop_failed_servers: bool = True, |
| 153 | strict: bool = False, |
| 154 | suppress_cancelled_error: bool = True, |
| 155 | connect_in_parallel: bool = False, |
| 156 | ) -> None: |
| 157 | self._all_servers = list(servers) |
| 158 | self._active_servers = list(servers) |
| 159 | self.connect_timeout_seconds = connect_timeout_seconds |
| 160 | self.cleanup_timeout_seconds = cleanup_timeout_seconds |
| 161 | self.drop_failed_servers = drop_failed_servers |
| 162 | self.strict = strict |
| 163 | self.suppress_cancelled_error = suppress_cancelled_error |
| 164 | self.connect_in_parallel = connect_in_parallel |
| 165 | self._workers: dict[MCPServer, _ServerWorker] = {} |
no outgoing calls