saastro-relay: persistent memory for working with AI on real projects
Every AI session starts from zero. saastro-relay is the MCP server I built to fix that: it syncs context and code snapshots between Claude.ai and Claude Code with a dual local + cloud architecture.
The problem nobody solves well
I work with AI every day. Not as an experiment — as a real part of how I design, build and maintain projects. Claude Code in the terminal, Claude.ai in the browser, both in parallel depending on the task.
And there’s a problem that keeps coming up: every session starts from zero.
You can have a project with months of technical decisions, well-thought-out architecture, established conventions. You open a new session and you have to re-explain everything. Or you copy and paste context manually. Or you work with an AI that knows nothing about what you’ve built.
That doesn’t scale.
saastro-relay is the tool I built to fix it.
What is saastro-relay
An MCP (Model Context Protocol) server that acts as a shared context bridge between Claude.ai and Claude Code.
The core idea is simple: instead of relying on each session remembering something, the context lives in structured .md files inside the repository itself. The server exposes that context as MCP tools. Any instance of Claude — whether in the browser or the terminal — can read and write that context.
Estructura de conocimiento por proyecto
/knowledge
architecture.md
Contexto narrativo — el porqué, decisiones, arquitectura. Lo escribe Claude.
architecture.snap.md
Snapshot automático — tipos, interfaces, firmas. Lo genera el extractor.
database.md + database.snap.md
Una sección por dominio del proyecto.
INDEX.md
Estado general del proyecto. Claude Code puede leerlo sin llamar al MCP.
Narrativa
Cambia despacio. Editada por Claude o manualmente. Versionable con git.
Snapshot
Cambia con cada commit. Generado automáticamente. Nunca se edita a mano.
Each project has a /knowledge directory with sections. Each section has two files:
{section}.md— narrative context: the why, the decisions, the architecture{section}.snap.md— automatic snapshot: types, interfaces and signatures extracted from real code
The separation is deliberate. The narrative is written by Claude (or me). The snapshot is generated by a tool automatically every time the code changes. They never mix because they change at different rates and for different reasons.
Dual architecture: local and cloud
This is where it gets interesting.
Claude Code runs locally — it has direct access to the filesystem and the SQLite database. Claude.ai is a cloud service — it needs to connect via HTTP to a server accessible from the internet.
Solution: two execution modes with the same 12-tool MCP API.
Arquitectura dual: local + cloud
Modo local — Bun
Runtime: Bun + TypeScript
Storage: bun:sqlite + filesystem
Watcher: Chokidar → snapshots
Git hook: post-commit.sh
Auth: sin auth en localhost
Expuesto via: Cloudflare Tunnel
Puerto: localhost:3456
Túnel: mcp.jcenlo.com
Modo cloud — CF Workers
Runtime: Workers V8 (serverless)
Storage: D1 + R2
Sin watcher (stateless)
Recibe snapshots via POST
Auth: OAuth 2.1 en D1
Expuesto via: Cloudflare edge
12 MCP tools — misma API
Por qué dos entry points y no uno
Workers V8 no soporta bun:sqlite, fs ni procesos persistentes. El SDK de MCP necesita estado de sesión que no sobrevive entre invocaciones de un Worker. Duplicar los handlers con backends distintos es más honesto que una abstracción forzada en v1.
Local mode (Bun)
- Runtime: Bun with TypeScript directly, no compilation
- Storage:
bun:sqlitefor metadata, filesystem for the.mdfiles - Watcher: Chokidar watches source files and regenerates snapshots automatically
- Git hook:
post-commit.shtriggers a snapshot after each commit - Auth: no auth on localhost (ADR-006), BRIDGE_SECRET as consent gate for the tunnel
- Tunnel: Cloudflare Tunnel exposes
mcp.jcenlo.com→localhost:3456
Cloud mode (Cloudflare Workers)
- Runtime: Workers V8 — serverless, no persistent processes
- Storage: D1 (SQLite compatible) for metadata, R2 for the
.mdfiles - Auth: full OAuth 2.1 persisted in D1, because Claude.ai requires it
- No watcher or extractor — stateless by definition
The reason for not abstracting both into one is pragmatic: Workers V8 doesn’t support bun:sqlite, fs or persistent processes. The MCP SDK needs session state between requests that doesn’t survive between Worker invocations. Duplicating the handlers with different backends is more honest than a forced abstraction for v1.
The data flow
Flujo de datos
Watcher local
Detecta cambios en archivos fuente (Chokidar)
Corre en segundo plano con el entorno de dev
Extractor
ctags → ast-grep → regex. Extrae tipos, interfaces, firmas exportadas
Solo la superficie pública — nunca internals
Filesystem
Escribe {section}.snap.md en /knowledge del repo
Versionable con git, legible sin el servidor
Worker (opcional)
POST /snapshot sincroniza el snapshot al cloud
Permite que Claude.ai acceda al código actualizado
Claude Code
Conecta a localhost:3456, llama a session_init
Lee contexto narrativo + snapshot fresco
Claude.ai
Conecta a mcp.jcenlo.com via OAuth, llama a session_init
Mismas herramientas, mismo contexto desde el cloud
When I work on a project:
- The local watcher detects changes in source files
- Extracts types, interfaces and signatures with a fallback chain:
ctags → ast-grep → regex - Writes the
.snap.mdto the local filesystem - Optionally pushes the snapshot to the Worker via
POST /snapshot - Claude Code connects to the local server on
localhost:3456and reads fresh context - Claude.ai connects via
mcp.jcenlo.comand accesses the same context from the Worker
The result: I open a new session in Claude.ai, call session_init with the project and topic, and have all context available in seconds. No copy-pasting. No re-explaining.
The design decisions that matter most
Building this involved making some non-obvious decisions.
The .md files live in the repo, not in the database
SQLite stores only metadata: status, timestamps, sync log. The actual content lives in the filesystem. This means context is versionable with git, readable directly without the server, and fully portable. No vendor lock-in.
Two separate knowledge layers
Narrative and snapshot in different files because they change for different reasons. If you mix them, the automatic extractor overwrites Claude’s work. If you separate them, each can evolve at its own pace.
Optimistic locking on save_context
Claude.ai and Claude Code can write to the same section. Last-write-wins would silently destroy valid work. save_context accepts a last_read_at and rejects if there were changes in between — it returns both versions and proposes a merge.
Namespace per project everywhere
A single server can manage multiple projects. Without namespacing, database in one project and database in another would be indistinguishable. The .saastro-relay file at the repo root acts as a namespace token.
What I learned building it
Working with AI effectively isn’t just about choosing the right model or writing good prompts. It’s about designing the information system around the model.
The difference between a session where AI delivers real value and one where you’re explaining the same thing for the fifth time is, almost always, context. saastro-relay is a bet on externalising that context into a persistent, structured layer.
I also learned something about building for yourself: the best tools I’ve built come from a friction I feel in my own workflow. Not from market analysis. This is one of them.
The code is in active development. The server runs in production at mcp.jcenlo.com.