Reference
askrelay <verb> [flags]Everything listed here is in the running binary.
| Verb | What |
|---|---|
serve |
run the relay HTTP server (see config) |
invite <email> |
mint a single-use enrollment invite, print the invite URL |
enroll <invite-url> |
enrol this machine as a device; writes ~/.config/askrelay/ (0600) and prints the credential to paste when connecting a browser client. Flags: -name (your display name), -label (device label), -config, -force — all before the URL |
device list|revoke |
list or revoke enrolled devices; run on the relay host, opens the DB directly |
daemon |
run the local notifier: holds a socket to the relay and raises a desktop notification when mail arrives |
mcp |
run the local stdio MCP server — this is what you configure in your AI client. Proxies the relay’s tool surface, and redacts and signs on this machine |
version |
print the build version — dev unless stamped at build time |
help (also -h, --help) |
show usage |
daemon and mcp are deliberately separate processes: an MCP server’s stdout
belongs to the protocol and is spawned per client session, while the notifier is
long-running. Both read the same config, and both log to stderr only.
Planned · WP-12
The human-facing operate verbs inbox, approve, and status are not
built — approvals happen through your AI client’s MCP tools.
HTTP surface
Section titled “HTTP surface”| Route | Auth | Purpose |
|---|---|---|
GET /healthz |
— | liveness + version |
POST /enroll/{token} |
invite token | enroll a device, return its credential |
GET /.well-known/oauth-protected-resource |
— | RFC 9728 resource metadata |
GET /.well-known/oauth-authorization-server |
— | RFC 8414 authorization-server metadata |
GET /oauth/jwks |
— | Ed25519 verification key (JWK Set) |
GET / POST /oauth/authorize |
device credential on POST |
login page + authorization endpoint |
POST /oauth/token |
PKCE | token endpoint (authorization_code, refresh_token) |
POST /oauth/register |
— | Dynamic Client Registration (RFC 7591) |
POST /mcp |
Bearer access token | Streamable HTTP MCP endpoint |
GET /ws |
device credential | delivery push + ack |
TLS is terminated by the reverse proxy in front of the relay.
GET /ws
Section titled “GET /ws”/ws is push and ack, in that order and nothing else. The relay pushes
message frames down; the only frame it accepts back is ack with a message
id. Any other frame type is logged and dropped. A frame is capped at 128 KiB.
Revocation is re-checked three times over: when the socket opens, on every inbound frame, and on a 15-second reconcile ticker that severs idle sockets belonging to a device revoked out of band.
Shipped
/ws is the daemon’s transport and is in use: askrelay daemon connects
with its device credential and receives message frames on delivery, plus
sign_request frames when one of its user’s drafts is approved (it answers
signature or sign_refused). The relay still accepts no outbound envelope
on /ws — a daemon cannot submit or release a message, only sign one the human
already approved, so the approval gate stays server-side and authoritative.
MCP tools
Section titled “MCP tools”An authenticated client sees these ten tools. Every inbound message rendered to a client is spotlighted (quarantined) — see Concepts.
| Tool | What it does |
|---|---|
find_people |
list the people you can message (email + name), to resolve a name to an address |
send_message |
start or continue a thread by sending a question/message |
check_inbox |
list threads with something awaiting you |
get_thread |
read a thread’s messages and state |
approve_message |
approve an inbound message (the inbound gate) |
decline_message |
decline an inbound message |
approve_reply |
release a drafted reply (the outbound gate). Its result reports attestation: device-signed when your daemon signed the exact approved bytes, relay-attested otherwise |
discard_reply |
discard a drafted reply |
set_thread_grant |
grant or revoke standing per-thread approval for a direction |
wait_for_activity |
long-poll until there’s activity, bounded by your client profile |
Exactly four carry the ReadOnlyHint annotation: find_people, check_inbox,
get_thread, and wait_for_activity. The other six change state and are
annotated as such.
wait_for_activity ceilings
Section titled “wait_for_activity ceilings”You ask for a timeout; the relay caps it by the client profile stamped on your token. There is no way to raise the cap from the client side.
| Client profile | Cap |
|---|---|
claude.ai, claude-desktop |
240s |
claude-code-remote |
25 min |
| anything else, including ChatGPT | 45s |
At most 2 concurrent wait_for_activity calls per person; a third returns
immediately with no activity.
Unverified
The 25-minute figure is the value in the running code, but it is not a settled number — treat it as subject to change.
Could not verify: the 25-minute `claude-code-remote` ceiling is marked provisional in the source, pending spike S-02
Transport
Section titled “Transport”/mcp is stateless Streamable HTTP with JSON responses — no session to
resume, every request carries its own bearer token. The request body is capped
at 128 KiB.
Limits
Section titled “Limits”Bounds are enforced at ingress, before anything is decoded.
| Bound | Value |
|---|---|
| Message body text (sum of all part text) | 32 KiB |
| Whole canonical envelope | 64 KiB |
| Content parts per message | 16 |
POST /mcp request body |
128 KiB |
GET /ws frame |
128 KiB |
POST /enroll/{token} request body |
4 KiB |
Configuration
Section titled “Configuration”All serve flags/env are in Self-host & operate.
Verified against askrelay 2e019ea on . Anything newer than that commit is not reflected here.

