Close terminates all MCP server connections and child processes, stops the config file watcher, and waits for any in-flight watcher-driven reload to complete.
()
| 870 | // processes, stops the config file watcher, and waits for any |
| 871 | // in-flight watcher-driven reload to complete. |
| 872 | func (m *Manager) Close() error { |
| 873 | // Mark the manager closed and signal closedCh first, then |
| 874 | // hand the watcher off and release the lock. Marking closed |
| 875 | // before w.Close() ensures that any in-flight |
| 876 | // handleWatchedConfigChange short-circuits and any Reload |
| 877 | // blocked in waitReload observes m.closedCh, instead of |
| 878 | // blocking firesWG.Wait() inside w.Close() until a 30 s |
| 879 | // connectAll times out. |
| 880 | m.mu.Lock() |
| 881 | m.closed = true |
| 882 | m.closeOnce.Do(func() { close(m.closedCh) }) |
| 883 | w := m.watcher |
| 884 | m.watcher = nil |
| 885 | m.mu.Unlock() |
| 886 | |
| 887 | // Close the watcher outside the manager lock. Its goroutine |
| 888 | // may call handleWatchedConfigChange, which takes m.mu, so |
| 889 | // holding m.mu while waiting for the watcher to drain would |
| 890 | // deadlock. Close on a nil watcher is a no-op. |
| 891 | if w != nil { |
| 892 | _ = w.Close() |
| 893 | } |
| 894 | |
| 895 | m.mu.Lock() |
| 896 | defer m.mu.Unlock() |
| 897 | |
| 898 | var errs []error |
| 899 | for _, entry := range m.servers { |
| 900 | if err := entry.client.Close(); err != nil { |
| 901 | // Subprocess kill signals are expected during shutdown. |
| 902 | // The stdio transport returns cmd.Wait() which surfaces |
| 903 | // "signal: killed" as an exec.ExitError. |
| 904 | var exitErr *exec.ExitError |
| 905 | if !errors.As(err, &exitErr) { |
| 906 | errs = append(errs, err) |
| 907 | } |
| 908 | } |
| 909 | } |
| 910 | m.servers = make(map[string]*serverEntry) |
| 911 | // Prevent an in-flight RefreshTools from repopulating tools |
| 912 | // after Close clears the cache. |
| 913 | m.serverGen++ |
| 914 | m.tools = nil |
| 915 | |
| 916 | // Cancel while holding the lock so waiters that observe |
| 917 | // m.ctx.Done also observe m.closed when checking closeErr. |
| 918 | m.cancel() |
| 919 | return errors.Join(errs...) |
| 920 | } |
| 921 | |
| 922 | // connectServer establishes a connection to a single MCP server |
| 923 | // and returns the connected client. It does not modify any Manager |