diff --git a/docs/api.md b/docs/api.md index 954f2c1..dd38042 100644 --- a/docs/api.md +++ b/docs/api.md @@ -149,7 +149,7 @@ "id": "secretary", "name": "Personal Secretary", "description": "General-purpose assistant", - "enabled_tools": ["todo", "mcp:navi-web:web_search", "filesystem", "..."], + "enabled_tools": ["todo", "mcp__navi_web__web_search", "filesystem", "..."], "llm_backend": "ollama", "model": ["gemma4:31b-cloud", "gemma4:26b-a4b-it-q4_K_M"], "temperature": 0.65, @@ -172,7 +172,7 @@ ```json [ { - "name": "mcp:navi-web:web_search", + "name": "mcp__navi_web__web_search", "description": "Search the web using DuckDuckGo.", "parameters": {"type": "object", "properties": {...}, "required": [...]} }, @@ -334,7 +334,7 @@ "tool_calls": [ { "id": "abc123", - "name": "mcp:navi-web:web_search", + "name": "mcp__navi_web__web_search", "arguments": { "query": "..." } } ] @@ -343,7 +343,7 @@ "role": "tool", "content": "tool result", "tool_call_id": "abc123", - "name": "mcp:navi-web:web_search" + "name": "mcp__navi_web__web_search" } ] } @@ -713,7 +713,7 @@ ```json { "type": "tool_started", - "tool": "mcp:navi-web:web_search", + "tool": "mcp__navi_web__web_search", "args": { "query": "weather in moscow" }, "is_subagent": false } @@ -727,7 +727,7 @@ ```json { "type": "tool_call", - "tool": "mcp:navi-web:web_search", + "tool": "mcp__navi_web__web_search", "args": { "query": "weather in moscow" }, "result": "Today +12°C, cloudy.", "success": true, diff --git a/docs/mechanics.md b/docs/mechanics.md index 9f4b9c2..a66e3b3 100644 --- a/docs/mechanics.md +++ b/docs/mechanics.md @@ -192,7 +192,7 @@ | **`McpClient`** | Official MCP SDK wrapper. stdio or SSE transport. | None | `client.py` | ⚠️ | | **`McpServerConfig`** | Pydantic model for server config. Auto-migrates legacy `mcp_servers.json` → `mcp_servers.d/`. | `mcp_servers.d/` directory | `config.py` | ⚠️ | | **`McpManager`** | Pool of `McpClient` instances. Lifecycle management, group resolution, tool listing. | `config_path` | `manager.py` | ⚠️ | -| **`McpTool` proxy** | `Tool` subclass forwarding to MCP server. Namespaced as `mcp::`. | None | `tools.py` | ✅ | +| **`McpTool` proxy** | `Tool` subclass forwarding to MCP server. Namespaced as `mcp____`. | None | `tools.py` | ✅ | ## Sessions (`navi/core/session.py`, `navi/core/pg_session_store.py`) @@ -289,7 +289,7 @@ | Mechanic | Description | Config / Flags | Files | Docs | |---|---|---|---|---| | **Lazy singleton initialization** | `get_session_store()`, `get_memory_store()`, `get_kv_store()`, `get_scheduler()`, `get_registries()`, etc. Module-level caching. | `DATABASE_URL`, `EMBEDDING_OLLAMA_HOST`, `EMBEDDING_OLLAMA_API_KEY` | `deps.py` | ⚠️ | -| **MCP manager & tool registration** | `get_mcp_manager()` lazily initializes and loads all servers. Clears and re-registers `mcp:*` tools. | None | `deps.py` | ❌ | +| **MCP manager & tool registration** | `get_mcp_manager()` lazily initializes and loads all servers. Clears and re-registers `mcp__*` tools. | None | `deps.py` | ❌ | | **Embedding backend wiring** | Dedicated `OllamaBackend` for embeddings (if `EMBEDDING_OLLAMA_HOST` set) or falls back to main chat backend. Injected into memory store. | `EMBEDDING_OLLAMA_HOST`, `EMBEDDING_OLLAMA_API_KEY`, `EMBEDDING_MODEL` | `deps.py` | ⚠️ | ## Scheduler / Recall (`navi/core/scheduler.py`) diff --git a/docs/profiles.md b/docs/profiles.md index 0d335d9..59eb327 100644 --- a/docs/profiles.md +++ b/docs/profiles.md @@ -40,13 +40,13 @@ | Key | Type | Default | Description | |---|---|---|---| | `enabled_tools` | list[str] | **required** | Tool names available in the main loop | -| `subagent_tools` | list[str] | `[]` | Tools available to sub-agents spawned from this profile. Falls back to `enabled_tools` (full list) if empty. **Also acts as a whitelist for MCP tools** — only `mcp::` entries listed here are exposed to the sub-agent. If the list is non-empty and contains no `mcp:` entries, the sub-agent receives no MCP tools at all. | +| `subagent_tools` | list[str] | `[]` | Tools available to sub-agents spawned from this profile. Falls back to `enabled_tools` (full list) if empty. **Also acts as a whitelist for MCP tools** — only `mcp____` entries listed here are exposed to the sub-agent. If the list is non-empty and contains no `mcp__` entries, the sub-agent receives no MCP tools at all. | `spawn_agent` may receive an optional `profile_id`. If omitted, the subagent uses the parent session's current profile. If provided, the subagent uses the selected profile's model, prompt, planning flags, and `subagent_tools`/`enabled_tools` fallback. ### MCP tools in sub-agents -When `subagent_tools` is non-empty, `mcp_servers` is filtered so that only MCP tools whose full name (`mcp::`) appears in `subagent_tools` are available to the sub-agent. This prevents a profile's main MCP servers from leaking into restricted sub-agent contexts. To grant a sub-agent access to a specific MCP tool, add it explicitly to `subagent_tools`, e.g. `mcp:navi-web:web_search`. +When `subagent_tools` is non-empty, `mcp_servers` is filtered so that only MCP tools whose full name (`mcp____`) appears in `subagent_tools` are available to the sub-agent. This prevents a profile's main MCP servers from leaking into restricted sub-agent contexts. To grant a sub-agent access to a specific MCP tool, add it explicitly to `subagent_tools`, e.g. `mcp__navi_web__web_search`. ### Thinking mechanics @@ -136,7 +136,7 @@ "model": ["gemma4:31b-cloud", "gemma4:26b-a4b-it-q4_K_M"], "temperature": 0.5, "max_iterations": 20, - "enabled_tools": ["todo", "scratchpad", "mcp:navi-web:web_search", "filesystem"], + "enabled_tools": ["todo", "scratchpad", "mcp__navi_web__web_search", "filesystem"], "subagent_tools": ["todo", "filesystem", "terminal"], "planning_enabled": true, "planning_mandatory": false, diff --git a/docs/tools.md b/docs/tools.md index 530847a..aebdf5f 100644 --- a/docs/tools.md +++ b/docs/tools.md @@ -10,10 +10,10 @@ | Tool | Name | Description | |---|---|---| -| `WebSearchTool` | `mcp:navi-web:web_search` | DuckDuckGo search | -| `WebViewTool` | `mcp:navi-web:web_view` | Fetch and render a URL | +| `WebSearchTool` | `mcp__navi_web__web_search` | DuckDuckGo search | +| `WebViewTool` | `mcp__navi_web__web_view` | Fetch and render a URL | | `FilesystemTool` | `filesystem` | Read/write/list/copy/grep/diff local files (path restrictions via config) | -| `HttpRequestTool` | `mcp:navi-web:http_request` | Generic HTTP client (GET/POST/etc.) | +| `HttpRequestTool` | `mcp__navi_web__http_request` | Generic HTTP client (GET/POST/etc.) | | `CodeExecTool` | `code_exec` | Execute Python in a subprocess sandbox | | `TerminalTool` | `terminal` | Run shell commands (command allowlist via config) | | `SshExecTool` | `ssh_exec` | SSH exec and SCP file transfer; connection pool keyed by session ID | @@ -29,12 +29,12 @@ | `ListProfilesTool` | `list_profiles` | List all available profiles | | `ShareFileTool` | `share_file` | Copy an existing local file into session files and return a download link | | `ContentPublishTool` | `content_publish` | Register an existing session file for inline viewing in chat | -| `McpTool` (navi-3d) | `mcp:navi-3d:compile_scad` | Compile an OpenSCAD script into a binary STL file | -| `McpTool` (navi-3d) | `mcp:navi-3d:lint_scad` | Lightweight OpenSCAD source linting before STL compilation | -| `McpTool` (navi-3d) | `mcp:navi-3d:render_stl` | Render preview PNG images from an STL file (up to 3 views) | -| `McpTool` (navi-web) | `mcp:navi-web:web_search` | Web search (SearXNG primary, DDG fallback, Brave tertiary) | -| `McpTool` (navi-web) | `mcp:navi-web:web_view` | Open a URL in a headless browser and return clean readable text | -| `McpTool` (navi-web) | `mcp:navi-web:http_request` | Raw HTTP request (GET/POST/PUT/PATCH/DELETE) | +| `McpTool` (navi-3d) | `mcp__navi_3d__compile_scad` | Compile an OpenSCAD script into a binary STL file | +| `McpTool` (navi-3d) | `mcp__navi_3d__lint_scad` | Lightweight OpenSCAD source linting before STL compilation | +| `McpTool` (navi-3d) | `mcp__navi_3d__render_stl` | Render preview PNG images from an STL file (up to 3 views) | +| `McpTool` (navi-web) | `mcp__navi_web__web_search` | Web search (SearXNG primary, DDG fallback, Brave tertiary) | +| `McpTool` (navi-web) | `mcp__navi_web__web_view` | Open a URL in a headless browser and return clean readable text | +| `McpTool` (navi-web) | `mcp__navi_web__http_request` | Raw HTTP request (GET/POST/PUT/PATCH/DELETE) | | `McpStatusTool` | `mcp_status` | Check connectivity and list tools for configured MCP servers | | `ReflectTool` | `reflect` | Self-reflection and analysis | | `ScheduleRecallTool` | `schedule_recall` | Schedule a headless callback for the current session (once/recurring/immediate) | diff --git a/docs/visual.html b/docs/visual.html index 57c3e40..4797cee 100644 --- a/docs/visual.html +++ b/docs/visual.html @@ -872,10 +872,10 @@
- - + + - + @@ -1091,8 +1091,8 @@
stream_start
tool_started spawn_agent is_subagent=false
turn_thinking is_subagent=true
-
tool_started mcp:navi-web:web_search is_subagent=true
-
tool_call mcp:navi-web:web_search is_subagent=true
+
tool_started mcp__navi_web__web_search is_subagent=true
+
tool_call mcp__navi_web__web_search is_subagent=true
tool_started filesystem is_subagent=true
tool_call filesystem is_subagent=true
tool_call spawn_agent is_subagent=false
diff --git a/manuals/lint_scad.md b/manuals/lint_scad.md index 85404bf..a0808ff 100644 --- a/manuals/lint_scad.md +++ b/manuals/lint_scad.md @@ -1,4 +1,4 @@ -# mcp:navi-3d:lint_scad +# mcp__navi_3d__lint_scad ## Что делает @@ -11,7 +11,7 @@ ## Формат вызова ```python -mcp:navi-3d:lint_scad( +mcp__navi_3d__lint_scad( session_id="...", source_path="bracket.scad" ) @@ -40,7 +40,7 @@ ### 2. Запустить линтинг ``` -mcp:navi-3d:lint_scad( +mcp__navi_3d__lint_scad( session_id="sess-abc", source_path="bracket.scad" ) @@ -55,7 +55,7 @@ После чистого линта: ``` -mcp:navi-3d:compile_scad( +mcp__navi_3d__compile_scad( session_id="sess-abc", source_path="bracket.scad", output_path="bracket.stl" diff --git a/manuals/model_3d.md b/manuals/model_3d.md index 3e86221..4eafde2 100644 --- a/manuals/model_3d.md +++ b/manuals/model_3d.md @@ -1,4 +1,4 @@ -# mcp:navi-3d:compile_scad +# mcp__navi_3d__compile_scad ## Что делает @@ -14,7 +14,7 @@ ## Формат вызова ```python -mcp:navi-3d:compile_scad( +mcp__navi_3d__compile_scad( session_id="...", source_path="handle.scad", output_path="handle.stl" @@ -46,7 +46,7 @@ ### 2. Проверить линтингом (рекомендуется) ``` -mcp:navi-3d:lint_scad( +mcp__navi_3d__lint_scad( session_id="sess-abc", source_path="bracket.scad" ) @@ -55,7 +55,7 @@ ### 3. Скомпилировать в STL ``` -mcp:navi-3d:compile_scad( +mcp__navi_3d__compile_scad( session_id="sess-abc", source_path="bracket.scad", output_path="bracket.stl" diff --git a/manuals/render_3d.md b/manuals/render_3d.md index 2f05ac0..98b4ac0 100644 --- a/manuals/render_3d.md +++ b/manuals/render_3d.md @@ -1,4 +1,4 @@ -# mcp:navi-3d:render_stl +# mcp__navi_3d__render_stl ## Что делает @@ -8,13 +8,13 @@ ## Предпосылки -1. **STL-файл уже должен существовать.** Сгенерируйте его через `mcp:navi-3d:compile_scad` заранее. +1. **STL-файл уже должен существовать.** Сгенерируйте его через `mcp__navi_3d__compile_scad` заранее. 2. **OpenSCAD должен быть установлен.** ## Формат вызова ```python -mcp:navi-3d:render_stl( +mcp__navi_3d__render_stl( session_id="...", source_path="bracket.stl", views=["iso", "front", "top"] @@ -46,7 +46,7 @@ ### 1. Сгенерировать STL ``` -mcp:navi-3d:compile_scad( +mcp__navi_3d__compile_scad( session_id="sess-abc", source_path="bracket.scad", output_path="bracket.stl" @@ -56,7 +56,7 @@ ### 2. Отрендерить ракурсы ``` -mcp:navi-3d:render_stl( +mcp__navi_3d__render_stl( session_id="sess-abc", source_path="bracket.stl", views=["iso", "front", "top"] diff --git a/manuals/spawn_agent.md b/manuals/spawn_agent.md index a444927..1560f5f 100644 --- a/manuals/spawn_agent.md +++ b/manuals/spawn_agent.md @@ -35,9 +35,9 @@ | Profile | Sub-agent tools | |---------|----------------| -| `secretary` | scratchpad, reflect, mcp:navi-web:web_search, mcp:navi-web:web_view, mcp:navi-web:http_request, filesystem, code_exec, image_view, memory, share_file, weather | -| `server_admin` | scratchpad, reflect, mcp:navi-web:web_search, mcp:navi-web:http_request, filesystem, code_exec, terminal, ssh_exec, image_view, share_file | -| `developer` | scratchpad, reflect, mcp:navi-web:web_search, mcp:navi-web:web_view, mcp:navi-web:http_request, filesystem, code_exec, terminal, image_view, reload_tools, test_tool, share_file | +| `secretary` | scratchpad, reflect, mcp__navi_web__web_search, mcp__navi_web__web_view, mcp__navi_web__http_request, filesystem, code_exec, image_view, memory, share_file, weather | +| `server_admin` | scratchpad, reflect, mcp__navi_web__web_search, mcp__navi_web__http_request, filesystem, code_exec, terminal, ssh_exec, image_view, share_file | +| `developer` | scratchpad, reflect, mcp__navi_web__web_search, mcp__navi_web__web_view, mcp__navi_web__http_request, filesystem, code_exec, terminal, image_view, reload_tools, test_tool, share_file | `spawn_agent` is always excluded — recursion is impossible. diff --git a/mcp_servers.d/gnexus-book.json b/mcp_servers.d/gnexus-book.json index e9197e8..e153810 100644 --- a/mcp_servers.d/gnexus-book.json +++ b/mcp_servers.d/gnexus-book.json @@ -25,5 +25,5 @@ "list_pending_changes" ] }, - "instructions": "MANDATORY for profiles that expose gnexus-book tools: Before answering any question about infrastructure, servers, services, networks, documentation, or system inventory, call gnexus-book tools first.\n\nUse only gnexus-book tool names that are present in the current tool schema. In Navi they are exposed with the mcp:gnexus-book: prefix (example: mcp:gnexus-book:search_docs), but each profile may expose only some groups. Do not invent or call gnexus-book tools that are not in the current tool list.\n\nQuery mapping by capability:\n- Status or facts about a server/service → search docs first, then read a specific doc or inventory item if those tools are available.\n- Service placement or topology → list inventory and relationships if available.\n- Documentation changes → read the target doc first, then propose a doc or inventory change if write tools are available.\n- Freshness questions → use freshness checks if available.\n- Repository validation/status → use repository tools only if they are available in the current tool schema; otherwise skip this step and continue with available read/write tools.\n\nDo not rely on memory for infrastructure facts. Memory is only for personal user facts and preferences. Always pull infrastructure state from gnexus-book when these tools are available to the active profile.\n\nDo not store raw secrets in documentation.\n\nABSOLUTE RULE — NEVER bypass MCP tools:\nYou MUST NOT use filesystem, terminal, code_exec, or any direct file access to read or write gnexus-book files. The MCP tools are the ONLY valid interface to this knowledge base. Violating this rule bypasses validation, corrupts repository state, and breaks consistency guarantees.\n- To read: use mcp:gnexus-book:search_docs, mcp:gnexus-book:read_doc, mcp:gnexus-book:list_inventory, mcp:gnexus-book:get_inventory_item.\n- To write: use mcp:gnexus-book:propose_doc_change, mcp:gnexus-book:propose_inventory_item_change, mcp:gnexus-book:apply_pending_change, mcp:gnexus-book:commit_changes.\n- NEVER call filesystem write, filesystem smart_edit, terminal, or code_exec on gnexus-book paths.\n\nBefore the final response, decide whether tool execution revealed stable reusable infrastructure facts, service configurations, or relationships. If yes and gnexus-book write tools are available, persist them before answering. If gnexus-book write tools are not available, report the facts that should be persisted. If the fact is user-specific rather than infrastructure documentation, use the memory tool instead. Choose the target based on scope, not habit." + "instructions": "MANDATORY for profiles that expose gnexus-book tools: Before answering any question about infrastructure, servers, services, networks, documentation, or system inventory, call gnexus-book tools first.\n\nUse only gnexus-book tool names that are present in the current tool schema. In Navi they are exposed with the mcp__gnexus_book__ prefix (example: mcp__gnexus_book__search_docs), but each profile may expose only some groups. Do not invent or call gnexus-book tools that are not in the current tool list.\n\nQuery mapping by capability:\n- Status or facts about a server/service → search docs first, then read a specific doc or inventory item if those tools are available.\n- Service placement or topology → list inventory and relationships if available.\n- Documentation changes → read the target doc first, then propose a doc or inventory change if write tools are available.\n- Freshness questions → use freshness checks if available.\n- Repository validation/status → use repository tools only if they are available in the current tool schema; otherwise skip this step and continue with available read/write tools.\n\nDo not rely on memory for infrastructure facts. Memory is only for personal user facts and preferences. Always pull infrastructure state from gnexus-book when these tools are available to the active profile.\n\nDo not store raw secrets in documentation.\n\nABSOLUTE RULE — NEVER bypass MCP tools:\nYou MUST NOT use filesystem, terminal, code_exec, or any direct file access to read or write gnexus-book files. The MCP tools are the ONLY valid interface to this knowledge base. Violating this rule bypasses validation, corrupts repository state, and breaks consistency guarantees.\n- To read: use mcp__gnexus_book__search_docs, mcp__gnexus_book__read_doc, mcp__gnexus_book__list_inventory, mcp__gnexus_book__get_inventory_item.\n- To write: use mcp__gnexus_book__propose_doc_change, mcp__gnexus_book__propose_inventory_item_change, mcp__gnexus_book__apply_pending_change, mcp__gnexus_book__commit_changes.\n- NEVER call filesystem write, filesystem smart_edit, terminal, or code_exec on gnexus-book paths.\n\nBefore the final response, decide whether tool execution revealed stable reusable infrastructure facts, service configurations, or relationships. If yes and gnexus-book write tools are available, persist them before answering. If gnexus-book write tools are not available, report the facts that should be persisted. If the fact is user-specific rather than infrastructure documentation, use the memory tool instead. Choose the target based on scope, not habit." } diff --git a/navi/api/routes/admin.py b/navi/api/routes/admin.py index a70b7dc..136a1c8 100644 --- a/navi/api/routes/admin.py +++ b/navi/api/routes/admin.py @@ -522,9 +522,12 @@ pass # Unregister old tools for this server + from navi.mcp.tools import build_mcp_name + tool_registry = get_tool_registry() + prefix = build_mcp_name(server_name, "") for name in list(tool_registry._external_names): - if name.startswith(f"mcp:{server_name}:"): + if name.startswith(prefix): tool_registry.unregister_external(name) # Connect fresh diff --git a/navi/api/routes/agents.py b/navi/api/routes/agents.py index 1de481a..509e007 100644 --- a/navi/api/routes/agents.py +++ b/navi/api/routes/agents.py @@ -47,20 +47,22 @@ mcp_manager: McpManager | None, tool_registry: ToolRegistry, ) -> list[str]: - """Expand profile.mcp_servers into concrete tool names (mcp_server_tool).""" + """Expand profile.mcp_servers into concrete tool names (mcp__server__tool).""" + from navi.mcp.tools import build_mcp_name + if not profile.mcp_servers or not mcp_manager: return [] names: list[str] = [] for server_name, groups in profile.mcp_servers.items(): if "*" in groups: - prefix = f"mcp:{server_name}:" + prefix = build_mcp_name(server_name, "") for tool in tool_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 mcp_manager.resolve_group(server_name, group_name): - full_name = f"mcp:{server_name}:{tool_name}" + full_name = build_mcp_name(server_name, tool_name) if full_name not in names: names.append(full_name) return names diff --git a/navi/core/subagent_runner.py b/navi/core/subagent_runner.py index b2a2330..e37577b 100644 --- a/navi/core/subagent_runner.py +++ b/navi/core/subagent_runner.py @@ -100,6 +100,8 @@ _model_var.set(profile.model) exclude = set(exclude_tools or []) + from navi.mcp.tools import build_mcp_name, is_mcp_tool + tool_source = profile.subagent_tools if profile.subagent_tools else profile.enabled_tools # If subagent_tools is a strict whitelist, filter mcp_servers to only @@ -108,13 +110,13 @@ # that are meant for the main agent only. mcp_servers = profile.mcp_servers if profile.subagent_tools: - mcp_tool_names = {n for n in profile.subagent_tools if n.startswith("mcp:")} + mcp_tool_names = {n for n in profile.subagent_tools if is_mcp_tool(n)} if mcp_tool_names: filtered_mcp_servers: dict[str, list[str]] = {} for server_name, groups in (profile.mcp_servers or {}).items(): 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}" + full_name = build_mcp_name(server_name, tool_name) if full_name in mcp_tool_names: filtered_mcp_servers.setdefault(server_name, []).append(group_name) mcp_servers = filtered_mcp_servers or None diff --git a/navi/core/tool_executor.py b/navi/core/tool_executor.py index 3500ba0..c9aac68 100644 --- a/navi/core/tool_executor.py +++ b/navi/core/tool_executor.py @@ -17,36 +17,57 @@ def _resolve_tool(tool_map: dict[str, Tool], name: str) -> tuple[str, Tool | None]: """Resolve exact tool names plus common MCP alias mistakes.""" + from navi.mcp.tools import is_mcp_tool, parse_mcp_name + tool = tool_map.get(name) if tool is not None: return name, tool + # Support bare tool name when the full MCP name ends with it + # e.g. "web_search" -> "mcp__navi_web__web_search" bare_matches = [ (candidate_name, candidate) for candidate_name, candidate in tool_map.items() - if candidate_name.startswith("mcp:") and candidate_name.endswith(f":{name}") + if is_mcp_tool(candidate_name) and candidate_name.endswith(f"__{name}") ] if len(bare_matches) == 1: return bare_matches[0] + # Normalized variant (dash vs underscore) normalized = name.replace("-", "_") normalized_matches = [ (candidate_name, candidate) for candidate_name, candidate in tool_map.items() - if candidate_name.startswith("mcp:") and candidate_name.replace("-", "_") == normalized + if is_mcp_tool(candidate_name) and candidate_name.replace("-", "_") == normalized ] if len(normalized_matches) == 1: return normalized_matches[0] - # Fallback: old underscore format like mcp_server_tool -> mcp:server:tool + # Fallback: old underscore format like mcp_server_tool -> mcp__server__tool old_format_matches = [ (candidate_name, candidate) for candidate_name, candidate in tool_map.items() - if candidate_name.startswith("mcp:") and name.startswith("mcp_") - and candidate_name.replace(":", "_") == name + if is_mcp_tool(candidate_name) and name.startswith("mcp_") ] - if len(old_format_matches) == 1: - return old_format_matches[0] + for candidate_name, candidate in old_format_matches: + parsed = parse_mcp_name(candidate_name) + if parsed is None: + continue + server_name, tool_name = parsed + # mcp_navi_web_search -> navi_web_search -> split into navi, web, search + # We try matching by removing the mcp_ prefix and comparing + expected_old = f"mcp_{server_name}_{tool_name}" + if expected_old == name: + return candidate_name, candidate + + # Extra fallback: legacy colon format mcp:server:tool (from old sessions) + legacy_matches = [ + (candidate_name, candidate) + for candidate_name, candidate in tool_map.items() + if is_mcp_tool(candidate_name) and name.replace(":", "__") == candidate_name + ] + if len(legacy_matches) == 1: + return legacy_matches[0] return name, None diff --git a/navi/core/tool_utils.py b/navi/core/tool_utils.py index bc557c8..d8821e7 100644 --- a/navi/core/tool_utils.py +++ b/navi/core/tool_utils.py @@ -30,17 +30,19 @@ names.append(name) # Expand MCP server groups into concrete tool names + from navi.mcp.tools import build_mcp_name + if mcp_servers and mcp_manager: for server_name, groups in mcp_servers.items(): if "*" in groups: - prefix = f"mcp:{server_name}:" + prefix = build_mcp_name(server_name, "") for tool in tool_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 mcp_manager.resolve_group(server_name, group_name): - full_name = f"mcp:{server_name}:{tool_name}" + full_name = build_mcp_name(server_name, tool_name) if full_name not in names: names.append(full_name) diff --git a/navi/mcp/tools.py b/navi/mcp/tools.py index e151b61..7bd1298 100644 --- a/navi/mcp/tools.py +++ b/navi/mcp/tools.py @@ -9,11 +9,41 @@ from .manager import McpManager +# Single source of truth for MCP tool naming. +_MCP_SEP = "__" + + +def build_mcp_name(server_name: str, tool_name: str) -> str: + """Return the canonical internal name for an MCP tool. + + Format: ``mcp____`` — avoids colons which confuse some LLMs. + """ + return f"mcp{_MCP_SEP}{server_name}{_MCP_SEP}{tool_name}" + + +def parse_mcp_name(name: str) -> tuple[str, str] | None: + """Parse an MCP tool name into (server_name, tool_name). + + Returns ``None`` if *name* is not a valid MCP tool name. + """ + prefix = f"mcp{_MCP_SEP}" + if not name.startswith(prefix): + return None + rest = name[len(prefix) :] + parts = rest.split(_MCP_SEP, 1) + if len(parts) != 2: + return None + return parts[0], parts[1] + + +def is_mcp_tool(name: str) -> bool: + return name.startswith(f"mcp{_MCP_SEP}") + class McpTool(Tool): """A :class:`Tool` proxy that forwards execution to an MCP server. - The name is ``mcp::`` to avoid collisions with built-in + The name is ``mcp____`` to avoid collisions with built-in and user-defined tools. """ @@ -30,7 +60,7 @@ self.description = description self.parameters = parameters self._manager = manager - self.name = f"mcp:{server_name}:{tool_name}" + self.name = build_mcp_name(server_name, tool_name) @staticmethod def _normalize_path_param(value: str) -> str: diff --git a/navi/profiles/developer/config.json b/navi/profiles/developer/config.json index 03cb890..495b66f 100644 --- a/navi/profiles/developer/config.json +++ b/navi/profiles/developer/config.json @@ -6,7 +6,7 @@ "full_description": { "specialization": "Full-stack software development: writing code in any language, debugging, running tests, working with files and project structure, git, APIs, scripting. Works on the user's own projects, not Navi's internals.", "when_to_use": "When the user wants to build something — a game, a script, an app, a web service, anything. For writing Navi tools specifically, use tool_developer instead.", - "key_tools": "filesystem, code_exec, terminal, ssh_exec, mcp:navi-web:web_search, mcp:navi-web:web_view, spawn_agent" + "key_tools": "filesystem, code_exec, terminal, ssh_exec, mcp__navi_web__web_search, mcp__navi_web__web_view, spawn_agent" }, "llm_backend": "ollama", "model": [ diff --git a/navi/profiles/discuss/system_prompt.txt b/navi/profiles/discuss/system_prompt.txt index 53f3bd9..f210c80 100644 --- a/navi/profiles/discuss/system_prompt.txt +++ b/navi/profiles/discuss/system_prompt.txt @@ -18,7 +18,7 @@ ## Tools -Use `mcp:navi-web:web_search` + `mcp:navi-web:web_view` when a factual grounding would strengthen the discussion — not for every question, only when currency or precision matters. +Use `mcp__navi_web__web_search` + `mcp__navi_web__web_view` when a factual grounding would strengthen the discussion — not for every question, only when currency or precision matters. Use project `docs/` when discussing an active project. Prefer `docs/index.md` as the map, then query specific docs rather than rereading broad source trees. diff --git a/navi/profiles/modeler_3d/config.json b/navi/profiles/modeler_3d/config.json index 39d7d07..94e1d6d 100644 --- a/navi/profiles/modeler_3d/config.json +++ b/navi/profiles/modeler_3d/config.json @@ -6,7 +6,7 @@ "full_description": { "specialization": "Physically coherent 3D geometry and STL generation. Generates STL files from OpenSCAD through dedicated 3D tools, and validates with OpenSCAD compilation plus preview render inspection.", "when_to_use": "When the user needs a physical object modeled as 3D geometry: replacement parts, mechanical assemblies, decorative items, functional prototypes, jigs, fixtures, or custom enclosures.", - "key_tools": "spawn_agent, filesystem, mcp:navi-3d:lint_scad, mcp:navi-3d:compile_scad, mcp:navi-3d:render_stl, image_view, content_publish" + "key_tools": "spawn_agent, filesystem, mcp__navi_3d__lint_scad, mcp__navi_3d__compile_scad, mcp__navi_3d__render_stl, image_view, content_publish" }, "llm_backend": "ollama", "model": [ diff --git a/navi/profiles/modeler_3d/subagent_system_prompt.txt b/navi/profiles/modeler_3d/subagent_system_prompt.txt index 3c1fd75..cd7d776 100644 --- a/navi/profiles/modeler_3d/subagent_system_prompt.txt +++ b/navi/profiles/modeler_3d/subagent_system_prompt.txt @@ -6,7 +6,7 @@ 1. Read the briefing and parent session context. 2. Identify the exact missing facts requested by the parent agent. -3. Use `mcp:navi-web:web_search`, `mcp:navi-web:web_view`, `filesystem`, and `image_view` as needed to gather evidence. +3. Use `mcp__navi_web__web_search`, `mcp__navi_web__web_view`, `filesystem`, and `image_view` as needed to gather evidence. 4. Prefer primary sources, product pages, datasheets, manuals, dimensions in local files, or images provided by the user. 5. Return only the facts found, source paths/URLs, confidence, and unresolved gaps. diff --git a/navi/profiles/modeler_3d/system_prompt.txt b/navi/profiles/modeler_3d/system_prompt.txt index c4e7659..6d2711b 100644 --- a/navi/profiles/modeler_3d/system_prompt.txt +++ b/navi/profiles/modeler_3d/system_prompt.txt @@ -27,9 +27,9 @@ All MCP 3D tools require the exact Navi `session_id` so files are resolved inside `session_files//`. Pass the current session ID from context. 1. **`filesystem write`** — write the OpenSCAD script (`.scad`) to the session directory. -2. **`mcp:navi-3d:lint_scad`** — check the `.scad` source for common LLM mistakes before compiling. -3. **`mcp:navi-3d:compile_scad`** — compile the `.scad` into a binary `.stl`. -4. **`mcp:navi-3d:render_stl`** — generate PNG previews from several angles for your own inspection. +2. **`mcp__navi_3d__lint_scad`** — check the `.scad` source for common LLM mistakes before compiling. +3. **`mcp__navi_3d__compile_scad`** — compile the `.scad` into a binary `.stl`. +4. **`mcp__navi_3d__render_stl`** — generate PNG previews from several angles for your own inspection. 5. **`image_view`** — inspect each PNG so YOU can verify geometry. PNG previews are for Navi, not for the user. 6. **`content_publish`** — publish the final STL after internal checks pass. Include `source_filename` when a real `.scad` source exists in the same session directory. @@ -142,8 +142,8 @@ When the next step requires a tool, call the tool in the same assistant turn. Do not end a message with an announcement such as "I will now compile", "I am moving to publication", or "Next I will publish" unless you also make the required tool call in that same turn. - If the STL is ready to publish, call `content_publish` immediately instead of saying you are going to publish it. -- If the SCAD is ready to compile, call `mcp:navi-3d:compile_scad` immediately instead of saying you are going to compile it. -- If the SCAD was just written or edited, call `mcp:navi-3d:lint_scad` immediately before `mcp:navi-3d:compile_scad`. +- If the SCAD is ready to compile, call `mcp__navi_3d__compile_scad` immediately instead of saying you are going to compile it. +- If the SCAD was just written or edited, call `mcp__navi_3d__lint_scad` immediately before `mcp__navi_3d__compile_scad`. - If previews are ready to inspect, call `image_view` immediately instead of saying you are going to inspect them. - Only send a text-only progress message when you are blocked, need a user decision, or are reporting a completed tool result. @@ -154,7 +154,7 @@ - Never type, shorten, or reconstruct session IDs from memory. - Use the exact `Session files directory` from system context or from tool output. - Keep the `.scad`, compiled `.stl`, and preview PNGs in that same current session directory. -- Prefer simple filenames with `mcp:navi-3d:compile_scad` when possible; the tool resolves them inside the current session directory. +- Prefer simple filenames with `mcp__navi_3d__compile_scad` when possible; the tool resolves them inside the current session directory. - Choose the final basename before compiling, for example `wind_turbine_blades_100mm.scad` and `wind_turbine_blades_100mm.stl`. Avoid renaming after compilation unless necessary. - If `content_publish` returns `not_found`, read the exact directory named in its error, list that directory, then copy/move or recreate the file there before retrying. Do not keep listing or editing another `session_files/` directory. @@ -165,12 +165,12 @@ 3. **Plan physical geometry** — use `scratchpad` to store `design_plan` with scale, modules, axis convention, physical orientation, tolerances, weak points, and preview checks. 4. **Run parameter sanity check** — for functional, mechanical, or parametric fit parts, use `scratchpad` to store `parameter_sanity_check` before writing `.scad`. 5. **Write OpenSCAD yourself** — create or edit the `.scad` source with `filesystem write` or `filesystem edit`. The file must be clean, parameterized, in the session directory, and include the source comments contract. -6. **Lint OpenSCAD** — call `mcp:navi-3d:lint_scad(session_id="...", source_path="...scad")` after the file exists. Fix every error before compiling. Treat warnings as reasons to inspect and revise when they affect geometry or source cleanliness. +6. **Lint OpenSCAD** — call `mcp__navi_3d__lint_scad(session_id="...", source_path="...scad")` after the file exists. Fix every error before compiling. Treat warnings as reasons to inspect and revise when they affect geometry or source cleanliness. 7. **Research missing facts when useful** — if exact dimensions, reference images, or local documents are needed, call `spawn_agent` with a narrow research brief. Use the returned facts to update `technical_spec` or `design_plan`; do not ask the subagent to design, write, review, compile, render, or publish the model. -8. **Compile STL** — call `mcp:navi-3d:compile_scad(session_id="...", source_path="...scad", output_path="...stl")`. -9. **Handle compile result** — proceed only if `mcp:navi-3d:compile_scad` returns success. If it returns `openscad_compile_error`, `no_output`, `scad_not_found`, `wrong_session_dir`, or another error, fix the cause and compile again. -10. **Render previews** — call `mcp:navi-3d:render_stl(session_id="...", source_path="...stl", views=["iso","front","top"])` or other relevant views. -11. **Inspect every preview** — call `image_view` on every PNG path returned by `mcp:navi-3d:render_stl`. Do not publish PNG previews unless the user explicitly asks for preview images. +8. **Compile STL** — call `mcp__navi_3d__compile_scad(session_id="...", source_path="...scad", output_path="...stl")`. +9. **Handle compile result** — proceed only if `mcp__navi_3d__compile_scad` returns success. If it returns `openscad_compile_error`, `no_output`, `scad_not_found`, `wrong_session_dir`, or another error, fix the cause and compile again. +10. **Render previews** — call `mcp__navi_3d__render_stl(session_id="...", source_path="...stl", views=["iso","front","top"])` or other relevant views. +11. **Inspect every preview** — call `image_view` on every PNG path returned by `mcp__navi_3d__render_stl`. Do not publish PNG previews unless the user explicitly asks for preview images. 12. **Run preview checklist** — compare all inspected previews against `technical_spec` and `design_plan`. Record the checklist result in `scratchpad` section `preview_check`. 13. **Revise before publishing** — if lint, compilation output, researched facts, or preview inspection reveals a substantial issue, edit the `.scad`, lint again, recompile, re-render, and inspect again. 14. **Publish final STL** — after the model passes the geometry gate, call `content_publish(filename="...stl", content_type="stl", source_filename="...scad")`. This step is mandatory for a successful task. @@ -206,10 +206,10 @@ - Functional, mechanical, and parametric fit parts have a `scratchpad` `parameter_sanity_check` confirming interface dimensions and formulas are internally consistent. - The final `.stl` and `.scad` source are in the current session files directory, not another `session_files/` directory. - The STL compiled successfully. -- Every preview image returned by `mcp:navi-3d:render_stl` was inspected with `image_view`; inspecting only `iso` is not enough. +- Every preview image returned by `mcp__navi_3d__render_stl` was inspected with `image_view`; inspecting only `iso` is not enough. - `scratchpad` section `preview_check` exists and says `Revision required: no`, or records the revision that was made after a failed check. -- OpenSCAD warnings/errors from `mcp:navi-3d:compile_scad` or `mcp:navi-3d:render_stl` were handled instead of ignored. -- `mcp:navi-3d:lint_scad` was run on the final `.scad`, and all lint errors were fixed before compilation. +- OpenSCAD warnings/errors from `mcp__navi_3d__compile_scad` or `mcp__navi_3d__render_stl` were handled instead of ignored. +- `mcp__navi_3d__lint_scad` was run on the final `.scad`, and all lint errors were fixed before compilation. - Any subagent use was limited to gathering missing factual information from web pages, local files, or images. The main agent made all modeling decisions. - For functional fit parts, exact compatibility dimensions are either verified, provided by the user, or the artifact is explicitly labeled as a parametric template that requires user measurements. diff --git a/navi/profiles/secretary/config.json b/navi/profiles/secretary/config.json index 53d8a9d..65316d2 100644 --- a/navi/profiles/secretary/config.json +++ b/navi/profiles/secretary/config.json @@ -6,13 +6,13 @@ "full_description": { "specialization": "General-purpose personal assistant. Web research, document writing, data analysis, email correspondence, planning, calculations, and any everyday task that doesn't require direct server access or tool development.", "when_to_use": "Default profile for most requests. If you're unsure which profile to use, this one is correct. Switch away only when the task clearly requires server/infrastructure access (server_admin) or modifying Navi's own tools (developer).", - "key_tools": "mcp:navi-web:web_search, mcp:navi-web:web_view, filesystem, code_exec, gmail, todo, scratchpad, spawn_agent, memory" + "key_tools": "mcp__navi_web__web_search, mcp__navi_web__web_view, filesystem, code_exec, gmail, todo, scratchpad, spawn_agent, memory" }, "llm_backend": "ollama", "model": [ + "kimi-k2.6:cloud", "gemma4:26b-a4b-it-q4_K_M", "gemma4:31b-cloud", - "kimi-k2.6:cloud", "qwen3.6:27b" ], "temperature": 0.45, diff --git a/navi/profiles/secretary/system_prompt.txt b/navi/profiles/secretary/system_prompt.txt index 488dc14..843bd11 100644 --- a/navi/profiles/secretary/system_prompt.txt +++ b/navi/profiles/secretary/system_prompt.txt @@ -69,11 +69,11 @@ --- ## Tool priorities -1. mcp:navi-web:web_search — first choice for current info, facts, documentation. +1. mcp__navi_web__web_search — first choice for current info, facts, documentation. 2. code_exec — calculations, data processing, text parsing, format conversion. -3. mcp:navi-web:web_view — view a specific page in full. +3. mcp__navi_web__web_view — view a specific page in full. 4. filesystem — read/write local documents, notes, data files. -5. mcp:navi-web:http_request — external APIs, webhooks, content not suited for search. +5. mcp__navi_web__http_request — external APIs, webhooks, content not suited for search. 6. image_view — whenever an image path or URL is mentioned. ## Output style diff --git a/navi/profiles/server_admin/config.json b/navi/profiles/server_admin/config.json index 2b7d24f..8087220 100644 --- a/navi/profiles/server_admin/config.json +++ b/navi/profiles/server_admin/config.json @@ -6,7 +6,7 @@ "full_description": { "specialization": "Remote server operations via SSH, system diagnostics, service management, log analysis, network troubleshooting, process monitoring, and infrastructure automation.", "when_to_use": "When the task involves SSH access to servers, running system commands, managing Linux services, analyzing logs, monitoring resources, or any hands-on infrastructure work.", - "key_tools": "ssh_exec, terminal, filesystem, code_exec, mcp:navi-web:web_search, spawn_agent, memory" + "key_tools": "ssh_exec, terminal, filesystem, code_exec, mcp__navi_web__web_search, spawn_agent, memory" }, "llm_backend": "ollama", "model": [ diff --git a/navi/profiles/server_admin/system_prompt.txt b/navi/profiles/server_admin/system_prompt.txt index 419d8dd..7693a95 100644 --- a/navi/profiles/server_admin/system_prompt.txt +++ b/navi/profiles/server_admin/system_prompt.txt @@ -46,7 +46,7 @@ 6. **Synthesise** — after all agents report back, write your conclusions and next steps. ### Plan → execution binding -- **TOOL** — direct local call (terminal, filesystem, mcp:navi-web:http_request for health checks). +- **TOOL** — direct local call (terminal, filesystem, mcp__navi_web__http_request for health checks). - **AGENT** — call `spawn_agent` for THIS STEP ONLY. One AGENT step = one spawn_agent call. If your plan has steps 1, 2, 3 all marked AGENT — you make three separate spawn_agent calls. Never bundle multiple steps into one call. Never pass your full plan to a single subagent. @@ -80,8 +80,8 @@ 1. ssh_exec — direct single-command checks on known hosts when spawning is overkill. 2. terminal — local machine operations. 3. filesystem — local config files, scripts. -4. mcp:navi-web:http_request — health check endpoints, REST APIs. -5. mcp:navi-web:web_search — error lookups, documentation. +4. mcp__navi_web__http_request — health check endpoints, REST APIs. +5. mcp__navi_web__web_search — error lookups, documentation. ## Execution environment `terminal`, `filesystem`, and `code_exec` run on the LOCAL machine (where Navi's server is running) — NOT on any remote host. diff --git a/navi/tools/list_tools.py b/navi/tools/list_tools.py index 9dacf57..e2708e6 100644 --- a/navi/tools/list_tools.py +++ b/navi/tools/list_tools.py @@ -5,6 +5,8 @@ import json from pathlib import Path +from navi.mcp.tools import build_mcp_name + from ._internal.base import Tool, ToolResult _USER_ENABLED_FILE = Path("tools/enabled.json") @@ -65,14 +67,14 @@ 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}:" + prefix = build_mcp_name(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}" + full_name = build_mcp_name(server_name, tool_name) if full_name not in names: names.append(full_name) diff --git a/navi/tools/reload_tools.py b/navi/tools/reload_tools.py index 01cb33a..1b23d0e 100644 --- a/navi/tools/reload_tools.py +++ b/navi/tools/reload_tools.py @@ -61,7 +61,8 @@ await self._mcp_manager.reload_all() from navi.api.deps import register_mcp_tools await register_mcp_tools(self._registry, self._mcp_manager) - mcp_tools = [t.name for t in self._registry.all() if t.name.startswith("mcp:")] + from navi.mcp.tools import is_mcp_tool + mcp_tools = [t.name for t in self._registry.all() if is_mcp_tool(t.name)] lines.append(f"MCP tools ({len(mcp_tools)}): {', '.join(mcp_tools) or 'none'}") except Exception as exc: has_errors = True diff --git a/tests/unit/core/test_tool_executor.py b/tests/unit/core/test_tool_executor.py index f2ed782..3888514 100644 --- a/tests/unit/core/test_tool_executor.py +++ b/tests/unit/core/test_tool_executor.py @@ -9,7 +9,7 @@ class TestToolExecutorMcpAliases: async def test_executes_bare_mcp_tool_alias(self): registry = ToolRegistry() - tool = FakeTool("mcp:gnexus-book:search_docs") + tool = FakeTool("mcp__gnexus_book__search_docs") registry.register(tool, builtin=True) executor = ToolExecutor(registry) @@ -19,12 +19,42 @@ ) assert images == [] - assert messages[0].name == "mcp:gnexus-book:search_docs" - assert messages[0].content == "executed mcp:gnexus-book:search_docs" + assert messages[0].name == "mcp__gnexus_book__search_docs" + assert messages[0].content == "executed mcp__gnexus_book__search_docs" - async def test_executes_mcp_tool_alias_with_underscore_server_name(self): + async def test_executes_mcp_tool_alias_with_dash_variant(self): registry = ToolRegistry() - tool = FakeTool("mcp:gnexus-book:search_docs") + tool = FakeTool("mcp__gnexus_book__search_docs") + registry.register(tool, builtin=True) + executor = ToolExecutor(registry) + + messages, images = await executor._execute_tool_calls( + [ToolCallRequest(id="1", name="mcp__gnexus-book__search_docs", arguments={"query": "git"})], + [tool], + ) + + assert images == [] + assert messages[0].name == "mcp__gnexus_book__search_docs" + assert messages[0].content == "executed mcp__gnexus_book__search_docs" + + async def test_executes_old_underscore_format_fallback(self): + registry = ToolRegistry() + tool = FakeTool("mcp__gnexus_book__search_docs") + registry.register(tool, builtin=True) + executor = ToolExecutor(registry) + + messages, images = await executor._execute_tool_calls( + [ToolCallRequest(id="1", name="mcp_gnexus_book_search_docs", arguments={"query": "git"})], + [tool], + ) + + assert images == [] + assert messages[0].name == "mcp__gnexus_book__search_docs" + assert messages[0].content == "executed mcp__gnexus_book__search_docs" + + async def test_executes_legacy_colon_format_fallback(self): + registry = ToolRegistry() + tool = FakeTool("mcp__gnexus_book__search_docs") registry.register(tool, builtin=True) executor = ToolExecutor(registry) @@ -34,20 +64,5 @@ ) assert images == [] - assert messages[0].name == "mcp:gnexus-book:search_docs" - assert messages[0].content == "executed mcp:gnexus-book:search_docs" - - async def test_executes_old_underscore_format_fallback(self): - registry = ToolRegistry() - tool = FakeTool("mcp:gnexus-book:search_docs") - registry.register(tool, builtin=True) - executor = ToolExecutor(registry) - - messages, images = await executor._execute_tool_calls( - [ToolCallRequest(id="1", name="mcp_gnexus-book_search_docs", arguments={"query": "git"})], - [tool], - ) - - assert images == [] - assert messages[0].name == "mcp:gnexus-book:search_docs" - assert messages[0].content == "executed mcp:gnexus-book:search_docs" + assert messages[0].name == "mcp__gnexus_book__search_docs" + assert messages[0].content == "executed mcp__gnexus_book__search_docs" diff --git a/tests/unit/test_mcp.py b/tests/unit/test_mcp.py index f73a3b2..ae94189 100644 --- a/tests/unit/test_mcp.py +++ b/tests/unit/test_mcp.py @@ -105,7 +105,7 @@ parameters={"type": "object", "properties": {}}, manager=mock_manager, ) - assert tool.name == "mcp:gnexus-book:search_docs" + assert tool.name == "mcp__gnexus-book__search_docs" async def test_execute_success(self): mock_manager = AsyncMock(spec=McpManager)
NameClassDescription
mcp:navi-web:web_searchMcpToolWeb search (SearXNG primary, DDG fallback, Brave tertiary)
mcp:navi-web:web_viewMcpToolOpen a URL in a headless browser and return clean text
mcp__navi_web__web_searchMcpToolWeb search (SearXNG primary, DDG fallback, Brave tertiary)
mcp__navi_web__web_viewMcpToolOpen a URL in a headless browser and return clean text
filesystemFilesystemToolRead/write/list local files (path allowlist via config)
mcp:navi-web:http_requestMcpToolRaw HTTP request — GET/POST/PUT/PATCH/DELETE
mcp__navi_web__http_requestMcpToolRaw HTTP request — GET/POST/PUT/PATCH/DELETE
code_execCodeExecToolExecute Python in a subprocess sandbox
terminalTerminalToolRun shell commands (command allowlist via config)
ssh_execSshExecToolSSH into remote hosts; connection pool keyed by session ID