Disconnect every connected server whose last-used timestamp is older than `now - idle_threshold_ms`. Returns the names of servers that were disconnected. Servers without a recorded timestamp are treated as **infinitely idle** and disconnected. The disconnect call itself can fail per-server (e.g. transport already closed); those failures are warn-logged but never panic — the result vec still inclu
(&self, idle_threshold_ms: u64)
| 210 | /// [`call_tool`](Self::call_tool) on the same server name will |
| 211 | /// require an explicit `connect` to come back online. |
| 212 | pub async fn disconnect_idle(&self, idle_threshold_ms: u64) -> Vec<String> { |
| 213 | let cutoff = now_epoch_ms().saturating_sub(idle_threshold_ms); |
| 214 | // Snapshot candidates so we don't hold both locks across await. |
| 215 | let candidates: Vec<String> = { |
| 216 | let clients = self.clients.read().await; |
| 217 | let last_used = self.last_used_at_ms.read().await; |
| 218 | clients |
| 219 | .keys() |
| 220 | .filter(|name| match last_used.get(*name) { |
| 221 | Some(ts) => *ts < cutoff, |
| 222 | // No timestamp -> never used since connect; treat as |
| 223 | // infinitely idle. |
| 224 | None => true, |
| 225 | }) |
| 226 | .cloned() |
| 227 | .collect() |
| 228 | }; |
| 229 | let mut disconnected = Vec::with_capacity(candidates.len()); |
| 230 | for name in candidates { |
| 231 | match self.disconnect(&name).await { |
| 232 | Ok(()) => disconnected.push(name), |
| 233 | Err(e) => tracing::warn!( |
| 234 | server = %name, |
| 235 | error = %e, |
| 236 | "MCP idle disconnect failed; entry already removed from registry" |
| 237 | ), |
| 238 | } |
| 239 | } |
| 240 | // Opportunistically purge orphan timestamps for servers that are no |
| 241 | // longer connected — `touch()` records a timestamp unconditionally |
| 242 | // (even for a never-connected name), and the candidate scan above |
| 243 | // only iterates `clients.keys()`, so without this sweep those |
| 244 | // orphan entries in `last_used_at_ms` would accumulate unbounded |
| 245 | // across the lifetime of a long-running manager. |
| 246 | { |
| 247 | let clients = self.clients.read().await; |
| 248 | self.last_used_at_ms |
| 249 | .write() |
| 250 | .await |
| 251 | .retain(|name, _| clients.contains_key(name)); |
| 252 | } |
| 253 | disconnected |
| 254 | } |
| 255 | |
| 256 | /// Get all registered server configurations |
| 257 | pub async fn all_configs(&self) -> Vec<McpServerConfig> { |
no test coverage detected