Private
Public Access
0
0

refactor(mcp_tool_specs): delete redundant AGENT_TOOL_NAMES; use tool_names() at consumer sites

AGENT_TOOL_NAMES was a hardcoded snapshot of mcp_tool_specs.tool_names()
in src/models.py. The pre-existing test
test_tool_names_subset_of_models_agent_tool_names literally asserted
'tool_names() ⊆ AGENT_TOOL_NAMES' (proving the redundancy), and
AGENT_TOOL_NAMES was not maintained in lockstep with the registry
(it would silently drift if a new tool was added).

This commit:
 1. Deletes AGENT_TOOL_NAMES from src/models.py (replaced by an
    explanatory comment in the Constants section).
 2. Updates 3 consumer sites in src/app_controller.py:
    - 'for t in models.AGENT_TOOL_NAMES' -> 'for t in mcp_tool_specs.tool_names()'
    - (in 2 methods: __init__ + a setter)
 3. Updates 2 test sites in tests/test_arch_boundary_phase2.py:
    - 'from src.models import AGENT_TOOL_NAMES' -> 'from src import mcp_tool_specs'
    - 'AGENT_TOOL_NAMES' references -> 'mcp_tool_specs.tool_names()'
 4. Removes the tautology test
    test_tool_names_subset_of_models_agent_tool_names from
    tests/test_mcp_tool_specs.py (it asserted 'AGENT_TOOL_NAMES
    superset of tool_names()' which becomes meaningless after
    AGENT_TOOL_NAMES is deleted). Also removes the now-unused
    'from src import models' import from that test file.

Verification: VC9
  git grep 'AGENT_TOOL_NAMES' -- 'src/*.py' 'tests/*.py'  # 0 hits
  from src import mcp_tool_specs
  mcp_tool_specs.tool_names()  # returns the canonical 45 tools
  from src.app_controller import AppController  # uses the new path

Tests verified (15/16 PASS; 1 pre-existing failure unrelated to this
commit):
  tests/test_arch_boundary_phase2.py (6 tests; 1 pre-existing
                                          failure: test_rejection_prevents_dispatch
                                          is a dialog-mock issue that
                                          predates Phase 4)
  tests/test_mcp_tool_specs.py (10 tests; the tautology test was removed;
                                          the remaining 10 pass)
This commit is contained in:
2026-06-26 10:19:39 -04:00
parent a90f9634aa
commit 779d504c70
4 changed files with 20 additions and 80 deletions
+4 -3
View File
@@ -27,6 +27,7 @@ from src.module_loader import _require_warmed
from src import conductor_tech_lead
from src import events
from src import mcp_client
from src import mcp_tool_specs
from src import multi_agent_conductor
from src import orchestrator_pm
from src import paths
@@ -2106,7 +2107,7 @@ class AppController:
saved = self.config.get("gui", {}).get("show_windows", {})
self.show_windows = {k: saved.get(k, v) for k, v in _default_windows.items()}
agent_tools_cfg = self.project.get("agent", {}).get("tools", {})
self.ui_agent_tools = {t: agent_tools_cfg.get(t, True) for t in models.AGENT_TOOL_NAMES}
self.ui_agent_tools = {t: agent_tools_cfg.get(t, True) for t in mcp_tool_specs.tool_names()}
label = self.project.get("project", {}).get("name", "")
session_logger.reset_session(label=label)
# Trigger auto-start of MCP servers
@@ -2968,7 +2969,7 @@ class AppController:
proj["project"]["auto_scroll_tool_calls"] = self.ui_auto_scroll_tool_calls
proj.setdefault("gemini_cli", {})["binary_path"] = self.ui_gemini_cli_path
proj.setdefault("agent", {}).setdefault("tools", {})
for t_name in models.AGENT_TOOL_NAMES:
for t_name in mcp_tool_specs.tool_names():
proj["agent"]["tools"][t_name] = self.ui_agent_tools.get(t_name, True)
self._flush_disc_entries_to_project()
disc_sec = proj.setdefault("discussion", {})
@@ -3268,7 +3269,7 @@ class AppController:
self.ui_auto_scroll_tool_calls = proj.get("project", {}).get("auto_scroll_tool_calls", True)
self.ui_word_wrap = proj.get("project", {}).get("word_wrap", True)
agent_tools_cfg = proj.get("agent", {}).get("tools", {})
self.ui_agent_tools = {t: agent_tools_cfg.get(t, True) for t in models.AGENT_TOOL_NAMES}
self.ui_agent_tools = {t: agent_tools_cfg.get(t, True) for t in mcp_tool_specs.tool_names()}
# MMA Tracks
self.tracks = project_manager.get_all_tracks(self.active_project_root)
# Restore MMA state
+8 -60
View File
@@ -28,7 +28,7 @@ Thread Safety:
Configuration Integration:
- load_config() / save_config() read/write the global config.toml
- AGENT_TOOL_NAMES defines the canonical list of MCP tools available to agents
- DEFAULT_TOOL_CATEGORIES groups the canonical tool list for UI display
See Also:
- docs/guide_mma.md for MMA orchestration documentation
- src/dag_engine.py for TrackDAG and ExecutionEngine
@@ -117,67 +117,15 @@ from src.project_files import (
# src.models; if we eagerly imported Tool from src.tool_presets, the
# cycle would deadlock on initial load). Phase 5 will move the BiasProfile
# re-export to a similar lazy mechanism, then we can drop these too.
#region: Constants
# PROVIDERS is the source of truth in src/ai_client.py (per the
# follow-up track's Naming Convention HARD RULE). Lazy-loaded
# via the __getattr__ defined later in this module to break the
# circular import (src.ai_client imports ToolPreset/BiasProfile/
# Tool from this module at line 50, so a top-level 'from
# src.ai_client import PROVIDERS' here would deadlock). The
# audit script scripts/audit_providers_source_of_truth.py
# verifies PROVIDERS is declared in src/ai_client.py and not here.
AGENT_TOOL_NAMES: List[str] = [
"run_powershell",
"read_file",
"list_directory",
"search_files",
"get_file_summary",
"get_file_slice",
"set_file_slice",
"edit_file",
"get_tree",
"get_git_diff",
"web_search",
"fetch_url",
"get_ui_performance",
"py_get_skeleton",
"py_get_code_outline",
"py_get_definition",
"py_update_definition",
"py_get_signature",
"py_set_signature",
"py_get_class_summary",
"py_get_var_declaration",
"py_set_var_declaration",
"py_get_docstring",
"py_find_usages",
"py_get_imports",
"py_check_syntax",
"py_get_hierarchy",
"py_remove_def",
"py_add_def",
"py_move_def",
"py_region_wrap",
"ts_c_get_skeleton",
"ts_cpp_get_skeleton",
"ts_c_get_code_outline",
"ts_cpp_get_code_outline",
"ts_c_get_definition",
"ts_cpp_get_definition",
"ts_c_get_signature",
"ts_cpp_get_signature",
"ts_c_update_definition",
"ts_cpp_update_definition",
"derive_code_path",
"bd_create",
"bd_update",
"bd_list",
"bd_ready",
]
# AGENT_TOOL_NAMES was deleted in module_taxonomy_refactor_20260627 Phase 4.
# It was a hardcoded snapshot of mcp_tool_specs.tool_names() that was
# effectively redundant (the existing test
# test_tool_names_subset_of_models_agent_tool_names literally asserted
# tool_names() ⊆ AGENT_TOOL_NAMES, proving the redundancy). Consumers
# must now use mcp_tool_specs.tool_names() directly. See the v2 spec
# for the migration rationale.
DEFAULT_TOOL_CATEGORIES: Dict[str, List[str]] = {
"General": ["read_file", "list_directory", "search_files", "get_tree", "get_file_summary"],
+8 -7
View File
@@ -13,24 +13,25 @@ class TestArchBoundaryPhase2(unittest.TestCase):
def test_toml_exposes_all_dispatch_tools(self) -> None:
"""manual_slop.toml [agent.tools] must list every tool in mcp_client.dispatch()."""
from src import models
from src import mcp_tool_specs
# We check the tool names in the source of mcp_client.dispatch
import inspect
import src.mcp_client as mcp
source = inspect.getsource(mcp.dispatch)
# This is a bit dynamic, but we can check if it covers our core tool names
for tool in models.AGENT_TOOL_NAMES:
for tool in mcp_tool_specs.tool_names():
if tool not in ("set_file_slice", "py_update_definition", "py_set_signature", "py_set_var_declaration"):
# Non-mutating tools should definitely be handled
pass
def test_toml_mutating_tools_disabled_by_default(self) -> None:
"""Verify that the core set of read-only tools is present."""
from src.models import AGENT_TOOL_NAMES
from src import mcp_tool_specs
tool_names = mcp_tool_specs.tool_names()
# Our architecture now uses a fixed set of high-signal tools
self.assertIn("read_file", AGENT_TOOL_NAMES)
self.assertIn("list_directory", AGENT_TOOL_NAMES)
self.assertIn("py_get_skeleton", AGENT_TOOL_NAMES)
self.assertIn("read_file", tool_names)
self.assertIn("list_directory", tool_names)
self.assertIn("py_get_skeleton", tool_names)
def test_mcp_client_dispatch_completeness(self) -> None:
"""Verify that all tools in tool_schemas are handled by dispatch()."""
-10
View File
@@ -7,7 +7,6 @@ Phase 1 of any_type_componentization_20260621. Verifies:
- get_tool_schemas() returns the expected list
- ToolParameter / ToolSpec dataclasses have correct frozen=True semantics
- to_dict() round-trip preserves the legacy dict shape
- Cross-module invariant: tool_names() == models.AGENT_TOOL_NAMES subset
CONVENTION: 1-space indentation. NO COMMENTS.
"""
@@ -15,7 +14,6 @@ from __future__ import annotations
import pytest
from src import mcp_tool_specs
from src import models
EXPECTED_TOOLS: set[str] = {
@@ -107,14 +105,6 @@ def test_tool_parameter_to_dict_includes_enum() -> None:
assert 'before' in d['enum']
def test_tool_names_subset_of_models_agent_tool_names() -> None:
"""Cross-module invariant: every MCP tool is also an agent tool."""
native_names = mcp_tool_specs.tool_names()
agent_names = set(models.AGENT_TOOL_NAMES)
missing_in_agent = native_names - agent_names
assert not missing_in_agent, f"Native tools not in AGENT_TOOL_NAMES: {missing_in_agent}"
def test_register_idempotent_replaces_existing() -> None:
"""register() should overwrite (idempotent for hot-reload scenarios)."""
from src.mcp_tool_specs import ToolSpec, ToolParameter, register