Skip to content

feat: 接入 Tavily web_search MCP 工具替代失效的内置 WebSearch#260

Merged
lishuceo merged 7 commits into
mainfrom
feat/tavily-websearch
Jun 9, 2026
Merged

feat: 接入 Tavily web_search MCP 工具替代失效的内置 WebSearch#260
lishuceo merged 7 commits into
mainfrom
feat/tavily-websearch

Conversation

@lishuceo

@lishuceo lishuceo commented Jun 9, 2026

Copy link
Copy Markdown
Owner

背景

anycode 通过 ANTHROPIC_BASE_URL 走代理/网关(如 litellm)时,Claude Code 内置的 WebSearch 失效——因为它是 Anthropic 服务端工具,网关并不实现它。本 PR 新增一个客户端 MCP 工具 web_search,在本进程内通过 Tavily Search API 发 HTTP 请求,绕开上游网关。

改动

  • src/websearch/tool.ts(新)— web_search 工具:POST https://api.tavily.com/search(Bearer 鉴权),返回标题/URL/摘要片段 + 可选综合答案。含纯函数 formatTavilyResponse、HTTP 错误映射、AbortController 超时。
  • src/config.ts — 新增 config.websearch配了 TAVILY_API_KEY 即自动启用WEBSEARCH_ENABLED 可显式覆盖)。新增 parsePositiveInt 守卫,非数字配置回退默认值。
  • src/claude/executor.ts — 启用时按 per-query 模式注册 web-search MCP server;readOnly 模式放行 mcp__web-search__(联网搜索为只读操作,沿用 feishu/memory/cron 先例)。
  • .env.example — 文档化 TAVILY_API_KEY 等配置项。

测试

  • 新增 src/websearch/__tests__/tool.test.ts(14 例):成功/无结果/缺 key/HTTP 401·429·500/网络错误/超时/max_results 边界/config 默认值回退/格式化纯函数。
  • src/__tests__/config.test.ts 补充 parsePositiveInt + websearch 自动启用逻辑回归。
  • 全量 npx vitest run:1462 通过;唯一失败的 memory/quality.test.ts 语义检索断言在干净 base 上同样失败,与本 PR 无关(预存在)。
  • npm run typecheck 通过。

Review

经多 agent 对抗式 review(correctness / security / convention 三维 + 逐条对抗验证),确认 5 条均为低危:

  • 已修:非数字 WEBSEARCH_TIMEOUT_MSNaNsetTimeout(fn, NaN) 秒触发超时(parsePositiveInt 守卫)。
  • 已缓解:上游错误 body 改为只写服务端日志,聊天提示截断到 200 字。
  • ℹ️ readOnly 放行联网搜索 = 首个触达公网的只读 MCP 工具,egress target 由运营方固定(非用户可控,无 SSRF);沿用现有先例,接受。
  • ℹ️ readOnly allow-list 第 5 个近似分支(by-design,未来可抽成集合)。

启用方式

在服务部署目录 .env 配置 TAVILY_API_KEY=tvly-xxxhttps://app.tavily.com 获取),重启服务即可。

🤖 Generated with Claude Code

lishuceo and others added 5 commits June 9, 2026 14:22
内置 WebSearch 是 Anthropic 服务端工具,当服务通过 ANTHROPIC_BASE_URL
走代理/网关(如 litellm)时网关不实现该工具导致其失效。新增的 web_search
作为客户端 MCP 工具在本进程内通过 Tavily API 执行 HTTP 请求,不依赖上游网关。

- src/websearch/tool.ts: web_search 工具 + createWebSearchMcpServer 工厂 + 纯函数 formatTavilyResponse
- src/config.ts: config.websearch(配了 TAVILY_API_KEY 即自动启用)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
config.websearch.enabled 且配置了 apiKey 时注册 web-search MCP server;
canUseTool 在 readOnly 模式下放行 mcp__web-search__ 工具(联网搜索为只读操作)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
覆盖成功/无结果/缺 key/HTTP 401·429·500/网络错误/超时/max_results 边界/
config 默认值回退/格式化纯函数;executor.test.ts 补充 websearch config mock。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
review 发现的两处低危问题:
- 非数字的 WEBSEARCH_TIMEOUT_MS/WEBSEARCH_MAX_RESULTS 会得到 NaN,
  setTimeout(fn, NaN) 被强转为 0 立即触发,导致每次搜索秒超时。
  新增 parsePositiveInt 守卫,非正整数回退默认值。
- Tavily 非 2xx 错误的完整 body 改为只写服务端日志(截断 500 字),
  返回聊天的提示仍由 describeHttpError 截断到 200 字。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Code Review — Tavily web_search MCP 工具

  • Read CLAUDE.md & PR intent
  • Review src/websearch/tool.ts
  • Review src/config.ts changes
  • Review src/claude/executor.ts changes
  • Review tests & .env.example
  • Post review feedback

View job run

enabled 改为以 key 为前提:未配 TAVILY_API_KEY 时即使 WEBSEARCH_ENABLED=true
也保持禁用。配了 key 后默认启用,WEBSEARCH_ENABLED 仅作为已配 key 情况下的
显式开/关覆盖。补充对应回归测试。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Claude finished @lishuceo's task in 1m 34s —— View job


Code Review — Tavily web_search MCP 工具

  • Read CLAUDE.md & PR intent
  • Check previous review comments (none found)
  • Review src/websearch/tool.ts
  • Review src/config.ts changes
  • Review src/claude/executor.ts changes
  • Review tests & .env.example
  • Post review feedback

✅ Approved

整体实现干净、测试充分,符合仓库约定。MCP 工具在客户端进程内发请求绕开上游网关的思路正确,按 per-query 模式注册、readOnly 放行联网搜索都沿用了 feishu/memory/cron 的既有先例。

做得好的点

  • formatTavilyResponse 抽成纯函数 + 14 例单测覆盖成功/空结果/缺 key/401·429·500/网络错误/超时/max_results 边界,覆盖到位。
  • parsePositiveInt 守卫 NaN → setTimeout(fn, NaN) 的隐患,并补了回归测试。
  • 上游错误 body 仅写服务端日志、返回聊天的 detail 截断到 200 字,敏感信息暴露面已收敛。
  • ESM .js 导入、config/logger 单例、AbortController + finally clearTimeout 资源清理都规范。
  • config.websearch.enabled 已强制依赖 TAVILY_API_KEY 存在,缺 key 时一律不启用,符合 PR 描述。

两点非阻塞观察(confidence < 75,无需改动)

  • executor.ts:699config.websearch.enabled && config.websearch.apiKey 与 config 内 enabled 已包含的 Boolean(TAVILY_API_KEY) 有重叠,属防御性冗余,无害。
  • tool.ts:112 max_resultsz.number() 允许 NaN/浮点;Math.min/max(NaN) 会得到 NaN 透传给 Tavily。该值来自 LLM,实际触发概率极低;若想更稳可在 schema 上加 .int()

无需阻塞合并的问题。PR 质量良好。 ✅

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Claude finished @lishuceo's task in 1m 38s —— View job


Code Review — Tavily web_search MCP 工具

  • 检查既往 inline review 评论是否已修复(无既往 inline 评论)
  • Review src/websearch/tool.ts
  • Review src/config.ts 改动
  • Review src/claude/executor.ts 改动
  • Review 测试 & .env.example
  • 给出 review 结论

✅ Approved

本次为 PR synchronize 后的复审。自上一次 approve 以来唯一的新提交是 63b7a8a(把 main 合并进分支),未引入新的功能代码,原实现保持干净、测试充分、符合仓库约定。无 ≥75 置信度的阻塞问题。

复核要点(均已满足)

  • formatTavilyResponse 纯函数 + 14 例单测覆盖成功/空结果/缺 key/401·429·500/网络错误/超时/max_results clamp/config 回退。
  • parsePositiveInt 守住了 NaN → setTimeout(fn, NaN) 的隐患(config.test.ts 已加回归:'abc'/'0'/'-5' 均回退默认值)。
  • 上游错误 body 仅 logger.warn 写服务端日志(截断 500 字),返回聊天的 detail 经 describeHttpError 收敛(截断 200 字),敏感信息暴露面已收紧。
  • config.websearch.enabled 强制依赖 TAVILY_API_KEY 存在,缺 key 时一律不启用,且 WEBSEARCH_ENABLED=false 可显式关闭 —— 四种组合均有 config 测试覆盖。
  • AbortController + finally { clearTimeout }、ESM .js 导入、config/logger 单例、per-query 注册 MCP server、readOnly 放行 mcp__web-search__ 均沿用既有先例。

非阻塞观察(confidence < 75,无需改动,沿用上轮结论)

  • executor.ts:729config.websearch.enabled && config.websearch.apiKey 与 config 内 enabled 已含的 Boolean(TAVILY_API_KEY) 有重叠,属防御性冗余,无害。
  • tool.ts:93 max_resultsz.number() 允许 NaN/浮点,Math.min/max(NaN) 会得 NaN 透传 Tavily;值来自 LLM,触发概率极低,若想更稳可在 schema 加 .int().min(1).max(20)
  • config.tsWEBSEARCH_DEPTHas 'basic' | 'advanced' 是无校验断言,typo 会原样透传 Tavily(接口侧会自行报错),影响很小。

PR 质量良好,可以合并。 ✅

@lishuceo lishuceo merged commit b82e5f9 into main Jun 9, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant