Self-hosted web search that reports how much of each answer to believe
Copy the AI prompt to install this server into Claude Code, Cursor, or another agent β or use 1-click editor setup below.
One-click editor setup isnβt available for this listing yet β we donβt have a confirmed install command, and weβd rather show nothing than point your editor at the wrong package or host. Follow the projectβs own setup instructions, linked above.
A search tool fails in ways that look exactly like success. An engine answers with somebody else's subject. A page returns text that is an anti-bot shield. Sixteen sources turn out to be two engines counted eight times. None of that raises an error, and the model on the other end builds on it.
This server's job is to make those cases distinguishable, in fields you can branch on. It runs on your machine, over your own metasearch instance, with your own model key β or none at all.
| Tool | What it does | Required | Notable options |
|---|---|---|---|
web_search | Finds pages and reads the top ones β one call, links with their text | query | read: false for links only Β· read_top how many to read Β· min_engines to force breadth Β· engines to name the engines yourself |
web_read | Reads pages by address: text, PDF, or a scan recognised by a vision model | urls | mode: browser for JS-rendered pages Β· expect to assert what must be there Β· offset to continue |
web_image_search | Finds images: the address of the FILE and, separately, of the page it sits on | query | max_results, page |
web_screenshot | A PNG of a page plus its text from the same visit, so the two can be cross-checked | url | max_chars for how much text Β· full_page Β· expect |
web_deep_search | Composes its own queries, reads in waves, and answers from several sources β saying what it could not confirm | question | waves |
Full argument reference, response shapes and failure modes: HOWTO-CALL.md.
From an open repository page to a working answer. Nothing is assumed to be on your disk already:
The fourth and fifth lines are not decoration. Without a value in
SEARXNG_SECRET the up command refuses β and that refusal is deliberate: with
no key of its own the metasearch does not fail, it comes up with a publicly known
one from its image template, silently. BROWSER_WS_SECRET is the secret path of
the browser sidecar's endpoint, and up refuses without it too: on a guessable
path, anything that can open a WebSocket to the sidecar β any container on the
compose network, and any page the browser itself opens β gets a browser of its
own, outside every check the module applies to the pages it reads. The compose
file hands the same value to the sidecar and, at the end of BROWSER_WS_URL, to
the adapter; neither prints it.
The sixth line creates the two paths the compose file mounts for caller
authentication and TLS β both off by default. A path that does not exist is
created by Docker as a root-owned DIRECTORY, and a token "file" that is a
directory reads as an unreadable list (see Configuration below).
Both are in .gitignore: they hold secrets once you use them.
The overlay publishes the port on loopback only. A published container port does not go through the host firewall's usual chain, so exposing it more widely is a separate, deliberate step β see Deployment.
MCP has two, and they answer different questions. HTTP β the commands above β is for a server that is already running somewhere. stdio is the protocol's default: the client starts the server as a process and talks to it through the pipes, which is how most desktop clients and wrappers work.
One JSON-RPC object per line in, one answer per line out. The mode is chosen explicitly and never guessed from whether a terminal is attached β that sign merely sits next to the subject, and one day it answers for a case nobody meant.
In stdio mode stdout is the protocol: answers and nothing else, with the log on stderr. One stray line of anything else breaks the client reading it.
Each process started this way is an instance of its own, even inside the
running container: its browser sessions (BROWSER_MAX_SESSIONS), its engine and
domain pacing and its /stats counters are separate from the HTTP server's.
Several stdio clients on one sidecar add up in its pids_limit like several
adapters do.
The sidecars do not depend on the choice; the metasearch does: SEARXNG_URL
must reach one, or every search refuses with cause: upstream (the default,
http://searxng:8080, is a name only the compose network knows). Started by a
client with no compose project around it, the module names what else is missing
instead of pretending: the browser path reports not_wired_up, and trouble
carries pool_unmeasured β the engine pool was never computed from observation.
From 0.3.4: the same command as the install block, with the same -f files.
Only the adapter image changes (ag-mod-search/adapter:0.3.5, used by the
adapter and the prober); the sidecar stays ag-mod-search/browser:1.49.1-3.
Two things you may notice: the door now logs one line per request (the
caller's address, the path, the status and the caller's token name β never the
token, the body or the query string), and a model call that runs out of the
time its caller had now returns a refusal that says so instead of running on.
From 0.3.3 or earlier, the 0.3.4 steps below apply too.
From 0.3.3: the same command as the install block, with the same -f files.
Only the adapter image changed; the sidecar stays ag-mod-search/browser:1.49.1-3.
Check AG_DOOR_AUTH first: a value that is not off, warn or on (in any
case, spaces around it ignored) now stops the adapter from starting, where it
used to mean off. From 0.3.2 or earlier, the 0.3.3 steps below apply too.
BROWSER_WS_SECRET must be in .env before up: add it as the install block
does, with openssl rand -hex 16. Then run the install block's own command,
with the same -f files you installed with:
docker compose -f docker-compose.yml -f wiring/expose-localhost.yml up -d --build.
It rebuilds both images and recreates the adapter, the prober and the sidecar,
since the image or the environment of each changed. Name no services on it:
up β¦ browser ag-search leaves the prober on the old image, and leaving out
the -f overlay recreates the adapter without its published port. The sidecar
image is ag-mod-search/browser:1.49.1-3; with the old sidecar, or with only one
side recreated, the browser path reads "not responding".
A malformed secret stops the whole module. Compose checks only that the value
is not empty. Characters outside A-Z a-z 0-9 _ - (base64 gives + / =) make the
sidecar exit with 78 in a loop; the adapter waits for a healthy sidecar, so it
never starts and up says "dependency failed to start".
If you run the door in warn or on, also read "Moving to on" under
Configuration: the prober now takes a token, a token with no scopes (or with
scopes written as a string) may call no tool, and /healthz?deep=1 needs a
token that may search or read.
If you already run 0.2.1 or earlier, read this before updating. Nothing here is a new feature you may ignore; it is what your existing calls will return differently.
The search and image answers carry fewer fields by default, and they say so
in their name: ag.search/3 and ag.images/3 instead of /2. A caller that
branched on contract will break loudly, which is the intent β a field that
simply vanished would read as "nothing was wrong" in most languages.
| in 0.2.x | in 0.3 |
|---|---|
search_aborted, engines_unasked | trouble.search_aborted, trouble.engines_unasked |
unresponsive_engines | trouble.unresponsive_engines |
engines_irrelevant | trouble.engines_irrelevant |
pool_source: "seed" | trouble.pool_unmeasured, with the reason |
arguments_adjusted | trouble.arguments_adjusted |
corroborated_by_url, corroborated_by_domain β always present, 1 on the cheap path | absent when only one engine found results: there 1 meant "nobody else was asked", not a measurement |
count, query, page, read, read_top, pages_*, timing_ms, engines_skipped, engines_used, tiers_used, pool_source, pool_reason | returned when you ask: verbose: true |
trouble is always present and empty when nothing went wrong, so if not trouble replaces the four separate checks. Nothing was deleted from the module β
the accounting moved behind a request.
No reviews yet β be the first to share how this listing worked for you.
Showcase your server listing on GitHub or your project documentation. Embed this dynamic SVG badge to highlight official listing status and live engagement.
[](https://allmcps.com/mcp/search-that-shows-its-work)<a href="https://allmcps.com/mcp/search-that-shows-its-work"><img src="https://allmcps.com/api/badge/search-that-shows-its-work?style=directory" alt="Search that shows its work on AllMCPs" /></a>