from __future__ import annotations
from typing import Any
from pathlib import Path
from navi.config import settings
from navi.tools._internal.base import Tool, ToolResult, current_session_id
from .manager import McpManager
class McpTool(Tool):
"""A :class:`Tool` proxy that forwards execution to an MCP server.
The name is ``mcp:<server>:<tool>`` to avoid collisions with built-in
and user-defined tools.
"""
def __init__(
self,
server_name: str,
tool_name: str,
description: str,
parameters: dict[str, Any],
manager: McpManager,
) -> None:
self.server_name = server_name
self.tool_name = tool_name
self.description = description
self.parameters = parameters
self._manager = manager
self.name = f"mcp:{server_name}:{tool_name}"
@staticmethod
def _normalize_path_param(value: str) -> str:
"""Strip session-files prefix so MCP servers receive bare filenames.
When the LLM passes a full relative path like
``session_files/<sid>/falcon9_rocket.scad`` the MCP server would
resolve it a second time, creating double nesting. We keep only
the basename.
"""
p = Path(value)
# If it looks like a nested path, take just the filename
if len(p.parts) > 1:
return p.name
return value
async def execute(self, params: dict[str, Any]) -> ToolResult:
# Defensive copy — never mutate the caller's dict
forwarded = dict(params)
# 1. Force the real session_id from the agent context so the LLM
# cannot hallucinate a wrong UUID (ghost-session bug).
sid = current_session_id.get()
if sid is not None:
forwarded["session_id"] = sid
# 2. For navi-3d, normalize source/output paths to bare filenames
# to prevent double path nesting.
if self.server_name == "navi-3d":
for key in ("source_path", "output_path"):
if key in forwarded:
forwarded[key] = self._normalize_path_param(forwarded[key])
try:
output, is_error = await self._manager.call_tool(
self.server_name, self.tool_name, forwarded
)
if is_error:
return ToolResult(
success=False,
output=output,
error="MCP tool reported an error",
)
return ToolResult(success=True, output=output)
except Exception as exc:
return ToolResult(success=False, output="", error=str(exc))