Cloudflare Tavily Search MCP
by KB-521
README.md
# Cloudflare Tavily Search MCP
一个部署在 Cloudflare Workers 上的远程网页搜索 MCP 服务。它把 Tavily 的低成本搜索包装成 `web_search` 工具,适合手机 AI 客户端、桌面 AI 客户端或其他支持远程 MCP 的应用。
项目默认面向个人使用:Cloudflare Workers 固定成本可以为 0,Tavily 免费方案每月提供 1,000 credits,基础搜索每次消耗 1 credit。
## 特性
- 使用当前 MCP TypeScript SDK v2 和 Streamable HTTP
- 自动兼容 SDK 提供的 2025-era stateless 客户端
- Tavily 搜索深度强制为 `basic`,避免意外使用双倍额度
- Bearer Token 鉴权,Tavily API Key 不会暴露给手机客户端
- 严格 Origin 校验和 MCP CORS 响应头
- Cloudflare Cache API 缓存重复查询
- 可选 Workers KV 月度搜索上限
- Tavily 超时、密钥错误、额度限制和服务异常的安全提示
- 完整单元测试与真实 MCP 客户端集成测试
## 架构
```text
手机 AI / MCP 客户端
│ HTTPS + Bearer Token
▼
Cloudflare Worker /mcp
├─ Origin 与鉴权
├─ Cache API
├─ 可选 KV 月度额度
└─ Tavily Search API (basic)
```
服务是无状态的,不使用 Durable Objects。每个 MCP HTTP 请求创建一个临时 server 实例,因此可以直接在 Workers 上横向扩展。
## 环境要求
- Node.js 22 或更高版本
- npm
- Cloudflare 免费账号
- Tavily API Key:[Tavily 控制台](https://app.tavily.com/)
## 本地运行
```bash
cd ~/Desktop/cloudflare-tavily-search-mcp
npm install
cp .dev.vars.example .dev.vars
```
编辑 `.dev.vars`:
```dotenv
TAVILY_API_KEY="tvly-your-api-key"
MCP_API_TOKEN="replace-with-a-long-random-token"
```
可以用下面的命令生成访问令牌:
```bash
openssl rand -hex 32
```
启动本地 Worker:
```bash
npm run dev
```
检查健康状态:
```bash
curl http://127.0.0.1:8787/health
```
## 部署到 Cloudflare
首次使用 Wrangler 时登录:
```bash
npx wrangler login
```
保存两个生产 secrets:
```bash
npx wrangler secret put TAVILY_API_KEY
npx wrangler secret put MCP_API_TOKEN
```
部署:
```bash
npm run deploy
```
部署完成后 MCP 地址类似:
```text
https://tavily-search-mcp.<你的子域>.workers.dev/mcp
```
## 手机 AI 客户端配置
客户端需要支持 MCP Streamable HTTP,并且能够设置请求头。
通用配置示例:
```json
{
"url": "https://tavily-search-mcp.example.workers.dev/mcp",
"headers": {
"Authorization": "Bearer 你设置的 MCP_API_TOKEN"
}
}
```
如果客户端只支持旧版 HTTP+SSE `/sse`,当前项目不会伪装成兼容:该端点会返回 HTTP 410,并提示改用 `/mcp`。这类客户端需要升级,或额外增加基于 Durable Objects 的旧版适配层。
## MCP 工具
### `web_search`
参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---:|---|
| `query` | string | 是 | 搜索词,2~500 字符 |
| `max_results` | number | 否 | 返回数量,最终不超过服务器的 `MAX_RESULTS` |
| `topic` | `general` / `news` / `finance` | 否 | 默认 `general` |
| `time_range` | `day` / `week` / `month` / `year` | 否 | 时间范围 |
| `include_domains` | string[] | 否 | 优先搜索的域名,最多 10 个 |
| `exclude_domains` | string[] | 否 | 排除的域名,最多 10 个 |
服务器固定发送:
```json
{
"search_depth": "basic",
"include_answer": false,
"include_raw_content": false,
"include_images": false
}
```
这样每次缓存未命中的正常搜索只消耗 1 Tavily credit。
## 配置项
| 名称 | 默认值 | 说明 |
|---|---:|---|
| `TAVILY_API_KEY` | 无 | 必填,使用 Worker secret |
| `MCP_API_TOKEN` | 无 | 必填,至少 16 字符,使用 Worker secret |
| `ALLOWED_ORIGINS` | 空 | 逗号分隔的浏览器 Origin;原生客户端不发送 Origin 时可以留空 |
| `MAX_RESULTS` | `5` | 每次最多结果数,范围 1~10 |
| `CACHE_TTL_SECONDS` | `900` | 搜索缓存秒数,设为 0 禁用 |
| `MAX_MONTHLY_SEARCHES` | `900` | KV 月度上限;设为 0 表示无限制 |
| `TAVILY_TIMEOUT_MS` | `15000` | Tavily 请求超时,范围 1000~30000 |
生产环境普通变量可以直接修改 `wrangler.jsonc`。密钥不要写进该文件或提交到 Git。
### 浏览器客户端的 Origin
原生手机应用和服务端 MCP 客户端通常不发送 `Origin`,默认即可使用。浏览器或 WebView 客户端发送 Origin 时,必须精确加入:
```jsonc
"ALLOWED_ORIGINS": "https://your-client.example,https://another.example"
```
只有明确知道风险时才配置 `*`。
## 可选:启用 KV 月度额度保护
没有 `USAGE_KV` 绑定时,服务仍能正常搜索,但 `MAX_MONTHLY_SEARCHES` 不会在 Worker 内执行。Tavily 免费账户通常会在免费额度耗尽后停止请求;如果启用了按量付费,建议同时启用 KV。
创建命名空间:
```bash
npx wrangler kv namespace create USAGE_KV
```
把返回的 ID 添加到 `wrangler.jsonc`:
```jsonc
"kv_namespaces": [
{
"binding": "USAGE_KV",
"id": "你的 KV namespace ID"
}
]
```
也可以参考 `wrangler.kv.example.jsonc`。默认上限是每月 900 次缓存未命中的请求,给 Tavily 免费额度留出余量。重复搜索命中缓存时不消耗本地月度额度。
KV 的读后写不是强事务计数;对个人单令牌使用足够可靠,但不适合作为多人高并发的精确计费系统。
## 验证与测试
```bash
npm run typecheck
npm test
npm run deploy:dry
```
使用官方 MCP Inspector 手动测试:
```bash
npx @modelcontextprotocol/inspector
```
在 Inspector 中填写 `/mcp` URL,并添加 `Authorization: Bearer ...` 请求头。
## HTTP 端点
| 端点 | 鉴权 | 用途 |
|---|---:|---|
| `GET /` | 否 | 服务信息 |
| `GET /health` | 否 | 配置状态,不泄露密钥 |
| `POST /mcp` | 是 | MCP Streamable HTTP 主入口 |
| `GET/DELETE /mcp` | 是 | 由 MCP SDK 按协议处理 |
| `/sse` | 否 | 返回 410,提示迁移到 `/mcp` |
## 常见问题
- `401 unauthorized`:客户端 Bearer Token 与 `MCP_API_TOKEN` 不一致。
- `403 origin_not_allowed`:把客户端的完整 Origin 加入 `ALLOWED_ORIGINS`。
- `410 legacy_sse_not_supported`:客户端使用了旧 `/sse` 协议,应选择 Streamable HTTP。
- Tavily 401:重新设置 `TAVILY_API_KEY` secret。
- Tavily 429:免费额度已用完或请求过快,等待额度重置。
- Worker 503 misconfigured:至少有一个必填 secret 没有配置。
- 中国大陆网络不稳定:可以为 Worker 绑定自定义域名,或改为部署到可稳定访问的香港服务器。
## 安全说明
- 不要把 Tavily Key 直接填进手机客户端。
- 为 `MCP_API_TOKEN` 使用至少 32 字节随机值。
- 不要公开分享 Worker URL 和访问令牌。
- 如果令牌泄露,运行 `npx wrangler secret put MCP_API_TOKEN` 立即轮换。
- 项目只提供搜索,不包含任意 URL 抓取工具,因此避免了常见 SSRF 风险。
## 项目结构
```text
src/
index.ts Worker 路由、鉴权和 MCP HTTP 入口
server.ts web_search MCP 工具
tavily.ts Tavily API 客户端
cache.ts Cache API 封装
quota.ts 可选 KV 月度上限
config.ts 环境配置解析
security.ts Bearer、Origin 与 CORS
tests/ 单元测试和 MCP 集成测试
docs/plans/ 设计与实施计划
```
## License
MIT
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessSyncing