[PrismSearch Logo]
PrismSearch
[CI]
[npm version]
[codecov]
[CodeQL]
[OpenSSF Scorecard]
[Node.js]
[License]
简体中文 | English
A multi-engine web search MCP (Model Context Protocol) server that aggregates search results from multiple search engines.
Like a prism splits light into a spectrum, PrismSearch disperses your search queries across multiple engines for multi-perspective results.
Why: web search for your AI agent, no search account required
The built-in web search of AI coding agents such as Claude Code and DeepSeek Harness usually depends on an official account or a separately configured search API key. Use a third-party model, go through your own gateway, or simply skip the search account, and the built-in search is often unavailable — the agent ends up coding "closed-book".
PrismSearch adds that capability back with one command:
- No account, no API key out of the box: the default engines (Bing, Baidu, …) scrape public result pages, so there is nothing to sign up for. Enable browser fetching (
fetcher.browser.enabled: true, requires Playwright) and Google works without a key too. Brave / Google API keys are picked up automatically when present for extra reliability
- Any model, any client: plain MCP — works with Claude Code, DeepSeek Harness, Cursor, VS Code, Cline and any other MCP-capable client
- More than search:
fetch_page reads web pages and PDFs (Markdown output, paginated for long documents), wechat_search searches WeChat Official Account articles — the "found it, now read it" half
- Works behind restrictive networks: Chinese sources (Baidu, Sogou, 360, WeChat) work out of the box; when an engine is blocked or rate-limited the request falls back to the next one automatically
- Clean results: sponsored results injected by search engines are removed — see "Ad filtering" below
[PrismSearch demo: web_search followed by fetch_page over MCP stdio]
[!WARNING]
Scraping engines fetch results from public web pages. You are responsible for complying with each search engine's and target site's terms of service, robots policies and local laws, and for keeping request rates reasonable.
The learning/research recommendation is not an additional restriction on the Apache-2.0 license. This project is not affiliated with any search engine company. See the full Disclaimer at the end.
Features
- 9 Search Sources: Google, Bing, Baidu, Brave, DuckDuckGo, Yandex, Sogou, 360 Search, plus WeChat Official Account articles — with an extensible engine plugin system
- 5 MCP Tools:
web_search, image_search, fetch_page, wechat_search, list_engines
- Dual Transport: stdio (local) and Streamable HTTP (remote) MCP transport
- Smart Engine Selection: Auto-selects the best engine based on availability, latency, success rate, and language matching, with cross-engine fallback
- Google Triple Strategy: Custom Search API → browser rendering → plain HTTP, with automatic fallback and sticky success memory
- Ad Filtering: Baidu promotions, Bing / Google / DuckDuckGo sponsored results and 360 / Sogou paid slots are dropped and don't eat into the requested count; anything that slips through is filtered again by its landing URL (paid-click tracking parameters)
- Web Page Fetching: Fetch and extract content from URLs in HTML, plain text, or Markdown; optional Playwright rendering for JS-heavy pages
- Image Search: Multi-engine image search with size/type filters and optional base64 thumbnails
- Caching: Configurable TTL and LRU eviction with pluggable backends —
cache.backend: sqlite | memory | redis (SQLite by default)
- Security: SSRF protection, rate limiting, domain whitelist/blacklist, optional Bearer auth for remote mode
- Hot Reload: Configuration file watching with automatic reload
- Flexible Config: YAML/JSON/TOML configuration with environment variable overrides
Example
A single web_search call returns structured, deduplicated results:
Every item in results[] always has title, url, snippet, source (the engine) and position, and may have timestamp; with mode=aggregate items also carry sources and the response carries engines. When the engine ignored some parameters the response carries warnings. The same JSON is returned both as structuredContent and as text in content.
Architecture
In stdio mode there is a single client, so rate limiting happens at the tool layer; in HTTP mode the transport rate-limits per client. Whether fetch_page is delegated to a worker is controlled by workers.useFor; when no worker is available it falls back to the local fetchers, and the final URL returned by a worker is SSRF-checked again.
Requirements
- Node.js ≥ 22.19
- Native module
better-sqlite3 ships prebuilt binaries for mainstream platforms (Windows/macOS/Linux, x64/arm64); if prebuilds are unavailable on your platform, a C++ build toolchain is required
- Optional: Playwright for browser rendering. It is an
optionalDependency — installed by default (browsers download on install); use npm install --omit=optional for a minimal install. To add it later: npm install playwright && npx playwright install chromium
Quick Start
Installation
Usage with Claude Code
Or add to your MCP settings:
Other clients
On Windows, if the client cannot find npx, set command to "cmd" and args to ["/c", "npx", "-y", "prismsearch"].
Use as an Agent Skill
skills/prismsearch/SKILL.md is an Agent Skill that teaches the agent when to use each tool: search first and then read the source pages, use timeRange / aggregate mode, fall back to browser rendering, page through long documents, and handle engine failures. The MCP server above still has to be connected; the skill only adds usage guidance.
To install it for Claude Code, copy the folder into your skills directory:
Other agents that support the Agent Skills format can load the same folder.
Use in DeepSeek Harness
dsh-plugin-prismsearch is a DeepSeek Harness plugin that installs both the MCP server and the skill in one step. In the DSH Web GUI, open Plugins → Add plugin and enter:
The plugin starts a pinned prismsearch version through npx, so Node.js >=22.19.0 must be on PATH. The first start downloads the package and can take a few seconds. Tools appear as mcp__prismsearch__web_search and so on. If BRAVE_API_KEY, GOOGLE_API_KEY or GOOGLE_CSE_ID are set where DSH starts, they are forwarded to the server. If you have already added an MCP entry with serverName: prismsearch by hand, remove it or rename it first, otherwise the two entries conflict. See the plugin README for details.
Engine capabilities
Parameter support per engine (web search)
How each web_search parameter is translated for each engine. "—" means the engine has no equivalent; the parameter is accepted but ignored, and the response's warnings lists which parameters were dropped. list_engines also declares per-engine support in its capabilities field.
Image search: Google (gl / hl / safe, size and type via tbs), Bing (setlang, safesearch=strict, size and type via qft), Brave (country / search_lang / safesearch; the API has no paging, so pages are sliced from up to 200 results), Baidu (size via z, GIF via ic; no region / language / safe search).
On page 1 the server over-fetches a few extra results so that de-duplication still fills count; later pages of an auto-selected search stick to the engine that served page 1 so results don't jump between engines.
WeChat articles are a separate wechat config section (not a regular engine) served by the wechat_search tool; returned links expire.
Scraping engines depend on page structure and may break briefly when a search engine redesigns; the project runs a daily live smoke test to catch that early.
Usage with HTTP Transport
The HTTP endpoint is http://127.0.0.1:3000/mcp (health check at /health). When exposing it beyond localhost, always enable auth: set PRISMSEARCH_AUTH_TOKENS or enable security.auth in your config.
CLI
The positional argument has the highest priority, above PRISMSEARCH_CONFIG and the default search paths (see "Configuration"). If the given file does not exist, the server exits with an error. Transport, port, etc. are not CLI flags — set them in the config file or via environment variables (see "Environment variables"). The CLI is intentionally a thin launcher: it does not provide a browser UI, hosted search account, or separate web dashboard; use an MCP-capable client (or the HTTP transport) to call the tools.
The npm package ships only this CLI / MCP server; it is not a JavaScript library. Do not import 'prismsearch' or deep-import files under dist/ — importing the entry point starts a server, and internal modules may change in any release. The stable, versioned interfaces are the MCP tool schemas, the configuration file, and the Worker protocol; to extend fetching, implement a Worker, and to add engines, contribute upstream or fork.
Privacy and local data
PrismSearch does not provide a hosted data service. In the default local stdio setup, requests and responses pass through the local process; cache entries are stored locally (SQLite by default) and logs go to the local process' stderr. If you enable remote HTTP transport, third-party API keys, browser fetching, proxies, Redis, or Workers, query text, URLs, headers, and fetched content may be sent to those providers or services. Review their privacy policies and configure retention, access controls, and log.redact for your deployment. Do not submit passwords, private tokens, personal data, or confidential content unless you have verified every service in the path.
Terms, copyright, and service access
Search snippets, images, pages, PDFs, and WeChat articles are supplied by third parties and may be protected by copyright or other rights. PrismSearch does not grant rights to copy, store, display, scrape, or redistribute third-party content. You are responsible for permission, attribution, robots directives, applicable law, and each provider's terms and rate limits. The Apache-2.0 license covers this repository's code only; it does not grant access to any search service, bypass authentication or anti-bot controls, or license third-party trademarks/content.
MCP Tools
web_search
Search the web using multiple search engines (Google, Bing, Baidu, 360, Sogou, DuckDuckGo, Yandex, Brave).
Parameters:
When the client passes _meta.progressToken, the server sends notifications/progress each time an engine is tried / started.
image_search
Search for images using multiple search engines (Google, Bing, Baidu, Brave).
Parameters:
Each result has title, imageUrl, thumbnailUrl, sourceUrl, position and source, and may have sourceTitle, width, height and thumbnailBase64; the response also carries engine, searchTime, cached, page and hasMore.
fetch_page
Fetch and extract content from a webpage URL.
Parameters:
totalLength is the full content length; bodyTruncated means the response body exceeded fetcher.maxBytes and only its beginning was fetched. With format=html the HTML is returned once, in content.
Main-content extraction uses Mozilla Readability first (@mozilla/readability + linkedom) and falls back to the built-in heuristics. PDFs have their text layer extracted (unpdf, first 50 pages); scanned PDFs without a text layer produce a warning. Every target URL is checked by the SSRF guard first (see "Security").
wechat_search
Search WeChat Official Account (微信公众号) articles via Sogou WeChat search. Returns article titles, account names, publish dates, snippets, and signed temporary article URLs.
Notes:
- Article URLs are signed and time-limited (typically valid for a few hours) — use
fetch_page promptly to retrieve full content.
- Results are cached for 10 minutes only (shorter than web search, due to URL expiry).
- Sogou applies rate limiting; the engine automatically rotates cookies and retries with backoff.
Parameters:
list_engines
List all registered search engines and their current status, including availability, latency, and supported features. No parameters.
Configuration
PrismSearch uses the first configuration file it finds, in this order:
- Path passed as the command-line argument (
npx prismsearch ./my-config.yaml)
- The
PRISMSEARCH_CONFIG environment variable
./prismsearch.{yaml,yml,json,toml} in the current working directory
~/.prismsearch/config.{yaml,yml,json,toml} (the directory follows PRISMSEARCH_HOME if set)
If no file is found, built-in defaults are used. Environment variables are applied on top of whichever source wins.
config/default.yaml is the annotated reference for every option. It is not loaded automatically — copy it to one of the locations above (or point PRISMSEARCH_CONFIG at it) and keep only the keys you want to change.
Enabling more engines: Only Google, Bing, Brave and Baidu are enabled by default. DuckDuckGo, Yandex, Sogou and 360 Search are listed in config/default.yaml with enabled: false — set it to true in your own config (or e.g. PRISMSEARCH_ENGINES__DUCKDUCKGO__ENABLED=true).
Search deadline: search.deadlineMs (default 45000) caps the total time of one web_search / image_search call across the whole fallback chain; once it is reached no further engines are tried, so a client is never left hanging while several engines time out one after another. Cancelling the MCP request also aborts the upstream requests in flight.
Ad filtering: two layers. (1) While parsing, each engine skips results carrying ad markers (sponsored labels, ad-only classes / attributes, ad regions) — always on. (2) The tool layer drops remaining results whose landing URL looks like an ad (ad-click redirect hosts, and paid tracking parameters such as sdclkid, gclid, msclkid, utm_medium=cpc/ppc/sem). Layer (2) can be turned off with search.adFilter: false (or PRISMSEARCH_SEARCH__ADFILTER=false). Baidu's own vertical cards (e.g. 爱采购 / 惠生活) are not ads and are kept.
Engine affinity: when a request carries region / language, the selector prefers engines that are good at that region / language. Each engine can override the built-in defaults with regions and languages (e.g. Baidu defaults to cn/hk/tw and Chinese languages); leave them empty to keep the built-ins, or set them to replace the built-ins entirely. Region matching is case-insensitive.
Google Search Setup
Since 2025, Google requires JavaScript rendering for its search result pages — plain HTTP
scraping only receives an "enable JavaScript" interstitial. PrismSearch's Google engine
supports three strategies, tried in order with automatic fallback and sticky success
memory: Custom Search API → browser rendering → plain HTTP.
Option A: Browser rendering (Playwright)
Then enable the browser fetcher in your config:
Option B: Google Custom Search API (recommended, most stable)
Free quota: 100 queries/day per API key. Requires two credentials:
How to get them:
- Open Google Cloud Console and create/select a project.
- Go to APIs & Services → Library, search for Custom Search API, click Enable.
- Go to APIs & Services → Credentials → Create Credentials → API key.
(Recommended: restrict the key to "Custom Search API".)
- Open Programmable Search Engine,
click Add, choose Search the entire web, and copy the Search engine ID (cx).
- Set the credentials via environment variables (recommended — never commit keys to git):
When both options are available, the API is preferred; on quota exhaustion (429/403)
the engine automatically falls back to browser rendering.
You can verify the setup end-to-end with:
Brave Search Setup
Brave offers a free API tier (2,000 queries/month, no credit card required).
Register at https://brave.com/search/api/, then:
Multiple comma-separated keys are rotated automatically on quota exhaustion.
Environment variables
Environment variables are applied on top of the config file (priority: built-in defaults < config file < environment variables).
A non-empty apiKey / searchEngineId in the config file takes precedence over GOOGLE_* / BRAVE_API_KEY; an empty string counts as unset, so the environment variable is still used.
Any config key can be overridden with the PRISMSEARCH_ prefix. Prefer double underscores between levels; single underscores inside a level are ignored and names are case-insensitive, so both RATE_LIMIT and RATELIMIT match rateLimit. Under engines, the next level is the engine name. The legacy single-underscore form (PRISMSEARCH_SERVER_PORT) still works. Variables that don't match an existing key are ignored with a startup warning.
Values are converted to the type of the target key: true / false become booleans, numbers become numbers, and array keys accept a JSON array or a comma-separated list. .env.example in the repository root lists the common variables.
Fetch Workers (optional)
Workers are optional fetch backends, not MCP servers and not additional search engines. MCP clients connect only to the main server; do not configure a Worker URL as an MCP endpoint or expose Worker ports publicly. Delegation crosses a trust boundary: the Worker operator can observe requested URLs, supplied headers, and fetched content. Use only Workers you trust, private networking or TLS for cross-host traffic, and a separate Worker token; local fallback can move traffic back to the main server's network.
Some pages only work with JavaScript rendering, and others fingerprint your TLS handshake. For those, offload the fetch to a separately deployed Worker. Both Worker types speak the same HTTP + JSON protocol (see docs/worker-protocol.md); the main service balances across them, checks their health, fails over, and falls back to local fetching when none are available.
The profile only decides which containers start; PRISMSEARCH_WORKERS_ENABLED=true is what turns on delegation in the main server (compose already wires both worker endpoints). Outside compose, enable them in config:
A Worker without a token configured will only bind to loopback — it refuses to expose itself open to the network.
Docker
Each release builds three multi-arch (amd64 / arm64) images:
Run the main server on its own:
- The image listens on
0.0.0.0 inside the container in HTTP mode, so PRISMSEARCH_AUTH_TOKENS is required; without it the server refuses to start. Clients send Authorization: Bearer <access token>
- The example publishes the port on the host's
127.0.0.1 only; put a TLS reverse proxy in front if you need to serve beyond the host
- The data directory is
/app/data (PRISMSEARCH_HOME); mount a volume there to persist the cache. To use your own config file, mount it and point PRISMSEARCH_CONFIG at it
- The main image has no browser (Playwright is not installed and
fetcher.browser is off). For JS rendering, run ts-worker and enable workers in config (see the previous section)
With compose (run from the repository root):
The two profiles can be combined. See the comments in docker/docker-compose.yml for per-service settings.
Security
- Auth: in HTTP mode, binding to a non-loopback address requires auth (Bearer token or
X-API-Key), otherwise the server refuses to start; tokens are compared in constant time
- Host / Origin checks: guard against DNS rebinding and cross-site requests; allow more with
server.allowedHosts / server.allowedOrigins
- Rate limiting: per-client buckets in HTTP mode (credential fingerprint or address); at the tool layer in stdio mode
- SSRF guard:
fetch_page and workers only allow http/https, block private, loopback, link-local and other reserved addresses, re-check resolved addresses at connect time (DNS rebinding), and check every redirect hop
- Errors and logs: errors returned to clients pass through an allowlist and internal details go only to the log; logs redact query strings and search terms by default
See SECURITY.md for how to report vulnerabilities.
SSRF whitelist
security.ssrf.whitelist only permits explicitly configured host names; it does not bypass IP validation. To permit a private target, use an exact host in security.ssrf.allowedPrivateHosts (no wildcards) and review the network boundary. Non-loopback Worker origins additionally require an exact workers.allowedOrigins entry (no trailing slash), a token, and HTTPS unless the endpoint explicitly sets the local/intranet allowInsecureHttp exception; loopback Workers (localhost, 127.0.0.1, ::1) may use plain HTTP. Therefore:
- Only whitelist domains you actually need and whose DNS you control; do not use public domains or broad wildcards
security.ssrf.blacklist takes precedence over the whitelist
- The whitelist matches host names only; literal IP URLs are still checked by IP rules
- Worker
allowedOrigins entries must be exact origins without a trailing slash; non-loopback origins require a token and HTTPS
Development
Optional end-to-end verification scripts live in scripts/dev/ (they run against the build output and hit real search engines); see CONTRIBUTING for usage.
Project Structure
Roadmap
FAQ
Google keeps failing or returns zero results.
Since 2025 Google requires JavaScript to render its result pages, so plain HTTP scraping rarely gets anything back. Choose one: configure GOOGLE_API_KEY + GOOGLE_CSE_ID for the official API (most reliable), enable fetcher.browser with Playwright installed, or enable ts-worker to delegate rendering. Otherwise the request falls back to other engines automatically.
I get rate-limited (429) or hit a captcha.
Scraping engines are sensitive to request volume. Lower the per-engine rateLimit, configure a proxy pool (fetcher.proxy), or prefer the Brave / Google APIs. Caching is on by default, so repeated queries never reach the search engine again.
The server exits immediately when I bind to 0.0.0.0 in HTTP mode.
That is a deliberate safeguard: binding to a non-loopback address requires authentication. Set PRISMSEARCH_AUTH_TOKENS and have clients send Authorization: Bearer <token>.
npm install fails while building better-sqlite3.
Usually a platform without a prebuilt binary and no C++ toolchain. Install build tools, or switch cache.backend to memory / redis.
Where do the cache and logs live?
The cache defaults to ~/.prismsearch/cache.db (override with PRISMSEARCH_HOME or cache.sqlite.path); logs go to stderr by default so they never corrupt the stdio protocol.
How do I check which config file was actually loaded?
Run npm run build && node scripts/check-cwd-config.mjs. It prints the effective config file, all candidate paths in priority order, the enabled engines, and whether each API key is set (never the value). Start there whenever a config change appears to have no effect.
Disclaimer
PrismSearch is offered under Apache-2.0 and may be used, modified, and redistributed under that license. The project recommends learning, research, and personal use as a safer starting point, but that recommendation is not an additional license restriction. Scraping engines fetch results from public web pages; users are responsible for complying with each search engine's and target site's terms of service, robots policies, copyright and privacy rules, local laws, and reasonable request rates. The project is not affiliated with Google, Microsoft, Brave, Baidu, Sogou, 360, Yandex, DuckDuckGo, Tencent, or any other company; all trademarks belong to their respective owners. The license covers this repository's code only and does not grant rights to third-party content, trademarks, or service access. Any consequences of use are the user's own.
For deployment, reverse-proxy, resource limits, health/metrics, data cleanup, and maintainer handover, see Operations Runbook. For dependency and third-party licensing boundaries, see Third-Party Notices.
Contributing
Contributions are welcome! See CONTRIBUTING.md (中文) for the development setup, branch/commit conventions, and a step-by-step guide to adding a new search engine. By participating you agree to follow the Code of Conduct.
Need help using it? See SUPPORT: usage questions go to Discussions, bugs go to Issues.
Found a security issue? Please report it privately per SECURITY.md.
License
Apache License 2.0 — see also NOTICE.