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
--replaceto 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.