PrismSearch
Provides web and image search over Baidu, using HTTP scraping or its JSON API, with support for Chinese-language content.
Provides web and image search via the Brave Search API when a Brave API key is configured.
Provides optional web search via DuckDuckGo HTTP scraping, disabled by default because frequent requests trigger bot challenges.
Provides web and image search through Google, using the Custom Search API, browser rendering, or plain HTTP with automatic fallback.
Provides optional Chinese-language web search via Sogou HTTP scraping.
Provides search for WeChat Official Account articles through the wechat_search tool.
Click on "Deploy 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., "@PrismSearchsearch the web for the latest Node.js LTS release and summarize the top result"
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.
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 reliabilityAny 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_pagereads web pages and PDFs (Markdown output, paginated for long documents),wechat_searchsearches WeChat Official Account articles — the "found it, now read it" halfWorks 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
# Claude Code
claude mcp add prismsearch -- npx -y prismsearchScraping 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 fullDisclaimer 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_enginesDual 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:
{
"results": [
{
"title": "Model Context Protocol - Introduction",
"url": "https://modelcontextprotocol.io/introduction",
"snippet": "The Model Context Protocol (MCP) is an open protocol that enables seamless integration between LLM applications and external data sources and tools.",
"source": "brave",
"position": 1
}
],
"totalResults": 10,
"engine": "brave",
"searchTime": 842,
"cached": false,
"page": 1,
"hasMore": true
}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
flowchart LR
Client["MCP client<br/>Claude Code / Cursor / …"]
subgraph Transport["Transport src/server"]
Stdio["stdio"]
Http["Streamable HTTP<br/>auth · Host/Origin check · rate limit"]
end
subgraph Tools["Tools src/tools"]
WS["web_search"]
IS["image_search"]
WX["wechat_search"]
FP["fetch_page"]
LE["list_engines"]
end
subgraph Search["Search orchestration"]
Sel["Engine selection / circuit breaker<br/>engine-selector"]
Chain["Fallback chain · aggregate RRF<br/>dedup · ad filter"]
end
Engines["Engine adapters src/engines<br/>Google · Bing · Brave · Baidu · DDG<br/>Yandex · Sogou · 360 · Sogou WeChat"]
Cache[("Cache src/cache<br/>sqlite / memory / redis")]
subgraph Fetch["Fetching src/fetcher"]
SSRF["SSRF guard<br/>src/security/ssrf-guard"]
HF["HTTP fetcher"]
BF["Browser fetcher<br/>Playwright"]
WK["Remote workers<br/>ts-worker / py-worker"]
EX["Content extraction<br/>Readability / PDF"]
end
Client --> Stdio & Http
Stdio & Http --> Tools
WS & IS --> Sel --> Chain --> Engines
WX --> Engines
LE -. status .-> Sel
WS & IS & WX & FP -. read/write .-> Cache
FP --> SSRF --> HF & BF & WK
HF & BF & WK --> EXIn 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.
Related MCP server: websearch-mcpserver
Requirements
Node.js ≥ 22.19
Native module
better-sqlite3ships prebuilt binaries for mainstream platforms (Windows/macOS/Linux, x64/arm64); if prebuilds are unavailable on your platform, a C++ build toolchain is requiredOptional: Playwright for browser rendering. It is an
optionalDependency— installed by default (browsers download on install); usenpm install --omit=optionalfor a minimal install. To add it later:npm install playwright && npx playwright install chromium
Quick Start
Installation
npm install prismsearchUsage with Claude Code
claude mcp add prismsearch -- npx -y prismsearchOr add to your MCP settings:
{
"mcpServers": {
"prismsearch": {
"command": "npx",
"args": ["-y", "prismsearch"],
"env": { "BRAVE_API_KEY": "optional" }
}
}
}Other clients
Client | Config location | Notes |
Claude Desktop |
| Same |
Cursor |
| Same |
VS Code (Copilot) |
| Top-level key is |
Windsurf / Cline etc. | Their own MCP config | Generic |
Remote (HTTP) | Any Streamable HTTP client | URL |
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:
# All projects
cp -r skills/prismsearch ~/.claude/skills/
# Current project only
cp -r skills/prismsearch .claude/skills/Other agents that support the Agent Skills format can load the same folder.
Engine capabilities
Engine | Web | Image | Method | Default | Notes |
✅ | ✅ | API / browser / HTTP | on | Custom Search API is the most reliable; plain HTTP usually hits the JS wall | |
Bing | ✅ | ✅ | HTTP scraping | on | |
Brave | ✅ | ✅ | Official API | on | Needs |
Baidu | ✅ | ✅ | HTTP scraping / JSON API | on | Chinese content |
DuckDuckGo | ✅ | — | HTTP scraping | off | Frequent requests trigger a bot challenge |
Yandex | ✅ | — | HTTP scraping | off | |
Sogou | ✅ | — | HTTP scraping | off | Chinese content |
360 Search | ✅ | — | HTTP scraping | off | Chinese content |
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.
Engine |
|
|
|
|
|
|
Google (scrape) |
|
|
|
|
|
|
Google (Custom Search API) |
|
|
|
|
| 10 per request; larger counts fetch several pages |
Bing |
|
|
|
|
| ~10 per page; larger counts stitch several pages |
Brave |
|
|
|
|
| up to 20 per request; larger counts fetch several pages |
Baidu | — | — | — |
|
|
|
DuckDuckGo |
| used for |
|
|
| truncated client-side |
Yandex |
|
| — | — |
|
|
Sogou | — | — | — |
|
| fixed 10 per page, truncated client-side |
360 Search | — | — | — |
|
| fixed 10 per page, truncated client-side |
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
# Start with the bundled HTTP example config
npx prismsearch config/http.yaml
# Or use environment variables
PRISMSEARCH_SERVER_TRANSPORT=http PRISMSEARCH_SERVER_PORT=3000 npx prismsearchThe 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
prismsearch [config-file] # start the server; the optional positional argument is a config file (YAML / JSON / TOML)
prismsearch --help # bilingual (English / Chinese) help including common environment variables (-h)
prismsearch --version # print the version (-v)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:
Parameter | Type | Required | Default | Description |
query | string | Yes | - | Search keywords, 1-500 characters after trimming |
engine | string | No | auto | Engine: |
region | string | No | - | Region code (e.g., |
language | string | No | - | Language code (e.g., |
count | integer | No | 10 | Number of results, 1-50 |
page | integer | No | 1 | Page number, 1-10 |
safeSearch | boolean | No | false | Enable safe search |
timeRange | string | No | - | Time filter: |
mode | string | No | fallback |
|
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:
Parameter | Type | Required | Default | Description |
query | string | Yes | - | Search keywords, 1-500 characters after trimming |
engine | string | No | auto | Image-capable engine: |
region | string | No | - | Region code, max 10 characters |
language | string | No | - | Language code, max 20 characters |
count | integer | No | 10 | Number of results, 1-50 |
page | integer | No | 1 | Page number, 1-10 |
safeSearch | boolean | No | false | Enable safe search |
imageSize | string | No | - | Filter by size: |
imageType | string | No | - | Filter by type: |
includeThumbnails | boolean | No | false | Inline base64 thumbnails in results (max 20, 50KB each) |
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:
Parameter | Type | Required | Default | Description |
url | string | Yes | - | URL to fetch, http/https only, max 4096 characters |
format | string | No | markdown | Output format: |
timeout | integer | No | 30000 ( | Total timeout in milliseconds, 1000-120000, shared by all fallback / re-fetch attempts |
method | string | No | http ( | Fetch method: |
waitForSelector | string | No | - | Browser only: CSS selector to wait for before extracting, max 500 characters; if it never appears, the content rendered so far is returned |
maxLength | integer | No | 20000 | Max characters of content returned per call, 100-1000000; when exceeded the result has |
startIndex | integer | No | 0 | Character offset to start from (≥ 0); use |
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_pagepromptly 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:
Parameter | Type | Required | Default | Description |
query | string | Yes | - | Search keywords (Chinese works best), 1-200 characters after trimming |
count | integer | No | 10 | Number of results, 1-10 |
page | integer | No | 1 | Page number, 1-10 |
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_CONFIGenvironment variable./prismsearch.{yaml,yml,json,toml}in the current working directory~/.prismsearch/config.{yaml,yml,json,toml}(the directory followsPRISMSEARCH_HOMEif 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)
npm install playwright
npx playwright install chromiumThen enable the browser fetcher in your config:
fetcher:
browser:
enabled: true
browserType: chromiumOption B: Google Custom Search API (recommended, most stable)
Free quota: 100 queries/day per API key. Requires two credentials:
Credential | Config field | Environment variable |
API Key |
|
|
Search Engine ID (cx) |
|
|
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):
# Windows (persistent)
setx GOOGLE_API_KEY "AIza..."
setx GOOGLE_CSE_ID "0123456789abcdef0"
# macOS / Linux
export GOOGLE_API_KEY="AIza..."
export GOOGLE_CSE_ID="0123456789abcdef0"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:
npm run build
node scripts/dev/verify-google-e2e.mjs "openai"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:
# Windows
setx BRAVE_API_KEY "BSA..."
# macOS / Linux
export BRAVE_API_KEY="BSA..."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).
Variable | Description |
| Config file path (YAML / JSON / TOML); the CLI positional argument takes precedence |
| Data directory, default |
| Comma-separated access tokens; a non-empty value turns on |
| Shared fetch-worker token; used when |
| Google Custom Search API key (comma-separated for rotation) and search engine ID (cx) |
| Brave Search API key (comma-separated for rotation); Brave is skipped when unset |
|
|
| HTTP port (default |
|
|
| Override any config key, see below |
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.
PRISMSEARCH_SECURITY__RATE_LIMIT__MAX_REQUESTS=30 # security.rateLimit.maxRequests
PRISMSEARCH_ENGINES__BING__ENABLED=false # engines[name=bing].enabled
PRISMSEARCH_ENGINES__DUCKDUCKGO__ENABLED=true
PRISMSEARCH_CACHE__BACKEND=redis
PRISMSEARCH_CACHE__TTL=7200
PRISMSEARCH_FETCHER__BROWSER__ENABLED=true
# Arrays: JSON or comma-separated
PRISMSEARCH_SECURITY__SSRF__WHITELIST='["intranet.example.com","*.corp.example"]'
PRISMSEARCH_SERVER__ALLOWED_HOSTS=search.example.com,localhostValues 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.
Worker | Capabilities | Best for |
| JS-only pages, captcha interstitial pages | |
| Fingerprint-based anti-bot, cleaner content extraction |
# Start the server plus both workers (both tokens are required)
export PRISMSEARCH_AUTH_TOKENS=<server access token>
export PRISMSEARCH_WORKER_TOKEN=<shared worker token>
export PRISMSEARCH_WORKERS_ENABLED=true
docker compose -f docker/docker-compose.yml --profile with-workers up -d --buildThe 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:
workers:
enabled: true
strategy: least-busy # or round-robin (smooth weighted)
useFor: browser # browser: delegate render requests only; always: delegate everything
fallbackToLocal: true
endpoints:
- { name: ts-1, url: http://ts-worker:8787, weight: 100 }
- { name: py-1, url: http://py-worker:8788, weight: 50 }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:
Image | Contents |
| Main server (HTTP mode, port 3000) |
| Browser-rendering worker (Playwright, port 8787) |
| Fingerprint-impersonation worker (curl_cffi + trafilatura, port 8788) |
Run the main server on its own:
docker run -d -p 127.0.0.1:3000:3000 \
-e PRISMSEARCH_AUTH_TOKENS=<access token> \
ghcr.io/xiaonerner0821/prismsearch:latestThe image listens on
0.0.0.0inside the container in HTTP mode, soPRISMSEARCH_AUTH_TOKENSis required; without it the server refuses to start. Clients sendAuthorization: Bearer <access token>The example publishes the port on the host's
127.0.0.1only; put a TLS reverse proxy in front if you need to serve beyond the hostThe data directory is
/app/data(PRISMSEARCH_HOME); mount a volume there to persist the cache. To use your own config file, mount it and pointPRISMSEARCH_CONFIGat itThe main image has no browser (Playwright is not installed and
fetcher.browseris off). For JS rendering, run ts-worker and enableworkersin config (see the previous section)
With compose (run from the repository root):
export PRISMSEARCH_AUTH_TOKENS=<access token>
docker compose -f docker/docker-compose.yml up -d
# Also start both workers and delegate to them (PRISMSEARCH_WORKER_TOKEN is required too)
export PRISMSEARCH_WORKER_TOKEN=<shared worker token>
PRISMSEARCH_WORKERS_ENABLED=true docker compose -f docker/docker-compose.yml --profile with-workers up -d
# Use the Redis cache (a password is required; the redis container refuses to start without one)
export PRISMSEARCH_REDIS_PASSWORD=<random long string>
PRISMSEARCH_CACHE_BACKEND=redis docker compose -f docker/docker-compose.yml --profile with-redis up -dThe 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 timeHost / Origin checks: guard against DNS rebinding and cross-site requests; allow more with
server.allowedHosts/server.allowedOriginsRate limiting: per-client buckets in HTTP mode (credential fingerprint or address); at the tool layer in stdio mode
SSRF guard:
fetch_pageand 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 hopErrors 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.blacklisttakes precedence over the whitelistThe whitelist matches host names only; literal IP URLs are still checked by IP rules
Worker
allowedOriginsentries must be exact origins without a trailing slash; non-loopback origins require a token and HTTPS
Development
# Install dependencies
npm install
# Build
npm run build
# Run in development mode
npm run dev
# Unit tests (offline)
npm test
# Tests with coverage (enforces minimum thresholds)
npm run test:coverage
# Live smoke tests against real search engines (opt-in, not run by default)
npm run test:live
# Lint and typecheck
npm run lint
npm run typecheck
# Worker tests
npm run test:worker # TS Worker
npm run test:worker:py # Python Worker (create the venv first, see CONTRIBUTING)
# Pre-release self-check: build + artifacts / version / required files / stdio handshake
npm run selfcheckOptional 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
prismsearch/
├── src/
│ ├── index.ts # Entry point
│ ├── version.ts # Single source of truth for version (reads package.json)
│ ├── server/ # MCP server & transports (stdio / Streamable HTTP)
│ ├── tools/ # MCP tool implementations (5 tools)
│ ├── engines/ # Search engine providers (google/bing/baidu/brave/
│ │ # duckduckgo/yandex/so/sogou/wechat)
│ ├── fetcher/ # HTTP & browser (Playwright) fetchers, content extraction
│ ├── workers/ # Fetch worker client (balancing, health checks, failover)
│ ├── cache/ # Caching layer (SQLite / Redis / in-memory)
│ ├── config/ # Configuration system (YAML/JSON/TOML + env overrides + hot reload)
│ ├── security/ # SSRF guard, rate limiter, auth, content filter
│ └── utils/ # Logger, dedup, API key rotation, proxy pool
├── config/ # Default & example configuration
├── docker/ # Dockerfile & docker-compose
├── docs/ # Protocol and design documents
├── scripts/ # Version / self-check scripts; dev/ holds dev verification scripts (see CONTRIBUTING)
├── tests/ # Unit & integration tests
└── workers/ # Optional remote fetch workers
├── ts-worker/ # Playwright rendering (Node)
└── py-worker/ # curl_cffi impersonation + trafilatura (Python)Roadmap
WebSocket long connection transport
Image search support(v0.3.1)Headless browser fetcher (Playwright)(v0.3.1)Docker deployment(v0.3.1)API key management & rotation(v0.3.1)Authentication for remote mode(v0.3.1)Redis cache backend(v0.3.1)Remote fetch workers (browser rendering / fingerprint impersonation)(v0.3.1)Worker load balancing & failover(v0.3.1)Content safety filtering(v0.3.1)
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.
Available Tools
5 toolsfetch_pageFetch PageARead-onlyIdempotent
Fetch a public web page and return it as markdown, plain text or HTML. Private/internal addresses are blocked. Use method=browser for JavaScript-rendered pages.
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | URL of the webpage to fetch (http/https only) | |
| format | No | Response format | markdown |
| method | No | http (fast) or browser (renders JavaScript) | |
| timeout | No | Timeout in milliseconds (1000-120000) | |
| maxLength | No | Max characters of content to return (default 20000); use nextStartIndex to read the rest | |
| startIndex | No | Character offset to start from (for reading long pages in segments; default 0) | |
| waitForSelector | No | Browser only: CSS selector to wait for before extracting |
Output Schema
| Name | Required | Description |
|---|---|---|
| url | Yes | |
| title | No | |
| cached | No | |
| content | Yes | |
| rawHtml | No | |
| warnings | No | |
| fetchTime | Yes | |
| fetchedBy | No | |
| truncated | No | |
| statusCode | Yes | |
| contentType | No | |
| totalLength | No | |
| bodyTruncated | No | |
| contentLength | No | |
| nextStartIndex | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare this is a safe, idempotent, open-world read, so the bar is lower. The description still adds real value beyond them: the SSRF guard on private/internal addresses and the JS-rendering trade-off for method=browser. It does not cover redirect/auth behavior, rate limits, or truncation semantics for long pages.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three short sentences, each with a distinct job: what it does, what is blocked, which mode to pick. The capability statement is front-loaded and nothing is repeated from the schema.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Output schema and full param coverage exist, so return values and parameters need no explanation here. The description leaves minor gaps around long-page truncation and failure behavior, though those are partly addressed in the schema's startIndex/maxLength notes.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
With 100% schema coverage, the schema already documents all seven parameters, so the baseline would be 3. The description earns an extra point by telling the agent when to pick browser over http (JavaScript-rendered pages), which is the one decision the schema enum alone leaves ambiguous.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (fetch) and resource (public web page) plus the return representations (markdown, text, HTML). This is unmistakably distinct from the sibling search tools (web_search, image_search, wechat_search, list_engines), which retrieve links rather than page content.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
'Use method=browser for JavaScript-rendered pages' gives concrete selection guidance for the one ambiguous mode choice, and 'Private/internal addresses are blocked' sets a clear scope boundary. It stops short of explicit routing against siblings (e.g., 'call web_search first to discover URLs'), so no exclusions are named.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
image_searchImage SearchARead-onlyIdempotent
Search images across engines that support it. Optionally inline small thumbnails as base64 data URIs.
| Name | Required | Description | Default |
|---|---|---|---|
| page | No | Page number (1-10) | |
| count | No | Number of results (1-50) | |
| query | Yes | Search keywords | |
| engine | No | Image-capable engine to use; auto-selected (with fallback) if omitted | |
| region | No | Region code, e.g. cn, us, hk | |
| language | No | Language code, e.g. zh-CN, en | |
| imageSize | No | Filter by image size | |
| imageType | No | Filter by image type | |
| safeSearch | No | Enable safe search | |
| includeThumbnails | No | Inline small thumbnails as base64 data URIs (max 20, 50KB each) |
Output Schema
| Name | Required | Description |
|---|---|---|
| page | Yes | |
| cached | Yes | |
| engine | Yes | |
| hasMore | Yes | |
| results | Yes | |
| warnings | No | |
| searchTime | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly, idempotent, non-destructive, open-world behavior, so the safety profile is covered. The description adds genuinely new context: that small thumbnails can be inlined as base64 data URIs, which affects response size and payload handling — a behavior not visible in annotations or the schema's short parameter note.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two tight sentences with the core action front-loaded and no filler. The second sentence largely restates the includeThumbnails parameter already documented in the schema, which is mild redundancy but not wasteful.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With 10 fully documented parameters and an output schema present, the description needn't explain return values, and pagination/limits are covered in the schema. The only real gap is the absence of guidance on choosing between this and the sibling search tools.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, including enums, defaults, ranges, and the engine auto-fallback note, so the schema carries the semantic load. The description adds nothing beyond what the schema already documents about parameters, making the baseline 3 appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource ('Search images') and the scope ('across engines that support it'), which distinguishes it from the generic web_search sibling. It stops short of explicitly naming web_search or list_engines as the alternative, so differentiation is implied rather than stated.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
No explicit when-to-use/when-not guidance and no mention of the obvious alternatives (web_search for text results, list_engines for discovering image-capable engines). The phrase 'engines that support it' hints that some engines are image-incapable, but the agent must infer when to prefer this tool.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_enginesList EnginesARead-onlyIdempotent
List all configured search engines with their enabled state, health, latency, success rate and supported tools.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| engines | Yes | |
| workers | No | |
| totalEnabled | Yes | |
| totalAvailable | Yes | |
| totalConfigured | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The annotations already declare readOnlyHint=true, idempotentHint=true, destructiveHint=false, and openWorldHint=false, so the safety profile is covered. The description adds that it returns all configured engines with health and performance data, but it does not add significant behavioral context such as auth requirements, rate limits, or pagination behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single front-loaded sentence that communicates the action, scope, and returned information with no wasted words. It is appropriately sized for a zero-parameter listing tool.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With zero parameters, a rich set of annotations, and an output schema available, the description provides enough context for an agent to understand what the tool does. No additional explanation of return values or parameter behavior is needed.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so there is no parameter semantics to explain and the baseline is 4. The description correctly does not invent or misrepresent any parameters.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb ('List') and resource ('all configured search engines') and enumerates the returned fields, making the purpose unambiguous. It is clearly distinguishable from the sibling search/fetch tools because it reports engine configuration and health rather than performing searches.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies this is for inspecting configured engines and their status, but it does not explicitly state when to use it instead of the search tools. There are no exclusions, prerequisites, or named alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
web_searchWeb SearchARead-onlyIdempotent
Search the web across multiple engines (Google, Bing, Brave, Baidu, DuckDuckGo, Sogou, 360, Yandex) with automatic engine selection and fallback. Returns titles, URLs and snippets.
| Name | Required | Description | Default |
|---|---|---|---|
| mode | No | fallback: one engine, switch on failure (default). aggregate: query 2-3 engines concurrently and merge results by rank fusion | fallback |
| page | No | Page number (1-10) | |
| count | No | Number of results (1-50) | |
| query | Yes | Search keywords | |
| engine | No | Search engine to use; auto-selected (with fallback) if omitted | |
| region | No | Region code, e.g. cn, us, hk | |
| language | No | Language code, e.g. zh-CN, en | |
| timeRange | No | Only return results published within the past day/week/month/year (engines without support ignore it and add a warning) | |
| safeSearch | No | Enable safe search |
Output Schema
| Name | Required | Description |
|---|---|---|
| page | Yes | |
| cached | Yes | |
| engine | Yes | |
| engines | No | |
| hasMore | Yes | |
| results | Yes | |
| warnings | No | |
| searchTime | Yes | |
| totalResults | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly, idempotent, openWorld and non-destructive, so the safety profile is covered. The description adds genuine behavioral context beyond that: automatic engine selection with fallback, and the shape of returned results. Rate limits and per-engine quirks are left to the schema.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two tight sentences, front-loaded with the core action and engine scope, then the return shape. No filler or redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists so return values needn't be explained, and the description still names titles/URLs/snippets. With full schema coverage and thorough annotations, the only gap is the missing routing guidance against sibling tools.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% with rich per-parameter descriptions including the fallback/aggregate mode semantics, so the schema already carries parameter meaning. The description adds nothing about parameters beyond restating the engine list, making baseline 3 appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Search the web across multiple engines') and enumerates the supported engines, so the agent knows exactly what the tool does. It does not explicitly contrast with siblings like image_search or fetch_page, leaving differentiation implicit.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is implied by the name and engine list, but there is no explicit 'use this when...' guidance and no mention of when to prefer fetch_page, image_search or wechat_search. The agent must infer the routing decision itself.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
wechat_searchWeChat Article SearchARead-onlyIdempotent
Search WeChat Official Account (微信公众号) articles via Sogou. Returns titles, account names, dates and short-lived signed URLs — fetch them promptly with fetch_page.
| Name | Required | Description | Default |
|---|---|---|---|
| page | No | Page number (1-10) | |
| count | No | Number of results (1-10) | |
| query | Yes | Search keywords (Chinese works best) |
Output Schema
| Name | Required | Description |
|---|---|---|
| page | Yes | |
| cached | Yes | |
| engine | Yes | |
| engines | No | |
| hasMore | Yes | |
| results | Yes | |
| warnings | No | |
| searchTime | Yes | |
| totalResults | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover read-only, idempotent, non-destructive, open-world behavior, so the bar is lower; the description adds a genuinely non-obvious trait — signed URLs are short-lived and must be fetched promptly. It omits rate limiting/anti-bot behavior from Sogou and any language caveat outside the schema, keeping it below a 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, zero waste: purpose first, then the critical ephemeral-URL caveat with the next tool named. Every clause earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return fields need not be enumerated, yet the description usefully flags the time-sensitivity that the schema cannot convey. Minor gaps remain around result quality/rate-limit caveats for a scraped source, but nothing an agent needs to call it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and all three parameters (query, page, count) carry their own descriptions with bounds, so the description adds no parameter-level meaning. Baseline 3 applies since the schema does the work.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource ('Search WeChat Official Account (微信公众号) articles via Sogou'), including the underlying provider, which no sibling offers. The scope is narrow enough that an agent can distinguish it from web_search and image_search without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit follow-up guidance — fetch returned URLs promptly with fetch_page — which is a clear usage sequence. It doesn't state when to prefer web_search instead (e.g., non-WeChat content), so usage context is clear but exclusions are absent.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
5 tool updates
v0.1.0- First observed
fetch_page - First observed
image_search - First observed
list_engines - First observed
web_search - First observed
wechat_search
TDQS
Scored across 5 tools
Each tool has a clearly distinct target: general web search, image search, WeChat-article search, raw page fetching, and engine introspection. The two search variants (wechat_search vs web_search) are explicitly scoped by source, so misselection is unlikely. fetch_page and list_engines occupy non-overlapping roles.
All names are snake_case and readable, with a coherent modifier+action pattern (wechat_search, image_search, web_search). The only deviation is list_engines, which puts the verb first, but it still fits the overall convention.
Five tools is a tight, well-scoped surface for a multi-engine search server, with no redundant or filler entries. Every tool maps to a distinct capability an agent would need.
The surface covers web, image, and WeChat search plus page retrieval and engine health — the core lifecycle for a search server. Minor gaps exist around result pagination/limits, batch fetching, or non-WeChat site-specific search, but agents can work around these via parameters and repeated calls.
Maintenance
Related MCP Connectors
Free web search for AI agents. No API key required. Hosted MCP in active development.
Your agent needs the open web — searched by more than one engine, and read as clean markdown rather than raw HTML. **What you can ask for** • "Search this question with two providers and tell me where they disagree." • "Scrape these 40 URLs into markdown, in one batch." • "Crawl this documentation site and give me every page." • "Do deep research on this topic and cite the sources." • "Find the academic papers behind this claim." **How to use it** Point any MCP client at https://mcp.aisa.one/search/mcp and sign in with OAuth — there is no key to create or paste. 30 tools across several independent providers: Tavily and Exa search, answers, contents and agent runs; Firecrawl scrape, batch scrape, crawl, map and search; Perplexity Sonar, Sonar Pro, reasoning and deep research; Oxylabs AI search and LLM jobs; OpenAI and Anthropic web search; and scholarly search. **Why this rather than the source** Several independent indexes behind one account, because one engine's blind spot is not visible from inside it. **It is also a door to the rest** The same login reaches 26 sources and 580+ operations. Find the page here, then ask the same agent who links to it or how much traffic it gets — without adding a second server. **What it costs** Finding and inspecting an operation is free. Running one is billed per call at API prices, with no seat and no monthly minimum, and every call takes max_price_usd so an agent cannot overspend by accident. **Where else it reaches** https://mcp.aisa.one/seo-serp/mcp for the Google results page itself, https://mcp.aisa.one/seo-serp-other-engines/mcp for Bing, Baidu and Naver.
Multi-engine search for AI agents. Trust scoring, local corpus, MCP-native. Self-hostable, BYOK.
Docs: https://docs.keenable.ai/mcp-server Keenable is a free, remote MCP server that gives agents access to the web index. Search the web with ranked results and date/site filters, then fetch any indexed page as clean markdown. Works out of the box with no account or API key.
Related MCP Servers
- AlicenseAqualityBmaintenanceEnables AI agents to perform multi-engine web search, fetch web pages, and extract clean Markdown content via MCP, with no API keys required.377 PyPI8MIT
- AlicenseNot gradedqualityAmaintenanceEnables LLMs to run web and academic searches across multiple engines (Baidu, Bing, DuckDuckGo, Tavily, Exa, Doubao and 9 scholarly sources) with local relevance scoring, deduplication and caching, plus SSRF-protected page fetching and PDF parsing, and works without any API key. It plugs into MCP clients like Claude Code, Cursor and Qwen Code, or can be embedded as a Go module.26MIT
- AlicenseAqualityBmaintenanceEnables any MCP client to run keyless multi-engine web searches with RRF fusion, fetch pages and documents as Markdown, and full-text search a local SQLite index. It keeps queries and cached content on the user's machine, degrading gracefully across engine tiers when providers are blocked or rate-limited.8MIT
- FlicenseNot gradedqualityAmaintenanceEnables AI agents to search the web through configured providers and crawl individual pages or entire sites into clean Markdown or JSON, including authenticated browsing and deduplicated output. It can run locally over STDIO or HTTP and integrates with MCP clients and command-line workflows.-