# dsh-web-search-free [![npm version](https://img.shields.io/npm/v/@sheep233_/dsh-web-search-free.svg)](https://www.npmjs.com/package/@sheep233_/dsh-web-search-free) [![npm downloads](https://img.shields.io/npm/dm/@sheep233_/dsh-web-search-free.svg)](https://www.npmjs.com/package/@sheep233_/dsh-web-search-free) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) > [**中文**](README.md) | **English** — DSH plugin ([`dsh-plugin`](https://github.com/topics/dsh-plugin)) · free web search provider + settings toggle + **MCP server (Claude Code / Codex compatible)** One-click switch from DeepSeek's **paid, token-billed web search** to the **same free search OpenCode uses** (anonymous, no API key) — and the **same free backend doubles as an MCP server** for Claude Code, Codex, or any MCP client. - 🆓 **Free backends**: Parallel (default) / Exa (backup) — the same free MCP endpoints OpenCode's built-in `websearch` tool uses. Anonymous, no key, no balance deducted. - 🔄 **DSH settings toggle**: official ↔ free, auto-maintains the profile patch. - 🔌 **MCP server**: zero-dependency, dual transport (**stdio + HTTP/SSE port**), drop-in via `claude mcp add` / `codex mcp add`. ## Quick start Published on npm: **[@sheep233_/dsh-web-search-free](https://www.npmjs.com/package/@sheep233_/dsh-web-search-free)** **MCP server (no clone needed, one command):** ```bash claude mcp add dsh-web-search -- npx -y @sheep233_/dsh-web-search-free codex mcp add dsh-web-search -- npx -y @sheep233_/dsh-web-search-free ``` **DSH plugin (official install method, default recommendation)**: use the `dsh plugin` CLI (official entry point, forwards to pnpm inside the profile directory), then declare the package in bundles: ```bash # Install the dependency (equivalent to pnpm add inside ~/.dsh/profiles/web/) dsh plugin --profile web add @sheep233_/dsh-web-search-free ``` Then add the package name to `dsh.profile.bundles` in `~/.dsh/profiles/web/package.json` (an ordered patch-layer stack; order = apply order) and restart DSH: ```jsonc { "dsh": { "profile": { "bundles": [ "@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app", // ...other bundles... "@sheep233_/dsh-web-search-free" ]}}, "dependencies": { "@sheep233_/dsh-web-search-free": "^0.3.0" } } ``` > Use `dsh plugin --profile web install` to re-install missing deps, and > `dsh --profile web --dump-config` to verify your layer/row made it into the > composed tree. Other install options (hot mount / local link / tgz) are described under 「Install」 below. ## Background: why the default uses the DeepSeek key DSH's `web_search` tool goes through the `ctx.web` seam. The default provider is `@deepseek-ai/dsh-web-search-deepseek` (id = `deepseek-official`, pulled in by the `dsh-base` dependency): every search calls DeepSeek's Anthropic-compatible API (`https://api.deepseek.com/anthropic/v1/messages`) with the native `web_search_20250305` server tool, reuses `DEEPSEEK_API_KEY`, and is **billed per token**. ## Features ### Free backends (inside DSH) Registers two free search providers into `ctx.web`; the model-facing `web_search` tool call stays unchanged: | provider id | backend | endpoint | notes | |---|---|---|---| | `parallel-free` (**default**) | Parallel Web Search | `https://search.parallel.ai/mcp` | anonymous, no key | | `exa-free` (backup) | Exa | `https://mcp.exa.ai/mcp` | anonymous, no key; set `EXA_API_KEY` for higher limits | ### Settings toggle (DSH Settings → Plugins → Configurable → Free Web Search) - **enabled switch** (default **off** = official search): on → free; off → official. - **Free backend picker**: Parallel (default) / Exa (backup), single choice. - Changes auto-maintain the `web` row `config.searchProvider` in the profile user patch `~/.dsh/profiles/web/cordis.patch.yml` (on → writes the selected provider id; off → removes the override block). > Do **not** write `~/.dsh/.env`: dsh-app-boot rejects any `DSH_*` variable in > `.env` (bootstrap-only environment guard, fails loud at startup). - **Restart DSH to apply** (`WebRuntime` reads the `web` row config at startup). ### MCP server (Claude Code / Codex / any MCP client) A standalone zero-dependency MCP server (`lib/mcp-server.js`, independent of DSH) exposing the `web_search` tool (params: `query` required, `provider` optional `parallel`/`exa`, `numResults` optional). **Dual transport**: **stdio** (default) and **HTTP/SSE** (listening port; Streamable HTTP `POST /` plus legacy `GET /sse` + `POST /messages`). #### Transport 1: stdio (default, no port) ```bash # Local path (after clone) claude mcp add dsh-web-search -- node /abs/path/dsh-web-search-free/lib/mcp-server.js codex mcp add dsh-web-search -- node /abs/path/dsh-web-search-free/lib/mcp-server.js # Or via npx (npm install -g @sheep233_/dsh-web-search-free first) claude mcp add dsh-web-search -- npx -y @sheep233_/dsh-web-search-free codex mcp add dsh-web-search -- npx -y @sheep233_/dsh-web-search-free ``` #### Transport 2: HTTP/SSE (listening port, dual-port mode) ```bash # Start the MCP server (default http://127.0.0.1:3100; tune with --host/--port) node /abs/path/dsh-web-search-free/lib/mcp-server.js --http --port 3100 ``` - **Claude Code (Streamable HTTP)**: ```bash claude mcp add --transport http dsh-web-search http://127.0.0.1:3100 ``` - **Codex (OpenAI Codex CLI)**: ```bash codex mcp add --transport http dsh-web-search http://127.0.0.1:3100 ``` - **Legacy SSE clients** (point the URL at the SSE channel): ```bash # Client config: http://127.0.0.1:3100/sse (GET /sse opens the channel, POST /messages sends requests) ``` - **Verify**: open `http://127.0.0.1:3100/` in a browser — it returns `{"name":"dsh-web-search-free","version":"0.3.0","transport":"http/sse","port":3100}`. #### Other MCP clients Any MCP client supporting **stdio** or **HTTP/SSE** transport works (command/URL as above). Optional env: `EXA_API_KEY` (higher Exa anonymous limits). ## Install (the DSH plugin itself) > ⚠️ **Why don't others see the card in Settings?** For the settings card to show > all of these must hold: > 1. The package is declared in `dsh.profile.bundles` (just adding it to > node_modules is **not** enough); > 2. **Restart DSH** after install (host assembly and the client graph are both > resolved at startup); > 3. **Refresh the browser** before opening Settings; > 4. The profile is `web` (`tui`/`headless` have no browser UI); > 5. Install via local path / `link:` / tgz / `npm i -g @sheep233_/dsh-web-search-free` > (`npx @sheep233_/dsh-web-search-free` also works for the MCP server). > After install, confirm with `dev_plugin_status` (`client ✓`), or open > Settings → Plugins → Configurable. **UI preview** (Settings → Plugins → Configurable → Free Web Search): ![Free Web Search settings card](assets/settings-card.jpg) **Option 1: official dsh install (default recommendation)** ```bash dsh plugin --profile web add @sheep233_/dsh-web-search-free ``` Then add `"@sheep233_/dsh-web-search-free"` to `dsh.profile.bundles` in `~/.dsh/profiles/web/package.json` (format under 「Quick start」) and restart DSH. Verify with `dsh --profile web --dump-config` — the plugin row should appear in the output. ```bash npm i -g @sheep233_/dsh-web-search-free # optional: global install (for link: or the MCP npx flow) ``` **Option 2: hot mount on this machine (dsh-super-injector, no restart)** ```bash dev_install_package # dir = plugin dir (package.json + lib/) ``` **Option 3: local path link (local development)** Declare in `~/.dsh/profiles/web/package.json`: ```jsonc { "dsh": { "profile": { "bundles": [ "@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app", // ...other bundles... "@sheep233_/dsh-web-search-free" ]}}, "dependencies": { "@sheep233_/dsh-web-search-free": "link:/abs/path/dsh-web-search-free" } } ``` **Option 4: tgz (offline environments)** ```bash npm run build && npm pack # Add the tgz to profile deps and reinstall (pnpm install), then restart ``` ## Verify ```bash npm run verify # patch sync + Parallel/Exa live search + provider matrix + MCP smoke (stdio & HTTP) ``` ## Revert to official Turn the enabled switch off in Settings (the override block is removed), restart DSH. (Or manually delete the block between `# >>> web-search-free` and `# <<< web-search-free` in `~/.dsh/profiles/web/cordis.patch.yml`.) ## Security / permissions - DSH plugin: once enabled, `web_search` requests go only to `search.parallel.ai` / `mcp.exa.ai`; it modifies `~/.dsh/profiles/web/cordis.patch.yml` (a boundary-marked `web` row override, format above) and nothing else. No key, no data collection, no listening. - MCP server: calls only the free endpoints above; reads none of your files/env (except optional `EXA_API_KEY`). stdio mode listens on no port; HTTP/SSE mode serves MCP endpoints only on your `--host:--port` (default `127.0.0.1:3100`), bound to the loopback address by default. ## Development ```bash bash scripts/build.sh # tsc: host + core + mcp-server; copy client into lib/ npm run verify # end-to-end verification ``` Layout: ``` src/core.ts # zero-dep search core: Parallel + Exa (shared by host & MCP) src/index.ts # host: dual provider registration + settings toggle + profile patch sync src/mcp-server.ts # MCP server: stdio + HTTP/SSE dual transport (bin: dsh-web-search-mcp) src/client/client.js # client: "Free Web Search" settings card (with backend picker) scripts/build.sh # build (auto-detects DSH_CHECKOUT) scripts/verify.mjs # end-to-end verification script cordis.patch.yml # bundle assembly layer (incl. web-row searchProvider unpin) ``` ## License MIT © sheep-programmer