deepseek-web-search-mcp
by RealmElysia
README.md
# deepseek-web-search-mcp
DeepSeek 原生网页搜索的独立封装:通过 Anthropic 兼容的 Messages 端点调用模型自带的
`web_search_20250305` 服务端搜索工具,返回「摘要 + Sources 来源列表」。
零运行时依赖(仅需 Node ≥ 18,使用原生 `fetch`),同一核心提供三种调用形式:
- **MCP server**(stdio)— 供 Claude Code、Codex、Cursor 及任何 MCP 客户端调用 `web_search` 工具
- **CLI** — `deepseek-web-search "query"`,输出 Markdown 或 JSON,供脚本/非 MCP agent shell 调用
- **库** — `import { search } from 'deepseek-web-search-mcp'`
移植自 deepseek-harness 的 `@deepseek-ai/dsh-web-search-deepseek`(provider 逻辑逐字保留)。
---
## 1. 联网搜索(web_search)与 网页抓取(web_fetch)的区别
两者是**互补关系,不是替代关系**——一个是「找」,一个是「取」,在产品里设计成串联使用:
search 找到候选 URL → fetch 取全文。
| | `web_search` 联网搜索(本工具) | `web_fetch` 网页抓取(dsh 的另一工具,本包未包含) |
|---|---|---|
| **输入** | 查询词(dsh 版一次 1–4 个;本 MCP 工具一次 1 个) | 一个具体的 HTTP(S) URL |
| **执行位置** | DeepSeek 服务器侧:发一次辅助模型请求(Messages API + 原生 `web_search_20250305` 服务端工具),检索在 DeepSeek 那边完成 | 本机:直接对目标 URL 发 HTTP GET |
| **返回** | 来源列表:URL、标题、摘要片段、发布时间——**没有全文** | 整页正文,解码为纯文本(输出上限 20 万字符) |
| **解决的问题** | 「网上哪些页面讲这个?」(发现) | 「把这个页面的完整内容读给我」(取回) |
| **成本** | 每次搜索消耗一个模型 turn(是真正的 LLM 请求) | 只有带宽,零模型成本 |
| **限制** | 结果数上限(默认 8)、60s 超时 | URL ≤ 2048、响应体 ≤ 5MB、解码 ≤ 10 万字符、30s 超时、最多 5 次**同源**重定向(跨源不自动跟随) |
| **安全面** | 只连搜索端点,检索目标由服务器选择,无 SSRF 风险 | URL 由模型选择 → 可被诱导访问内网地址;dsh 的 web-fetch-http **未实现** SSRF/私网防护,这是 dsh 默认禁用 fetch 的原因 |
dsh 里 `web_search` 的系统提示词原话就是:
> "Follow up with web_fetch when you need the full content of a specific result."
本包只提供 search 一侧;搜索结果中的 URL 可以交给你的 agent 自带的 fetch / WebFetch
工具(如 Claude Code 的 `WebFetch`、`mcp__fetch`)继续读取全文。
## 2. 实现方式
核心是一次 **Anthropic 兼容的 Messages 模型调用**(非搜索 API):
1. 向 `${DEEPSEEK_SEARCH_BASE_URL}/messages` POST 一个 Messages 请求,请求体带上服务端工具
`tools: [{ type: 'web_search_20250305', name: 'web_search', max_uses }]`,
用户消息为 `Perform a web search for the query: <查询词>`。
2. DeepSeek 在**服务端**执行网页搜索(可执行多次,受 `max_uses` 限制),返回的
`content` 数组中包含:
- `text` 块 — 模型写的检索说明/摘要,以及带 `citations[]` 的引用片段
(每个 citation 含 `url` + `cited_text`,即该来源被引用的原文摘录)
- `web_search_tool_result` 块 — 结构化搜索结果条目(`url` / `title` / `page_age`,
通常**不含**摘要文本)
3. 客户端解析(`mapAnthropicResponse`):
- 从各 `text` 块的 `citations[]` 建立 `url → cited_text` 映射(片段的唯一来源)
- 遍历 `web_search_tool_result` 条目,按 URL 去重,把片段、发布时间拼到每条来源上
- 把 `text` 块正文合并为摘要 `content`
- 响应中没有 `web_search_tool_result` 块视为错误(不回退到「从散文里抠链接」)
4. 渲染为 Markdown:摘要段 + `Sources:` 列表 + 引用提示行。
实现细节(与 dsh 原版一致):
- 认证同时发 `x-api-key` 和 `Authorization: Bearer` 两种头——官方 DeepSeek 认前者,
Anthropic 兼容代理(如 higress 网关)常认后者,两者都发即可通吃
- `fetch` 使用 `redirect: 'error'`,重定向按错误处理
- 全链路支持 `AbortSignal` 取消(超时 / SIGINT),取消错误码为 `WEB_ABORTED`
- **一次搜索 = 一次模型调用**,会消耗少量 token(默认上限 `max_tokens: 4096`);
这是 DeepSeek 原生搜索的设计,换取结构化、带引用片段的结果
## 3. 安装
### 方式 A:直接使用(推荐,零安装)
克隆或拷贝本目录到目标机器,构建一次即可:
```bash
npm install # 仅装 devDependencies(typescript、@types/node)
npm run build # 产物在 dist/
```
之后 `dist/` 目录可以单独拷走——不依赖 `node_modules`,任何 Node ≥ 18 的机器解包即用。
也可以 `npm pack` 得到 tgz,`npm i -g deepseek-web-search-mcp-0.1.0.tgz` 全局安装两个命令。
### 方式 B:从 npm 安装(发布后)
```bash
npm i -g deepseek-web-search-mcp
```
### Claude Code 接入
```bash
claude mcp add web-search -- node /path/to/deepseek-web-search-mcp/dist/mcp.js
# 全局安装后可用:
claude mcp add web-search -- deepseek-web-search-mcp
```
或在项目/全局 `mcpServers` 配置里写(key 建议放 MCP env,不进命令行):
```json
{
"mcpServers": {
"web-search": {
"command": "node",
"args": ["/path/to/deepseek-web-search-mcp/dist/mcp.js"],
"env": {
"DEEPSEEK_API_KEY": "sk-...",
"DEEPSEEK_SEARCH_BASE_URL": "https://your-gateway/v1"
}
}
}
}
```
### Codex 接入(~/.codex/config.toml)
```toml
[mcp_servers.deepseek_web_search]
command = "node"
args = ["D:/deepseek-web-search-mcp/dist/mcp.js"]
env = { DEEPSEEK_API_KEY = "sk-..." }
```
### 其他 agent / 脚本(非 MCP)
```bash
deepseek-web-search "deepseek v4 release notes" # Markdown:摘要 + Sources
deepseek-web-search --json "..." # 结构化 JSON
deepseek-web-search --max-results 3 "..." # 限制来源数
```
退出码:`0` 成功,`2` 参数错误,`3` 凭证缺失,`4` provider 错误(stderr 含 `WebError.code`)。
## 4. 环境要求
- **Node.js ≥ 18**(依赖原生 `fetch`、`AbortSignal.any`、`URL.canParse`;推荐 ≥ 20)
- 运行时**零 npm 依赖**——`npm pack` 产物只有 `dist/` + README,无供应链面
- 网络可达 `DEEPSEEK_SEARCH_BASE_URL` 指定的端点(直连 `api.deepseek.com` 或自建网关均可)
- TypeScript / `@types/node` 仅构建期需要
## 5. 配置说明
全部通过环境变量配置,无配置文件:
| 变量 | 默认值 | 说明 |
|---|---|---|
| `DEEPSEEK_API_KEY` | **必填** | API key(也可用 `DEEPSEEK_API_KEY_LITERAL` 传字面量 key) |
| `DEEPSEEK_SEARCH_BASE_URL` | `https://api.deepseek.com/anthropic/v1` | Anthropic 兼容端点 base,`/messages` 自动追加 |
| `DEEPSEEK_SEARCH_MODEL` | `deepseek-v4-flash` | 模型名(需为支持原生 web_search 的模型) |
| `DEEPSEEK_SEARCH_MAX_TOKENS` | `4096` | 单次请求生成 token 上限 |
| `DEEPSEEK_SEARCH_MAX_USES` | `5` | 每次请求服务端最多执行几轮搜索(影响耗时与 token 消耗) |
| `DEEPSEEK_SEARCH_API_VERSION` | `2023-06-01` | `anthropic-version` 请求头 |
| `DEEPSEEK_API_KEY_ENV` | `DEEPSEEK_API_KEY` | 从哪个环境变量读 key(想复用别的变量名时设置) |
另有工具级参数(每次调用传入,非环境变量):
- MCP / 库:`maxResults` — 返回来源数上限,默认 `8`(超出部分丢弃并标记 `truncated: true`)
- CLI / MCP server:单次搜索超时默认 `60s`(dsh 原版同样给 60s,因为一次搜索是一次完整模型调用)
**注意事项:**
- 搜索端点是 **Anthropic 兼容 Messages 端点**,与 chat-completions 端点不同,故本包
刻意**不读** `DEEPSEEK_BASE_URL`(那是 chat 用的),避免一个变量两用导致请求打错地方。
走代理网关时设 `DEEPSEEK_SEARCH_BASE_URL`(如 `https://your-gateway/v1`)。
- key 缺失时:CLI 退出码 3;MCP `tools/call` 返回 `isError: true` 并附 `WEB_PROVIDER_CREDENTIAL_MISSING` 说明,不会崩溃。
## 开发
```bash
npm run build # tsc → dist/
npm test # node --test(mapAnthropicResponse 去重/片段拼接/截断/格式化)
```
目录结构:`src/provider.ts`(HTTP + 响应映射核心,移植自 dsh)、`src/mcp.ts`(手写最小
stdio JSON-RPC server)、`src/cli.ts`、`src/format.ts`(Markdown 渲染)、`src/config.ts`
(环境变量解析)、`src/index.ts`(库入口)。
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues