From da209a592dbfb5963ecfe861851274b3e8c75e71 Mon Sep 17 00:00:00 2001 From: Chris PeBenito Date: Tue, 14 Jul 2026 16:15:18 +0000 Subject: [PATCH 1/5] tox.ini: Add positional argument support. Pass these args to pytest. Signed-off-by: Chris PeBenito --- tox.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 5d6ac622..cfb043b1 100644 --- a/tox.ini +++ b/tox.ini @@ -19,7 +19,7 @@ deps = {[testenv]deps} commands_pre = coverage --version coverage erase {[testenv]commands_pre} -commands = coverage run --source=setools,setoolsgui -m pytest tests +commands = coverage run --source=setools,setoolsgui -m pytest {posargs} tests coverage report [testenv:lint] @@ -58,4 +58,4 @@ deps = networkx>=2.6 pytest-xvfb setuptools;python_version>="3.12" commands_pre = {envpython} setup.py build_ext -i -commands = {envpython} -m pytest tests +commands = {envpython} -m pytest {posargs} tests From bed8fc0edae1f92a996ebc6ca7ace66d5beeb1b4 Mon Sep 17 00:00:00 2001 From: Chris PeBenito Date: Tue, 14 Jul 2026 16:23:29 +0000 Subject: [PATCH 2/5] server.py: Change TOOL_PREFIX to handle other prefixes. Signed-off-by: Chris PeBenito --- setools/mcp/server.py | 76 +++++----- tests/library/mcp/test_server.py | 234 ++++++++++++++++--------------- 2 files changed, 157 insertions(+), 153 deletions(-) diff --git a/setools/mcp/server.py b/setools/mcp/server.py index e4e2978b..06fe7e6f 100644 --- a/setools/mcp/server.py +++ b/setools/mcp/server.py @@ -30,7 +30,7 @@ __all__ = ("MCPEncoder",) -TOOL_PREFIX: Final[str] = "setools_" +TOOL_PREFIX: Final[str] = "setools_tool_" class DiffComponent(str, enum.Enum): @@ -95,6 +95,7 @@ def __init__(self, default_policy: str | None = None) -> None: for name in dir(self): if name.startswith(TOOL_PREFIX) and callable(getattr(self, name)): + self.log.info(f"Registering tool: {name}") self.mcp.tool()(getattr(self, name)) def run(self, transport: Literal["stdio", "sse", "streamable-http"] = "stdio", @@ -148,7 +149,8 @@ def _serialize_results(result: Any, count: int, truncated: bool) -> str: # # MCP Tools # - def setools_get_policy_info( + + def setools_tool_get_policy_info( self, policy_path: Annotated[ str | None, @@ -159,7 +161,7 @@ def setools_get_policy_info( """Return statistics and metadata about an SELinux policy.""" return self._serialize_results(self._load_policy(policy_path), 1, False) - def setools_search_te_rules( + def setools_tool_search_te_rules( self, ruletypes: Annotated[ list[str] | None, @@ -229,7 +231,7 @@ def setools_search_te_rules( return self._collect_results(q, max_results=max_results) - def setools_search_rbac_rules( + def setools_tool_search_rbac_rules( self, ruletypes: Annotated[ list[str] | None, @@ -271,7 +273,7 @@ def setools_search_rbac_rules( return self._collect_results(q, max_results=max_results) - def setools_search_mls_rules( + def setools_tool_search_mls_rules( self, ruletypes: Annotated[ list[str] | None, @@ -300,7 +302,7 @@ def setools_search_mls_rules( return self._collect_results(q, max_results=max_results) - def setools_list_types( + def setools_tool_list_types( self, name: Annotated[str | None, "Type name (or regex pattern) to filter by. "] = None, name_regex: Annotated[bool, "Treat name as a regular expression."] = False, @@ -332,7 +334,7 @@ def setools_list_types( return self._collect_results(q, max_results=max_results) - def setools_list_type_attributes( + def setools_tool_list_type_attributes( self, name: Annotated[str | None, "Attribute name (or regex pattern) to filter by."] = None, name_regex: Annotated[bool, "Treat name as a regular expression."] = False, @@ -351,7 +353,7 @@ def setools_list_type_attributes( return self._collect_results(q, max_results=max_results) - def setools_list_roles( + def setools_tool_list_roles( self, name: Annotated[str | None, "Role name (or regex pattern) to filter by."] = None, name_regex: Annotated[bool, "Treat name as a regular expression."] = False, @@ -370,7 +372,7 @@ def setools_list_roles( return self._collect_results(q, max_results=max_results) - def setools_list_role_types( + def setools_tool_list_role_types( self, type_name: Annotated[str, "Type name (or regex pattern) to find associated roles for."], type_regex: Annotated[bool, "Treat type_name as a regular expression."] = False, @@ -389,7 +391,7 @@ def setools_list_role_types( return self._collect_results(q, max_results=max_results) - def setools_list_users( + def setools_tool_list_users( self, name: Annotated[ str | None, "User name (or regex pattern) to filter by." @@ -410,7 +412,7 @@ def setools_list_users( return self._collect_results(q, max_results=max_results) - def setools_list_classes( + def setools_tool_list_classes( self, name: Annotated[ str | None, "Class name (or regex pattern) to filter by." @@ -431,7 +433,7 @@ def setools_list_classes( return self._collect_results(q, max_results=max_results) - def setools_list_commons( + def setools_tool_list_commons( self, name: Annotated[ str | None, "Common name (or regex pattern) to filter by." @@ -447,7 +449,7 @@ def setools_list_commons( return self._collect_results(q, max_results=max_results) - def setools_list_booleans( + def setools_tool_list_booleans( self, name: Annotated[ str | None, "Boolean name (or regex pattern) to filter by." @@ -470,7 +472,7 @@ def setools_list_booleans( return self._collect_results(q, max_results=max_results) - def setools_list_sensitivities( + def setools_tool_list_sensitivities( self, name: Annotated[ str | None, "Sensitivity name (or regex pattern) to filter by." @@ -493,7 +495,7 @@ def setools_list_sensitivities( return self._collect_results(q, max_results=max_results) - def setools_list_categories( + def setools_tool_list_categories( self, name: Annotated[ str | None, "Category name (or regex pattern) to filter by." @@ -516,7 +518,7 @@ def setools_list_categories( return self._collect_results(q, max_results=max_results) - def setools_list_polcaps( + def setools_tool_list_polcaps( self, name: Annotated[ str | None, "Policy capability name (or regex pattern) to filter by." @@ -532,7 +534,7 @@ def setools_list_polcaps( return self._collect_results(q, max_results=max_results) - def setools_list_permissive_types( + def setools_tool_list_permissive_types( self, name: Annotated[ str | None, "Type name (or regex pattern) to filter by." @@ -550,7 +552,7 @@ def setools_list_permissive_types( return self._collect_results(q, max_results=max_results) - def setools_list_typebounds( + def setools_tool_list_typebounds( self, child: Annotated[ str | None, "Bound (child) type name (or regex pattern) to filter by." @@ -578,7 +580,7 @@ def setools_list_typebounds( return self._collect_results(q, max_results=max_results) - def setools_list_constraints( + def setools_tool_list_constraints( self, tclass: Annotated[ list[str] | None, @@ -607,7 +609,7 @@ def setools_list_constraints( return self._collect_results(q, max_results=max_results) - def setools_list_defaults( + def setools_tool_list_defaults( self, tclass: Annotated[ list[str] | None, "Object class(es) to filter by." @@ -630,7 +632,7 @@ def setools_list_defaults( return self._collect_results(q, max_results=max_results) - def setools_list_fs_uses( + def setools_tool_list_fs_uses( self, fs: Annotated[ str | None, "Filesystem type name (or regex pattern) to filter by." @@ -653,7 +655,7 @@ def setools_list_fs_uses( return self._collect_results(q, max_results=max_results) - def setools_list_genfscons( + def setools_tool_list_genfscons( self, fs: Annotated[ str | None, "Filesystem type name (or regex pattern) to filter by." @@ -675,7 +677,7 @@ def setools_list_genfscons( return self._collect_results(q, max_results=max_results) - def setools_list_initialsids( + def setools_tool_list_initialsids( self, name: Annotated[ str | None, "Initial SID name (or regex pattern) to filter by." @@ -691,7 +693,7 @@ def setools_list_initialsids( return self._collect_results(q, max_results=max_results) - def setools_list_portcons( + def setools_tool_list_portcons( self, ports: Annotated[ str | None, @@ -727,7 +729,7 @@ def setools_list_portcons( return self._collect_results(q, max_results=max_results) - def setools_list_netifcons( + def setools_tool_list_netifcons( self, name: Annotated[ str | None, "Network interface name (or regex pattern) to filter by." @@ -743,7 +745,7 @@ def setools_list_netifcons( return self._collect_results(q, max_results=max_results) - def setools_list_nodecons( + def setools_tool_list_nodecons( self, network: Annotated[ str | None, @@ -764,7 +766,7 @@ def setools_list_nodecons( return self._collect_results(q, max_results=max_results) - def setools_list_ibpkeycons( + def setools_tool_list_ibpkeycons( self, pkeys: Annotated[ str | None, @@ -796,7 +798,7 @@ def setools_list_ibpkeycons( return self._collect_results(q, max_results=max_results) - def setools_list_ibendportcons( + def setools_tool_list_ibendportcons( self, name: Annotated[ str | None, "Infiniband device name (or regex pattern) to filter by." @@ -814,7 +816,7 @@ def setools_list_ibendportcons( return self._collect_results(q, max_results=max_results) - def setools_list_iomemcons( + def setools_tool_list_iomemcons( self, addr: Annotated[ str | None, @@ -841,7 +843,7 @@ def setools_list_iomemcons( return self._collect_results(q, max_results=max_results) - def setools_list_ioportcons( + def setools_tool_list_ioportcons( self, ports: Annotated[ str | None, @@ -869,7 +871,7 @@ def setools_list_ioportcons( return self._collect_results(q, max_results=max_results) - def setools_list_pcidevicecons( + def setools_tool_list_pcidevicecons( self, device: Annotated[ str | None, "PCI device address in hex to filter by, e.g. '0xc800'." @@ -883,7 +885,7 @@ def setools_list_pcidevicecons( return self._collect_results(q, max_results=max_results) - def setools_list_pirqcons( + def setools_tool_list_pirqcons( self, irq: Annotated[int | None, "IRQ number to filter by."] = None, max_results: Annotated[int, "Maximum number of results."] = 200, @@ -895,7 +897,7 @@ def setools_list_pirqcons( return self._collect_results(q, max_results=max_results) - def setools_list_devicetreecons( + def setools_tool_list_devicetreecons( self, path: Annotated[ str | None, "Device tree path (or regex pattern) to filter by." @@ -911,7 +913,7 @@ def setools_list_devicetreecons( return self._collect_results(q, max_results=max_results) - def setools_analyze_dta( + def setools_tool_analyze_dta( self, mode: Annotated[ str, @@ -994,7 +996,7 @@ def setools_analyze_dta( return self._serialize_results(results, len(results), truncated) - def setools_analyze_info_flow( + def setools_tool_analyze_info_flow( self, mode: Annotated[ str, @@ -1079,7 +1081,7 @@ def setools_analyze_info_flow( return self._serialize_results(results, len(results), truncated) - def setools_diff_policies( + def setools_tool_diff_policies( self, left_policy: Annotated[str, "Path to the left (baseline) policy file."], right_policy: Annotated[str, "Path to the right (new) policy file."], @@ -1241,7 +1243,7 @@ def _cap(items: Any, limit: int) -> tuple[list[Any], bool]: return self._serialize_results(differences, count, any_truncated) - def setools_lookup_file_context( + def setools_tool_lookup_file_context( self, path: Annotated[str, "The file path to look up in the file_contexts."], filetype: Annotated[ diff --git a/tests/library/mcp/test_server.py b/tests/library/mcp/test_server.py index 14a721d0..bd6802b0 100644 --- a/tests/library/mcp/test_server.py +++ b/tests/library/mcp/test_server.py @@ -56,13 +56,13 @@ def assert_payload(payload: str) -> dict: @pytest.mark.obj_args(SELINUX_POLICY) class TestGetPolicyInfo: def test_returns_valid_json(self, mcp_server: SEToolsMCPServer) -> None: - result = assert_payload(mcp_server.setools_get_policy_info())["result"] + result = assert_payload(mcp_server.setools_tool_get_policy_info())["result"] assert "version" in result assert "mls" in result assert "counts" in result def test_counts_are_positive(self, mcp_server: SEToolsMCPServer) -> None: - counts = assert_payload(mcp_server.setools_get_policy_info())["result"]["counts"] + counts = assert_payload(mcp_server.setools_tool_get_policy_info())["result"]["counts"] assert counts["types"] > 0 assert counts["allow_rules"] > 0 @@ -70,48 +70,48 @@ def test_counts_are_positive(self, mcp_server: SEToolsMCPServer) -> None: @pytest.mark.obj_args(XEN_POLICY, xen=True) class TestGetPolicyInfoXen: def test_xen_policy(self, mcp_server: SEToolsMCPServer) -> None: - result = assert_payload(mcp_server.setools_get_policy_info())["result"] + result = assert_payload(mcp_server.setools_tool_get_policy_info())["result"] assert result["target_platform"] == "xen" @pytest.mark.obj_args(SELINUX_POLICY) class TestSearchTERules: def test_unfiltered_returns_rules(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_search_te_rules(max_results=5)) + data = assert_payload(mcp_server.setools_tool_search_te_rules(max_results=5)) assert data["count"] > 0 assert "result" in data def test_source_filter(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_search_te_rules(ruletypes=["allow"], - source="type30", - source_indirect=False, - max_results=10)) + data = assert_payload(mcp_server.setools_tool_search_te_rules(ruletypes=["allow"], + source="type30", + source_indirect=False, + max_results=10)) for rule in data["result"]: assert rule["ruletype"] == "allow" assert rule["source"] == "type30" def test_tclass_filter(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_search_te_rules(ruletypes=["allow"], - tclass=["infoflow"], - max_results=5)) + data = assert_payload(mcp_server.setools_tool_search_te_rules(ruletypes=["allow"], + tclass=["infoflow"], + max_results=5)) for rule in data["result"]: assert rule["tclass"] == "infoflow" def test_truncation(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_search_te_rules(max_results=2)) + data = assert_payload(mcp_server.setools_tool_search_te_rules(max_results=2)) assert data["count"] == 2 assert data["truncated"] is True def test_no_results(self, mcp_server: SEToolsMCPServer) -> None: # Use a regex pattern that matches nothing - data = assert_payload(mcp_server.setools_search_te_rules(source="^__nonexistent_type_z__$", - source_regex=True)) + data = assert_payload(mcp_server.setools_tool_search_te_rules( + source="^__nonexistent_type_z__$", source_regex=True)) assert data["count"] == 0 assert data["truncated"] is False def test_rule_dict_has_required_keys(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_search_te_rules(ruletypes=["allow"], - max_results=1)) + data = assert_payload(mcp_server.setools_tool_search_te_rules(ruletypes=["allow"], + max_results=1)) rule = data["result"][0] for key in ("statement", "ruletype", "source", "target", "tclass"): assert key in rule @@ -120,23 +120,23 @@ def test_rule_dict_has_required_keys(self, mcp_server: SEToolsMCPServer) -> None @pytest.mark.obj_args(SELINUX_POLICY) class TestSearchRBACRules: def test_unfiltered_returns_rules(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_search_rbac_rules(max_results=5)) + data = assert_payload(mcp_server.setools_tool_search_rbac_rules(max_results=5)) assert data["count"] > 0 def test_role_allow_filter(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_search_rbac_rules(ruletypes=["allow"], - max_results=5)) + data = assert_payload(mcp_server.setools_tool_search_rbac_rules(ruletypes=["allow"], + max_results=5)) for rule in data["result"]: assert rule["ruletype"] == "allow" def test_role_transition_filter(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_search_rbac_rules(ruletypes=["role_transition"], - max_results=5)) + data = assert_payload(mcp_server.setools_tool_search_rbac_rules( + ruletypes=["role_transition"], max_results=5)) for rule in data["result"]: assert rule["ruletype"] == "role_transition" def test_rule_dict_has_required_keys(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_search_rbac_rules(max_results=1)) + data = assert_payload(mcp_server.setools_tool_search_rbac_rules(max_results=1)) rule = data["result"][0] for key in ("statement", "ruletype", "source", "target"): assert key in rule @@ -145,17 +145,17 @@ def test_rule_dict_has_required_keys(self, mcp_server: SEToolsMCPServer) -> None @pytest.mark.obj_args(SELINUX_POLICY) class TestSearchMLSRules: def test_unfiltered_returns_rules(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_search_mls_rules(max_results=5)) + data = assert_payload(mcp_server.setools_tool_search_mls_rules(max_results=5)) assert data["count"] > 0 def test_range_transition_filter(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_search_mls_rules(ruletypes=["range_transition"], - max_results=5)) + data = assert_payload(mcp_server.setools_tool_search_mls_rules( + ruletypes=["range_transition"], max_results=5)) for rule in data["result"]: assert rule["ruletype"] == "range_transition" def test_rule_dict_has_required_keys(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_search_mls_rules(max_results=1)) + data = assert_payload(mcp_server.setools_tool_search_mls_rules(max_results=1)) rule = data["result"][0] for key in ("statement", "ruletype", "source", "target", "tclass", "default"): assert key in rule @@ -164,28 +164,28 @@ def test_rule_dict_has_required_keys(self, mcp_server: SEToolsMCPServer) -> None @pytest.mark.obj_args(SELINUX_POLICY) class TestListTypes: def test_unfiltered(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_types(max_results=10)) + data = assert_payload(mcp_server.setools_tool_list_types(max_results=10)) assert data["count"] > 0 def test_name_filter(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_types(name="type18")) + data = assert_payload(mcp_server.setools_tool_list_types(name="type18")) assert data["count"] == 1 assert data["result"][0]["name"] == "type18" def test_name_regex(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_types(name="type2.", name_regex=True, - max_results=20)) + data = assert_payload(mcp_server.setools_tool_list_types(name="type2.", name_regex=True, + max_results=20)) for t in data["result"]: assert "type2" in t["name"] def test_type_dict_has_required_keys(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_types(max_results=1)) + data = assert_payload(mcp_server.setools_tool_list_types(max_results=1)) t = data["result"][0] for key in ("name", "permissive", "attributes", "aliases"): assert key in t def test_permissive_filter(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_types(permissive=True, max_results=10)) + data = assert_payload(mcp_server.setools_tool_list_types(permissive=True, max_results=10)) # All returned types should be permissive for t in data["result"]: assert t["permissive"] @@ -194,22 +194,22 @@ def test_permissive_filter(self, mcp_server: SEToolsMCPServer) -> None: @pytest.mark.obj_args(SELINUX_POLICY) class TestListAttributes: def test_unfiltered_returns_attrs(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_type_attributes(max_results=10)) + data = assert_payload(mcp_server.setools_tool_list_type_attributes(max_results=10)) assert data["count"] > 0 def test_name_filter(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_type_attributes(name="attr0")) + data = assert_payload(mcp_server.setools_tool_list_type_attributes(name="attr0")) assert data["count"] == 1 assert data["result"][0]["name"] == "attr0" def test_attr_dict_has_required_keys(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_type_attributes(max_results=1)) + data = assert_payload(mcp_server.setools_tool_list_type_attributes(max_results=1)) attr = data["result"][0] for key in ("name", "types"): assert key in attr def test_domain_attr_contains_type(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_type_attributes(name="attr0")) + data = assert_payload(mcp_server.setools_tool_list_type_attributes(name="attr0")) types = data["result"][0]["types"] assert "type1" in types @@ -217,15 +217,15 @@ def test_domain_attr_contains_type(self, mcp_server: SEToolsMCPServer) -> None: @pytest.mark.obj_args(SELINUX_POLICY) class TestListRoles: def test_unfiltered_returns_roles(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_roles(max_results=10)) + data = assert_payload(mcp_server.setools_tool_list_roles(max_results=10)) assert data["count"] > 0 def test_object_r_present(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_roles(name="object_r")) + data = assert_payload(mcp_server.setools_tool_list_roles(name="object_r")) assert data["count"] == 1 def test_role_dict_has_required_keys(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_roles(max_results=1)) + data = assert_payload(mcp_server.setools_tool_list_roles(max_results=1)) role = data["result"][0] for key in ("name", "types"): assert key in role @@ -234,26 +234,27 @@ def test_role_dict_has_required_keys(self, mcp_server: SEToolsMCPServer) -> None @pytest.mark.obj_args(SELINUX_POLICY) class TestListRoleTypes: def test_types(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_role_types(type_name="type0")) + data = assert_payload(mcp_server.setools_tool_list_role_types(type_name="type0")) assert data["count"] > 0 def test_no_results_for_nonexistent_type(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_role_types(type_name="__nonexistent_xyz__")) + data = assert_payload(mcp_server.setools_tool_list_role_types( + type_name="__nonexistent_xyz__")) assert data["count"] == 0 @pytest.mark.obj_args(SELINUX_POLICY) class TestListUsers: def test_unfiltered_returns_users(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_users(max_results=10)) + data = assert_payload(mcp_server.setools_tool_list_users(max_results=10)) assert data["count"] > 0 def test_name_filter(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_users(name="user1")) + data = assert_payload(mcp_server.setools_tool_list_users(name="user1")) assert data["count"] == 1 def test_user_dict_has_required_keys(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_users(max_results=1)) + data = assert_payload(mcp_server.setools_tool_list_users(max_results=1)) user = data["result"][0] for key in ("name", "roles"): assert key in user @@ -262,11 +263,11 @@ def test_user_dict_has_required_keys(self, mcp_server: SEToolsMCPServer) -> None @pytest.mark.obj_args(SELINUX_POLICY) class TestListClasses: def test_unfiltered_returns_classes(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_classes(max_results=20)) + data = assert_payload(mcp_server.setools_tool_list_classes(max_results=20)) assert data["count"] > 0 def test_filter(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_classes(name="infoflow7")) + data = assert_payload(mcp_server.setools_tool_list_classes(name="infoflow7")) assert data["count"] == 1 assert data["result"][0]["name"] == "infoflow7" @@ -274,11 +275,11 @@ def test_filter(self, mcp_server: SEToolsMCPServer) -> None: @pytest.mark.obj_args(SELINUX_POLICY) class TestListCommons: def test_unfiltered_returns_commons(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_commons(max_results=20)) + data = assert_payload(mcp_server.setools_tool_list_commons(max_results=20)) assert data["count"] > 0 def test_filter(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_commons(name="hi_c")) + data = assert_payload(mcp_server.setools_tool_list_commons(name="hi_c")) assert data["count"] == 1 assert len(data["result"][0]["perms"]) > 0 @@ -286,16 +287,16 @@ def test_filter(self, mcp_server: SEToolsMCPServer) -> None: @pytest.mark.obj_args(SELINUX_POLICY) class TestListBooleans: def test_unfiltered_returns_booleans(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_booleans(max_results=10)) + data = assert_payload(mcp_server.setools_tool_list_booleans(max_results=10)) assert data["count"] > 0 def test_state_true_filter(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_booleans(state=True, max_results=10)) + data = assert_payload(mcp_server.setools_tool_list_booleans(state=True, max_results=10)) for b in data["result"]: assert b["default_state"] is True def test_state_false_filter(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_booleans(state=False, max_results=10)) + data = assert_payload(mcp_server.setools_tool_list_booleans(state=False, max_results=10)) for b in data["result"]: assert b["default_state"] is False @@ -303,11 +304,11 @@ def test_state_false_filter(self, mcp_server: SEToolsMCPServer) -> None: @pytest.mark.obj_args(SELINUX_POLICY) class TestListSensitivities: def test_unfiltered(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_sensitivities(max_results=10)) + data = assert_payload(mcp_server.setools_tool_list_sensitivities(max_results=10)) assert data["count"] > 0 def test_name_filter(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_sensitivities(name="s0")) + data = assert_payload(mcp_server.setools_tool_list_sensitivities(name="s0")) assert data["count"] == 1 assert data["result"][0]["name"] == "s0" @@ -315,36 +316,36 @@ def test_name_filter(self, mcp_server: SEToolsMCPServer) -> None: @pytest.mark.obj_args(SELINUX_POLICY) class TestListCategories: def test_unfiltered_returns_categories(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_categories(max_results=10)) + data = assert_payload(mcp_server.setools_tool_list_categories(max_results=10)) assert data["count"] > 0 def test_filteer(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_categories(name="c0")) + data = assert_payload(mcp_server.setools_tool_list_categories(name="c0")) assert data["count"] == 1 @pytest.mark.obj_args(SELINUX_POLICY) class TestListPolcaps: def test_unfiltered_returns_polcaps(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_polcaps()) + data = assert_payload(mcp_server.setools_tool_list_polcaps()) assert data["count"] > 0 def test_name_filter(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_polcaps(name="open_perms")) + data = assert_payload(mcp_server.setools_tool_list_polcaps(name="open_perms")) assert data["count"] == 1 @pytest.mark.obj_args(SELINUX_POLICY) class TestListPermissiveTypes: def test_unfiltered(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_permissive_types()) + data = assert_payload(mcp_server.setools_tool_list_permissive_types()) assert data["count"] > 0 # @pytest.mark.obj_args(SELINUX_POLICY) # class TestListTypebounds: # def test_unfiltered(self, mcp_server: SEToolsMCPServer) -> None: -# data = assert_payload(mcp_server.setools_list_typebounds()) +# data = assert_payload(mcp_server.setools_tool_list_typebounds()) # assert data["count"] > 0 # assert "typebounds" in data @@ -352,38 +353,38 @@ def test_unfiltered(self, mcp_server: SEToolsMCPServer) -> None: @pytest.mark.obj_args(SELINUX_POLICY) class TestListConstraints: def test_unfiltered(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_constraints()) + data = assert_payload(mcp_server.setools_tool_list_constraints()) assert data["count"] > 0 def test_tclass_filter(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_constraints(tclass=["infoflow"])) + data = assert_payload(mcp_server.setools_tool_list_constraints(tclass=["infoflow"])) for c in data["result"]: assert c["tclass"] == "infoflow" def test_explicit_ruletypes(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_constraints(ruletypes=["constrain"])) + data = assert_payload(mcp_server.setools_tool_list_constraints(ruletypes=["constrain"])) for c in data["result"]: assert c["ruletype"] == "constrain" def test_constraint_dict_has_required_keys(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_constraints()) + data = assert_payload(mcp_server.setools_tool_list_constraints()) c = data["result"][0] for key in ("statement", "ruletype", "tclass"): assert key in c def test_invalid_ruletype_raises(self, mcp_server: SEToolsMCPServer) -> None: with pytest.raises(ValueError): - mcp_server.setools_list_constraints(ruletypes=["not_a_real_ruletype"]) + mcp_server.setools_tool_list_constraints(ruletypes=["not_a_real_ruletype"]) # @pytest.mark.obj_args(SELINUX_POLICY) # class TestListDefaults: # def test_unfiltered(self, mcp_server: SEToolsMCPServer) -> None: -# data = assert_payload(mcp_server.setools_list_defaults()) +# data = assert_payload(mcp_server.setools_tool_list_defaults()) # assert data["count"] > 0 # def test_ruletype_filter(self, mcp_server: SEToolsMCPServer) -> None: -# data = assert_payload(mcp_server.setools_list_defaults(ruletypes=["default_role"])) +# data = assert_payload(mcp_server.setools_tool_list_defaults(ruletypes=["default_role"])) # for d in data["defaults"]: # assert d["ruletype"] == "default_role" @@ -391,17 +392,17 @@ def test_invalid_ruletype_raises(self, mcp_server: SEToolsMCPServer) -> None: @pytest.mark.obj_args(SELINUX_POLICY) class TestListFsUses: def test_unfiltered_returns_entries(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_fs_uses()) + data = assert_payload(mcp_server.setools_tool_list_fs_uses()) assert data["count"] > 0 def test_fs_filter(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_fs_uses(fs="fs0")) + data = assert_payload(mcp_server.setools_tool_list_fs_uses(fs="fs0")) assert data["count"] > 0 for fsu in data["result"]: assert fsu["fs"] == "fs0" def test_ruletype_filter(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_fs_uses(ruletypes=["fs_use_xattr"])) + data = assert_payload(mcp_server.setools_tool_list_fs_uses(ruletypes=["fs_use_xattr"])) for fsu in data["result"]: assert fsu["ruletype"] == "fs_use_xattr" @@ -409,17 +410,17 @@ def test_ruletype_filter(self, mcp_server: SEToolsMCPServer) -> None: @pytest.mark.obj_args(SELINUX_POLICY) class TestListGenfscons: def test_unfiltered_returns_entries(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_genfscons()) + data = assert_payload(mcp_server.setools_tool_list_genfscons()) assert data["count"] > 0 def test_fs_filter(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_genfscons(fs="fs149")) + data = assert_payload(mcp_server.setools_tool_list_genfscons(fs="fs149")) assert data["count"] > 0 for g in data["result"]: assert g["fs"] == "fs149" def test_genfscon_dict_has_required_keys(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_genfscons(max_results=1)) + data = assert_payload(mcp_server.setools_tool_list_genfscons(max_results=1)) g = data["result"][0] for key in ("statement", "fs", "path", "context"): assert key in g @@ -428,11 +429,11 @@ def test_genfscon_dict_has_required_keys(self, mcp_server: SEToolsMCPServer) -> @pytest.mark.obj_args(SELINUX_POLICY) class TestListInitialsids: def test_unfiltered_returns_entries(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_initialsids()) + data = assert_payload(mcp_server.setools_tool_list_initialsids()) assert data["count"] > 0 def test_name_filter(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_initialsids(name="kernel")) + data = assert_payload(mcp_server.setools_tool_list_initialsids(name="kernel")) assert data["count"] == 1 assert data["result"][0]["name"] == "kernel" @@ -440,17 +441,17 @@ def test_name_filter(self, mcp_server: SEToolsMCPServer) -> None: @pytest.mark.obj_args(SELINUX_POLICY) class TestListPortcons: def test_unfiltered_returns_entries(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_portcons()) + data = assert_payload(mcp_server.setools_tool_list_portcons()) assert data["count"] > 0 def test_port_filter(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_portcons(ports="80")) + data = assert_payload(mcp_server.setools_tool_list_portcons(ports="80")) assert data["count"] > 0 for pc in data["result"]: assert pc["ports_low"] <= 80 <= pc["ports_high"] def test_protocol_filter(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_portcons(protocol="tcp", max_results=10)) + data = assert_payload(mcp_server.setools_tool_list_portcons(protocol="tcp", max_results=10)) for pc in data["result"]: assert pc["protocol"] == "tcp" @@ -458,124 +459,125 @@ def test_protocol_filter(self, mcp_server: SEToolsMCPServer) -> None: @pytest.mark.obj_args(SELINUX_POLICY) class TestListNetifcons: def test_unfiltered_returns_entries(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_netifcons()) + data = assert_payload(mcp_server.setools_tool_list_netifcons()) assert data["count"] > 0 def test_name_filter(self, mcp_server: SEToolsMCPServer) -> None: - data_filtered = assert_payload(mcp_server.setools_list_netifcons(name="eth0")) + data_filtered = assert_payload(mcp_server.setools_tool_list_netifcons(name="eth0")) assert data_filtered["count"] >= 1 @pytest.mark.obj_args(SELINUX_POLICY) class TestListNodecons: def test_unfiltered_returns_entries(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_nodecons()) + data = assert_payload(mcp_server.setools_tool_list_nodecons()) assert data["count"] > 0 def test_nodecon_dict_has_required_keys(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_nodecons(max_results=1)) + data = assert_payload(mcp_server.setools_tool_list_nodecons(max_results=1)) n = data["result"][0] for key in ("statement", "network", "ip_version", "context"): assert key in n def test_ipv4_filter(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_nodecons(ip_version="ipv4", max_results=5)) + data = assert_payload(mcp_server.setools_tool_list_nodecons(ip_version="ipv4", + max_results=5)) for n in data["result"]: assert n["ip_version"] == "ipv4" def test_network_filter(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_nodecons(network="0.0.0.0/0")) + data = assert_payload(mcp_server.setools_tool_list_nodecons(network="0.0.0.0/0")) assert data["count"] > 0 @pytest.mark.obj_args(SELINUX_POLICY) class TestListIbpkeycons: def test_unfiltered(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_ibpkeycons()) + data = assert_payload(mcp_server.setools_tool_list_ibpkeycons()) assert data["count"] > 0 @pytest.mark.obj_args(SELINUX_POLICY) class TestListIbendportcons: def test_unfiltered(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_ibendportcons()) + data = assert_payload(mcp_server.setools_tool_list_ibendportcons()) assert data["count"] > 0 @pytest.mark.obj_args(XEN_POLICY, xen=True) class TestListIomemcons: def test_unfiltered(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_iomemcons()) + data = assert_payload(mcp_server.setools_tool_list_iomemcons()) assert data["count"] > 0 @pytest.mark.obj_args(XEN_POLICY, xen=True) class TestListIoportcons: def test_unfiltered(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_ioportcons()) + data = assert_payload(mcp_server.setools_tool_list_ioportcons()) assert data["count"] > 0 @pytest.mark.obj_args(XEN_POLICY, xen=True) class TestListPcidevicecons: def test_unfiltered(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_pcidevicecons()) + data = assert_payload(mcp_server.setools_tool_list_pcidevicecons()) assert data["count"] > 0 @pytest.mark.obj_args(XEN_POLICY, xen=True) class TestListPirqcons: def test_unfiltered(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_pirqcons()) + data = assert_payload(mcp_server.setools_tool_list_pirqcons()) assert data["count"] > 0 @pytest.mark.obj_args(XEN_POLICY, xen=True) class TestListDevicetreecons: def test_unfiltered(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_list_devicetreecons()) + data = assert_payload(mcp_server.setools_tool_list_devicetreecons()) assert data["count"] > 0 @pytest.mark.obj_args(DTA_POLICY) class TestAnalyzeDTA: def test_transitions_out(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_analyze_dta(mode="TransitionsOut", source="start", - max_results=5)) + data = assert_payload(mcp_server.setools_tool_analyze_dta(mode="TransitionsOut", + source="start", max_results=5)) assert data["count"] > 0 def test_transitions_in(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_analyze_dta(mode="TransitionsIn", target="trans1", - max_results=5)) + data = assert_payload(mcp_server.setools_tool_analyze_dta(mode="TransitionsIn", + target="trans1", max_results=5)) assert data["count"] > 0 def test_shortest_paths(self, mcp_server: SEToolsMCPServer) -> None: # Find a target that kernel_t can reach - data = assert_payload(mcp_server.setools_analyze_dta(mode="ShortestPaths", source="trans2", - target="trans3", max_results=5)) + data = assert_payload(mcp_server.setools_tool_analyze_dta(mode="ShortestPaths", + source="trans2", target="trans3", max_results=5)) assert data["count"] > 0 assert isinstance(data["result"][0]["path"], list) def test_invalid_mode_raises(self, mcp_server: SEToolsMCPServer) -> None: with pytest.raises(ValueError): - mcp_server.setools_analyze_dta(mode="bad_mode") + mcp_server.setools_tool_analyze_dta(mode="bad_mode") @pytest.mark.obj_args(INFOFLOW_POLICY) class TestAnalyzeInfoFlow: def test_flows_out(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_analyze_info_flow(mode="FlowsOut", source="node1", - perm_map_path=PERM_MAP, max_results=5)) + data = assert_payload(mcp_server.setools_tool_analyze_info_flow(mode="FlowsOut", + source="node1", perm_map_path=PERM_MAP, max_results=5)) assert data["count"] > 0 def test_flows_in(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_analyze_info_flow(mode="FlowsIn", target="node2", - perm_map_path=PERM_MAP, max_results=5)) + data = assert_payload(mcp_server.setools_tool_analyze_info_flow(mode="FlowsIn", + target="node2", perm_map_path=PERM_MAP, max_results=5)) assert data["count"] > 0 def test_shortest_paths(self, mcp_server: SEToolsMCPServer) -> None: # Find a target that kernel_t can flow to - data = assert_payload(mcp_server.setools_analyze_info_flow(mode="ShortestPaths", + data = assert_payload(mcp_server.setools_tool_analyze_info_flow(mode="ShortestPaths", source="node1", target="node4", perm_map_path=PERM_MAP, max_results=5)) assert data["count"] > 0 @@ -583,14 +585,14 @@ def test_shortest_paths(self, mcp_server: SEToolsMCPServer) -> None: def test_invalid_mode_raises(self, mcp_server: SEToolsMCPServer) -> None: with pytest.raises(ValueError): - mcp_server.setools_analyze_info_flow(mode="bad_mode") + mcp_server.setools_tool_analyze_info_flow(mode="bad_mode") @pytest.mark.obj_args(DIFF_LEFT_POLICY, DIFF_RIGHT_POLICY) class TestDiffPolicies: def test_identical_policies(self, mcp_server2: tuple[SEToolsMCPServer, str, str]) -> None: server, left, _ = mcp_server2 - data = assert_payload(server.setools_diff_policies( + data = assert_payload(server.setools_tool_diff_policies( left, left, components=["types"],)) assert data["result"]["types"]["added"] == [] assert data["result"]["types"]["removed"] == [] @@ -600,7 +602,7 @@ def test_identical_policies(self, mcp_server2: tuple[SEToolsMCPServer, str, str] def test_diff(self, mcp_server2: tuple[SEToolsMCPServer, str, str]) -> None: # tests/library/mcp/diff_left.conf vs policy-full.33 differ in TE rules server, left, right = mcp_server2 - data = assert_payload(server.setools_diff_policies( + data = assert_payload(server.setools_tool_diff_policies( left, right, components=["te_rules", "types"])) diffs = data["result"] # The two full policy files should differ in at least TE rules or types @@ -618,12 +620,12 @@ def test_invalid_component_raises(self, mcp_server2: tuple[SEToolsMCPServer, str, str]) -> None: server, left, right = mcp_server2 with pytest.raises(ValueError): - server.setools_diff_policies(left, right, components=["bad_component"]) + server.setools_tool_diff_policies(left, right, components=["bad_component"]) def test_te_rules_component_structure(self, mcp_server2: tuple[SEToolsMCPServer, str, str]) -> None: server, left, right = mcp_server2 - data = assert_payload(server.setools_diff_policies( + data = assert_payload(server.setools_tool_diff_policies( left, right, components=["te_rules"])) te = data["result"]["te_rules"] for key in ("added_allows", "removed_allows", "modified_allows"): @@ -632,7 +634,7 @@ def test_te_rules_component_structure(self, def test_rbac_rules_component(self, mcp_server2: tuple[SEToolsMCPServer, str, str]) -> None: server, left, right = mcp_server2 - data = assert_payload(server.setools_diff_policies( + data = assert_payload(server.setools_tool_diff_policies( left, right, components=["rbac_rules"])) rbac = data["result"]["rbac_rules"] for key in ("added_role_allows", "removed_role_allows", @@ -642,7 +644,7 @@ def test_rbac_rules_component(self, def test_mls_rules_component(self, mcp_server2: tuple[SEToolsMCPServer, str, str]) -> None: server, left, right = mcp_server2 - data = assert_payload(server.setools_diff_policies( + data = assert_payload(server.setools_tool_diff_policies( left, right, components=["mls_rules"])) mls = data["result"]["mls_rules"] for key in ("added_range_transitions", "removed_range_transitions"): @@ -651,7 +653,7 @@ def test_mls_rules_component(self, def test_portcons_component(self, mcp_server2: tuple[SEToolsMCPServer, str, str]) -> None: server, left, right = mcp_server2 - data = assert_payload(server.setools_diff_policies( + data = assert_payload(server.setools_tool_diff_policies( left, right, components=["portcons"])) pc = data["result"]["portcons"] for key in ("added", "removed"): @@ -661,33 +663,33 @@ def test_portcons_component(self, @pytest.mark.obj_args(SELINUX_POLICY) class TestLookupFileContext: def test_lookup_default_filetype(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_lookup_file_context( + data = assert_payload(mcp_server.setools_tool_lookup_file_context( path="/var/run/test7", fc_path=FILE_CONTEXTS)) assert data["count"] == 1 assert data["result"] == "user0:object_r:type7:s4:c5" def test_lookup_file_filetype(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_lookup_file_context( + data = assert_payload(mcp_server.setools_tool_lookup_file_context( path="/usr/bin/test0", filetype="file", fc_path=FILE_CONTEXTS)) assert data["result"] == "user0:object_r:type0:s4:c5" def test_lookup_dir_filetype(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_lookup_file_context( + data = assert_payload(mcp_server.setools_tool_lookup_file_context( path="/usr/lib/test1", filetype="dir", fc_path=FILE_CONTEXTS)) assert data["result"] == "user0:object_r:type1:s4:c5" def test_lookup_regex_match(self, mcp_server: SEToolsMCPServer) -> None: - data = assert_payload(mcp_server.setools_lookup_file_context( + data = assert_payload(mcp_server.setools_tool_lookup_file_context( path="/opt/test_regex/subdir/file", fc_path=FILE_CONTEXTS)) assert data["result"] == "user0:object_r:type8:s4:c5" def test_lookup_no_match_raises(self, mcp_server: SEToolsMCPServer) -> None: from setools.exception import NoFileContextsMatch with pytest.raises(NoFileContextsMatch): - mcp_server.setools_lookup_file_context( + mcp_server.setools_tool_lookup_file_context( path="/nonexistent/path/that/does/not/match", fc_path=FILE_CONTEXTS) def test_lookup_nonexistent_fc_path_raises(self, mcp_server: SEToolsMCPServer) -> None: with pytest.raises(OSError): - mcp_server.setools_lookup_file_context( + mcp_server.setools_tool_lookup_file_context( path="/some/path", fc_path="/nonexistent/file_contexts") From 69eea7e8b35ecb05a63cc8bccbd15bc7359eea3e Mon Sep 17 00:00:00 2001 From: Chris PeBenito Date: Tue, 14 Jul 2026 16:24:14 +0000 Subject: [PATCH 3/5] server.py: Add prompts. Signed-off-by: Chris PeBenito --- setools/mcp/server.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/setools/mcp/server.py b/setools/mcp/server.py index 06fe7e6f..8d6c4a62 100644 --- a/setools/mcp/server.py +++ b/setools/mcp/server.py @@ -31,6 +31,7 @@ __all__ = ("MCPEncoder",) TOOL_PREFIX: Final[str] = "setools_tool_" +PROMPT_PREFIX: Final[str] = "setools_prompt_" class DiffComponent(str, enum.Enum): @@ -97,6 +98,9 @@ def __init__(self, default_policy: str | None = None) -> None: if name.startswith(TOOL_PREFIX) and callable(getattr(self, name)): self.log.info(f"Registering tool: {name}") self.mcp.tool()(getattr(self, name)) + elif name.startswith(PROMPT_PREFIX) and callable(getattr(self, name)): + self.log.info(f"Registering prompt: {name}") + self.mcp.prompt()(getattr(self, name)) def run(self, transport: Literal["stdio", "sse", "streamable-http"] = "stdio", host: str = "127.0.0.1", port: int = 8000) -> None: @@ -146,6 +150,44 @@ def _serialize_results(result: Any, count: int, truncated: bool) -> str: cls=MCPEncoder, indent=2) + # + # Prompts + # + def setools_prompt_summarize( + self, + name: Annotated[str, "Domain (type) or program to summarize access for."], + ) -> str: + """Summarize the access of a domain (type) or program.""" + return \ + f"Summarize the access of {name}. If this cannot be resolved to a type" \ + "attempt to look up the executable type in file_contexts and if this is an" \ + "entrypoint type, summarize the access of the related domain." + + def setools_prompt_cve( + self, + cve: Annotated[str, "CVE identifier, e.g., CVE-2026-31431"], + ) -> str: + """Find domains might be vulnerable to a CVE.""" + return \ + "Based on the attached SELinux policy, what programs may be able to exploit " \ + f"{cve}. Do not speculate." + + def setools_prompt_dta_out( + self, + name: Annotated[str, "Domain (type) to summarize transitions for."], + ) -> str: + """List the transitions out of a domain (type).""" + return \ + f"List the direct transitions out of {name}." + + def setools_prompt_dta_in( + self, + name: Annotated[str, "Domain (type) to summarize transitions for."], + ) -> str: + """List the transitions into a domain (type).""" + return \ + f"List the direct transitions into {name}." + # # MCP Tools # From 7a23b99a84e9d0c1bebcd02a1a651b50d1a1f448 Mon Sep 17 00:00:00 2001 From: Chris PeBenito Date: Tue, 14 Jul 2026 20:50:18 +0000 Subject: [PATCH 4/5] Change to standalone FastMCP. The FastMCP in the mcp package is a legacy version. Change to the actively developed separate FastMCP package. Signed-off-by: Chris PeBenito --- README.md | 2 +- pyproject.toml | 2 +- setools-mcp | 8 ++++---- setools/mcp/server.py | 14 ++++++++------ tox.ini | 2 +- 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index ea83f22a..0623a450 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ To run SETools graphical tools, the following packages are also required: To run SETools AI tools, the following packages are also required: -* mcp +* FastMCP 2.0+ To build SETools, the following development packages are required, in addition to the development packages from the above list: diff --git a/pyproject.toml b/pyproject.toml index dbe70970..2815a78a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,7 +45,7 @@ dependencies = [] optional-dependencies.analysis = ["networkx>=2.6", "pygraphviz"] optional-dependencies.gui = ["PyQt6"] -optional-dependencies.mcp = ["mcp>=1.0"] +optional-dependencies.mcp = ["fastmcp>=2.0"] optional-dependencies.test = ["tox"] [tool.setuptools] diff --git a/setools-mcp b/setools-mcp index 110a59bd..6b098cf1 100755 --- a/setools-mcp +++ b/setools-mcp @@ -10,12 +10,12 @@ import warnings parser = argparse.ArgumentParser( description="SETools MCP server — exposes setools policy analysis as MCP tools.") parser.add_argument("--policy", metavar="PATH", help="Default SELinux policy file to analyze.") -parser.add_argument("--transport", choices=["stdio", "sse"], default="stdio", - help="MCP transport to use (default: stdio).") +parser.add_argument("--transport", choices=["stdio", "http"], default="stdio", + help="MCP transport to use (stdio, http, default: stdio).") parser.add_argument("--host", default="127.0.0.1", - help="Bind host for SSE transport (default: 127.0.0.1).") + help="Bind host for HTTP transport (default: 127.0.0.1).") parser.add_argument("--port", type=int, default=8000, - help="Bind port for SSE transport (default: 8000).") + help="Bind port for HTTP transport (default: 8000).") parser.add_argument("-v", "--verbose", action="store_true", help="Print extra informational messages") parser.add_argument("--debug", action="store_true", dest="debug", help="Enable debugging.") diff --git a/setools/mcp/server.py b/setools/mcp/server.py index 8d6c4a62..7dcd7e00 100644 --- a/setools/mcp/server.py +++ b/setools/mcp/server.py @@ -10,7 +10,7 @@ from typing import Annotated, Any, Final, Literal try: - from mcp.server.fastmcp import FastMCP + from fastmcp import FastMCP except ImportError as iex: logging.getLogger(__name__).debug(f"{iex.name} failed to import.") @@ -102,13 +102,15 @@ def __init__(self, default_policy: str | None = None) -> None: self.log.info(f"Registering prompt: {name}") self.mcp.prompt()(getattr(self, name)) - def run(self, transport: Literal["stdio", "sse", "streamable-http"] = "stdio", + def run(self, transport: Literal["stdio", "http"] = "stdio", host: str = "127.0.0.1", port: int = 8000) -> None: """Start the MCP server with the given transport.""" - if transport == "sse": - self.mcp.settings.host = host - self.mcp.settings.port = port - self.mcp.run(transport=transport) + if transport == "http": + self.log.info(f"Running MCP server over HTTP at {host}:{port}") + self.mcp.run(transport=transport, host=host, port=port) + else: + self.log.info("Running MCP server over stdio") + self.mcp.run(transport=transport) # # Helpers diff --git a/tox.ini b/tox.ini index cfb043b1..5fc20064 100644 --- a/tox.ini +++ b/tox.ini @@ -50,7 +50,7 @@ passenv = USERSPACE_SRC XAUTHORITY deps = networkx>=2.6 cython>=0.29.14 - mcp>=1.0 + fastmcp>=2.0 pytest>=6.0 PyQt6>=6.0 pygraphviz From c44e7ea6e7ab3bc8d781fdc0040b0cfa633761d0 Mon Sep 17 00:00:00 2001 From: Chris PeBenito Date: Wed, 22 Jul 2026 12:13:32 -0400 Subject: [PATCH 5/5] Context: Minor style change in factory_from_string(). Signed-off-by: Chris PeBenito --- setools/policyrep/context.pxi | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setools/policyrep/context.pxi b/setools/policyrep/context.pxi index d4ffa6fc..9716244c 100644 --- a/setools/policyrep/context.pxi +++ b/setools/policyrep/context.pxi @@ -58,15 +58,15 @@ cdef class Context(PolicyObject): f"{ctx} is invalid: Range {c._range} not in user {c.user}'s " f"allowed range {c.user.mls_range}") + c.policy = policy + return c + except IndexError as ex: raise InvalidContext("f{ctx} is invalid: Context is incomplete.") from ex except InvalidSymbol as ex: raise InvalidContext(f"{ctx} is invalid: {ex}") from ex - c.policy = policy - return c - def __str__(self): if self._range: return f"{self.user}:{self.role}:{self.type_}:{self.range_}"