Skip to main content
Glama
DeepSeek Search & Fetch MCP Server

DeepSeek Search & Fetch MCP Server

by BillYuan01
README.md
# qwenpaw-deepseek-search-mcp

QwenPaw 的 DeepSeek 搜索与抓取 MCP 服务器。安装后,QwenPaw 里的 agent 会多出两个工具:

| 工具 | 作用 | 使用场景 |
|---|---|---|
| `deepseek_search` | 联网检索,返回标题、URL、摘要、站点、时间、权威度 | 查询训练数据之后发生的事 |
| `deepseek_fetch` | 抓取单个网页,转成 Markdown 正文 | 读取搜索结果全文,用于总结、对比或入库 |

如果 QwenPaw 里已经配置好 DeepSeek 提供商(能正常对话即可),安装后不需要再填 API key。服务器会读取 QwenPaw 保存的 provider key 并解密使用。实现方式见 [免填 key 的处理](#免填-key-的处理)。

## 目录

- [工作原理](#工作原理)
- [安装到 QwenPaw](#安装到-qwenpaw)
- [验证](#验证)
- [key 解析](#key-解析)
- [工具参考](#工具参考)
- [计费](#计费)
- [排障](#排障)
- [复用与改造](#复用与改造)
- [目录结构](#目录结构)
- [安全](#安全)

## 工作原理

### QwenPaw 如何加载

```
┌──────────────────────────┐
│ QwenPaw                  │
│  agent(default / 其他) │
│    │                     │
│    ├─ drivers/mcp/*.yaml │  配置启动命令、参数、env、暴露策略
│    │        │            │
│    │        ▼            │
│    │   QwenPaw 管理 → MCP 卡片(显示为 deepseek)
│    │        │            │
│    │        ▼  按需启动子进程
│    │   ┌──────────┐  stdin/stdout  ┌──────────────────┐
│    └──►│ server.py│◄──────────────►│ Agent 的工具列表 │
│        │ FastMCP  │   JSON-RPC     └──────────────────┘
│        └────┬─────┘
└─────────────┼──────────────┘
              │ HTTPS POST
              ▼
      api.deepseek.com/v1/tools/search | /v1/tools/fetch
```

- 服务器按需启动。QwenPaw 在需要时运行 `python server.py`,通过 stdio 收发 JSON-RPC,调用结束后进程退出,不占端口,也不需要守护进程或反向代理。
- 管理页里一张卡片对应一个服务器进程。`deepseek` 卡片包含 `deepseek_search` 和 `deepseek_fetch` 两个工具,可以在卡片的「配置」中查看。
- 工具名会带前缀,agent 侧看到的是 `deepseek__deepseek_search`。前缀来自 MCP 客户端名,同时安装多个同类服务器时不会冲突。

### 一次工具调用的过程

以 `deepseek_search` 为例,相关代码都在 `server.py`:

1. 注册:`@mcp.tool()` 装饰 `deepseek_search()`,FastMCP 根据函数签名和 docstring 生成 `tools/list` 的 JSON Schema。参数名、类型、默认值和描述都来自函数定义,不需要手写 schema。
2. 参数整理:`lang` 拼成 `lang:<值>` 追加到 query;`count` 限制在 1 到 50;`ori_question` 截断到 128 字符。
3. 发起请求:`_post("/v1/tools/search", payload)` 带上 `Authorization: Bearer <key>`,以 JSON 形式 POST。超时由 `DEEPSEEK_TIMEOUT` 控制,默认 60 秒。
4. 错误处理:HTTP 4xx/5xx 保留上游返回的 body,输出 `{"error": "HTTP 401", "detail": {...}}`;网络异常输出 `{"error": "URLError: ..."}`。agent 可以直接读到失败原因。
5. 裁剪字段:上游每条结果有十几个字段,这里只保留 8 个(`title`、`url`、`summary`、`snippet`、`site_name`、`date`、`authority`、`rerank_score`),减少上下文占用。
6. 序列化:`json.dumps(..., ensure_ascii=False)`,中文按原样输出。

`deepseek_fetch` 使用同样的流程,请求体只有 `{"url": ...}`,返回 `{"title", "content"}`。

### key 解析

`resolve_api_key()` 按以下顺序查找,命中即返回:

| 顺序 | 来源 | 适用情况 |
|---|---|---|
| 1 | 环境变量 `DEEPSEEK_API_KEY` | 手动指定 key |
| 2 | `DEEPSEEK_API_KEY_FILE` 指向的文件(取首个非空行) | 不希望把 key 写进 yaml |
| 3 | QwenPaw 内置 DeepSeek 提供商的 secret | 已在 QwenPaw 配置过 DeepSeek 提供商 |
| 4 | 以上都没有,返回错误并列出尝试过的来源 | 无 |

### 免填 key 的处理

QwenPaw 将 provider 的 key 加密保存在:

```
~/.qwenpaw.secret/providers/builtin/deepseek.json
        └── { "api_key": "ENC:xxxx..." }
```

带 `ENC:` 前缀的内容是密文,直接用于请求会返回 401。`_decrypt_if_needed()` 按以下逻辑处理:

1. 没有 `ENC:` 前缀,按明文返回。
2. 有前缀且本机安装了 QwenPaw,调用 `qwenpaw.security.secret_store.decrypt` 解密。
3. 有前缀但解密失败(缺少 master key 或版本不匹配),返回空字符串,继续尝试下一个来源。

`resolve_api_key()` 只有在前两级都为空时才会读取这份 secret。没有配置 DeepSeek 提供商的环境不会命中第 3 级,最终会进入第 4 级并给出明确的错误信息。

## 安装到 QwenPaw

### 准备

```bash
git clone https://github.com/BillYuan01/qwenpaw-deepseek-search-mcp.git
cd qwenpaw-deepseek-search-mcp

python3 -m venv .venv
. .venv/bin/activate            # Windows: .venv\Scripts\activate
pip install -r requirements.txt
```

需要 Python 3.10 以上版本。

### 配置 driver

每个 agent 有独立的 MCP 配置目录:

```
~/.qwenpaw/workspaces/<agent-id>/drivers/mcp/<名字>.yaml
```

例如 `default` agent 对应 `~/.qwenpaw/workspaces/default/drivers/mcp/deepseek_search.yaml`。新建该文件:

```yaml
name: deepseek_search
protocol: mcp
endpoint:
  transport: stdio
  command: /absolute/path/to/qwenpaw-deepseek-search-mcp/.venv/bin/python
  args:
    - /absolute/path/to/qwenpaw-deepseek-search-mcp/server.py
  cwd: /absolute/path/to/qwenpaw-deepseek-search-mcp
  env: {}
credentials: {}
config:
  display_name: deepseek
  description: DeepSeek 官方 Search / Fetch API(网页检索 + 正文抓取)
enabled: true
policy:
  default_effect: deny
  rules:
    - subject: '*'
      effect: ask
      target:
        kind: tool
        name: '*'
      principal:
        source_type: '*'
        source_value: '*'
        subject_type: '*'
        subject_value: '*'
      condition: null
```

- `env` 留空。如果 QwenPaw 已配置 DeepSeek 提供商,第 3 级会自动命中。
- `command` 和 `args` 使用绝对路径。QwenPaw 启动子进程时的工作目录不确定。
- `policy` 控制工具暴露和审批。上面的配置是默认拒绝、调用时询问,可以按需要调整。

### 界面确认

打开 QwenPaw 管理 → MCP,应看到名为 `deepseek` 的卡片:

- 标注「本地 stdio」
- 开关处于打开状态
- 点「配置」可以看到 `deepseek_search` 和 `deepseek_fetch` 两项,均为勾选状态

该页面不会自动轮询。从外部修改 yaml 后,需要刷新页面才能看到新卡片。

### 重启会话

MCP 工具在会话启动时注入。已经在运行的会话看不到新工具,新建会话即可,不需要重启 QwenPaw 服务。

## 验证

按成本从低到高:

```bash
# 只检查 key 来源,不产生调用费用
python server.py --check-key
# OK   来源: qwenpaw:provider   长度: 35

# 完整链路:list_tools + search + fetch,约 ¥0.01
python test_client.py
# key 来源: qwenpaw:provider(长度 35)
# tools: ['deepseek_fetch', 'deepseek_search']
# search ok: 3 results
# fetch ok: Example Domain

# 在 QwenPaw 新建会话中测试
# 「用 deepseek_search 查一下今天的 AI 新闻」
```

`--check-key` 只打印来源和长度,不打印 key 本身。`test_client.py` 使用官方 `mcp` 客户端加 `stdio_client`,连接方式与 QwenPaw 一致。

## 工具参考

### `deepseek_search`

| 参数 | 类型 | 默认 | 说明 |
|---|---|---|---|
| `query` | string | 必填 | 搜索词,1 到 128 字符;支持 `site:` `-site:` `-term` `"精确短语"` `lang:` `after:` `before:` |
| `count` | int | 5 | 返回条数,限制在 1 到 50 |
| `need_content` | bool | false | 为 true 时额外返回整页正文 `content` |
| `ori_question` | string | "" | 用户原始问题,用于改善检索,截断到 128 字符 |
| `lang` | string | "" | 拼成 `lang:<值>` 追加到 query,如 `zh`、`en` |

返回:

```json
{
  "results": [
    {
      "title": "…",
      "url": "https://…",
      "summary": "…",
      "snippet": "…",
      "site_name": "…",
      "date": "2026-01-01",
      "authority": "…",
      "rerank_score": 0.87
    }
  ]
}
```

### `deepseek_fetch`

| 参数 | 类型 | 说明 |
|---|---|---|
| `url` | string | 必须是 http/https 完整 URL,不含用户名密码,长度不超过 8192 |

返回:

```json
{ "title": "页面标题", "content": "# Markdown 正文……" }
```

两个工具出错时都返回 `{"error": "...", "detail": ...}`。

## 计费

- 官方定价:5 元 / 1000 次成功请求,search 与 fetch 同价,单次约 ¥0.005。
- 计费规则以 [DeepSeek 官方文档](https://api-docs.deepseek.com/guides/search) 为准。本项目只做封装,不加价,也不转发到第三方。
- `count` 和 `need_content` 不影响单价,一次请求计一次。

## 排障

| 现象 | 原因 | 处理 |
|---|---|---|
| 管理页看不到卡片 | 修改 yaml 后没有刷新页面 | 刷新页面;确认文件位于当前 agent 的 `drivers/mcp/` 下 |
| 卡片存在,agent 无法调用 | 旧会话没有注入新工具 | 新建会话 |
| 日志出现 `MCP server not configured` | 该 agent 的 MCP 配置未加载或路径错误 | 检查 `drivers/mcp/*.yaml` 的 `enabled` 和路径 |
| 调用时要求批准 | `policy` 设置为 `ask` | 在卡片或 policy 中调整为允许 |
| `HTTP 401` | key 无效,或 `ENC:` 密文未解密 | 用 `--check-key` 查看来源;来源为 `qwenpaw:provider` 仍报 401 说明解密失败 |
| `HTTP 402` | 账户余额不足 | 到 DeepSeek 控制台充值 |
| `HTTP 403` | key 没有工具 API 权限 | 确认账号已开通 |
| `HTTP 429` | 触发限流 | 降低并发,退避重试 |
| `缺少 DEEPSEEK_API_KEY` | 三级来源均未命中 | 配置 QwenPaw 的 DeepSeek 提供商,或在 `env` 中填写 key |
| 客户端一直显示连接中 | `command` 路径错误或依赖未安装 | 手动运行 `python server.py` 查看 stderr |
| 客户端报 JSON 解析错误 | stdout 被日志污染 | 调试输出改用 `print(..., file=sys.stderr)` |

## 复用与改造

这套结构可以复用到其他 HTTP API:

1. 更换后端:修改 `BASE_URL`(也支持环境变量 `DEEPSEEK_BASE_URL`)和 `_post()` 中的 path。
2. 增加工具:新增一个 `@mcp.tool()` 函数,签名和 docstring 会直接生成 schema。
3. 修改 key 来源:在 `resolve_api_key()` 中插入一级,保持先具体后通用的顺序。
4. 多实例:同一个 `server.py` 可以配置多次,用不同的 `env`(不同 key 或 base_url)区分,界面中显示为两张独立卡片。

## 目录结构

```
.
├── server.py                   # key 解析、两个工具、错误处理
├── test_client.py              # 冒烟测试,使用官方 mcp 客户端直连 stdio
├── requirements.txt            # mcp>=1.0.0
├── examples/
│   └── qwenpaw-driver.yaml     # driver 配置模板
├── AGENTS.md                   # 代码修改约定
├── LICENSE                     # MIT
└── README.md
```

## 安全

- key 通过环境变量、文件或 QwenPaw secret 注入,代码中没有硬编码凭据。
- `.gitignore` 包含 `.env`、`.venv/`、`__pycache__/`。
- 服务器除读取 key 外不访问本地文件,网络请求只发往 `api.deepseek.com`。

## License

[MIT](LICENSE)

---

标准 stdio MCP 服务器,任何 MCP 客户端都可以运行 `python server.py`。