doReload reads MCP config files and performs a differential reconnect. Unchanged servers keep their existing client; new or changed servers get a fresh connection; removed servers are closed.
(ctx context.Context, mcpConfigFiles []string)
| 496 | // changed servers get a fresh connection; removed servers are |
| 497 | // closed. |
| 498 | func (m *Manager) doReload(ctx context.Context, mcpConfigFiles []string) error { |
| 499 | allConfigs, snap := m.parseAndDedup(ctx, mcpConfigFiles) |
| 500 | |
| 501 | wanted := make(map[string]ServerConfig, len(allConfigs)) |
| 502 | for _, cfg := range allConfigs { |
| 503 | wanted[cfg.Name] = cfg |
| 504 | } |
| 505 | |
| 506 | diff, err := m.classifyServers(wanted) |
| 507 | if err != nil { |
| 508 | return err |
| 509 | } |
| 510 | |
| 511 | connected := m.connectAll(ctx, diff.toConnect) |
| 512 | |
| 513 | replaced, err := m.installServers(wanted, diff, connected, snap) |
| 514 | if err != nil { |
| 515 | return err |
| 516 | } |
| 517 | |
| 518 | // Close removed and replaced servers outside the lock to |
| 519 | // avoid leaking child processes and to avoid blocking |
| 520 | // concurrent readers on subprocess I/O. |
| 521 | // Note: a concurrent CallTool that captured a removed |
| 522 | // entry's client before the swap may call a closed client. |
| 523 | // This is a narrow race that self-heals on the next request. |
| 524 | for _, entry := range diff.toClose { |
| 525 | _ = entry.client.Close() |
| 526 | } |
| 527 | for _, entry := range replaced { |
| 528 | _ = entry.client.Close() |
| 529 | } |
| 530 | |
| 531 | // Refresh tools outside the lock to avoid blocking |
| 532 | // concurrent reads during network I/O. |
| 533 | if err := m.RefreshTools(ctx); err != nil { |
| 534 | logger := m.logger.With(agentchat.Fields(ctx)...) |
| 535 | logger.Warn(ctx, "failed to refresh MCP tools after connect", slog.Error(err)) |
| 536 | } |
| 537 | return nil |
| 538 | } |
| 539 | |
| 540 | // parseAndDedup reads all config files and returns a deduplicated |
| 541 | // list of server configs. Missing files are silently skipped; |
no test coverage detected