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 4: network mode

What you will have at the end. rypsok listening over HTTP behind a reverse proxy, two callers with their own tokens and their own rate limits, and a runtime connecting to it.

What you need. Tutorial 1 done, a reverse proxy that terminates TLS (any of the usual ones), and a way to give each caller its token.

1. The configuration

# Tutorial 4: Streamable HTTP behind a reverse proxy that terminates TLS.
# A bind off loopback requires callers; each caller's token is read from
# the environment or a file, never written here.
config_version = 1

[service.http]
bind = "0.0.0.0:8737"
allowed_hosts = ["rypsok.example", "rypsok.example:443"]
legacy_revisions = true

[[service.http.auth.callers]]
id = "mimlys"
token_env = "RYPSOK_TOKEN_MIMLYS"

[[service.http.auth.callers]]
id = "ops"
token_file = "/run/secrets/rypsok-ops"

[engines.wikipedia]
type = "wikipedia"
categories = ["general", "science"]
  • bind off loopback (0.0.0.0, or an address) requires callers. The default, 127.0.0.1:8737, needs none: on loopback every request is the caller local.
  • allowed_hosts lists the names the proxy forwards in the Host header. A bind on 0.0.0.0 or :: requires this list. Any other Host is refused with 403.
  • A caller has an identifier and a token read from the environment or a file. A token is at least 32 bytes. Generate one:
openssl rand -base64 32

2. Start and check

export RYPSOK_TOKEN_MIMLYS=...   # the token of the caller "mimlys"
rypsok check --config rypsok.toml
rypsok serve --transport http --config rypsok.toml

The log’s first line is listening with the address and the number of callers. rypsok does not terminate TLS: put the proxy in front, forward /mcp to 127.0.0.1:8737 on the rypsok host, and let the proxy carry the TLS certificate.

3. Call it

Every request is a POST to /mcp with the bearer token and the three headers of the protocol revision beside the JSON body. The Accept header lists both application/json and text/event-stream: the protocol requires a client to list both, and the server answers 406 to a request that lists one:

curl -sS https://rypsok.example/mcp \
  -H "Authorization: Bearer $RYPSOK_TOKEN_MIMLYS" \
  -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \
  -H 'MCP-Protocol-Version: 2026-07-28' -H 'Mcp-Method: tools/call' -H 'Mcp-Name: engine_status' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"engine_status","arguments":{},
       "_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28",
                "io.modelcontextprotocol/clientInfo":{"name":"probe","version":"0"},
                "io.modelcontextprotocol/clientCapabilities":{}}}}'

The answer is the same JSON the stdio transport gives. What a status means: 401 the token is missing or wrong; 403 a Host not in the list, or an Origin that is not allowed (browsers never call rypsok, so none is allowed unless listed); 404 a path other than /mcp; 413 a body above the limit; 400 a missing protocol header; 406 an Accept that does not list both media types; 415 a body that is not a JSON-RPC message; 408 a request that took longer than request_timeout_s; 503 (with Retry-After) a legacy handshake while max_sessions sessions are open. An unknown tool is not an HTTP error: the answer is 200 with the JSON-RPC error unknown tool in its body. Troubleshooting lists them with their fixes.

4. Connect a runtime

{
  "mcpServers": {
    "rypsok": {
      "url": "https://rypsok.example/mcp",
      "headers": {
        "Authorization": "Bearer ${RYPSOK_TOKEN_MIMLYS}"
      }
    }
  }
}

Each caller has its own rate limits; one caller that exhausts its limit does not slow another.

5. Run it as a service

The deployment page has a systemd unit with the token as a credential and the outbound traffic restricted at the operating-system level, and the container image.

Next: Tutorial 5: the scraping switch.