Skip to main content
Glama
soizo

cc-switch-websearch

by soizo

cc-switch Web Search

Built-in web_search MCP 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_search MCP 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.json on 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


Related MCP server: searxng-mcp

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:

# 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 | head

For Podman the same command works — just replace docker with podman and drop the sudo / usermod steps.

3. Wire it into cc-switch

  1. Build cc-switch from your patched source: pnpm install && pnpm tauri dev

  2. Open Settings > Advanced > Web Search

  3. Paste http://127.0.0.1:8888 into the SearXNG URL field and click Save

  4. Click Test search — you should see real results within a few seconds

  5. Flip the Enable Web Search switch on. cc-switch will:

    • Start the in-process MCP HTTP server

    • Write the cc-switch-websearch entry into ~/.claude/mcp_servers.json

  6. Restart Claude Code. The web_search tool 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 .patch files (e.g. git format-patch output). 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 make git apply fail 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.py exits 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.py is 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}.tsx

  • src/hooks/useWebSearch.ts

  • src/lib/api/webSearch.ts

  • src/types/websearch.ts

  • src-tauri/src/commands/web_search.rs

  • src-tauri/src/web_search/{mod,mcp_server,tools,searxng}.rs

Modified (15):

  • src/components/settings/SettingsPage.tsx

  • src/i18n/locales/{en,ja,zh,zh-TW}.json

  • src-tauri/Cargo.toml

  • src-tauri/src/{lib,store}.rs

  • src-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. Update equals.

  • A find: { attribute: { name: value, equals: logConfig } } rule where the upstream renamed the AccordionItem value. Update equals.

  • 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.

A
license - permissive license
-
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

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