transom
transom lets a program ask a person a question and get the answer back without a chat. A question waits in an inbox, on the web and soon on an Android phone, until someone replies; its answer returns to the program through a waiting HTTP request, an event stream, an MCP tool, or a signed webhook. transom also sends one-way notifications, like alerts.
plans/00-design.md is the design, and
plans/completed/13-the-http-api.md is the whole API as
examples. The server, its web UI, and its MCP server are built; the
Android app is next.
Running it
transom needs Redis, run with append-only persistence: an answer lost after someone gave it is the worst failure transom can have. It holds no passwords or API keys, and trusts the token issuers its settings name.
docker run -p 8080:8080 \
-e TRANSOM_REDIS_URL=redis://redis:6379 \
-e TRANSOM_ISSUERS__AUTHENTIK__URL=https://auth.example.com/application/o/transom/ \
-e TRANSOM_ISSUERS__AUTHENTIK__AUDIENCE=transom \
-e TRANSOM_ISSUERS__AUTHENTIK__NAME_CLAIM=preferred_username \
ghcr.io/chrisguidry/transom:<tag>
Settings come from TRANSOM_ environment variables or a YAML file named
by --config; plans/completed/03-sign-in.md describes the
issuers' settings. TRANSOM_RETENTION, 2 days by default, is how long
transom keeps an exchange after it ends, or a notification after it
arrives, before deleting it: seconds, or a duration such as 36h or
2d. An open question is kept until it ends. TRANSOM_WATCHDOG_URL
is a URL transom sends a GET to every TRANSOM_WATCHDOG_INTERVAL
(10 minutes by default) while it can reach Redis, for a dead man's
switch such as a healthchecks.io check.
Several transom processes can share one Redis and one
TRANSOM_REDIS_PREFIX, behind one load balancer, and any of them can
stop at any time; plans/completed/17-replicas.md
describes how.
Adding transom to claude.ai
claude.ai's custom connectors sign a person in to transom's MCP server
through the issuer that transom's browser sign-in uses. transom answers
an MCP request without a token with a 401 whose WWW-Authenticate
header names its OAuth protected resource metadata, at
/.well-known/oauth-protected-resource/mcp, and that document names
every issuer with a client_id.
- At the issuer, allow transom's client to redirect to
https://claude.ai/api/mcp/auth_callbackandhttps://claude.com/api/mcp/auth_callback, as well as transom's own/login/callback. - Set transom's
public_url, for exampleTRANSOM_PUBLIC_URL=https://transom.example.com, and give the issuer itsclient_idandclient_secret. The issuer issues the connector's access tokens to that client, so itsaudiencemust equal theclient_id. - In claude.ai, add a custom connector with the URL
https://transom.example.com/mcp. Under its advanced settings, enter the same client ID and client secret. Issuers such as Authentik do not offer dynamic client registration, so the connector cannot register a client of its own.
The connector then acts as the person who signs in, under the same identity as their browser.
transom logs to stderr: one line for each request, with its path, status,
caller, and the reason for a failure, and one for each webhook and push it
delivers. It never logs a query, a header, a body, or a push endpoint.
RUST_LOG=debug adds the health checks.
Agent skills
skills/ holds Agent Skills for agents that
use transom: send-a-notification, ask-a-question, read-the-inbox,
and become-a-sender. Each gives the MCP tool call and the same request
with curl, which reads the server's URL from TRANSOM_URL and the token
from TRANSOM_TOKEN or the file TRANSOM_TOKEN_FILE names. The
repository is its own plugin marketplace for Claude Code and Codex.
In Claude Code:
/plugin marketplace add chrisguidry/transom
/plugin install transom@transom
The skills are namespaced by the plugin's name, so asking a question is
/transom:ask-a-question.
In Codex, add the marketplace, then install transom from it:
codex plugin marketplace add chrisguidry/transom
codex plugin add transom@transom
Any other agent that reads the Agent Skills format can take skills/
directly, for example with npx skills add https://github.com/chrisguidry/transom.
Developing
The tests run against a real Redis, which they start themselves with
Docker, so the only thing a machine needs is a Docker daemon that the
docker command reaches. Each test process starts one container from the
image that test-redis/src/lib.rs pins, on a port that Docker chooses,
and its tests share that Redis, each under a key prefix of its own. The
container is removed when the process exits, however it exits, so any
number of checkouts and worktrees can run the tests at once. Set
TRANSOM_TEST_REDIS_URL to run them against a Redis of your own instead.
The Android app's tests and android/end-to-end.sh get their Redis the
same way, from the test-redis binary, which prints a Redis URL and keeps
that Redis until its standard input closes:
cargo run -p test-redis # Ctrl-D or Ctrl-C removes the container
prek run --all-files runs every check that CI runs.