【Bug已解决】In-app Browser plugin works but tool discovery routes agents to standalone browser MCP sessions 解决方案原始报错线索In-app Browser plugin works but tool discovery routes agents to standalone browser MCP sessions应用内置浏览器插件明明能用但工具发现机制却把智能体引导去连一个独立的浏览器 MCP 会话造成重复、割裂的浏览上下文。一、现象长什么样系统里有两个「浏览器」能力一个是应用内浏览器插件in-app browser智能体本该用它因为它和当前会话共享上下文另一个是独立的浏览器 MCP 服务standalone browser MCP是另一个进程、另一套页面状态。 结果智能体想「在当前页面点个按钮」工具发现却返回了「独立浏览器 MCP」的那个工具于是操作发生在独立的浏览器会话里应用内页面毫无反应用户明明装了应用内浏览器却被引导去起一个多余的独立浏览器进程两个会话的 cookie / 登录态 / 打开的标签页互相看不到任务永远完不成。 根因是工具发现只按「能力名」路由忽略了「该用哪个实例」这一关键维度。二、背景工具发现与路由的两层问题2.1 工具发现的本质工具发现 系统把「我能做什么」以结构化描述名称、描述、参数、所属实例暴露给智能体/调度器供其选择。常见格式如 MCP 的tools/list、OpenAI 的function声明。2.2 名字相同但实例不同的冲突当多个后端都提供browser_navigate这类同名工具时单纯「按名字选第一个」就会随机或按注册顺序命中错误实例。需要额外维度来区分instance_id属于哪个进程/会话capability_scope能力作用域当前会话 vs 全局priority/affinity与当前上下文的亲缘度。2.3 路由一致性同一任务内多次调用应路由到同一个实例否则前一次「打开页面」和后一次「点击按钮」落在不同会话状态断裂。三、为什么路由错根因3.1 发现只匹配名字不带实例维度工具描述里没有instance_id/scope调度器看到browser_navigate就选了注册表里第一个恰好是独立 MCP。3.2 没有「亲缘度 / 优先级」应用内浏览器和独立 MCP 平起平坐调度器无从偏好「与当前会话同源」的那个。3.3 会话不绑定每次工具调用都重新做发现选择没有「本次任务绑定到某个实例」的机制导致前后调用落到不同实例。3.4 重复实例未去重应用内浏览器和独立 MCP 提供等价能力系统没标记「二者可合并/优先内置」于是多起一个进程。四、最小可运行复现按名字选错实例下面演示「只按名字匹配」如何命中错误实例class Tool: def __init__(self, name, instance_id, scope): self.name name self.instance_id instance_id self.scope scope REGISTRY [ Tool(browser_navigate, standalone_mcp, global), # 独立 MCP先注册 Tool(browser_navigate, in_app_browser, current_session), # 应用内后注册 ] def discover_by_name_only(name: str) - Tool: # 错误只按名字取第一个 for t in REGISTRY: if t.name name: return t return None if __name__ __main__: chosen discover_by_name_only(browser_navigate) print(被路由到:, chosen.instance_id) # standalone_mcp —— 错了运行打印standalone_mcp——智能体被引去了独立会话而非应用内浏览器。五、解决方案一工具描述带上实例与亲缘度让每个工具携带instance_id和affinity发现阶段就暴露差异from dataclasses import dataclass dataclass class ToolV2: name: str instance_id: str scope: str # current_session / global affinity: int 0 # 越大越优先同会话的更高 REGISTRY_V2 [ ToolV2(browser_navigate, standalone_mcp, global, affinity0), ToolV2(browser_navigate, in_app_browser, current_session, affinity10), ]六、解决方案二带「上下文亲缘」的路由选择发现时不只匹配名字还要结合「当前会话上下文」偏好同实例的工具def discover_with_affinity(name: str, ctx_session_id: str) - ToolV2: candidates [t for t in REGISTRY_V2 if t.name name] if not candidates: return None def score(t: ToolV2) - int: s t.affinity # 与当前会话同源的实例额外加权 if t.scope current_session: s 100 return s return max(candidates, keyscore) if __name__ __main__: chosen discover_with_affinity(browser_navigate, ctx_session_idsess-1) print(正确路由到:, chosen.instance_id) # in_app_browser ✅加上「同会话加权」应用内浏览器自然胜出智能体用上共享上下文的实例。七、解决方案三任务级会话绑定保证一致性选定实例后把「本次任务 → 实例」绑定下来后续调用都走同一实例避免前后割裂class SessionBinder: def __init__(self): self.binding {} # task_id - instance_id def resolve(self, task_id: str, name: str, ctx_session_id: str) - ToolV2: if task_id in self.binding: inst self.binding[task_id] # 复用已绑定实例保证前后调用一致 for t in REGISTRY_V2: if t.name name and t.instance_id inst: return t # 首次按亲缘度选择并绑定 chosen discover_with_affinity(name, ctx_session_id) self.binding[task_id] chosen.instance_id return chosen if __name__ __main__: binder SessionBinder() t1 binder.resolve(task-A, browser_navigate, sess-1) t2 binder.resolve(task-A, browser_click, sess-1) print(t1.instance_id, t2.instance_id) # 同一实例状态连续任务 A 的「导航」和「点击」都落在in_app_browser状态连续任务可完成。八、解决方案四等价能力去重与优先级策略当多个实例提供等价能力应明确「内置优先、独立兜底」并在发现结果里标记可合并避免无意义多开def dedupe_and_rank(candidates): 同一 name 的多个实例返回优先级最高的一个并记录备选。 if not candidates: return None, [] ranked sorted(candidates, keylambda t: (-t.affinity, t.scope ! current_session)) return ranked[0], ranked[1:] if __name__ __main__: cands [t for t in REGISTRY_V2 if t.name browser_navigate] primary, fallback dedupe_and_rank(cands) print(主用:, primary.instance_id, | 备选:, [f.instance_id for f in fallback]) # 主用: in_app_browser | 备选: [standalone_mcp]内置实例作主用、独立 MCP 作兜底既避免重复起进程又保留降级能力。九、排查清单「工具发现路由到错误会话」按下面排查工具描述是否带instance_id/scope/affinity只靠名字必冲突发现是否结合当前会话上下文同会话实例应加权优先任务内是否绑定实例避免前后调用落到不同实例造成状态断裂等价能力是否去重内置优先、独立兜底别无谓多开注册的多个同名工具顺序是否决定命中顺序不应成为路由依据失败是否回退到备选实例主用不可用时应切兜底而非报错日志是否打印实际路由到的 instance_id可观测才能排查跨插件隔离不同插件提供的同名工具是否误共享上下文。十、小结「工具发现把智能体路由到错误会话」的根因是发现只按能力名匹配缺少「实例归属 / 会话亲缘」维度且不做任务级绑定。通用修复丰富工具描述每个工具携带instance_id/scope/affinity让差异在发现期可见第五节亲缘度路由结合当前会话上下文偏好同会话实例第六节任务级绑定task_id → instance_id绑定保证一次任务内前后调用一致第七节消灭状态断裂去重与兜底等价能力内置优先、独立兜底避免多开第八节。 一句话工具发现的粒度必须是「能力 实例 会话」三元组而不是「能力名」。只按名字选必然在多个相似实例间选错把「该用哪个实例」作为路由的一等公民智能体才能稳定地用上正确的那一处能力——这与第 107 篇可观测性、第 121 篇认证隔离共同指向「分布式能力必须被精确寻址」的工程原则。