fix(mcp): keep a persistent host's providers through a partial deploy

mcp_servers narrowed the MCP providers to application_closure(deployment.whitelist), which on a persistent host drops every provider an earlier run deployed and a partial run does not touch. sys-svc-mcp-reconcile then converges each client present on the host against that narrowed set: on echoserver the --id web-opt-rdr-domains run rdr-3 removed the Prometheus registration from Flowise.

deployment now reports ephemeral (runtime act or github), and the closure narrows only there, where a whitelist is all a run deploys; a persistent host narrows nothing, as a full deploy already did with its empty whitelist, so its clients keep every provider it serves. CI keeps its narrowing unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kevinveenbirkenbachandClaude Opus 5.5 committed 2026-10-25 09:24:28 +01:00
1 parent f8385c1cca
commit ca4f53d0e4
4 files changed
+50 -13

No files matched your search

@@ -29,6 +29,7 @@ class TestDeploymentLookup(unittest.TestCase):
"inventory",
"deployed",
"runtime",
"ephemeral",
"all",
},
)
@@ -225,6 +226,7 @@ class TestDeploymentLookup(unittest.TestCase):
)
self.assertEqual(result[0]["deployed"], ["only-this"])
self.assertEqual(result[0]["runtime"], runtime)
self.assertTrue(result[0]["ephemeral"])
@patch("plugins.lookup.deployment.list_invokable_app_ids")
def test_deployed_uses_groups_in_persistent_runtime(self, mock_list):
@@ -243,6 +245,7 @@ class TestDeploymentLookup(unittest.TestCase):
result[0]["deployed"],
["partial", "still-running", "also-running"],
)
self.assertFalse(result[0]["ephemeral"])
if __name__ == "__main__":
@@ -1,13 +1,14 @@
"""Unit tests for ``plugins/lookup/mcp_servers.py``.
Only the pure builder and the credential resolver are exercised; the lookup
wrapper forwards ``roles_with_service``, ``applications`` and ``users`` into
them.
The pure builder, the credential resolver and the deploy closure are exercised;
the lookup wrapper forwards ``roles_with_service``, ``applications`` and ``users``
into them.
"""
import importlib
import unittest
from typing import ClassVar
from unittest.mock import MagicMock, patch
from ansible.errors import AnsibleError
@@ -83,6 +84,34 @@ class TestSelectDeployed(unittest.TestCase):
self.assertEqual([], plugin_module.select_deployed(None, {"web-app-hermes"}))
class TestDeployClosure(unittest.TestCase):
def closure(self, deployment):
def get(name, **kwargs):
plugin = MagicMock()
if name == "deployment":
plugin.run.return_value = [deployment]
else:
plugin.run.side_effect = lambda terms, variables=None: [
{*terms[0], "svc-db-postgres"}
]
return plugin
with patch.object(plugin_module.lookup_loader, "get", side_effect=get):
return plugin_module.LookupModule()._deploy_closure(None, {})
def test_a_partial_run_on_a_persistent_host_narrows_nothing(self):
self.assertEqual(
set(),
self.closure({"ephemeral": False, "whitelist": ["web-opt-rdr-domains"]}),
)
def test_an_ephemeral_run_narrows_to_the_whitelist_closure(self):
self.assertEqual(
{"web-app-hermes", "svc-db-postgres"},
self.closure({"ephemeral": True, "whitelist": ["web-app-hermes"]}),
)
class TestBuildMcpDiscovery(unittest.TestCase):
def test_no_servers_yields_nothing(self):
for servers in (None, []):