feat(mcp): update_group_members puts hosts into an inventory group or takes them out (#5487)
CI / lint (push) Successful in 2s
CI / integration (push) Successful in 1m9s
CI / unit (push) Successful in 1m29s
CI / publish (push) Successful in 1m19s

#5487 "MCP: edit inventory group membership". Group membership decides which
hosts a group alert rule covers, and the MCP could only read it. The new
operator tool takes a group and lists to add and remove, each a target's
name or id or the Steward host it is linked to; an unknown name refuses the
whole change. It answers with the group's targets after and the alert rules
covering the group (whose per-host rules follow within a minute), and audits
as ansible.group.updated by "<user> via MCP", as the Inventory page does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-09 11:48:08 -04:00
co-authored by Claude Opus 5.5
parent 73cfbda1da
commit 57e040f1d0
4 changed files with 169 additions and 4 deletions
+13 -1
View File
@@ -24,7 +24,7 @@ VARS = [
VIEWER_TOOLS = {"list_playbooks", "get_playbook", "list_inventory", "list_ansible_runs",
"get_ansible_run", "list_schedules", "wait_for_run"}
OPERATOR_TOOLS = {"run_playbook", "cancel_ansible_run", "create_schedule", "update_schedule",
"delete_schedule"}
"delete_schedule", "update_group_members"}
def test_split_vars_keeps_secrets_apart_from_what_is_stored():
@@ -156,3 +156,15 @@ def test_schedule_update_holds_only_changed_variables(monkeypatch):
automation.check_caller_vars({**stored, "release": "2"}, stored)
with pytest.raises(ValueError, match="Only an admin"):
automation.check_caller_vars({"ansible_ssh_common_args": "-o ProxyCommand=x"}, stored)
def test_group_members_are_named_by_target_or_by_linked_host():
"""#5487: a target by its name or id, or the Steward host linked to it; an
unknown name refuses the lot, so a typo changes nothing."""
web = NS(id="t1", name="web01", host_id="h1")
db = NS(id="t2", name="db01", host_id=None)
hosts = {"h1": "VDNT-Docker02"}
got = automation.member_targets(["vdnt-docker02", "t2", "web01", " "], [web, db], hosts)
assert [t.name for t in got] == ["web01", "db01"]
with pytest.raises(ValueError, match="no target or linked host named 'nas'.*Nothing was changed"):
automation.member_targets(["web01", "nas"], [web, db], hosts)