fix(mcp): preserve live connectors and bound shutdown waiting - #924
Conversation
Keep materialized connector objects/status during manager refresh. Cancel registered lifecycle tasks under the registry lock, then drain outside it with a five-second deadline. Retain ownership on incomplete cleanup and deduplicate the shutdown callback. Restart ordering and pending startup ownership remain separate concerns.
|
The preservation/shutdown fix now passes all upstream CI checks, including the real local stdio cleanup regression. This gives the narrowly scoped patch a green review baseline without bundling restart sequencing or connector-entry containment.
The job is labeled Python 3.13 but its test paths show Python 3.14.7; local focused/control qualification used Python 3.13.13. CI warnings include plugin HTTP-client deprecations, deliberately exercised deprecated config APIs, SQLite resource warnings and unawaited test coroutines. They were inspected and not suppressed; only the three local coroutine warnings have independent base controls in this submission evidence. Green checks do not imply warning-free operation, successful cleanup of every remote connector, or release qualification. Run: https://github.com/mpfaffenberger/code_puppy/actions/runs/34171236267 |
|
The follow-up restart fix is now proposed in #930 as a dependent draft. It relies on this PR’s unlocked, bounded lifecycle stop before replacing a connector and rebinding the requesting agent. Your lifecycle patch alone still reproduces the premature-rebind command control; the combined candidate passes the restart regressions. #930 must not merge first. |
|
Resubmitted as #932 after revert #931. GitHub cannot reopen a merged PR, so the replacement is a fresh single-commit application on current main. The patch is unchanged: matching stable patch IDs and byte-identical changed files verified. All nine focused regressions pass; broader checks are running. The dependent draft #930 now points to #932. |
Refreshing MCP configuration can replace live connector objects, and stopping a healthy connector can deadlock while cleanup waits for the same registry lock. Preserve existing connectors during refresh and drain registered lifecycle tasks outside that lock, so shutdown has a bounded wait instead of hanging indefinitely.
Problem
_initialize_servers()rebuilds already-materialized connectors and resets their status even when only a new configuration needs materializing.stop_server()holds the registry lock while awaiting a lifecycle task whose cleanup needs that lock to unregister itself.Change
This is one bounded preservation/shutdown correction in two runtime files. It changes no CLI commands, dependencies, package version, or sync API signatures. The open MCP capability refactor (#834) touches manager documentation, not these lifecycle decisions.
Validation
Base
ce1fe372(v0.0.827), Linux / Python 3.13.13, current upstream lock installed in a dedicated environment: core plugins 0.0.44, Pydantic AI 2.35.0, MCP 1.27.1 andfastmcp-slim3.4.7 (which supplies thefastmcpclient import). The patch adds or changes no dependencies relative to that base. The patch was applied independently to current upstream and requalified; it carries no other pending fixes or integration history.python -m pytest -q -o addopts= tests/mcp/test_shutdown_preservation.py: 9 passed.python -m pytest -q -o addopts= tests/mcp tests/agents tests/tools tests/command_line/mcp tests/test_completions_and_small_modules.py tests/test_claude_refresh_review.py: 1886 passed, 7 skipped, 3 warnings, including the newer upstream completion and OAuth regression tests.Tests ran with disposable HOME/XDG, no inherited credentials, and blocked socket connect/DNS/bind. The local stdio child uses only the Python standard library; it is not a remote connector or a managed service. Six skips cover unimplemented MCP search; one requires a case-insensitive filesystem. All three coroutine warnings reproduce on the unpatched base, and none was suppressed.
Boundaries
This deliberately does not fix restart/reload/remove ordering, pending startup ownership, late startup registration, manager/CLI completion reporting, or completion-time agent rebinding. Those require separate changes. The deadline bounds how long callers wait; it does not force-terminate a process or prove failed cleanup eventually succeeds. Concurrent stop-all snapshots registered contexts rather than closing a global startup gate. Operations must remain on the owning event loop.
Connector-entry containment is also separate: this patch does not swallow startup failures or replay tool calls. Preserving existing objects during refresh is not an implicit hot-reload of their configuration.
The earlier base's tilde-completion assertion was corrected upstream in
612de2b1; its tests pass in the updated candidate. No release-readiness claim is made here.