diff --git a/apps/api/src/services/ai_providers/permissions.py b/apps/api/src/services/ai_providers/permissions.py index cd24fea1..088e448c 100644 --- a/apps/api/src/services/ai_providers/permissions.py +++ b/apps/api/src/services/ai_providers/permissions.py @@ -29,6 +29,17 @@ _WRITE_TOOL_MARKERS = ( "send", ) +_WRITE_TOOL_NAMES = { + "execute_sql", + "exec_sql", + "run_sql", + "raw_sql", + "ssh_exec", + "ssh_execute", + "shell", + "run_command", +} + _K8S_READ_TOOL_MARKERS = ( "describe", "events", @@ -45,6 +56,8 @@ def is_read_only_tool(tool: MCPTool) -> bool: """Return whether a tool is considered side-effect free.""" name = tool.name.lower() + if name in _WRITE_TOOL_NAMES: + return False if any(marker in name for marker in _WRITE_TOOL_MARKERS): return False return True diff --git a/apps/api/tests/test_agent_loop_foundation.py b/apps/api/tests/test_agent_loop_foundation.py index a5b94396..84505391 100644 --- a/apps/api/tests/test_agent_loop_foundation.py +++ b/apps/api/tests/test_agent_loop_foundation.py @@ -50,6 +50,8 @@ def test_agent_tool_permissions_are_role_scoped(): assert is_tool_allowed(prom_query, "hermes") is True assert is_tool_allowed(db_tool, "hermes") is True assert is_tool_allowed(k8s_restart, "elephant_alpha") is False + assert is_tool_allowed(_tool("database", "execute_sql"), "elephant_alpha") is False + assert is_tool_allowed(_tool("ssh_host", "run_command"), "elephant_alpha") is False filtered = filter_tools_for_agent([k8s_get, k8s_restart, prom_query], "nemotron") assert [tool.name for tool in filtered] == ["kubectl_get", "prometheus_query"]