diff --git a/navi/core/registry.py b/navi/core/registry.py index fe31b6f..a27ee4c 100644 --- a/navi/core/registry.py +++ b/navi/core/registry.py @@ -231,7 +231,8 @@ list_profiles_tool = ListProfilesTool(profile_registry=profiles) tools.register(list_profiles_tool, builtin=True) - # Patch backend registry into spawn_tool now that it's available + # Patch cross-registry references now that all registries are built + list_tool._profile_registry = profiles spawn_tool._backend_registry = backends # Context providers diff --git a/navi/main.py b/navi/main.py index 581d030..918520a 100644 --- a/navi/main.py +++ b/navi/main.py @@ -86,7 +86,7 @@ mcp_manager = await get_mcp_manager() tool_registry = get_tool_registry() await register_mcp_tools(tool_registry, mcp_manager) - for tool_name in ("reload_tools", "mcp_status", "spawn_agent"): + for tool_name in ("reload_tools", "mcp_status", "spawn_agent", "list_tools"): tool = tool_registry.get(tool_name) if hasattr(tool, "_mcp_manager"): tool._mcp_manager = mcp_manager diff --git a/navi/tools/list_tools.py b/navi/tools/list_tools.py index 45969d3..6f5de25 100644 --- a/navi/tools/list_tools.py +++ b/navi/tools/list_tools.py @@ -1,28 +1,95 @@ """Built-in tool that returns the current list of available tools.""" +from __future__ import annotations + +import json +from pathlib import Path + from .base import Tool, ToolResult +_USER_ENABLED_FILE = Path("tools/enabled.json") + + +def _load_user_enabled_tools() -> list[str]: + try: + return json.loads(_USER_ENABLED_FILE.read_text()) + except Exception: + return [] + class ListToolsTool(Tool): name = "list_tools" description = ( - "Returns the actual list of tools currently available to you, with their descriptions. " - "Call this when asked what you can do, or before creating a new tool to check if it already exists." + "Returns the list of tools available to the active profile (or all tools if no profile is given). " + "Pass profile_id to preview what a specific profile can do — useful before switching profiles." ) parameters = { "type": "object", - "properties": {}, + "properties": { + "profile_id": { + "type": "string", + "description": "Optional profile ID. When provided, returns only the tools enabled for that profile.", + } + }, "required": [], } - def __init__(self, registry=None) -> None: + def __init__( + self, + registry=None, + profile_registry=None, + mcp_manager=None, + ) -> None: self._registry = registry + self._profile_registry = profile_registry + self._mcp_manager = mcp_manager async def execute(self, params: dict) -> ToolResult: if self._registry is None: return ToolResult(success=False, output="Registry not available.", error="no_registry") - lines = [] + profile_id = params.get("profile_id") + if profile_id and self._profile_registry is not None: + try: + profile = self._profile_registry.get(profile_id) + except Exception: + return ToolResult(success=False, output=f"Profile '{profile_id}' not found.", error="profile_not_found") + + names = list(profile.enabled_tools) + extra = _load_user_enabled_tools() + for name in extra: + if name not in names: + names.append(name) + + # Expand MCP server groups into concrete tool names + if profile.mcp_servers and self._mcp_manager: + for server_name, groups in profile.mcp_servers.items(): + if "*" in groups: + prefix = f"mcp:{server_name}:" + for tool in self._registry.all(): + if tool.name.startswith(prefix) and tool.name not in names: + names.append(tool.name) + else: + for group_name in groups: + for tool_name in self._mcp_manager.resolve_group(server_name, group_name): + full_name = f"mcp:{server_name}:{tool_name}" + if full_name not in names: + names.append(full_name) + + tools = [] + for name in names: + try: + tools.append(self._registry.get(name)) + except Exception: + pass + + lines = [f"Tools for profile '{profile_id}' ({len(tools)} total):"] + for tool in tools: + lines.append(f"• {tool.name}: {tool.description}") + return ToolResult(success=True, output="\n".join(lines)) + + # No profile specified — return all tools + lines = [f"All available tools ({len(self._registry.all())} total):"] for tool in self._registry.all(): lines.append(f"• {tool.name}: {tool.description}")