open-context open-context
October 9, 2026 · 7 min read

Inside open-context's BYODB: One Interface, 15 Backends

open-context's BYODB ("bring your own database") architecture lets its MCP context store run on 15 different databases — from a zero-config JSON file to Postgres, MongoDB, DynamoDB, and SurrealDB — without rewriting storage logic for each one. It does this by grouping backends into three shared implementations (file, SQL, document/key-value) plus one bespoke multi-model adapter, so adding a new database means writing a small driver, not a new storage engine.

Key takeaways

  • BYODB supports 15 backends across four families: file (JSON), SQL (SQLite, Postgres, MySQL, SQL Server, DuckDB, libSQL/Turso, Cloudflare D1, Google Cloud SQL), document/key-value (MongoDB, Redis, Firestore, DynamoDB, in-memory), and multi-model (SurrealDB).
  • CRUD logic is written once per family — a SQL engine needs only a ~50-line Dialect, and a document store needs six DocumentDriver methods — not a full reimplementation.
  • Every backend must pass the same ~50-test conformance suite unmodified; a backend that can't satisfy it gets a documented limitation, not a relaxed test.
  • Database drivers are optional peer dependencies, loaded only when their scheme is used, so the default install and Docker image stay small.
  • Switching backends is non-destructive: opencontext db migrate only reads the source store and never deletes it.

The problem: one context store, fifteen very different databases

open-context's MCP server gives Claude persistent memory through six tools — save_context, recall_context, and friends — all backed by a context store. By default that store is a JSON file at ~/.opencontext/contexts.json, which needs zero configuration. But a JSON file doesn't scale to thousands of entries, doesn't work across machines, and isn't what a team already running Postgres or Redis wants to add to its stack. So open-context lets the store live anywhere: set OPENCONTEXT_DB_URL to a connection string and the same CLI, web UI, and MCP tools keep working, unchanged, against SQLite, eight different SQL engines, five document/key-value stores, or SurrealDB.

The naive way to build that would be fifteen separate implementations of save, recall, search, update, and delete — one per database. open-context does the opposite: it defines the storage contract once, as a ContextStoreAdapter interface, and shares the actual CRUD logic across every backend in the same family.

Three adapter families, not fifteen implementations

Looking at src/store/adapters/, there are exactly three shared implementations plus one standalone adapter:

File — adapters/json.ts

The default. Reads and writes a single JSON document on disk. No network, no driver, no setup — this is what every existing install already uses and nothing changes for them.

SQL — adapters/sql.ts + a Dialect per engine

SQLite, DuckDB, libSQL/Turso, Cloudflare D1, Postgres, Google Cloud SQL, MySQL, and SQL Server all share one createSqlAdapter implementation. What differs between them is captured in a small Dialect object: how bind placeholders are written (?, $1, or @p1), the DDL that creates the schema, and how string concatenation works. SQLite, Postgres, and DuckDB share one portable schema outright — every column is TEXT, and tags are stored as a JSON-encoded string rather than a native array, so no query needs to branch on engine. SQL Server is the one dialect that diverges on all four points, which is exactly why they're configurable properties instead of hardcoded constants.

Document / key-value — adapters/document.ts + a DocumentDriver per engine

MongoDB, Redis, Firestore, DynamoDB, and the in-memory test backend all implement the same six-method DocumentDriver interface: connect, close, ping, get, put, remove, and list. Everything else — search, tag filtering, ordering, cascading bubble deletes — is implemented once in createDocumentAdapter and reused by all five. Adding a new NoSQL backend means writing those six methods, not the whole storage contract.

Multi-model — adapters/surreal.ts

SurrealDB is the one bespoke adapter, since it doesn't fit cleanly into either the SQL or document model. It still implements the same ContextStoreAdapter interface as everything else — callers never know the difference.

What stays constant no matter which database you pick

The point of a shared contract is that behavior doesn't drift between backends. open-context's conformance suite (tests/store/conformance.ts, ~50 tests) is the only definition of "correct," and it enforces a handful of invariants across all 15 backends:

Search is where the underlying mechanism visibly differs even though the results don't: SQL backends push the substring filter down into the database with a query. Document and key-value stores have no portable case-insensitive substring predicate, so opencontext reads the whole collection and filters in application memory instead. Same output, different cost — which is also why the project recommends moving off the document family once a store grows large, something covered in more depth in the SQLite vs. Postgres comparison.

Adding backend number sixteen

Because the contract is centralized, the repo documents a fixed checklist for adding a new database: register the scheme, parse any backend-specific connection-string fields, write a Dialect or DocumentDriver (not a full adapter), register it in the build switch, load the driver with importOptional() so a missing package produces an install instruction instead of a crash, declare it as an optional peer dependency, add it to the Docker Compose test stack, and run it against the conformance suite until every test passes. Nothing about the CLI, web UI, or MCP tool definitions changes — the new backend simply becomes another valid connection string.

Why this matters beyond open-context's own code

The underlying goal is the same one that motivates open-context's export tooling: don't let your tools lock your data into one vendor's shape. A context store that only ran on one database would force a choice between "stay small" and "rewrite everything to scale." BYODB means a solo developer can start with the zero-config JSON file, a team can promote to Postgres without touching application code, and an edge deployment can use Cloudflare D1 — all speaking to the exact same ContextStoreAdapter and passing the exact same test suite.

See which backend fits your setup — check what's installed, test a connection, and migrate without losing data.

Explore open-context →

FAQ

Is BYODB tied to a specific database vendor?

No. BYODB is open-context's own adapter layer, not a product from any database vendor. It supports 15 backends across four families — file, SQL, document/key-value, and multi-model — and treats them all as equal citizens behind one interface.

Do I need to install anything to use BYODB?

Not for the default setup. The JSON file, SQLite, and in-memory backends work with zero extra installs. Remote backends like Postgres, MongoDB, or DynamoDB need their driver as an optional peer dependency, and opencontext tells you exactly what to run if it's missing.

Does switching database backends delete my existing context?

No. The opencontext db migrate command only ever reads from the source backend and writes to the target, so your original store is left intact unless you explicitly pass --replace to empty the target first.

How does open-context guarantee all 15 backends behave the same way?

Every adapter must pass the same conformance suite — about 50 tests covering ordering, search semantics, and read-your-writes consistency — unmodified. If a backend can't satisfy a test, that's documented as a limitation, not a reason to weaken the test.

Is search identical across every backend?

The results are identical, but the mechanism differs. SQL backends push case-insensitive substring filtering down into the database. Document and key-value backends (MongoDB, Redis, Firestore, DynamoDB) have no portable substring predicate, so opencontext reads the collection and filters in memory instead.