mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-08-05 02:45:28 +00:00
fix(tools): stop unusable browser tools from crowding out browser_navigate
Tool retrieval returns a top-K (8 by default), and @playwright/mcp alone exposes 30 tools. Measured across three typical browsing requests on a local setup, the agent was handed browser_drop, browser_handle_dialog, browser_close, browser_console_messages and the whole xy-mouse family -- while browser_navigate was missing in two cases out of three. On "click the login button", six of the eight slots went to mouse primitives. Without the tool that opens a page, every other browser tool is dead weight. So the model answered that it could not reach the internet, and that answer was correct. It reads as a refusal or a hallucination, which is what makes it expensive to diagnose: the browser is installed, the MCP server is connected and reports 30 tools, and calling browser_navigate by hand works fine. Two guards: MCP_INDEX_DENIED keeps 18 tools out of the index -- pointer primitives (browser_click and browser_hover work off the accessibility snapshot and need no coordinates), debugging aids, session plumbing, and browser_run_code_unsafe, which runs arbitrary JavaScript in the page. They stay connected and callable; they just no longer compete for a retrieval slot. MCP_COMPANIONS pulls browser_navigate and browser_snapshot in whenever any browser tool is retrieved. The server prefix comes from the hit itself rather than a constant, so the rule holds under any server id -- verified with a second prefix. Matching is on the bare tool name, after the server prefix, so neither guard depends on the browser being registered as builtin_browser. Measured on the same three queries after the change: browser_navigate present three times out of three, no denied tool leaking through, and every slot filled with something that works -- navigate, snapshot, find, click, type, press_key, select_option. The running app indexes 12 MCP tools instead of 30, and the agent then completed a real navigate-then-read task it had previously refused. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
d8a2059df8
commit
830f7cd6d7
1 changed files with 48 additions and 0 deletions
|
|
@ -61,6 +61,40 @@ ASSISTANT_ALWAYS_AVAILABLE = frozenset({
|
|||
"ui_control",
|
||||
})
|
||||
|
||||
# MCP tools that must never be indexed for retrieval.
|
||||
#
|
||||
# Retrieval returns a top-K (8 by default), and @playwright/mcp alone exposes
|
||||
# 30 tools. In practice that means the agent is handed browser_drop,
|
||||
# browser_handle_dialog or browser_close while browser_navigate — the only tool
|
||||
# that opens a page — does not surface at all. The model then reports that it
|
||||
# cannot browse the web, which is accurate but reads as a refusal.
|
||||
#
|
||||
# Matched against the bare tool name (the part after the server prefix), so
|
||||
# this holds whatever server id the browser is registered under. Denied tools
|
||||
# stay connected and callable; they just no longer compete for a slot.
|
||||
MCP_INDEX_DENIED = frozenset({
|
||||
# Low-level pointer primitives — browser_click/browser_hover work off the
|
||||
# accessibility snapshot and need no coordinates.
|
||||
"browser_mouse_click_xy", "browser_mouse_down", "browser_mouse_up",
|
||||
"browser_mouse_move_xy", "browser_mouse_drag_xy", "browser_mouse_wheel",
|
||||
"browser_drag", "browser_drop",
|
||||
# Debugging aids, not task tools.
|
||||
"browser_console_messages", "browser_network_request",
|
||||
"browser_network_requests", "browser_evaluate",
|
||||
# Arbitrary JavaScript in the page. Deliberately out of reach.
|
||||
"browser_run_code_unsafe",
|
||||
# Session plumbing the agent should not spend its budget on.
|
||||
"browser_close", "browser_resize", "browser_tabs",
|
||||
"browser_handle_dialog", "browser_file_upload",
|
||||
})
|
||||
|
||||
# Tools that are worthless on their own. If retrieval surfaces any browser
|
||||
# tool, the agent also needs the two that open a page and read it back —
|
||||
# otherwise it receives, say, browser_find with no way to reach a page first.
|
||||
MCP_COMPANIONS = {
|
||||
"browser_": ("browser_navigate", "browser_snapshot"),
|
||||
}
|
||||
|
||||
COLLECTION_NAME = "odysseus_tool_index"
|
||||
|
||||
# ── Tool description registry ──
|
||||
|
|
@ -266,6 +300,10 @@ class ToolIndex:
|
|||
if len(name_desc) == 2:
|
||||
name = name_desc[0].strip()
|
||||
desc = name_desc[1].strip()
|
||||
# Keep denied tools out of the index entirely, so they
|
||||
# cannot crowd out the ones that do the actual work.
|
||||
if name.rsplit("__", 1)[-1] in MCP_INDEX_DENIED:
|
||||
continue
|
||||
# Include server identity in the indexed text so RAG can
|
||||
# distinguish "list_emails for server-a" from "list_emails for server-b"
|
||||
server_ctx = f" (server: {current_server})" if current_server else ""
|
||||
|
|
@ -522,6 +560,16 @@ class ToolIndex:
|
|||
base = set(always_include or ALWAYS_AVAILABLE)
|
||||
retrieved = self.retrieve(query, k=k)
|
||||
base.update(retrieved)
|
||||
# Pull in the companions of anything retrieved. The server prefix is
|
||||
# taken from the hit itself rather than hardcoded, so this keeps
|
||||
# working if the browser is registered under a different id.
|
||||
for name in retrieved:
|
||||
prefix, _, bare = name.rpartition("__")
|
||||
for marker, companions in MCP_COMPANIONS.items():
|
||||
if bare.startswith(marker):
|
||||
base.update(
|
||||
f"{prefix}__{c}" if prefix else c for c in companions
|
||||
)
|
||||
# Keyword-based force-include for common intents. Match on word
|
||||
# boundaries, not raw substrings, so short hints like "fix", "line",
|
||||
# "serve", "reply" or "unread" don't fire inside unrelated words
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue