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

Tutorial 2: a keyed engine

What you will have at the end. Two engines answering each search, Wikipedia and Brave Search, with the key kept out of the file.

What you need. Tutorial 1 done, and a Brave Search subscription token.

1. Add the engine

# Tutorial 2: Wikipedia plus Brave Search. The key is read from the
# environment variable named here; an inline key is refused at startup.
config_version = 1

[search]
languages = ["en", "ja"]

[engines.wikipedia]
type = "wikipedia"
categories = ["general", "science"]
preference = 10

[engines.brave]
type = "brave"
categories = ["general", "news"]
preference = 20
api_key_env = "BRAVE_API_KEY"

Three things to notice:

  • api_key_env names the environment variable that holds the key. api_key_file is the alternative: a file whose content is the key. Writing the key itself into the file (api_key = "...") is refused at startup, so a configuration file is safe to share.
  • categories says what each engine answers. A search names a category (general by default); only engines serving it are asked.
  • preference orders engines when results tie; lower comes first.

2. Check twice

Without the key:

rypsok check --config rypsok.toml

The Brave line says inactive: environment variable BRAVE_API_KEY is unset or empty, and the exit code is still 0: an engine without its key is inactive, not an error, so the same file runs on a machine without the key. Now with it:

export BRAVE_API_KEY=...
rypsok check --config rypsok.toml

The Brave line now ends with credential resolved.

3. Search, and read the engines’ part

Run the pipe of Tutorial 1 again. Two engines answer; a result that both returned appears once, ranked higher. Ask for the engines explicitly with engine_status:

{
  "request": {
    "jsonrpc": "2.0",
    "id": 3,
    "method": "tools/call",
    "params": {
      "name": "engine_status",
      "arguments": {},
      "_meta": {
        "io.modelcontextprotocol/protocolVersion": "2026-07-28",
        "io.modelcontextprotocol/clientInfo": {
          "name": "tutorial",
          "version": "1"
        },
        "io.modelcontextprotocol/clientCapabilities": {}
      }
    }
  },
  "response": {
    "jsonrpc": "2.0",
    "id": 3,
    "result": {
      "resultType": "complete",
      "content": [
        {
          "type": "text",
          "text": "{\"engines\":[{\"id\":\"wikipedia\",\"latency_ms\":null,\"state\":\"closed\"}],\"notices\":[],\"outcome\":\"ok\"}"
        }
      ],
      "structuredContent": {
        "engines": [
          {
            "id": "wikipedia",
            "latency_ms": null,
            "state": "closed"
          }
        ],
        "notices": [],
        "outcome": "ok"
      },
      "isError": false
    }
  }
}

Each engine’s state is closed (in use), open (failing lately and skipped for a while), half_open (one probe in flight) or disabled (by configuration or for lack of a key; a disabled engine also has a reason, disabled or no_credential, which tells the two apart). latency_ms is the median of its recent requests.

4. When Brave refuses the key

A wrong or expired key gives every search the outcome partial with the notice {"code": "engine_unavailable", "engine": "brave", "reason": "auth_failure"}, and Wikipedia’s results. 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.

What Brave’s terms mean for you

Brave’s results may be used for inference and must not be stored beyond the call or redistributed; rypsok therefore never caches them, whatever the cache setting. Wikipedia’s results may be cached. Each engine’s record of its provider’s terms is in the maintainers’ part of this book.

Next: Tutorial 3: reading a page.