DeepSeek Search Everywhere
Web search, page fetching and multi-source research via the DeepSeek Search & Fetch API.
What can you do with DeepSeek Search Everywhere?
- Search with content in one request:
need_contentreturns page text inline, so there is usually no - Batch fetch and research:
deepseek_fetch_manyfetches up to 10 URLs concurrently; - Result hygiene: canonical-URL and title/site deduplication, per-result and total character budgets,
- Optional query rewrite to English through
deepseek-flash(thinking disabled), plus an explicitlang - Cache and limiter: TTL + LRU cache with single-flight, per-endpoint token buckets,
refreshto bypass. - Safe by default: static SSRF checks, secret redaction, no telemetry. See Security.
Install
Add this to your client's MCP configuration (Claude Desktop: claude_desktop_config.json; Cursor: .cursor/mcp.json; VS Code and others use the same "mcpServers" shape).
deepseek-search-everywhere-mcpclaude mcp add deepseek-search-everywhere -e 'DEEPSEEK_API_KEY=<your deepseek_api_key>' -e 'DEEPSEEK_BASE_URL=https://api.deepseek.com' -- uvx deepseek-search-everywhere-mcp==1.0.0
{
"mcpServers": {
"deepseek-search-everywhere": {
"command": "uvx",
"args": [
"deepseek-search-everywhere-mcp==1.0.0"
],
"env": {
"DEEPSEEK_API_KEY": "<your deepseek_api_key>",
"DEEPSEEK_BASE_URL": "https://api.deepseek.com"
}
}
}
}
Environment variables
DEEPSEEK_API_KEYrequired secret DeepSeek API key used for the Search & Fetch API.DEEPSEEK_BASE_URLOverride the DeepSeek API base URL.
Documentation
From the project's README on GitHub, refreshed weekly. Links open on GitHub.
Contents
DeepSeek Search Everywhere MCP
An MCP server that gives AI agents web search, page fetching and multi-source research through the official DeepSeek Search & Fetch API.
It is a thin, careful layer over that API: results are deduplicated, content is capped to a budget you set, repeated calls are served from an in-memory cache, and outgoing calls are paced by a local rate limiter. Six tools, stdio by default, optional bearer-protected streamable HTTP.
It is a standard MCP server, so it works with any MCP-compatible AI client or agent: Claude Code, Claude Desktop, Google Antigravity, Cursor, Windsurf, VS Code (GitHub Copilot), Gemini CLI, Codex CLI, Zed, opencode and others. Local clients launch it over stdio; remote or shared setups can connect over HTTP.
Not affiliated with DeepSeek. This is an independent, community-maintained project.
Features
- Search with content in one request:
need_contentreturns page text inline, so there is usually no need for a second fetch per result. - Batch fetch and research:
deepseek_fetch_manyfetches up to 10 URLs concurrently;deepseek_researchgathers sources in a single pass: one search, then direct fetches for pages whose search content is missing. It does not iterate or summarise. - Result hygiene: canonical-URL and title/site deduplication, per-result and total character budgets, Markdown unescaping.
- Optional query rewrite to English through
deepseek-flash(thinking disabled), plus an explicitlangoperator for non-English queries. - Cache and limiter: TTL + LRU cache with single-flight, per-endpoint token buckets,
refreshto bypass. - Safe by default: static SSRF checks, secret redaction, no telemetry. See Security.
Requirements
- Python 3.11 or newer
- A DeepSeek API key (
DEEPSEEK_API_KEY) from the DeepSeek platform
Install
The server is published on PyPI and listed in the
official MCP Registry
as io.github.radoxtech/deepseek-search-everywhere-mcp.
# Run without installing (uv)
uvx deepseek-search-everywhere-mcp
# Or install with pip
pip install deepseek-search-everywhere-mcp
The HTTP transport needs the http extra (it pulls in uvicorn):
pip install "deepseek-search-everywhere-mcp[http]"
To run the latest unreleased code from main, or from a source checkout:
uvx --from git+https://github.com/radoxtech/deepseek-search-everywhere-mcp deepseek-search-everywhere-mcp
git clone https://github.com/radoxtech/deepseek-search-everywhere-mcp.git
cd deepseek-search-everywhere-mcp
pip install -e ".[http]"
Check that the key and connectivity work (prints your balance, then exits):
export DEEPSEEK_API_KEY=sk-...
deepseek-search-everywhere-mcp --check
Quick start
All examples use stdio. Replace sk-... with your key.
The server is client-agnostic: the examples below cover common clients, and any other MCP client (Antigravity, Cursor, Windsurf, VS Code, Gemini CLI, Codex CLI, Zed, ...) works with the same command, arguments and environment variable in its own MCP configuration format.
Claude Code
claude mcp add deepseek-search-everywhere -e DEEPSEEK_API_KEY=sk-... -- \
uvx deepseek-search-everywhere-mcp
Claude Desktop
Edit claude_desktop_config.json (Settings, Developer, Edit Config):
{
"mcpServers": {
"deepseek-search-everywhere": {
"command": "uvx",
"args": ["deepseek-search-everywhere-mcp"],
"env": { "DEEPSEEK_API_KEY": "sk-..." }
}
}
}
opencode
In opencode.json:
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"deepseek-search-everywhere": {
"type": "local",
"command": ["uvx", "deepseek-search-everywhere-mcp"],
"environment": { "DEEPSEEK_API_KEY": "{env:DEEPSEEK_API_KEY}" },
"enabled": true
}
}
}
Any stdio client
Most clients (including Antigravity, Cursor and Windsurf) use an mcpServers JSON block. Use the uvx
form from the Claude Desktop example, or, if the package is already installed, point the client at the
console script:
{
"mcpServers": {
"deepseek-search-everywhere": {
"command": "deepseek-search-everywhere-mcp",
"env": { "DEEPSEEK_API_KEY": "sk-..." }
}
}
}
python -m deepseek_search_everywhere_mcp is equivalent.
Streamable HTTP
For clients that connect over HTTP. A bearer token is always required; the server refuses to start without one.
pip install "deepseek-search-everywhere-mcp[http]"
export DEEPSEEK_API_KEY=sk-...
export MCP_BEARER_TOKEN="$(python -c 'import secrets; print(secrets.token_urlsafe(32))')"
deepseek-search-everywhere-mcp --transport streamable-http # http://127.0.0.1:8000/mcp
Options: --host (default 127.0.0.1), --port (default 8000), --path (default /mcp). The token is read
from the environment only, never from the command line. The server runs stateless and answers with JSON
(no SSE streams). Client entry:
{
"mcpServers": {
"deepseek-search-everywhere": {
"url": "http://127.0.0.1:8000/mcp",
"headers": { "Authorization": "Bearer <MCP_BEARER_TOKEN>" }
}
}
}
Claude Code: claude mcp add --transport http deepseek-search-everywhere http://127.0.0.1:8000/mcp --header "Authorization: Bearer $MCP_BEARER_TOKEN".
The server speaks plain HTTP; when binding beyond loopback, put TLS in front of it (see SECURITY.md).
Tools
| Tool | Purpose | Parameters (defaults) |
|---|---|---|
deepseek_web_search | Search; titles, URLs, dates, authority, rerank scores, optional page content | query, count=10 (1-50), ori_question, need_content=False, lang, max_chars_per_result=4000, max_total_chars=40000, dedupe=True, rewrite=False, refresh=False, summary_chars, strip_boilerplate=True |
deepseek_search_and_read | Search with page content in one request; the recommended retrieval path | query, count=5 (1-50), ori_question, lang, max_chars_per_result=4000, max_total_chars=30000, dedupe=True, rewrite=False, refresh=False, summary_chars, strip_boilerplate=True |
deepseek_fetch | Fetch one known URL as Markdown | url, clean=True, max_chars=20000, refresh=False, strip_boilerplate=True |
deepseek_fetch_many | Fetch 1-10 URLs concurrently (up to 5 at a time) | urls, max_chars_per_url=4000, max_total_chars=20000, clean=True, refresh=False, strip_boilerplate=True |
deepseek_research | Single-pass source gathering: one search with content, then fetch the sources whose content is missing or shorter than 200 characters (at most 5 fetches). No iteration, no summary | question, max_pages=5 (1-50), per_page_chars=6000, total_chars=30000, lang, rewrite=False, dedupe=True, refresh=False, strip_boilerplate=True |
deepseek_balance | Account balance | none |
Notes on the parameters:
queryandquestionare limited to 128 characters and supportsite:,-site:,"phrase",lang:,after:andbefore:.ori_question(max 128 characters) is the natural-language question and improves ranking.langis an explicit language operator such ases,de,jaorpt-BR. Without it, results for non-English queries may be mixed-language; for named entities, compare with and without it.- With
dedupe=True, searches ask the upstream for a few extra results (max(2, ceil(count * 0.5)), capped at 50) and trim tocountafter removing duplicates, so you still getcountresults when the index returns repeats. Withdedupe=Falseexactlycountare requested. - Without
need_content, each search result carries a shortsummaryinstead of page text. It is capped atsummary_chars(default 400) and never longer thanmax_chars_per_result;summary_chars=0leaves it out. With content, asummaryonly stands in for a result that came back without content, andsummary_charsthen defaults tomax_chars_per_result. Summaries count towardsmax_total_chars. rewrite=True(also indeepseek_research) translates the query to English withdeepseek-flashbefore searching. It is skipped whenlangis set and falls back silently to the original query if the rewrite fails.clean=True(fetch tools and search content) removes backslash escapes from every ASCII punctuation character (\(,\-,\[,\/,\#...). Fenced code blocks and inline code spans are left untouched.strip_boilerplate=True(keyword-only, on every tool that returns page content) removes known site-interface lines. DeepSeek returns Reddit threads with the site UI localised to Chinese whatever the thread's language: the comment-sort menu (排序方式:,最佳,新...), the# 评论区域heading, the archived-post banner,[已删除]author lines and the sign-up footer. Rules are per site (Reddit only for now, matched onreddit.comand its subdomains) and only drop lines that consist entirely of a known label, so user text, Chinese included, and code blocks are kept. It runs aftercleanand before the character caps, so budgets count the cleaned text; it does not change caching.strip_boilerplate=Falsereturns the content as fetched.deepseek_fetch_manyskips empty entries, fetches duplicate URLs (same canonical URL) once, keeps the input order, and reports a failing URL as an item withok: falseinstead of failing the batch. All fetches share a 120 s deadline; URLs still pending then are reported asdeadline_exceeded. Each page gets at mostmin(max_chars_per_url, max_total_chars // number of URLs)characters.deepseek_researchis single-pass source gathering, not an agent: it makes exactly one search plus the gap fetches, never refines the query or searches again, and does no summarising. It takes the samelang,rewriteanddedupeoptions as search; the envelope'squery_usedshows the query that was sent. It does no summarising and makes no model call unlessrewrite=True: it returns sources for the caller to synthesise. Each source carriestitle,url,dateandsite(both null when the upstream gave none),authorityandrerank_score, so stale or off-topic sources are visible without opening them.
Example calls:
deepseek_search_and_read(query="rust async runtime comparison", count=5)
deepseek_web_search(query="gobierno de España presupuestos 2026", lang="es", need_content=False)
deepseek_fetch_many(urls=["https://example.com/a", "https://example.com/b"], max_total_chars=12000)
deepseek_research(question="how does HTTP/3 connection migration work", max_pages=4)
deepseek_research(question="CBOS 2025 korzystanie z internetu Polacy", lang="pl")
Response envelope
Every tool returns a JSON object with ok.
| Field | Where | Meaning |
|---|---|---|
ok | all | true on success, false on failure |
cached | all tools except balance; also per item in deepseek_fetch_many and per source in deepseek_research | true when the response came from the in-memory cache. For deepseek_fetch_many and deepseek_research it is true only when no upstream request was made and at least one response came from the cache |
cached_count | deepseek_fetch_many, deepseek_research | how many responses in this call were served from the cache |
requests_used | deepseek_fetch_many, deepseek_research | upstream requests actually made (cache hits are free) |
elapsed_ms | all, including errors | wall time of the tool call in milliseconds (monotonic clock); a cache hit reports its own, near-zero time |
notes | all success envelopes | human-readable remarks: dedupe counts, rewrite applied or failed, lang applied, truncation ("Some page content was truncated to respect the character budget.") |
error | failures | { "code", "message", "status"? }; status is the upstream HTTP status when there was one |
partial | deepseek_fetch_many, deepseek_research | true when some items or sources failed; each failed one carries its own error. The top-level ok stays true, so check partial |
failed_count | deepseek_fetch_many, deepseek_research | number of failed items or sources |
Search results carry title (the URL host without www. when the upstream title is blank; a title repeated around -, such as Reddit's X - X, is collapsed to X), url, date, authority (0-4, -1 unknown), rerank_score, site, snippet,
and content with chars or summary where available. deepseek_fetch returns title, content, chars,
truncated. chars is always the length of the content returned, in every tool.
content_chars is deprecated and will be removed in the next release: on deepseek_fetch it equals chars;
on search results it still reports the cleaned page length before the per-result cap.
Error codes
| Code | Meaning |
|---|---|
missing_api_key | no key in DEEPSEEK_API_KEY or the fallback file |
ssrf_blocked | URL rejected locally: internal host, literal private/loopback address, or embedded credentials |
bad_url, invalid_url, empty_url, url_too_long | malformed or oversized URL |
empty_query, query_too_long, bad_count, bad_lang, bad_request | invalid arguments |
rate_limited_local | no local rate-limit token became available within the maximum wait |
response_too_large | upstream body exceeded the cap (8 MiB search, 32 MiB fetch) |
deadline_exceeded | batch item not finished within the shared batch deadline |
timeout, network_error, request_failed, invalid_json | transport-level failures |
internal_error | unexpected exception while fetching one batch item |
other / http_error | upstream API errors keep the code from DeepSeek (or http_error) and set error.status |
Response size and cost
Serialized JSON size of one response per mode, measured on 2026-10-28 against the live API with the default parameters of each tool (compact JSON, UTF-8 bytes):
| Call | Size |
|---|---|
deepseek_web_search, count=3, need_content=false (default summary_chars) | 3.7 KB |
deepseek_web_search, count=3, need_content=false, summary_chars=0 | 2.3 KB |
deepseek_web_search, count=3, need_content=true | 15.1 KB |
deepseek_search_and_read, count=3 | 15.1 KB |
deepseek_fetch, one documentation page (default max_chars) | 20.9 KB |
deepseek_fetch_many, 3 documentation pages | 13.3 KB |
deepseek_research, max_pages=3 | 20.1 KB |
These are single samples, not guarantees: sizes vary with the query and the pages returned, and are bounded by
max_chars_per_result, max_total_chars and the other character budgets, not by a fixed number.
Cost: DeepSeek bills Search and Fetch per successful request, not per returned result, so count does not change
the price of a call. See the DeepSeek Search & Fetch guide for
current prices, billing rules and limits; they may change and this project does not track them. A tool call that
needs several upstream requests (for example deepseek_research fetching pages whose search content is missing,
or deepseek_fetch_many) is billed for each of them, and rewrite=true additionally uses deepseek-flash tokens.
Cache hits are not billed.
Cache and rate limiting
The cache and the limiter live in the server process and are not shared between processes.
| Search | Fetch | Query rewrite | |
|---|---|---|---|
| Cache TTL | 5 min | 30 min | 24 h |
| Entry limit | 128 | 128 | 1024 |
| Size limit | 16 MiB | 32 MiB | 1 MiB |
| Rate limit (sustained / burst) | 10 / s, 20 | 5 / s, 10 | not limited |
- Only successful, non-empty responses are cached. Errors are never cached.
- Identical concurrent calls share one upstream request (single-flight); only the one that made the request pays a limiter token, and none of them reports
cached: true. refresh=trueskips the cache read for that call and replaces the stored entry.dedupechanges the upstream request (the search over-fetches), sodedupe=trueanddedupe=falsecalls do not share a cache entry.- Cache hits and rewrite calls cost no limiter token. One logical call takes one token, retries included.
- If a token would take longer than 5 s to become available, the call fails immediately with
rate_limited_localrather than queueing indefinitely. - Transient failures (HTTP 429 and gateway 5xx) are retried once.
DEEPSEEK_MCP_CACHE=0disables all caches.
Configuration
| Variable | Required | Purpose |
|---|---|---|
DEEPSEEK_API_KEY | yes | DeepSeek platform key |
DEEPSEEK_BASE_URL | no | API base URL (default https://api.deepseek.com) |
MCP_BEARER_TOKEN | HTTP only | token clients must send as Authorization: Bearer ... |
DEEPSEEK_MCP_CACHE | no | 1 (default) or 0 to disable caching |
DEEPSEEK_MCP_LOG_LEVEL | no | stderr log level for the server, the MCP SDK and uvicorn: DEBUG, INFO, WARNING (default), ERROR, CRITICAL |
DEEPSEEK_AUTH_JSON | no | path to a JSON file used as the key fallback (see Security) |
.env.example lists the same variables. The server does not read .env files itself; export the variables or
set them in your MCP client's env.
Security
Full policy and reporting instructions: SECURITY.md.
- Static SSRF checks only. URLs are validated before the cache, the limiter or the network are touched:
non-http(s) schemes, internal hostnames, literal private/loopback/link-local addresses and URLs with embedded
credentials return
ssrf_blocked. The server does not resolve DNS, and DeepSeek performs the actual fetch, so this is not network isolation. - Key handling. The key is sent only in the
Authorizationheader to the configured API base URL. Keys, bearer tokens and similar secrets are redacted from error messages and notes. - HTTP transport requires the bearer token on every request (constant-time comparison, never logged).
- No telemetry. The only outbound destination is the DeepSeek API.
- Legacy opencode fallback. If
DEEPSEEK_API_KEYis unset, the server reads the key from~/.local/share/opencode/auth.json({"deepseek": {"key": "..."}}), or from the file named byDEEPSEEK_AUTH_JSON. This exists only for opencode users; prefer the environment variable. - Tool inputs (queries, URLs) are sent to DeepSeek to do the work. Do not pass secrets in them.
Limitations
deepseek_fetchreturns HTTP 404 for PDFs and Reddit and times out on some sites. Usedeepseek_search_and_readwith the page title or asite:-scoped query instead.- Fetch returns only title and content: no final URL, status code or publication date.
- Search and fetch only. No site mapping or crawling, no asynchronous jobs, a single provider (DeepSeek).
- The cache and rate limiter are per process; running several server processes multiplies the effective rate.
- Local rate limits are conservative defaults and do not replace DeepSeek's own quotas.
- The HTTP transport is plain HTTP, stateless and JSON-only; there are no OAuth and no SSE streams.
Development
git clone https://github.com/radoxtech/deepseek-search-everywhere-mcp.git
cd deepseek-search-everywhere-mcp
python -m venv .venv && . .venv/bin/activate
pip install -e ".[test,lint,http]" build twine
pytest -q # offline suite; no network, no key needed
ruff check . # lint
python -m build # sdist + wheel in dist/
python -m twine check dist/* # validate package metadata
The offline tests clear DEEPSEEK_* variables and point the key fallback at a nonexistent file, so they cannot
reach the network. CI runs lint, tests and build on Python 3.11, 3.12 and 3.13.
See CONTRIBUTING.md for the contribution workflow and commit conventions.
Live tests
The live suite calls the real DeepSeek API end to end and uses deepseek-flash as an LLM judge to grade whether
results are relevant and answer the question. It is excluded from the default run, skips itself without a key,
and a full run costs a few cents at most (it also makes deepseek-flash judge calls).
cp .env.example .env # then put your key in DEEPSEEK_API_KEY=
set -a && . ./.env && set +a # export the variables; tests do not read .env themselves
pytest -m live
Judge scores and reasons are printed after the run. .env is git-ignored; never commit it.
Releasing
Versions follow Semantic Versioning and are automated with
release-please from
Conventional Commits. Everything runs in
.github/workflows/release.yml:
- Every push to
mainruns release-please. It opens (or updates) a release PR that bumps the version inpyproject.toml,server.jsonand.release-please-manifest.jsonand prepends a section toCHANGELOG.md. While the version is below 1.0.0,featbumps the patch version and breaking changes bump the minor one;feat,fix,perf,refactor,docsanddepscommits appear in the changelog. - Merging the release PR makes release-please tag
vX.Y.Zand create the GitHub Release. - The same workflow run then builds the tagged commit, checks that the tag equals the versions in
pyproject.tomlandserver.json, runstwine checkand a clean-wheel smoke test, publishes to PyPI through Trusted Publishing (OIDC, no tokens), attaches the artifacts to the GitHub Release and updates the MCP Registry.
Publishing happens in the same workflow because tags and PRs created with GITHUB_TOKEN do not trigger other
workflows.
Manual fallback: bump the version in pyproject.toml, server.json and .release-please-manifest.json
(tests/test_version_sync.py checks the first two agree), update CHANGELOG.md, commit, and push a tag. The
workflow then publishes and creates the GitHub Release itself.
git tag -a v0.2.1 -m "v0.2.1" && git push origin v0.2.1
bash scripts/smoke_package.sh # optional local check: build, install into a clean venv, run the CLI
Running the workflow manually (workflow_dispatch) builds and verifies the package and publishes to TestPyPI
only.
One-time repository setup:
- Settings, Actions, General: enable "Allow GitHub Actions to create and approve pull requests" so release-please can open its PR.
- Settings, Environments: create a
pypienvironment (andtestpypifor manual runs); adding required reviewers there gates every PyPI publish. - On PyPI (and TestPyPI), add a trusted publisher for this repository with workflow
release.ymland environmentpypi(testpypion TestPyPI).
Publishing to the MCP Registry manually
server.json describes the PyPI package for the MCP Registry. The
README carries an mcp-name comment that the registry uses to verify package ownership. The release workflow
does this automatically; to publish by hand:
python scripts/validate_server_json.py # structural checks; also runs `mcp-publisher validate` if installed
mcp-publisher login github # the io.github.radoxtech namespace requires GitHub authentication
mcp-publisher publish
Publish to PyPI first: the registry checks that the package version exists. release-please keeps pyproject.toml
and server.json in step; tests/test_version_sync.py fails when they drift apart.
Changelog
See CHANGELOG.md for release notes.
License
MIT. Not affiliated with DeepSeek.
Frequently asked questions
What is DeepSeek Search Everywhere?
Web search, page fetching and multi-source research via the DeepSeek Search & Fetch API.
How do I install DeepSeek Search Everywhere?
It runs on your machine. In Claude Code: claude mcp add deepseek-search-everywhere -e 'DEEPSEEK_API_KEY=<your deepseek_api_key>' -e 'DEEPSEEK_BASE_URL=https://api.deepseek.com' -- uvx deepseek-search-everywhere-mcp==1.0.0. For other clients, paste the JSON configuration from this page into their MCP settings.
Does DeepSeek Search Everywhere need an API key?
Yes. It expects DEEPSEEK_API_KEY, which you set in its configuration.
Is DeepSeek Search Everywhere open source?
Yes. Its code is on GitHub under the MIT license.
Who maintains DeepSeek Search Everywhere?
It is published by radoxtech. AI.tn lists it but does not maintain or endorse it.
From the official MCP Registry. Listing is not an endorsement: review a server's code and permissions before giving it access to your data.