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"]
bindoff 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 callerlocal.allowed_hostslists the names the proxy forwards in theHostheader. A bind on0.0.0.0or::requires this list. Any otherHostis 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.