diff --git a/src/mcp_client.py b/src/mcp_client.py index e691fcb7..ddef6a5b 100644 --- a/src/mcp_client.py +++ b/src/mcp_client.py @@ -69,6 +69,7 @@ from typing import Optional, Callable, Any, cast from scripts import py_struct_tools from src import beads_client +from src import mcp_tool_specs from src import models from src import outline_tool from src import summarize @@ -1009,10 +1010,10 @@ def get_tree_result(path: str, max_depth: int = 2) -> Result[str]: entries = [e for e in entries if not e.name.startswith('.') and e.name not in ('__pycache__', 'venv', 'env') and e.name != "history.toml" and not e.name.endswith("_history.toml")] for i, entry in enumerate(entries): is_last = (i == len(entries) - 1) - connector = "└── " if is_last else "├── " + connector = "└── " if is_last else "├── " if entry.is_dir(): lines.append(f"{prefix}{connector}{entry.name}/") - extension = " " if is_last else "│ " + extension = " " if is_last else "│ " lines.extend(_build_tree(entry, current_depth + 1, prefix + extension)) else: lines.append(f"{prefix}{connector}{entry.name}") @@ -1941,7 +1942,7 @@ async def async_dispatch(tool_name: str, tool_input: dict[str, Any]) -> str: """ [C: src/rag_engine.py:RAGEngine._async_search_mcp, tests/test_external_mcp.py:test_external_mcp_real_process] """ - native_names = {t['name'] for t in MCP_TOOL_SPECS} + native_names = mcp_tool_specs.tool_names() if tool_name in native_names: return await asyncio.to_thread(dispatch, tool_name, tool_input) @@ -1953,9 +1954,9 @@ async def async_dispatch(tool_name: str, tool_input: dict[str, Any]) -> str: def get_tool_schemas() -> list[dict[str, Any]]: """ - [C: tests/test_arch_boundary_phase2.py:TestArchBoundaryPhase2.test_mcp_client_dispatch_completeness, tests/test_external_mcp.py:test_get_tool_schemas_includes_external, tests/test_mcp_client_beads.py:test_bd_mcp_tools] + [C: tests/test_arch_boundary_phase2.py:TestArchBoundaryPhase2.test_mcp_client_dispatch_completeness, tests/test_external_mcp.py:test_get_tool_schemas_includes_external, tests/test_mcp_client.py:test_bd_mcp_tools] """ - res = list(MCP_TOOL_SPECS) + res = [s.to_dict() for s in mcp_tool_specs.get_tool_schemas()] manager = get_external_mcp_manager() for tname, tinfo in manager.get_all_tools().items(): res.append({ @@ -1969,779 +1970,5 @@ def get_tool_schemas() -> list[dict[str, Any]]: # ------------------------------------------------------------------ tool schema helpers # These are imported by ai_client.py to build provider-specific declarations. -MCP_TOOL_SPECS: list[dict[str, Any]] = [ - { - "name": "py_remove_def", - "description": "Excises a specific class or function definition from a Python file using AST-derived line ranges, preserving surrounding formatting and comments.", - "parameters": { - "type": "object", - "properties": { - "path": { "type": "string", "description": "Path to the .py file." }, - "name": { "type": "string", "description": "The name of the class or function to remove. Use 'ClassName.method_name' for methods." } - }, - "required": ["path", "name"] - } - }, - { - "name": "py_add_def", - "description": "Inserts a new definition into a specific context (module level or within a specific class).", - "parameters": { - "type": "object", - "properties": { - "path": { "type": "string", "description": "Path to the .py file." }, - "name": { "type": "string", "description": "Context path (e.g. 'ClassName' or empty for module level)." }, - "new_content": { "type": "string", "description": "The code to insert." }, - "anchor_type": { "type": "string", "enum": ["before", "after", "top", "bottom"], "description": "Where to insert relative to the anchor." }, - "anchor_symbol": { "type": "string", "description": "Symbol name to anchor to if anchor_type is 'before' or 'after'." } - }, - "required": ["path", "name", "new_content", "anchor_type"] - } - }, - { - "name": "py_move_def", - "description": "Relocates a definition within a file or across different Python files.", - "parameters": { - "type": "object", - "properties": { - "src_path": { "type": "string", "description": "Path to the source .py file." }, - "dest_path": { "type": "string", "description": "Path to the destination .py file." }, - "name": { "type": "string", "description": "The name of the class or function to move." }, - "dest_name": { "type": "string", "description": "Context path in destination file (e.g. 'ClassName' or empty)." }, - "anchor_type": { "type": "string", "enum": ["before", "after", "top", "bottom"], "description": "Where to insert in destination." }, - "anchor_symbol": { "type": "string", "description": "Anchor symbol in destination." } - }, - "required": ["src_path", "dest_path", "name", "dest_name", "anchor_type"] - } - }, - { - "name": "py_region_wrap", - "description": "Wraps a specified block of code (e.g., a set of methods) in #region: Name and #endregion: Name tags.", - "parameters": { - "type": "object", - "properties": { - "path": { "type": "string", "description": "Path to the .py file." }, - "start_line": { "type": "integer", "description": "1-based start line number." }, - "end_line": { "type": "integer", "description": "1-based end line number (inclusive)." }, - "region_name": { "type": "string", "description": "The name of the region." } - }, - "required": ["path", "start_line", "end_line", "region_name"] - } - }, - { - "name": "read_file", - "description": ( - "Read the full UTF-8 content of a file within the allowed project paths. " - "Use get_file_summary first to decide whether you need the full content." - ), - "parameters": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Absolute or relative path to the file to read.", - } - }, - "required": ["path"], - }, - }, - { - "name": "list_directory", - "description": ( - "List files and subdirectories within an allowed directory. " - "Shows name, type (file/dir), and size. Use this to explore the project structure." - ), - "parameters": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Absolute path to the directory to list.", - } - }, - "required": ["path"], - }, - }, - { - "name": "search_files", - "description": ( - "Search for files matching a glob pattern within an allowed directory. " - "Supports recursive patterns like '**/*.py'. " - "Use this to find files by extension or name pattern." - ), - "parameters": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Absolute path to the directory to search within.", - }, - "pattern": { - "type": "string", - "description": "Glob pattern, e.g. '*.py', '**/*.toml', 'src/**/*.rs'.", - }, - }, - "required": ["path", "pattern"], - }, - }, - { - "name": "get_file_summary", - "description": ( - "Get a compact heuristic summary of a file without reading its full content. " - "For Python: imports, classes, methods, functions, constants. " - "For TOML: table keys. For Markdown: headings. Others: line count + preview. " - "Use this before read_file to decide if you need the full content." - ), - "parameters": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Absolute or relative path to the file to summarise.", - } - }, - "required": ["path"], - }, - }, - { - "name": "py_get_skeleton", - "description": ( - "Get a skeleton view of a Python file. " - "This returns all classes and function signatures with their docstrings, " - "but replaces function bodies with '...'. " - "Use this to understand module interfaces without reading the full implementation." - ), - "parameters": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Path to the .py file.", - } - }, - "required": ["path"], - }, - }, - { - "name": "py_get_code_outline", - "description": ( - "Get a hierarchical outline of a code file. " - "This returns classes, functions, and methods with their line ranges and brief docstrings. " - "Use this to quickly map out a file's structure before reading specific sections." - ), - "parameters": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Path to the code file (currently supports .py).", - } - }, - "required": ["path"], - }, - }, - { - "name": "ts_c_get_skeleton", - "description": ( - "Get a skeleton view of a C file. " - "This returns all function signatures and structs, " - "but replaces function bodies with '...'. " - "Use this to understand C interfaces without reading the full implementation." - ), - "parameters": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Path to the C file.", - } - }, - "required": ["path"], - }, - }, - { - "name": "ts_cpp_get_skeleton", - "description": ( - "Get a skeleton view of a C++ file. " - "This returns all classes, structs and function signatures, " - "but replaces function bodies with '...'. " - "Use this to understand C++ interfaces without reading the full implementation." - ), - "parameters": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Path to the C++ file.", - } - }, - "required": ["path"], - }, - }, - { - "name": "ts_c_get_code_outline", - "description": ( - "Get a hierarchical outline of a C file. " - "This returns structs and functions with their line ranges. " - "Use this to quickly map out a file's structure before reading specific sections." - ), - "parameters": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Path to the C file.", - } - }, - "required": ["path"], - }, - }, - { - "name": "ts_cpp_get_code_outline", - "description": ( - "Get a hierarchical outline of a C++ file. " - "This returns classes, structs and functions with their line ranges. " - "Use this to quickly map out a file's structure before reading specific sections." - ), - "parameters": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Path to the C++ file.", - } - }, - "required": ["path"], - }, - }, - { - "name": "ts_c_get_definition", - "description": ( - "Get the full source code of a specific function or struct definition in a C file. " - "This is more efficient than reading the whole file if you know what you're looking for." - ), - "parameters": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Path to the C file.", - }, - "name": { - "type": "string", - "description": "The name of the function or struct to retrieve.", - } - }, - "required": ["path", "name"], - }, - }, - { - "name": "ts_cpp_get_definition", - "description": ( - "Get the full source code of a specific class, function, or method definition in a C++ file. " - "This is more efficient than reading the whole file if you know what you're looking for." - ), - "parameters": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Path to the C++ file.", - }, - "name": { - "type": "string", - "description": "The name of the class or function to retrieve. Use 'ClassName::method_name' for methods.", - } - }, - "required": ["path", "name"], - }, - }, - { - "name": "ts_c_get_signature", - "description": "Get only the signature part of a C function.", - "parameters": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Path to the C file." - }, - "name": { - "type": "string", - "description": "Name of the function." - } - }, - "required": ["path", "name"] - } - }, - { - "name": "ts_cpp_get_signature", - "description": "Get only the signature part of a C++ function or method.", - "parameters": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Path to the C++ file." - }, - "name": { - "type": "string", - "description": "Name of the function/method (e.g. 'ClassName::method_name')." - } - }, - "required": ["path", "name"] - } - }, - { - "name": "ts_c_update_definition", - "description": "Surgically replace the definition of a function in a C file using AST to find line ranges.", - "parameters": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Path to the C file." - }, - "name": { - "type": "string", - "description": "Name of function." - }, - "new_content": { - "type": "string", - "description": "Complete new source for the definition." - } - }, - "required": ["path", "name", "new_content"] - } - }, - { - "name": "ts_cpp_update_definition", - "description": "Surgically replace the definition of a class or function in a C++ file using AST to find line ranges.", - "parameters": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Path to the C++ file." - }, - "name": { - "type": "string", - "description": "Name of class/function/method." - }, - "new_content": { - "type": "string", - "description": "Complete new source for the definition." - } - }, - "required": ["path", "name", "new_content"] - } - }, - { - "name": "get_file_slice", - "description": "Read a specific line range from a file. Useful for reading parts of very large files.", - "parameters": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Path to the file." - }, - "start_line": { - "type": "integer", - "description": "1-based start line number." - }, - "end_line": { - "type": "integer", - "description": "1-based end line number (inclusive)." - } - }, - "required": ["path", "start_line", "end_line"] - } - }, - { - "name": "set_file_slice", - "description": "Replace a specific line range in a file with new content. Surgical edit tool.", - "parameters": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Path to the file." - }, - "start_line": { - "type": "integer", - "description": "1-based start line number." - }, - "end_line": { - "type": "integer", - "description": "1-based end line number (inclusive)." - }, - "new_content": { - "type": "string", - "description": "New content to insert." - } - }, - "required": ["path", "start_line", "end_line", "new_content"] - } - }, - { - "name": "edit_file", - "description": "Replace exact string match in a file. Preserves indentation and line endings. Drop-in replacement for native edit tool.", - "parameters": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Path to the file." - }, - "old_string": { - "type": "string", - "description": "The text to replace." - }, - "new_string": { - "type": "string", - "description": "The replacement text." - }, - "replace_all": { - "type": "boolean", - "description": "Replace all occurrences. Default false." - } - }, - "required": ["path", "old_string", "new_string"] - } - }, - { - "name": "py_get_definition", - "description": ( - "Get the full source code of a specific class, function, or method definition. " - "This is more efficient than reading the whole file if you know what you're looking for." - ), - "parameters": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Path to the .py file.", - }, - "name": { - "type": "string", - "description": "The name of the class or function to retrieve. Use 'ClassName.method_name' for methods.", - } - }, - "required": ["path", "name"], - }, - }, - { - "name": "py_update_definition", - "description": "Surgically replace the definition of a class or function in a Python file using AST to find line ranges.", - "parameters": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Path to the .py file." - }, - "name": { - "type": "string", - "description": "Name of class/function/method." - }, - "new_content": { - "type": "string", - "description": "Complete new source for the definition." - } - }, - "required": ["path", "name", "new_content"] - } - }, - { - "name": "py_get_signature", - "description": "Get only the signature part of a Python function or method (from def until colon).", - "parameters": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Path to the .py file." - }, - "name": { - "type": "string", - "description": "Name of the function/method (e.g. 'ClassName.method_name')." - } - }, - "required": ["path", "name"] - } - }, - { - "name": "py_set_signature", - "description": "Surgically replace only the signature of a Python function or method.", - "parameters": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Path to the .py file." - }, - "name": { - "type": "string", - "description": "Name of the function/method." - }, - "new_signature": { - "type": "string", - "description": "Complete new signature string (including def and trailing colon)." - } - }, - "required": ["path", "name", "new_signature"] - } - }, - { - "name": "py_get_class_summary", - "description": "Get a summary of a Python class, listing its docstring and all method signatures.", - "parameters": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Path to the .py file." - }, - "name": { - "type": "string", - "description": "Name of the class." - } - }, - "required": ["path", "name"] - } - }, - { - "name": "py_get_var_declaration", - "description": "Get the assignment/declaration line for a variable.", - "parameters": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Path to the .py file." - }, - "name": { - "type": "string", - "description": "Name of the variable." - } - }, - "required": ["path", "name"] - } - }, - { - "name": "py_set_var_declaration", - "description": "Surgically replace a variable assignment/declaration.", - "parameters": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Path to the .py file." - }, - "name": { - "type": "string", - "description": "Name of the variable." - }, - "new_declaration": { - "type": "string", - "description": "Complete new assignment/declaration string." - } - }, - "required": ["path", "name", "new_declaration"] - } - }, - { - "name": "get_git_diff", - "description": ( - "Returns the git diff for a file or directory. " - "Use this to review changes efficiently without reading entire files." - ), - "parameters": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "Path to the file or directory.", - }, - "base_rev": { - "type": "string", - "description": "Base revision (e.g. 'HEAD', 'HEAD~1', or a commit hash). Defaults to 'HEAD'.", - }, - "head_rev": { - "type": "string", - "description": "Head revision (optional).", - } - }, - "required": ["path"], - }, - }, - { - "name": "web_search", - "description": "Search the web using DuckDuckGo. Returns the top 5 search results with titles, URLs, and snippets. Chain this with fetch_url to read specific pages.", - "parameters": { - "type": "object", - "properties": { - "query": { - "type": "string", - "description": "The search query." - } - }, - "required": ["query"] - } - }, - { - "name": "fetch_url", - "description": "Fetch the full text content of a URL (stripped of HTML tags). Use this after web_search to read relevant information from the web.", - "parameters": { - "type": "object", - "properties": { - "url": { - "type": "string", - "description": "The full URL to fetch." - } - }, - "required": ["url"] - } - }, - { - "name": "get_ui_performance", - "description": "Get a snapshot of the current UI performance metrics, including FPS, Frame Time (ms), CPU usage (%), and Input Lag (ms). Use this to diagnose UI slowness or verify that your changes haven't degraded the user experience.", - "parameters": { - "type": "object", - "properties": {} - } - }, - { - "name": "py_find_usages", - "description": "Finds exact string matches of a symbol in a given file or directory.", - "parameters": { - "type": "object", - "properties": { - "path": { "type": "string", "description": "Path to file or directory to search." }, - "name": { "type": "string", "description": "The symbol/string to search for." } - }, - "required": ["path", "name"] - } - }, - { - "name": "py_get_imports", - "description": "Parses a file's AST and returns a strict list of its dependencies.", - "parameters": { - "type": "object", - "properties": { - "path": { "type": "string", "description": "Path to the .py file." } - }, - "required": ["path"] - } - }, - { - "name": "py_check_syntax", - "description": "Runs a quick syntax check on a Python file.", - "parameters": { - "type": "object", - "properties": { - "path": { "type": "string", "description": "Path to the .py file." } - }, - "required": ["path"] - } - }, - { - "name": "py_get_hierarchy", - "description": "Scans the project to find subclasses of a given class.", - "parameters": { - "type": "object", - "properties": { - "path": { "type": "string", "description": "Directory path to search in." }, - "class_name": { "type": "string", "description": "Name of the base class." } - }, - "required": ["path", "class_name"] - } - }, - { - "name": "py_get_docstring", - "description": "Extracts the docstring for a specific module, class, or function.", - "parameters": { - "type": "object", - "properties": { - "path": { "type": "string", "description": "Path to the .py file." }, - "name": { "type": "string", "description": "Name of symbol or 'module' for the file docstring." } - }, - "required": ["path", "name"] - } - }, - { - "name": "get_tree", - "description": "Returns a directory structure up to a max depth.", - "parameters": { - "type": "object", - "properties": { - "path": { "type": "string", "description": "Directory path." }, - "max_depth": { "type": "integer", "description": "Maximum depth to recurse (default 2)." } - }, - "required": ["path"] - } - }, - { - "name": "bd_create", - "description": "Create a new Bead in the active Beads repository.", - "parameters": { - "type": "object", - "properties": { - "title": { "type": "string", "description": "Title of the Bead." }, - "description": { "type": "string", "description": "Description of the Bead." } - }, - "required": ["title", "description"] - } - }, - { - "name": "bd_update", - "description": "Update an existing Bead.", - "parameters": { - "type": "object", - "properties": { - "bead_id": { "type": "string", "description": "ID of the Bead to update." }, - "status": { "type": "string", "description": "New status for the Bead." } - }, - "required": ["bead_id", "status"] - } - }, - { - "name": "bd_list", - "description": "List all Beads in the active Beads repository.", - "parameters": { - "type": "object", - "properties": {} - } - }, - { - "name": "bd_ready", - "description": "Check if the Beads repository is initialized in the current workspace.", - "parameters": { - "type": "object", - "properties": {} - } - }, - { - "name": "derive_code_path", - "description": ( - "Recursively traces the execution path of a specific function or method across multiple files. " - "Identifies call chains and data hand-offs to build an intensive technical map." - ), - "parameters": { - "type": "object", - "properties": { - "target": { - "type": "string", - "description": "Fully qualified name of the target (e.g., 'src.ai_client.send') or class.method.", - }, - "max_depth": { - "type": "integer", - "description": "Maximum recursion depth for the call graph (default 5).", - }, - }, - "required": ["target"], - }, - } -] -TOOL_NAMES: set[str] = {t['name'] for t in MCP_TOOL_SPECS} +TOOL_NAMES: set[str] = mcp_tool_specs.tool_names()