DeepSeek Search MCP
# deepseek-search-mcp
[](https://github.com/evilwk/deepseek-search-mcp/actions/workflows/ci.yml)
[](https://www.npmjs.com/package/@evilwk/deepseek-search-mcp)
[](LICENSE)
> **Unofficial.** A community project. Not affiliated with, endorsed by, or supported by DeepSeek. It only calls the public [Search / Fetch REST APIs](https://api-docs.deepseek.com/zh-cn/guides/search) with your own API key.
A TypeScript MCP server that wraps DeepSeek's Search and Fetch APIs into two read-only tools, so any MCP-capable agent can use DeepSeek's own search engine regardless of which model it runs on.
| Tool | What it does | Upstream |
|---|---|---|
| `search` | Ranked web results (`title` / `url` / `snippet` / `summary` / `date` / `authority`); supports `site:`, `lang:`, `after:`, `before:`, quoted phrases | `POST /v1/tools/search` |
| `fetch` | One page as Markdown text, truncated to `MAX_CONTENT_CHARS` | `POST /v1/tools/fetch` |
Verified end to end: 6/6 checks in [`scripts/smoke.ts`](scripts/smoke.ts) (discovery, happy path, 429 retry, 401 surfacing, missing key, truncation), run against a mock API so no key or quota is needed.
## Install
```bash
# from npm
npx -y @evilwk/deepseek-search-mcp
# straight from GitHub, no registry involved
npx -y github:evilwk/deepseek-search-mcp
# from source
git clone https://github.com/evilwk/deepseek-search-mcp
cd deepseek-search-mcp && pnpm install && pnpm build
```
Get a key at <https://platform.deepseek.com/api_keys>. Search and Fetch are billed per successful request (¥5 / 1000), so the account needs a positive balance — an HTTP 402 from the server means it ran out.
## Run
```bash
export DEEPSEEK_API_KEY=sk-...
node dist/index.js # stdio, for local agents
node dist/index.js --http # Streamable HTTP, for sharing
```
A one-liner sanity check of the stdio handshake:
```bash
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"t","version":"1"}}}' \
| node dist/index.js
```
## Client configuration
Each snippet below launches the server from a local build (`node /abs/path/.../dist/index.js`). If you installed from npm or GitHub instead, swap that for `npx`:
```jsonc
// from npm
{ "command": "npx", "args": ["-y", "@evilwk/deepseek-search-mcp"] }
// straight from GitHub
{ "command": "npx", "args": ["-y", "github:evilwk/deepseek-search-mcp"] }
```
**Claude Code**
```bash
claude mcp add deepseek-search -e DEEPSEEK_API_KEY=sk-... -- node /abs/path/deepseek-search-mcp/dist/index.js
```
**Generic `mcpServers` block** (Cursor, Claude Desktop, DSH, most hosts)
```json
{
"mcpServers": {
"deepseek-search": {
"command": "node",
"args": ["/abs/path/deepseek-search-mcp/dist/index.js"],
"env": { "DEEPSEEK_API_KEY": "sk-..." }
}
}
}
```
**Codex CLI** (`~/.codex/config.toml`)
```toml
[mcp_servers.deepseek-search]
command = "node"
args = ["/abs/path/deepseek-search-mcp/dist/index.js"]
env = { DEEPSEEK_API_KEY = "sk-..." }
```
**Pi** (`~/.pi/agent/mcp.json`, or `.pi/mcp.json` in a trusted project)
```json
{
"mcpServers": {
"deepseek-search": {
"command": "node",
"args": ["/abs/path/deepseek-search-mcp/dist/index.js"],
"env": { "DEEPSEEK_API_KEY": "${DEEPSEEK_API_KEY}" },
"description": "DeepSeek 自研搜索引擎:网页搜索与单页 Markdown 抓取",
"exposure": "direct",
"timeout": 60
}
}
}
```
Pi's default `exposure` is `codemode`: the tools are registered but never declared to the model, so it would only reach them from codemode scripts or via `tool_search`. Two read-only tools belong at the top level — hence `"exposure": "direct"`. Validate with `pi mcp list`, inspect with `/mcp`, and run `/reload` after editing the file outside a session.
**OpenCode** (`~/.config/opencode/opencode.json`, or project `opencode.json`)
```jsonc
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"deepseek-search": {
"type": "local",
"command": ["node", "/abs/path/deepseek-search-mcp/dist/index.js"],
"enabled": true,
"environment": { "DEEPSEEK_API_KEY": "{env:DEEPSEEK_API_KEY}" }
}
}
}
```
Note the shape differences from the Claude-style blocks above: `command` is an **array**, the env map is called `environment`, and interpolation is `{env:NAME}` rather than `${NAME}`. For the HTTP mode below, use `"type": "remote"` with `url` + `headers` and set `"oauth": false`, otherwise OpenCode will try an OAuth flow the bearer token already replaces.
## HTTP mode (shared / remote)
```bash
MCP_PORT=3000 MCP_AUTH_TOKEN=change-me DEEPSEEK_API_KEY=sk-... node dist/index.js --http
# -> http://127.0.0.1:3000/mcp (Streamable HTTP, session-based)
```
The DeepSeek key stays server-side; callers only need `MCP_AUTH_TOKEN` as a bearer token. There is no OAuth flow here — if you expose this beyond localhost, put it behind a real gateway. It bills your account.
## Environment variables
| Var | Default | Notes |
|---|---|---|
| `DEEPSEEK_API_KEY` | — | Required for tool calls |
| `DEEPSEEK_BASE_URL` | `https://api.deepseek.com` | For testing against a mock |
| `MAX_CONTENT_CHARS` | `40000` | `fetch` truncation limit |
| `MCP_PORT` / `MCP_HOST` | `3000` / `127.0.0.1` | HTTP mode only |
| `MCP_AUTH_TOKEN` | unset | HTTP mode; unset = no auth, localhost only |
## Design notes
- **stdout is forbidden territory in stdio mode.** All logging goes to `stderr`; a stray `console.log` corrupts the JSON-RPC stream and produces the classic "server disconnected" symptom.
- **Retries match the documented policy.** 408 / 429 / 5xx get two retries with 500 ms → 1 s backoff (enough for the account-wide 5 QPS cap). 402 is a hard stop — retrying only burns quota.
- **Errors are model-readable.** Failures come back as `isError: true` with an explicit "retry" / "do not retry" hint, so the agent does not hammer a permanent 4xx.
- **`need_content` defaults to false.** `summary` (600–800 chars) answers most questions; full page bodies would inflate the context window by orders of magnitude.
- **`readOnlyHint: true`** on both tools, so hosts can auto-approve them without a prompt.
- **The API client is transport-agnostic.** `src/deepseek.ts` imports nothing from the MCP SDK — it can back a CLI or an HTTP tool just as easily as this server.
## Layout
```
src/deepseek.ts REST client: retry, timeouts, truncation, typed errors
src/server.ts McpServer + tool registration (zod schemas, annotations)
src/index.ts entry: stdio by default, --http for Streamable HTTP
src/http.ts express 5 + session map + bearer auth
scripts/smoke.ts mock-API end-to-end test, no real key needed
```
## Skill or MCP?
Both are thin shells over `src/deepseek.ts`. Use MCP when the host cannot execute shell commands or needs typed tool schemas; use a Skill when the host has `bash` and you want to avoid a resident process and per-tool schema overhead. This repo is the MCP half.
TDQS
Scored across 2 tools
Search and fetch target clearly different operations: search returns ranked results, while fetch retrieves a single page's full content. The descriptions reinforce the intended use sequence (search then fetch), leaving no meaningful ambiguity.
Both tool names are simple lowercase verbs (search, fetch), following a consistent single-verb convention. There is no mixing of styles or noun phrasing.
Two tools is slightly below the typical 3–15 range, but each is well-scoped and earns its place for a focused web search and retrieval server. The surface is minimal yet sufficient for the core workflow.
The server is read-only, and its domain lifecycle is search then fetch; both operations are present with useful filtering parameters. No obvious gaps exist for basic web search and page retrieval.