Architecture
System Overview
Local-first path:
MCP Client → retrace-mcp (npm package, stdio) → SQLite (~/.retrace/journal.db)
Cloud path:
MCP Client → HTTPS (/mcp) → Next.js app (packages/web) → PostgresComponents
MCP Server
A Model Context Protocol server that provides journal tools to AI agents (Copilot, Cursor, Claude, etc.). Runs locally on the user's machine.
- Local mode (default): Uses SQLite at
~/.retrace/journal.db. No authentication required. - Cloud mode: Connects to the hosted API via HTTP. Requires
RETRACE_TOKENandRETRACE_API_URLenvironment variables.
Mode selection is automatic based on whether env vars are set.
API Server
A Next.js application serving both the REST API and the web UI. Handles:
- User registration and authentication (NextAuth.js sessions with GitHub/Google OAuth + bearer tokens)
- API token management (create, list, revoke)
- Journal entry CRUD with encryption at rest
- Project management with encrypted names/descriptions
- Query, search, and summarization endpoints
- MCP streamable HTTP endpoint (
/mcp)
Data Flow
- User configures MCP server in their editor
- AI agent calls MCP tools (e.g.,
log_entry) - MCP server forwards request to API (cloud mode) or writes to SQLite (local mode)
- API authenticates via bearer token, writes to Postgres
- User can query via MCP tools or web dashboard
Authentication
- Web sessions: NextAuth.js with GitHub and Google OAuth providers, plus email/password credentials
- API tokens:
rt_prefix + 64 hex characters (32 random bytes). Only SHA-256 hash stored in database. - API routes: Support both session cookies (browser) and Bearer tokens — session auth is tried first
- MCP: OAuth 2.1 with PKCE or Bearer token
Encryption Design
Sensitive entry fields (content, project, people, tags, refs, component) are encrypted at rest using per-user keys derived via HKDF from a server-side ENCRYPTION_KEY + user ID:
- Entry payload encryption: AES-256-GCM. All sensitive fields are stored in a single encrypted JSON blob (
payload_encrypted). - Project encryption: Project names and descriptions are individually encrypted (
name_encrypted,description_encrypted) with a blind index token (name_token) for lookup. - Blind indexes: HMAC-based tokens enable filtering on people, tags, and component without decrypting. Projects use a FK (
project_id) instead. - Search: Content is tokenized into words (3+ characters), each HMAC'd into a blind index token. Candidate rows are fetched via GIN index, then decrypted and verified in memory to eliminate false positives.
- Protection scope: Server-side encryption at rest — protects against database breaches but the server decrypts at runtime for authenticated users.
Database Schema
Key tables (auto-migrated via instrumentation.ts):
- users — id, email, password_hash, name
- api_tokens — id, user_id, token_hash, name, created_at, last_used_at
- entries — id, user_id, project_id (FK → projects), entry_number, payload_encrypted, search_tokens[], people_tokens[], tags_tokens[], component_token, category, source, timestamp
- projects — id, user_id, name_encrypted, name_token, description_encrypted, created_at
- oauth_clients — client_id, client_secret, redirect_uris, client_name
- oauth_codes — code, client_id, user_id, redirect_uri, code_challenge, scope, expires_at
Technology Stack
| Component | Technology | Rationale |
|---|---|---|
| MCP Server | TypeScript + MCP SDK | Official SDK, type-safe |
| Local DB | SQLite (better-sqlite3) | Zero config, fast, portable |
| API + Web | Next.js (App Router) | Full-stack React, SSR, API routes |
| Cloud DB | PostgreSQL | Relational, supports arrays, time-series |
| Auth | bcrypt + NextAuth.js + SHA-256 tokens | Sessions for web, bearer for API |
| Encryption | AES-256-GCM + HMAC blind indexes | Encrypted at rest, searchable |
| Monorepo | npm workspaces | Built-in, no extra tooling |