diff --git a/navi/core/registry.py b/navi/core/registry.py index a27ee4c..6a60183 100644 --- a/navi/core/registry.py +++ b/navi/core/registry.py @@ -8,6 +8,7 @@ from navi.profiles.base import AgentProfile from navi.tools import ( CodeExecTool, + CreateMcpServerTool, DeleteToolTool, FilesystemTool, ImageViewTool, @@ -19,6 +20,7 @@ ScratchpadTool, SwitchProfileTool, TerminalTool, + TestMcpToolTool, TestToolTool, TodoTool, Tool, @@ -190,11 +192,14 @@ manual_tool = ToolManualTool(registry=tools) memory_tool = MemoryTool(memory_store) if memory_store else None mcp_status_tool = McpStatusTool() + create_mcp_server_tool = CreateMcpServerTool() + test_mcp_tool_tool = TestMcpToolTool() builtins = [FilesystemTool(ai_helper=ai_helper), CodeExecTool(), TerminalTool(), SshExecTool(), ImageViewTool(), ShareFileTool(), ContentPublishTool(), TestToolTool(), TodoTool(), ScratchpadTool(), ReflectTool(ai_helper=ai_helper), - reload_tool, write_tool, delete_tool, list_tool, manual_tool, mcp_status_tool] + reload_tool, write_tool, delete_tool, list_tool, manual_tool, + mcp_status_tool, create_mcp_server_tool, test_mcp_tool_tool] if memory_tool: builtins.append(memory_tool) for builtin in builtins: diff --git a/navi/main.py b/navi/main.py index 918520a..8aaa71e 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", "list_tools"): + for tool_name in ("reload_tools", "mcp_status", "test_mcp_tool", "spawn_agent", "list_tools"): tool = tool_registry.get(tool_name) if hasattr(tool, "_mcp_manager"): tool._mcp_manager = mcp_manager diff --git a/navi/mcp/client.py b/navi/mcp/client.py index 0a94267..94f5e3c 100644 --- a/navi/mcp/client.py +++ b/navi/mcp/client.py @@ -93,7 +93,9 @@ if not self._connected: return try: - await self._cleanup() + await asyncio.wait_for(self._cleanup(), timeout=5.0) + except asyncio.TimeoutError: + logger.warning("MCP server %r disconnect timed out, forcing cleanup", self.name) except (asyncio.CancelledError, RuntimeError): # Graceful shutdown during app teardown — SSE transport teardown # throws CancelledError / RuntimeError from anyio task scopes. diff --git a/navi/tools/mcp_status.py b/navi/tools/mcp_status.py index 168825e..9a78d1d 100644 --- a/navi/tools/mcp_status.py +++ b/navi/tools/mcp_status.py @@ -8,8 +8,10 @@ class McpStatusTool(Tool): name = "mcp_status" description = ( - "Show the status of all configured MCP servers and the tools they expose. " - "Use this to discover what external tools are currently available." + "List all configured MCP servers, their connection status, and the number of tools each exposes. " + "Use this ONLY for discovery — to see which servers are connected and what tools exist. " + "Do NOT use mcp_status to test whether a specific tool works. " + "For testing individual tools, use test_mcp_tool instead." ) parameters = { "type": "object", @@ -17,11 +19,15 @@ "required": [], } - def __init__(self, manager: McpManager | None = None) -> None: - self._manager = manager + def __init__(self, mcp_manager: McpManager | None = None) -> None: + self._mcp_manager = mcp_manager async def execute(self, params: dict) -> ToolResult: - if self._manager is None: + manager = self._mcp_manager + if manager is None: + from navi.api.deps import _mcp_manager as _global_mcp_manager + manager = _global_mcp_manager + if manager is None: return ToolResult( success=False, output="", @@ -29,7 +35,7 @@ ) lines: list[str] = [] - for name, client in self._manager.clients.items(): + for name, client in manager.clients.items(): status = "connected" if client.connected else "disconnected" lines.append(f"Server: {name} ({status})") try: