From b4d1a01faa6496190fec7291a79bd36300b7466e Mon Sep 17 00:00:00 2001 From: Rohit C Prasad Date: Fri, 21 Aug 2026 12:02:53 -0700 Subject: [PATCH] Remove MCP server: also shut down the live connection and purge OAuth tokens/DCR --- coworker/server/manager.py | 13 +++++++++++++ tests/test_mcp.py | 24 ++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/coworker/server/manager.py b/coworker/server/manager.py index a8d50ab8..e09821ee 100644 --- a/coworker/server/manager.py +++ b/coworker/server/manager.py @@ -1425,6 +1425,19 @@ class SessionManager: self._mcp_auth_hints.discard(name) if self._prefs.get("mcp_last_test", {}).pop(name, None) is not None: self._save_prefs() + # Removing a server must not leave its connection running until the next + # restart, nor its OAuth tokens + DCR registration in the secret store — + # "Remove" is the user saying this server is GONE (owner review 2026-08-21). + # The route runs in a threadpool; the shutdown event belongs to the loop. + conn = self.mcp._conns.get(name) + if conn is not None: + if self._loop is not None: + self._loop.call_soon_threadsafe(conn.shutdown.set) + else: + conn.shutdown.set() + from ..mcp import oauth as mcp_oauth + + mcp_oauth.sign_out(name, self.secrets) return {"ok": ok, "name": name} async def mcp_tools(self, name: str) -> dict[str, Any]: diff --git a/tests/test_mcp.py b/tests/test_mcp.py index 1713ae90..06f595f5 100644 --- a/tests/test_mcp.py +++ b/tests/test_mcp.py @@ -472,3 +472,27 @@ async def test_verify_tears_down_a_dead_connection_and_reconnects(): assert out is fresh assert dead.shutdown.is_set() assert "srv" not in mgr._conns + + +def test_delete_mcp_shuts_down_connection_and_forgets_tokens(tmp_path): + """Remove server = the server is GONE: live connection told to shut down and + the OAuth token/DCR profile purged, not just the config entry deleted.""" + from types import SimpleNamespace + + from coworker.mcp import oauth as mcp_oauth + from coworker.mcp.client import _Conn + from coworker.mcp.config import put_global_server + from coworker.server import SessionManager + + mgr = SessionManager(data_dir=tmp_path / "data") + put_global_server("gone-srv", {"url": "https://x.example/mcp", "auth": "oauth"}) + mgr.secrets.put( + mcp_oauth.PROFILE_PREFIX + "gone-srv", {"tokens": {"access_token": "A"}} + ) + conn = _Conn(SimpleNamespace(), tools=[]) + mgr.mcp._conns["gone-srv"] = conn + + res = mgr.delete_mcp("gone-srv") + assert res["ok"] + assert conn.shutdown.is_set() + assert mgr.secrets.get(mcp_oauth.PROFILE_PREFIX + "gone-srv") is None