open-context open-context
September 23, 2026 · 6 min read

How to Migrate Your MCP Context Store Between Databases

Run opencontext db migrate --to "<new-connection-string>" to copy every saved context and bubble into a new database backend. The command only reads from your current store, writes bubbles first so context links stay intact, and never touches the source — so a failed or partial migration can't cost you any data, and you can always just run it again.

Key takeaways

  • opencontext db migrate --to <url> copies all contexts and bubbles from your active store into any of the 15 supported backends.
  • Migration is source-read-only: the original database is never modified, even if the target write fails halfway through.
  • Add --from <url> to copy between two databases directly, without changing which one you're actively using.
  • By default the target is added to (copy mode); pass --replace to empty it first.
  • The same migration flow is available without a terminal from the Database page in the web UI.

Why you'd want to move your MCP context store

open-context ships with a JSON file at ~/.opencontext/contexts.json as the default store for everything Claude saves through the Model Context Protocol — save_context, recall_context, and the other MCP tools all read and write through it. That's fine while you're the only one using it on one machine. It stops being fine the moment you want a second machine to see the same memories, want a team to share one store, or just want a real database backing the CLI and MCP server you now run in production. open-context supports 15 backends for exactly this reason, and the whole point of db migrate is to move between them without losing anything you've already saved.

How the migration actually works

The migration logic lives in src/store/migrate.ts and is deliberately narrow: it reads every bubble and every context from the source, then writes them to the target. Two details matter for anyone doing this against real data.

The source is never touched

Everything is read from the source before the target is touched at all. If the write to the target fails partway — a dropped connection, a bad credential, a full disk — your original store is exactly as it was before you ran the command. There's no rollback logic because there's nothing to roll back; you fix the problem and run the same command again.

Bubbles go first, then contexts get remapped

Every backend generates its own ids when a row is created, so a bubble's id on SQLite won't match what Postgres assigns it. The migration creates bubbles on the target first, keeps a map from old id to new id, and then writes each context with its bubbleId translated through that map. Contexts that don't belong to a bubble are copied as-is. The result is a target store where every context-to-bubble relationship is intact, even though none of the underlying ids match the source.

Step-by-step: moving from JSON to Postgres

This is the most common jump — starting on the zero-config default, then moving to something a team or a server can share.

1. Test the target connection first

opencontext db test "postgres://user:pass@localhost:5432/opencontext"

This connects and pings the database without saving anything or touching your existing store — a cheap way to catch a typo'd password or an unreachable host before you commit to anything.

2. Run the migration

opencontext db migrate --to "postgres://user:pass@localhost:5432/opencontext"

With no --from, the source defaults to whatever store you're currently using — the default JSON file if you haven't configured anything else. The command prints how many contexts and bubbles it copied and confirms the source was left alone.

3. Switch your active store

opencontext db use "postgres://user:pass@localhost:5432/opencontext"

This is the step that actually matters for Claude: db use tests the connection again and, if it works, saves it to ~/.opencontext/config.json so the CLI, the HTTP server, and the MCP server all read and write through Postgres from now on. Your JSON file is left on disk, untouched, as a backup.

Copying without switching

Sometimes you want a copy of your data somewhere else without changing what Claude actively uses — staging a backup, testing a new backend before committing, or pulling one teammate's SQLite file into a shared Postgres instance. Pass both flags explicitly and neither database becomes active:

opencontext db migrate \
  --from "sqlite:///Users/me/opencontext.db" \
  --to "postgres://user:pass@shared-db:5432/opencontext"

Add --replace if the target already has contexts you want overwritten rather than added to — useful for keeping a staging database in sync with production on a schedule.

Why the same command works for all 15 backends

migrateStore only calls the generic ContextStoreAdapter interface — listContexts, listBubbles, createBubble, saveContext — so it has no idea whether either side is JSON, SQLite, MongoDB, or DynamoDB. That's only possible because every backend passes the same conformance test suite: identical ordering, identical search semantics, and identical handling of missing fields. If a backend behaved even slightly differently, a JSON-to-Mongo migration could silently reorder contexts or drop an unset field — the suite is what makes cross-backend migration something you can trust rather than something you have to double-check by hand afterward.

Doing it without a terminal

The Database Settings page in the web UI wraps the exact same operation behind PUT /api/db/migrate — pick a target backend, test it, and migrate with a click. It's the same code path as the CLI, so the guarantees are identical: read-only source, additive by default, and a confirmation once the copy finishes. This is worth knowing if you're choosing where your AI memory should live and want to try a backend before fully committing to it.

Already outgrown the default JSON store?

Try open-context's 15 database backends →

FAQ

Does migrating between databases delete or modify my original data?

No. open-context's db migrate command only ever reads from the source store. If the write to the target fails partway through, the source is untouched and you can simply retry the command.

Can I copy data between two databases without switching my active store?

Yes. Pass both --from and --to to opencontext db migrate and neither database becomes your active store — it's a plain copy. Omit --from and it defaults to whatever store you're currently using.

What happens to bubbles and their linked contexts during a migration?

They're preserved. Bubbles are created on the target first, then each context is written with its bubbleId remapped to the new bubble's id, since every backend generates its own ids on create.

Will migrating overwrite data already in my target database?

By default no — migration is additive and adds to whatever the target already holds. Pass --replace to empty the target's contexts and bubbles before copying.

How do I make Claude actually use the new database after migrating?

Run opencontext db use "<new-url>" to save it as your default, or set the OPENCONTEXT_DB_URL environment variable, which always takes precedence. The MCP server and CLI pick up either one automatically.