DeepSeek Harness Plugin Hub

Publish and manage complete Harness Profiles. Discover Plugins for your next setup.

Explore

PluginsPresetsDocsNews

Community

Publish a pluginContactReport an issue

Resources

Plugin Hub on GitHubDeepSeek HarnessSystem statusPrivacy notice
© 2026 DeepSeek Harness Plugin HubPowered byPaxTech

Independent and unofficial. Not affiliated with, authorized by, or endorsed by DeepSeek.

Websearch Stack — DSH Plugin for DeepSeek Harness
DeepSeek Harness Plugin Hub
ProfilesPluginsCategoriesNewsDocsSign inManage Profiles
ProfilesPluginsCategoriesNewsDocsSign in
← Plugins
W

dsh-websearch-stack

Websearch Stack

Multi-provider web search stack for DeepSeek Harness: keyless defaults, ordered fallback chain, and a self-hosted SearXNG option.

The plugin will be installed here. Keep web if you are unsure.

npx -y @deepseek-ai/dsh plugin --profile web add github:orfeomorello/dsh-websearch-stack#d5d17982ce7e1f351e678fcef1b29730143b14b8
READMECompatibilityVersions

Compatibility and provenance

Websearch Stack is published as dsh-websearch-stack and currently resolves to version 0.1.0. The Hub verifies its manifest and preserves the exact installation source for reproducible installs.

DSH compatibility
*
Runtime surfaces
web
Release source
github
Registry updated
10/10/2026

Versions

0.1.0stable
10/10/2026

Related plugins

Loading related plugins…

Latest
0.1.0
DSH
*
HMR
Process restart
Tree shaking
Safe tree shaking not declared
Unpacked size
Unavailable
Files
Unavailable
Surface
web
License
MIT
Source
github
GitHub
★ 1
Weekly downloads
0
Last push
10/10/2026
View source ↗
README badge

Click the badge to copy Markdown for your README.

Do you maintain this Plugin?Claim benefit · Priority security scan

Verify the GitHub repository declared in package.json to manage this listing. After you claim it, Hub will prioritize a security scan of the current version and publish the result when it passes.

Claim this Plugin →
Report an issue

Related plugins

More verified plugins in search-research.

Weknora@wxg-prc-cpg/dsh-weknoraWeKnora knowledge retrieval tools for DeepSeek Harness (dsh): semantic search, document reading and RAG/agent answers over your own knowledge bases.Free Searchdsh-free-searchFree web search for DeepSeek Harness: 24 engines (Bing/DuckDuckGo/AnySearch/SearXNG/Exa/Tavily/Keenable/Firecrawl/Parallel keyless; Zhihu global/site via MCP and Perplexity/SerpBase/DeepSeek/You.com/Baidu/Kimi/Aliyun/Doubao with key; OpenAI/Gemini/Claude Dejadsh-dejadeja-vu memory for DeepSeek Harness: the session history of forty other coding agents, searchable and recalled before each step — a local index, no LLM.Builtin Browserdsh-builtin-browserShared real browser plugin for DeepSeek Harness: install-and-use — a visible native browser the human can take over, driven by the agent over CDP. DOM-level interaction (React/Vue safe), per-task session isolation, cookie persistence (dsh_browser_auth), C

README

dsh-websearch-stack

Multi-provider web search for DeepSeek Harness (dsh). The plugin registers one search provider on the ctx.web seam; that provider runs an ordered fallback chain over the backends you enable.

Status: v0.1.0, MVP. Six backends implemented and unit-tested. Verified live: Tavily keyless and Parallel MCP. The others need an API key or a local instance and are not yet verified — see Known limitations.

Features

  • Six search backends: Tavily (keyless-capable), Parallel MCP (no credentials), SearXNG (self-hosted), Brave, TinyFish, Exa.
  • Ordered fallback chain: a recoverable error (network, timeout, rate limit, bad response) moves to the next backend; a terminal one aborts; if all fail the error lists every attempt.
  • Keys without a restart: the four key-backed backends are configured from the web client's Plugins page, written through the Host's credential store and resolved per request — never into a settings file, with environment variables as the fallback.
  • Boot-time chain: the chain order, the SearXNG URL, the result cap, the timeout and the cache TTL are volatile fields set from your profile's patch layer (see Configuration).

Install

lib/ is committed, so installing from GitHub needs no build step.

dsh plugin --profile web add github:orfeomorello/dsh-websearch-stack

To try on a disposable profile first:

dsh --profile web --dump-config > before.txt
dsh plugin --profile web add github:orfeomorello/dsh-websearch-stack
dsh --profile web --dump-config > after.txt

Quick start

The default chain ['tavily', 'parallel-mcp'] works with no key and no configuration: Tavily runs keyless, Parallel needs no credentials. Install the plugin, restart the profile, and ask anything that needs current information — web_search answers through the chain.

From there:

  • want more or different sources? reorder the chain or add a backend in the profile patch (see Configuration) — SearXNG, Brave, TinyFish and Exa each need their own setup;
  • want your own instance? follow SearXNG: no external API, no key, your infra;
  • want higher-quality Tavily results or Brave/Exa? paste a key on the plugin's page in the web client; it takes effect on the next request.

Configuration

Two surfaces, because the web client's Plugins page can only write credentials:

WhatWhereWhen it takes effect
API keys (TAVILY_API_KEY, BRAVE_SEARCH_API_KEY, TINYFISH_API_KEY, EXA_API_KEY)the Plugins page → this bundle's page, through the settings cardthe next request, no restart
enabled_providers, searxng_url, default_max_results, default_timeout_ms, cache_ttl_secondsyour profile's cordis.patch.yml (or a --patch overlay)at the next profile boot

The second list is a host limitation, not a plugin one: in @deepseek-ai/dsh-client-ui-plugin-manager 0.2.0-rc.2 the page renders plugins.bundle.config without a config form, so a bundle's own fields never get a widget. The fields are declared volatile (each one a reference the Host can rewrite in place), so a form written against plugins.row.config — or any CLI tooling that writes the namespace — would apply them live; today the profile patch is the way.

Boot-time values

The values live in the profile's own patch layer, $DSH_HOME/profiles/<profile>/cordis.patch.yml, keyed by this plugin's row id. This shape is verified with dsh --profile <profile> --dump-config:

# ~/.dsh/profiles/web/cordis.patch.yml
- id: websearch-stack
  name: dsh-websearch-stack
  config:
    enabled_providers: [tavily, parallel-mcp]
    default_max_results: 5

For a one-off try without touching the file, an overlay applied after every other layer:

dsh --profile web --patch ./searxng.yml
# searxng.yml
- id: websearch-stack
  name: dsh-websearch-stack
  config:
    enabled_providers: [searxng, tavily]
    searxng_url: http://localhost:8080

An absent field keeps its default. An unknown value is refused by the schema, so a typo fails the boot instead of silently disabling a backend.

A Compose-style setup can keep the URL in the environment instead: the entry-list YAML dialect evaluates !!js scalars at entry activation, with Node globals in scope.

# ~/.dsh/profiles/web/cordis.patch.yml
- id: websearch-stack
  name: dsh-websearch-stack
  config:
    enabled_providers: [searxng, tavily]
    searxng_url: !!js process.env.SEARXNG_URL ?? ''

--dump-config prints the expression node unevaluated — the dump is the stored patch, not the resolved value.

OptionDefaultMeaning
enabled_providers['tavily', 'parallel-mcp']Fallback chain, in order. Values: tavily, parallel-mcp, searxng, brave, tinyfish, exa. An empty list disables the plugin (see How the fallback works).
searxng_url—Base URL of a self-hosted SearXNG instance, e.g. http://localhost:8080. Required for searxng.
default_max_results5Maximum sources per query when the request names none (1–20).
default_timeout_ms10000Per-backend request timeout in milliseconds (1 000–60 000).
cache_ttl_seconds60Cache TTL for identical queries. 0 disables the cache.

SearXNG

The self-hosted option: your instance, no external API, no key. It needs two things — an instance with the JSON format enabled, and searxng_url pointing at it.

  1. Run an instance (Docker Compose, the project's recommended layout):
mkdir -p ./searxng/core-config/
cd ./searxng/
curl -fsSLO https://raw.githubusercontent.com/searxng/searxng/master/container/docker-compose.yml
curl -fsSLO https://raw.githubusercontent.com/searxng/searxng/master/container/.env.example
cp -i .env.example .env
docker compose up -d
  1. Enable the JSON format in core-config/settings.yml — json is not in the default search.formats (html only):
search:
  formats:
    - html
    - json
  1. Point the plugin at it (profile patch, then restart the profile):
- id: websearch-stack
  name: dsh-websearch-stack
  config:
    enabled_providers: [searxng, tavily, parallel-mcp]
    searxng_url: http://localhost:8080

The backend calls GET <searxng_url>/search?q=…&format=json&language=en and reads results[].{url,title,content}. Without searxng_url the backend reports itself unavailable and the chain moves on. SearXNG's own installation details are in its container and search: settings pages.

Backends

IdKeyNotes
tavilyTAVILY_API_KEY (optional)Without a key sends x-tavily-access-mode: keyless (verified live). With a key, Bearer.
parallel-mcpnoneJSON-RPC tools/call on web_search (arguments objective and search_queries), no credentials. Verified live.
searxngnoneSelf-hosted instance, requires searxng_url. Not verified (needs an instance).
braveBRAVE_SEARCH_API_KEYX-Subscription-Token. Not verified with a real key.
tinyfishTINYFISH_API_KEYX-API-Key. Not verified with a real key.
exaEXA_API_KEYx-api-key, POST /search. Not verified with a real key.

Keys are read from the Host credential store, with the environment variables above as the fallback: a key written from the settings card is resolved per request, so it needs no environment export. The config YAML never holds secrets.

Settings card (web client)

The plugin ships a configuration card for the dsh web client (dsh.client.platform: "web"). It appears on the Plugins page under the dsh-websearch-stack bundle and manages the four key-backed API keys through the Host's credentials service:

  • the state badge shows only whether a key is configured (describe), never the value;
  • what you write lands in the Host credential store (e.g. $DSH_HOME/.credentials.yaml), not in any settings document;
  • the provider re-resolves the reference on the Host's credentials/reference-updated event, so a key written from the page takes effect on the next request without a reboot;
  • a field left blank does not clear the stored key;
  • each field is named after its provider — Tavily API key, Brave Search API key, TinyFish API key, Exa API key — and the Search providers section below links every backend to its owner's site (see below).

UI status: the page and the card were exercised in a live web client (the bundle page, its title, the key controls and the providers section render). What the host does not render is a form for this bundle's own config fields — see Configuration and Known limitations.

What the plugin page shows

The Plugins page's detail for this bundle states the bundle's own identity — its display title from locale/en.json, its version, and the package name (the one you install elsewhere, in a <code> line) — then this plugin's configuration: the four key controls, named after their provider, and a Search providers section listing every backend the chain vocabulary accepts:

ProviderWhat it takesWhere to configure
TavilyAPI key optional (works keyless)https://app.tavily.com/home
ParallelNo key neededhttps://docs.parallel.ai/
SearXNGSelf-hosted instancehttps://docs.searxng.org/
Brave SearchAPI keyhttps://brave.com/search/api/
TinyFishAPI keyhttps://docs.tinyfish.ai/search-api/reference
ExaAPI keyhttps://exa.ai/

Each row links to its owner's site, where a key is created or the service is documented. The section is the bundle's contribution to the page's plugins.detail.section slot; the copy lives in src/client/card.ts.

Coexisting with the native DeepSeek provider

The web profile already ships web-search-deepseek (provider id deepseek-official), and @deepseek-ai/dsh-base pins the seam to it:

- id: web
  name: '@deepseek-ai/dsh-web'
  config:
    searchProvider: deepseek-official
    fetchProvider: http

That pin is the whole problem for any third-party search provider: the DeepSeek provider's available() is true even without DEEPSEEK_API_KEY (it resolves the credential per search, not at selection time), so with the pin in force every web_search fails with WEB_PROVIDER_CREDENTIAL_MISSING and never reaches a provider this bundle registers.

This bundle's cordis.patch.yml therefore overrides that row (later bundle layers win; the profile's own patch, the home patch and --patch overlays are later still and can pin anything they want):

- id: web
  name: '@deepseek-ai/dsh-web'
  config:
    searchProvider: websearch-stack
    fetchProvider: http

A patch replaces the matched row's whole config — never a deep merge — so fetchProvider: http is restated exactly as the base row leaves it. Without @deepseek-ai/dsh-base in the profile the web row does not exist, the override is skipped with a loader warning, and the seam auto-selects this provider as the only usable one.

To hand web_search back to the native provider, re-pin it in your profile's cordis.patch.yml (or --patch), restating both keys:

- id: web
  config:
    searchProvider: deepseek-official
    fetchProvider: http

How the fallback works

The seam accepts a single usable search provider when none is configured. So the plugin registers exactly one provider, websearch-stack, that walks the backends in order:

  • a backend that is not available (no key, no SearXNG URL) is skipped before any request;
  • a recoverable error (network, timeout, rate limit, invalid response, auth or credits) moves to the next backend;
  • a terminal error (e.g. cancellation) stops the chain and rethrows;
  • if all fail, the error lists every attempt.

The chain order is read on every request, so an edit to enabled_providers in the profile patch takes effect from the next boot, and every backend in the chain is re-resolved per request. When the chain is empty — or every backend in it is unavailable — available() is false and the seam answers WEB_PROVIDER_CONFIGURED_UNAVAILABLE, because the bundle patch pins searchProvider to this provider. Re-add a backend in the profile patch to make web_search work again.

Development

npm install
npm run typecheck     # tsc --noEmit
npm test              # unit/integration tests (node:test)
npm run build         # rebuild lib/ from src/ (also emits the client bundle)
npm run bundle:client # rebuild only lib/client.js (ModuleLoader bundle)

lib/ is committed. After changing src/, run npm run build and commit lib/ too — CI fails if they drift apart.

Known limitations

  • Live services partially verified. Tavily keyless and Parallel MCP verified live. Brave, TinyFish, Exa and SearXNG are not yet: they need API keys or a local instance.
  • The chain is not editable from the web client. The Plugins page renders a bundle's configuration card (the API keys here) but not a form for the bundle's own config fields: in @deepseek-ai/dsh-client-ui-plugin-manager 0.2.0-rc.2, plugins.bundle.config is rendered without the form prop, while plugins.row.config receives it. A plugins.row.config entry for the plugin's row could close this gap; until then, edit $DSH_HOME/profiles/<profile>/cordis.patch.yml and restart the profile (see Configuration).
  • Pinned seam, by design. The bundle patch pins web.searchProvider to websearch-stack, because the base profile pins it to the DeepSeek provider, which is available() without a key. Consequence: with an empty chain web_search fails with WEB_PROVIDER_CONFIGURED_UNAVAILABLE instead of falling back to the native provider (see Coexisting with the native DeepSeek provider).
  • No fetch. Page fetching is handled by DSH's dsh-web-fetch-http provider, not this plugin.
  • Not a sandbox. The code runs inside the host process with its privileges, like every dsh plugin.

License

MIT — see LICENSE.