Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Troubleshooting

Start with rypsok check --config <file>: it validates the file, prints the effective settings and each engine’s state, and names the key of every problem. Then look at the logs: standard error, one JSON object per line, level info by default ([logging] level). Nothing rypsok says ever goes to standard output except protocol messages.

At startup

SymptomCauseFix
check exits 1 and names a keyThe value is outside its range or of the wrong form; the message says whichChange the named key; the configuration page lists every range
search.max_results: expected an integer from 1 to 20, found text (line 3, column 15)A value of the wrong kind: text for a number, a decimal for an integer, a negative or too large number. The message gives the key, what it holds, what was found (a text is never repeated) and the placeWrite the value as the page says (true and false without quotes, numbers without quotes)
TOML syntax error: ... (line 1, column 6)The file is not valid TOML at that placeFix the place named; the same line and column are in the file as you see it
engines.w: defined more than once (line 5, column 10)A table or a key appears twiceRemove or merge one
search.engine_timeout_ms: must be between 100 and 300 (search.deadline_ms 500 minus search.reserve_ms 200)The range of this key depends on other keys; the message names them with their valuesChange this key, or the one it names
egress.deny[0]: a host pattern must not end with a dotA rule written as blocked.invalid.Write it without the dot: blocked.invalid
engines.<id>.api_key: give the secret by api_key_env or api_key_fileThe key was written into the fileMove it to an environment variable or a file and name it
engines.<id>.type: no such driverA driver name that does not existOne of wikipedia, brave, duckduckgo
... scrapes a human-facing interface; it runs only with [scraping] enabled = true ...A scraping engine without the switchRead Tutorial 5, then decide
service.http.bind: a non-loopback bind needs [service.http.auth] callersA bind off loopback without tokensAdd callers, or bind on loopback
service.http.bind: a bind on an unspecified address (0.0.0.0, ::) needs service.http.allowed_hostsNo Host name a client would sendList the names the proxy forwards
An engine line ends with inactive: environment variable ... is unset or emptyThe key is not in the environment of the process that runs rypsokExport it where rypsok starts (a service has its own environment)
...: the value is not the name of an environment variable (it is not shown)api_key_env or token_env holds something that is not a variable name (upper-case letters, digits and underscores): most often the secret itself, pasted into the key that should name the variablePut the secret in the environment (export BRAVE_API_KEY=...) and write only its name in the file; rotate the secret if the file was shared, since it was written there
Exit code 2A usage error on the command linerypsok --help

During searches

SymptomCauseFix
unavailable with reason no_engineNo enabled, active engine serves the category askedCheck categories of the engines and the engines’ state in check
Every search is partial with one engine auth_failureThe provider refuses the keyCheck the key and the subscription. The key is read at startup; a corrected key takes effect at the next restart, and the engine keeps being asked meanwhile, so no cooldown delays it
partial with rate_limitedThe provider’s or the engine’s rate is used up; the notice may carry when to retryWait; lower requests by asking for fewer engines, or raise the plan
partial with timeout for one engine, oftenThe provider is slow for the deadline configuredRaise [search] engine_timeout_ms within deadline_ms, or accept
engine_status shows an engine openIt failed several times in a row and rests; after the cooldown one call probes itNothing to do; fix the cause of the failures if there is one (key, network, provider)
denied with reason secret_in_queryThe query carries what looks like a credential, at the position givenRemove it; or set [privacy] secret_detection to mask or warn if your use needs it
empty for a query that should matchEngines search literally; a category mismatch asks the wrong enginesFewer words; the right category
Results arrive slowlyThe deadline is the ceiling, not the usual case; one slow engine is waited for up to engine_timeout_msLower the timeouts or the number of engines per request

Reading pages

SymptomCauseFix
denied with reason egressThe URL names a loopback, private, link-local or metadata address literally, or a port that is not listed, or a host name that never names a public site (localhost, a single label, *.local)Nothing for a private address: it is refused by design. For another port, add it to [fetch] ports; for a host you trust, an [egress] allow rule
unavailable with reason networkThe site could not be reached, for whatever reason: no such name, a refused or dropped connection, a name that resolves only to addresses the policy refusesRetry later or choose another page. The answer is the same whatever the reason, so that it tells nothing about names inside your network; the operator’s log says whether the policy refused it (refused, with the denial’s name and no host)
denied with reason robots_txtThe site’s robots.txt disallows the page for rypsok or *, or the page a redirect led toRespect it; [fetch] robots_txt = false turns the check off, at your risk
denied with reason strict_modeStrict mode is on and the URL was not returned by a search in this processFetch what a search returned, or list the host in [egress] allow
denied with reason schemeEither the URL is not http or https at all (ftp://…), or it is a plain http URL, the operator did not allow plain HTTP, and the retry over https failed; the message says whichUse an https URL. rypsok retries http over https first; set [egress] allow_http = true only if you must
redirectedThe page moved to another hostCall again with detail.location, or set [fetch] follow_cross_host_redirects = true
unavailable with http_statusThe site answered 4xx or 5xx; the status is in detail.status, the body is never returnedFor 408, 425, 429 and 5xx: later (the message ends “retry later”; wait retry_after_ms when present). For another 4xx (404, 403, 410): the site refused the request or has no such page, and the same URL will not help; use another page
unsupported_contentNot HTML, XHTML, plain text or Markdown; detail.media_type says what it wasAnother page
too_largeMore bytes than [fetch] max_bytesRaise the limit within its range, or another page
content is short or misses the part you wantedThe extractor chose another block as the main content, or the page builds its content with scripts, which rypsok does not runmax_chars higher does not help; another page or the site’s text version

Over HTTP

StatusCauseFix
200 with a JSON-RPC error -32602, unknown toolA tools/call for a tool rypsok does not serve: the error is in the answer, not in the HTTP status (HTTP 400 before 0.10.0). engine_status is not served when [status] enabled = falseUse a name from tools/list
400A protocol header (MCP-Protocol-Version, Mcp-Method, Mcp-Name) is missing or does not agree with the body, or a POST of a handshake-based revision that is not initialize carries no session (Session ID is required; HTTP 422 before 0.10.0) (answered by the SDK; counted, not logged per request)Send the headers (Tutorial 4); for a handshake revision, initialize first and the session id after
401The bearer token is missing or wrong (the gate)The token of a configured caller, at least 32 bytes
403The Host header is not in allowed_hosts, or the request carried an Origin that is not allowed (answered by the SDK; logged as sdk_forbidden)List the host in allowed_hosts. Browsers never call rypsok; if yours must, list the origin in allowed_origins
404A path other than /mcp (the gate)/mcp
406Accept does not list both application/json and text/event-stream (a type with q=0 does not count; the types are matched without regard to case); the protocol requires a client to list both (answered by the SDK; counted, not logged per request)Send Accept: application/json, text/event-stream
408A request was still running after request_timeout_s (the gate)A slow client or proxy: raise request_timeout_s, or fix the link
413The body is above max_request_bytes (the gate, or the SDK for a streamed body)A tool call is small; check what the client sends
415The Content-Type is not application/json, or the body is not a JSON-RPC message, for example a request with an id of null (answered by the SDK)Send a JSON-RPC request with an integer or string id
431The request line and headers together are above 16 KiB (LIM-H7; answered by hyper before the gate, so neither logged nor counted; over HTTP/2 the connection is closed)Send fewer or shorter headers; a token is at most a few hundred bytes
503 with Retry-After: 30The handshake-based revisions hold max_sessions live sessions already (the gate); the 30 s is a hint, sessions expire after 300 s without trafficRetry later, use the 2026-07-28 revision (it keeps no session), or raise max_sessions
JSON-RPC error -32022 on initializelegacy_revisions = false and the client uses the handshake-based revisionsUse the current revision, or set legacy_revisions = true

Logs

Follow one call. Every event of a tool call has the same request_id and the caller. Find the call event of the call you care about (it has the tool, the outcome and the latency_ms), then select its id: jq -c 'select(.request_id == "01a2b3c4d5e-000007")' on the log gives that call’s engine events, its refusals and its end, in order, and nothing of another call that ran at the same time. jq -c 'select(.caller == "ops")' gives one caller’s calls. The engine_status tool logs a call event too.

Each line has an event. The ones you will look for: call (one per tool call with its outcome and latency), engine_failed (engine and class), refused (what was refused and why), http_refused, circuit_opened and circuit_closed, counters (every minute). The gate logs http_refused with a reason for too_many_connections, body_too_large, unauthorized, request_timeout, session_limit and sdk_forbidden (a foreign Host or Origin), with the address family and never the address or a header value. A missing protocol header (400) and a wrong Accept (406) are not logged per request; every refusal of the gate and of the SDK behind it is counted, per reason, in the counters event (refusals, under http). A connection that sent nothing, or only the first bytes of a request or of the HTTP/2 preface, and was closed after the idle time is counted there as idle_connection and not logged. The SDK writes its own warning for a refused Host or Origin, and one error line for each refused handshake. The query text never appears in a log unless [logging] log_queries = true at level debug, which you turn on only to diagnose and turn off again.