Cc Switch Websearch — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Cc Switch Websearch (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
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.
web_search MCP tool exposed via in-process HTTP on a random freeport (127.0.0.1:<port>/mcp)
~/.claude/mcp_servers.json onenable, automatic removal on disable
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
suitable for Claude to cite directly
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 statusDuckDuckGo'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 | headFor Podman the same command works — just replace docker with podman and drop the sudo / usermod steps.
</details>
pnpm install && pnpm tauri devhttp://127.0.0.1:8888 into the SearXNG URL field and click Savecc-switch-websearch entry into ~/.claude/mcp_servers.jsonweb_search tool is now available — try"Search the web for the latest Rust 1.85 release notes and summarise."
.
├── 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. |
.patch files or post-patch files?Earlier revisions of this project used two other approaches, both with real downsides:
git format-patch output). Aunified 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.
patch/ directory of files at theirfinal 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:
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.
new cc-switch release, install.py exits with a clear error naming the file + the spec rule. No silent skips, no half-applied state.
spec's anchors. To add a new feature, append rules. install.py is generic — the spec is data, the engine is code.
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}.rsModified (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.rsThe 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.
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:
find: { node: mod_item, field: name, equals: ... } rule wherethe upstream renamed the mod. Update equals.
find: { attribute: { name: value, equals: logConfig } } rulewhere the upstream renamed the AccordionItem value. Update equals.
find: { table: dependencies } rule where upstream restructuredCargo.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 topThe 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.ymlinstall.py): MIT.Issues and PRs welcome — please open them on this repository rather than on the upstream cc-switch tracker.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.