cc-switch-websearch
Provides a web search tool backed by a self-hosted SearXNG instance, enabling fresh web results without a premium search API key.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@cc-switch-websearchsearch for latest AI news"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
cc-switch Web Search
Built-in
web_searchMCP tool for cc-switch (Claude Code / Codex / Gemini CLI provider manager), backed by a self-hosted SearXNG JSON API.
Once installed, cc-switch starts an in-process MCP HTTP server, registers
the entry in ~/.claude/mcp_servers.json, and Claude Code can call
mcp__cc-switch-websearch__web_search for fresh web results — no premium
search API key required.
Attribution. This integration and the bundled installer were produced by Minimax-M3 (a Claude Opus 4.8 based coding agent developed by Minimax). Use, modify, and redistribute under the terms of the upstream cc-switch MIT licence.
What you get
A
web_searchMCP tool exposed via in-process HTTP on a random free port (127.0.0.1:<port>/mcp)Automatic registration of the entry in
~/.claude/mcp_servers.jsonon enable, automatic removal on disableA new Web Search section under Settings > Advanced in cc-switch with an enable/disable switch, a SearXNG instance URL input
Save, live status (port, running indicator, last query at), and a Test search box for one-shot queries
Plain-text natural-language reports (title, URL, snippet per result) suitable for Claude to cite directly
Related MCP server: searxng-mcp
Quick start
1. Apply
From the repository root:
python3 install.py applyOr target a specific checkout:
python3 install.py apply --source ~/Code/cc-switchThe installer reads spec.yaml and applies it to the target cc-switch
source tree. New files (under spec/files/) are copied into place; for
each modification rule the installer finds a tree-sitter anchor in the
user's file (Rust/TSX/TS) or parses the file as JSON/TOML, then makes
the edit in place. Backups of every modified file are written under
<source>/.spec-backups/<timestamp>/ so revert can restore the tree
exactly. Pass --auto-deps to also run pnpm install and
cargo check afterwards.
To revert later:
python3 install.py revertTo check the current state:
python3 install.py status2. Self-host a SearXNG instance
DuckDuckGo's HTML endpoint rejects any Rust HTTP client with a captcha, and every public SearXNG instance we tested sits behind an Anubis / Cloudflare challenge — neither works from a headless tool. You need a self-hosted SearXNG. Pick the one-liner for your platform:
# Install OrbStack if you don't have a Docker runtime yet
brew install --cask orbstack
open /Applications/OrbStack.app # first launch: ~30 s to bring up the Linux VM
# Persist a permissive settings.yml (disables IP rate-limit, enables JSON)
mkdir -p ~/.config/searxng
cat > ~/.config/searxng/settings.yml <<EOF
use_default_settings: true
server:
secret_key: "$(openssl rand -hex 32)"
botdetection:
ip_limit:
enable: false
link_token: false
search:
formats:
- json
EOF
# Start SearXNG
docker run -d --name searxng --restart unless-stopped \
-p 8888:8080 \
-v ~/.config/searxng/settings.yml:/etc/searxng/settings.yml:ro \
searxng/searxng
# Smoke test
curl -s 'http://127.0.0.1:8888/search?q=rust+language&format=json' | python3 -m json.tool | head# Install Docker Desktop if you don't have a Docker runtime yet
winget install Docker.DockerDesktop
# Launch Docker Desktop from the Start menu and wait for the whale icon to settle
# Persist a permissive settings.yml
New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.config\searxng" | Out-Null
@"
use_default_settings: true
server:
secret_key: "$(-join ((1..32) | ForEach-Object { '{0:x2}' -f (Get-Random -Maximum 256) }))"
botdetection:
ip_limit:
enable: false
link_token: false
search:
formats:
- json
"@ | Set-Content -Encoding UTF8 "$env:USERPROFILE\.config\searxng\settings.yml"
# Start SearXNG
docker run -d --name searxng --restart unless-stopped `
-p 8888:8080 `
-v "$env:USERPROFILE\.config\searxng\settings.yml:/etc/searxng\settings.yml:ro" `
searxng/searxng
# Smoke test
(Invoke-WebRequest 'http://127.0.0.1:8888/search?q=rust+language&format=json').Content# Install Docker if you don't have it yet (Debian / Ubuntu shown)
sudo apt-get update && sudo apt-get install -y docker.io
sudo usermod -aG docker $USER && newgrp docker
sudo systemctl enable --now docker
# Persist a permissive settings.yml
mkdir -p ~/.config/searxng
cat > ~/.config/searxng/settings.yml <<EOF
use_default_settings: true
server:
secret_key: "$(openssl rand -hex 32)"
botdetection:
ip_limit:
enable: false
link_token: false
search:
formats:
- json
EOF
# Start SearXNG
docker run -d --name searxng --restart unless-stopped \
-p 8888:8080 \
-v ~/.config/searxng/settings.yml:/etc/searxng/settings.yml:ro \
searxng/searxng
# Smoke test
curl -s 'http://127.0.0.1:8888/search?q=rust+language&format=json' | python3 -m json.tool | headFor Podman the same command works — just replace docker with podman
and drop the sudo / usermod steps.
3. Wire it into cc-switch
Build cc-switch from your patched source:
pnpm install && pnpm tauri devOpen Settings > Advanced > Web Search
Paste
http://127.0.0.1:8888into the SearXNG URL field and click SaveClick Test search — you should see real results within a few seconds
Flip the Enable Web Search switch on. cc-switch will:
Start the in-process MCP HTTP server
Write the
cc-switch-websearchentry into~/.claude/mcp_servers.json
Restart Claude Code. The
web_searchtool is now available — try "Search the web for the latest Rust 1.85 release notes and summarise."
Repository layout
.
├── README.md # this file
├── LICENSE # our MIT
├── install.py # apply / revert / status / dev
├── spec.yaml # the patch — single source of truth
├── spec/files/ # new file contents at their cc-switch paths
│ ├── src/
│ └── src-tauri/
├── requirements.txt # tree-sitter + grammar deps for install.py
└── upstream/ # unmodified cc-switch clone (gitignored)Path | Purpose |
| Spec-driven patch engine. Loads |
| The single source of truth for the patch. 10 new files ( |
| The 10 new files the patch adds, at their natural cc-switch relative paths. |
| Python deps for |
| Reference clone of unmodified cc-switch. Gitignored; |
Why "patch as spec" instead of .patch files or post-patch files?
Earlier revisions of this project used two other approaches, both with real downsides:
Static
.patchfiles (e.g.git format-patchoutput). A unified diff is fragile: it depends on the exact line numbers and surrounding context of the upstream file at the time the diff was generated. One whitespace tweak or a single upstream commit can makegit applyfail with a three-way merge conflict.Static post-patch files (a
patch/directory of files at their final state). Sidesteps the diff-fragility problem but forces the patch to one specific upstream version — apply the 3.16.3 snapshot to a 3.16.0 checkout and you get code that doesn't compile.
The current design — a declarative spec applied at install time via tree-sitter — trades a touch of complexity for both:
Robust to upstream drift. Each modification is anchored on a semantic identifier (
find: { node: mod_item, field: name, equals: usage_script }), not a line number. As long as the anchor still exists in the user's file, the patch lands.Fails loudly when it can't. If an anchor no longer exists in a new cc-switch release,
install.pyexits with a clear error naming the file + the spec rule. No silent skips, no half-applied state.Easy to update. To rebase on a new cc-switch release, edit the spec's anchors. To add a new feature, append rules.
install.pyis generic — the spec is data, the engine is code.
Files the patch adds / modifies
The spec applies 25 changes — 10 new files and 15 modifications. The
full list lives at the top of spec.yaml; in short:
New (10):
src/components/websearch/{WebSearchPanel,WebSearchStatusBadge}.tsxsrc/hooks/useWebSearch.tssrc/lib/api/webSearch.tssrc/types/websearch.tssrc-tauri/src/commands/web_search.rssrc-tauri/src/web_search/{mod,mcp_server,tools,searxng}.rs
Modified (15):
src/components/settings/SettingsPage.tsxsrc/i18n/locales/{en,ja,zh,zh-TW}.jsonsrc-tauri/Cargo.tomlsrc-tauri/src/{lib,store}.rssrc-tauri/src/commands/mod.rs
Why SearXNG (and not DuckDuckGo)?
The original implementation scraped html.duckduckgo.com — the same
approach used by NousResearch/hermes-agent. In practice every Rust
HTTP client (reqwest, ureq, with or without http2, with or without
custom TLS fingerprints) gets back an HTTP 202 captcha page. The only
way we found to make a successful request is to shell out to curl,
which is not a sustainable build-time dependency for a Tauri app.
Public SearXNG instances (baresearch.org, disroot.org,
search.hbubli.cc, …) all sit behind Anubis or Cloudflare challenges —
same problem. The integration is therefore designed around a
user-configured, self-hosted SearXNG instance. The one-liners in
§2 bring one up locally in under a minute on any platform.
Rebasing on a new cc-switch release
The spec is anchored on identifiers (function names, JSX attribute
values, JSON keys, TOML tables), not line numbers — so most upstream
changes apply cleanly without any edit to spec.yaml. When a new
cc-switch release renames or removes one of those anchors, install.py apply will fail with a clear error naming the file and the anchor that
no longer matches. To recover:
python3 install.py revert # restore from the last .spec-backups/
cd ~/Code/cc-switch
git fetch upstream
git rebase upstream/main # or: git pull upstream main
python3 install.py apply # re-apply; if a rule now fails, edit
# spec.yaml to point at the new anchor
pnpm install && pnpm tauri devThe most likely edits after a rebase are:
A
find: { node: mod_item, field: name, equals: ... }rule where the upstream renamed the mod. Updateequals.A
find: { attribute: { name: value, equals: logConfig } }rule where the upstream renamed the AccordionItem value. Updateequals.A
find: { table: dependencies }rule where upstream restructured Cargo.toml. Update the table path.
If you want to start clean against a new cc-switch release without trying to rebase an already-patched tree:
python3 install.py revert # restore from backup
git pull upstream main # pull new upstream
python3 install.py apply # re-apply the spec on topVerifying the SearXNG instance is reachable
The panel's Test search button is the fastest check. From a terminal:
curl -s 'http://127.0.0.1:8888/search?q=hello&format=json' | python3 -m json.tool | headYou should see a JSON object with a results array. If you get HTML
or a 403, the instance is up but is missing the
botdetection.ip_limit: false / search.formats: [json] overrides —
re-check the settings.yml in §2.
Container lifecycle:
docker ps --filter name=searxng # is it running?
docker logs --tail 50 searxng # what went wrong?
docker restart searxng # after editing settings.ymlLicence & attribution
Integration code: MIT, same as upstream cc-switch.
Installer (
install.py): MIT.Generated by Minimax-M3 (Minimax).
Issues and PRs welcome — please open them on this repository rather than on the upstream cc-switch tracker.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/soizo/cc-switch-websearch'
If you have feedback or need assistance with the MCP directory API, please join our Discord server