cc-switch-websearch
Provides a web_search MCP tool for cc-switch, backed by a self-hosted SearXNG instance, enabling AI assistants like Claude Code to perform web searches without a premium API key.
README
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 disable - A 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
Quick start
1. Apply
From the repository root:
python3 install.py apply
Or target a specific checkout:
python3 install.py apply --source ~/Code/cc-switch
The 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 revert
To check the current state:
python3 install.py status
2. 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:
<details> <summary><b>macOS (OrbStack recommended)</b></summary>
# 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
</details>
<details> <summary><b>Windows (PowerShell, Docker Desktop)</b></summary>
# 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
</details>
<details> <summary><b>Linux (Docker)</b></summary>
# 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 | head
For Podman the same command works — just replace docker with podman
and drop the sudo / usermod steps.
</details>
3. Wire it into cc-switch
- Build cc-switch from your patched source:
pnpm install && pnpm tauri dev - Open Settings > Advanced > Web Search
- Paste
http://127.0.0.1:8888into the SearXNG URL field and click Save - Click 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 |
|---|---|
install.py |
Spec-driven patch engine. Loads spec.yaml, applies it to a target cc-switch source via tree-sitter (Rust/TSX/TS) or stdlib (JSON/TOML). Apply backs up every modified file under <source>/.spec-backups/<ts>/; revert restores from that backup. |
spec.yaml |
The single source of truth for the patch. 10 new files (creates:) and 15 modifications (modifies:) declared as data. Maintained by AI. To change what the patch does, edit this file — install.py is generic. |
spec/files/ |
The 10 new files the patch adds, at their natural cc-switch relative paths. install.py copies each into the target tree. |
requirements.txt |
Python deps for install.py: PyYAML, tree-sitter, tree-sitter-rust, tree-sitter-typescript, tomli_w. Install with pip install -r requirements.txt. |
upstream/ |
Reference clone of unmodified cc-switch. Gitignored; install.py bootstraps test-target/ from here when no real cc-switch source is detected. |
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 dev
The 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 top
Verifying 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 | head
You 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.yml
Licence & 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.
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.