From f24dbadab370b25b232d049de82a234173f8ef43 Mon Sep 17 00:00:00 2001 From: botinate <285686135+botinate@users.noreply.github.com> Date: Tue, 9 Jun 2026 12:15:34 -0700 Subject: [PATCH] fix(agent): execute fenced tool calls with inline args and bare email tool names Two bugs made local (Ollama) models unable to use email tools, leaving raw fences like ```list_email_accounts {}``` in the chat: 1. _TOOL_BLOCK_RE required a newline right after the fence tag, so a tool call with args on the same line ("```list_email_accounts {}") never matched and was never executed. The fence now matches with optional spaces/newline after the tag. 2. Even when parsed, bare email tool names had no dispatch branch in tool_execution.py and fell through to "Unknown tool type". They now route to the email MCP server as mcp__email__, matching how function_call_to_tool_block already maps them for native callers. Co-Authored-By: Claude Opus 4.8 --- src/tool_execution.py | 17 +++++++++++++++++ src/tool_parsing.py | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/tool_execution.py b/src/tool_execution.py index 751bc13af..9b4f6a351 100644 --- a/src/tool_execution.py +++ b/src/tool_execution.py @@ -767,6 +767,23 @@ async def execute_tool_block( elif tool == "vault_unlock": desc = "vault_unlock" result = await do_vault_unlock(content, owner=owner) + elif tool in {"list_email_accounts", "send_email", "list_emails", "read_email", + "reply_to_email", "bulk_email", "archive_email", "delete_email", + "mark_email_read", "search_emails", "draft_email", "draft_email_reply", + "ai_draft_email_reply", "download_attachment"}: + # Bare email tool name from fenced-block models (e.g. Ollama) — route to MCP email server + mcp = get_mcp_manager() + qualified = f"mcp__email__{tool}" + if mcp: + try: + args = json.loads(content) if content.strip().startswith("{") else {} + except (json.JSONDecodeError, TypeError): + args = {} + desc = f"email: {tool}" + result = await mcp.call_tool(qualified, args) + else: + desc = f"email: {tool}" + result = {"error": "MCP manager not available", "exit_code": 1} elif tool.startswith("mcp__"): # MCP tool dispatch mcp = get_mcp_manager() diff --git a/src/tool_parsing.py b/src/tool_parsing.py index 3f296c2e6..d08530b81 100644 --- a/src/tool_parsing.py +++ b/src/tool_parsing.py @@ -21,7 +21,7 @@ logger = logging.getLogger(__name__) # Pattern 1: ```bash ... ``` fenced code blocks _TOOL_BLOCK_RE = re.compile( - r"```(" + "|".join(TOOL_TAGS) + r")\s*\n([\s\S]*?)```", + r"```(" + "|".join(TOOL_TAGS) + r")[ \t]*\n?([\s\S]*?)```", re.IGNORECASE, )