Skip to content

Architecture & encryption

The installable package (oase-mcp) is split so that “how to talk to Oase” is cleanly separated from the tool surface:

Layer Where What it is
Passive REST client src/client/ Everything that knows how to interact with the Oase API over HTTP: Promise login/auth, token refresh and shared config, and the full REST client — invite-link joining, key fetching, AES-256-GCM encryption/decryption, and sending/reading messages and posts, reactions, and media. Request/response only: no socket, no realtime push. No MCP dependency, no agent behavior.
MCP server src/mcp/ The tool surface exposed to MCP clients. All tools are on-demand wrappers over the passive layer — the server itself never acts unprompted.

The passive layer has a clean exported surface (importable as oase-mcp), so other consumers — a bot, a bridge, your own integration — can build on the API client without pulling in the MCP layer. dist/index.js is the MCP server entry point, so client registrations keep working across versions.

The agent is a persistent Promise user — the same identity provider the Oase app uses. A one-time browser login exchanges a single-use Promise id_token for Oase’s own long-lived refresh token; from then on Promise is never contacted again. No Promise credentials are stored — only the resulting Oase refresh token, in ~/.oase-mcp/config.json (mode 0600). Access tokens are short-lived, kept in memory, and refreshed automatically; refreshes are serialized across concurrent server processes via a lock file, because the backend rotates the refresh token on every refresh and revokes the session if it ever sees a stale one (anti-replay).

The server talks to the production Oase backend exactly like the app does: sign in → join via invite link → fetch the oase key from the KMS → AES-256-GCM encrypt → post the message.

Oase encrypts message content with a per-oase symmetric AES-256-GCM key held in escrow by the backend: every participant of an oase can fetch that oase’s raw key from the KMS by presenting a backend-signed proof of membership. The MCP server does the same, then produces the exact cipher-bundle shape the app expects — so its messages decrypt and render normally for everyone in the oase. There are no device keypairs or enrollment ceremonies involved.

No message is ever sent in plaintext — the send endpoint requires a cipher bundle, and decryption of received messages happens client-side in the server. Media attachments are encrypted the same way: modern uploads travel as an encrypted container using the same per-oase key (with the original filename and mime type encrypted alongside), and read_media decrypts them locally.