Skip to content

Architecture

Data flow

Agent → HTTP (MCP JSON-RPC) → klaxon-server (axum + PostgreSQL)

                              REST + WebSocket → Web dashboard
                              REST + WebSocket → Mobile (Expo)

                              FCM push → mobile + Web Push → browser
                              SSE (MCP spec) ← Agent (subscribed)

Browser / Mobile → /auth/*, /oauth/* → klaxon-auth (OAuth 2.1 AS)

                                  GitHub · Google · Apple · magic link

In production, a single Ingress path-routes /auth/*, /oauth/*, and /.well-known/oauth-authorization-server to the auth pod; everything else (including /.well-known/oauth-protected-resource) goes to the server pod. Both binaries share one Postgres database. A W3C traceparent header propagates browser → auth → server so one OpenTelemetry trace spans the whole stack.

Rust crates

The Cargo workspace has four crates:

crates/klaxon-server/ — resource server

The primary binary. REST handlers, the MCP JSON-RPC endpoint, WebSocket fan-out, the push/webhook workers, and rate limiting. Runs on port 3000 by default. A --worker flag re-executes the same binary in worker mode for the background job loop.

ModulePurpose
main.rsServer / worker mode dispatch
config.rsTyped configuration from env
state.rsAppState (PgPool + BillingConfig)
mcp.rsJSON-RPC 2.0 dispatch + tool registry + SSE
ws.rsWebSocket endpoint — first-message auth because browser WebSocket can't send headers
handlers/REST handlers — items, channels, teams, roles, invitations, admin, billing, webhooks, templates, push, audit, settings, stats
billing/gate.rsThin wrapper around klaxon-auth-core's shared gate
worker.rsSnooze / archive / push queue / webhook delivery / email / cleanup
metrics.rsPrometheus middleware
rate_limit.rsRedis-backed sliding window; Postgres fallback

crates/klaxon-auth/ — OAuth 2.1 Authorization Server

Runs on port 3001. RFC 8414 metadata discovery, RFC 7591 dynamic client registration, PKCE S256, refresh-token rotation, RFC 7009 revocation. Handles IdP login for GitHub, Google, Apple (ES256 JWT client_secret + JWKS-verified id_token), and magic link. First-time signup auto-creates a personal org + user + identity row. Owns the invitation handler (both admin-side create and public-facing accept).

crates/klaxon-auth-core/ — shared extractors

Consumed by both binaries. Provides the AuthUser axum extractor that resolves bearer tokens against three Postgres tables (sessions, api_keys, oauth_tokens). Also holds the permission string matcher (has_permission), the billing gate (check_limit), PKCE S256 verification, and set_resource_metadata_url for the RFC 9728 WWW-Authenticate header. Generic over any state S: FromRef<PgPool>.

crates/klaxon-telemetry/ — shared OTel init

One function: init(cfg) -> TelemetryGuard that wires traces (OTLP/gRPC), logs (opentelemetry-appender-tracing with trace_id correlation), and W3C propagators. The guard owns the providers; Drop flushes. Layer ordering is load-bearing — the trace layer must install before the log appender or log records lose trace_id.

Database

PostgreSQL 16+ with 20 migrations — one shared schema per deployment, queried by both binaries. Every tenant-scoped table carries org_id UUID NOT NULL REFERENCES orgs(id) ON DELETE CASCADE. Migrations live in crates/klaxon-server/migrations/ and run at server startup (whichever binary starts first wins the race; the migration system is idempotent).

Key design choices:

  • UUID primary keys with gen_random_uuid()
  • TIMESTAMPTZ for all timestamps (not text)
  • JSONB for form schemas, actions, responses, metadata
  • TEXT[] for tags, UUID[] for related items
  • Postgres ENUM types for item_level, item_status, actor_type, channel_mode
  • Cursor-based pagination via compound (org_id, created_at DESC, id DESC) indexes
  • LISTEN/NOTIFY on klaxon_events channel for multi-replica event broadcast

See Database for the full schema.

Client Packages

PackageNamePurpose
packages/protocol/@ottercoders/klaxon-protocolZod schemas — the TypeScript contract
packages/common/@ottercoders/klaxon-commonShared components, widgets, hooks, API transport
packages/ui/@klaxon/uiDesktop client entry point
packages/web/@klaxon/webBrowser web app
apps/klaxon-mobile/klaxon-mobileExpo React Native mobile app

Shared transport layer

packages/common/src/api.ts provides invoke() and listen() abstractions. invoke() maps command names to REST API calls (with OpenTelemetry-instrumented fetch), and listen() is a local event emitter fed by a WebSocket connection. All widgets work identically across web and mobile (with React Native adaptations) — the transport is swapped, not the UI code.

Multi-Tenancy

Every API request is scoped to an organization (org_id). The AuthUser extractor resolves the bearer token to (user_id, org_id, agent_id) and every database query filters by org_id.

Channel membership controls item visibility: users only see items in channels they belong to (decision D2 from the migration questionnaire).