MorphDB
// a backend for vibe-coded sites

Schema fluid.
API stable.

A coding agent reshapes your data model as fast as it changes the UI — no migrations — while the site keeps hitting the same frozen endpoints. And one instance hosts every site you build, each a fully isolated tenant. The demo below isn't a mockup — it's wired to the live database. Try it:

your app · created on first write a private, isolated tenant on the live database — only this key can read it
schema · the agent edits this
task
  • titlestring
  • doneboolean
your site · what your users see
No tasks yet.
Persist one and your site renders it right here — like a real app would.
rendered from GET /objects/task · same URL forever, however the schema morphs
//Step 1. Type a task and persist it — it saves to a real database and renders on the right.
01 the problem

When you vibe-code, the data model never sits still.

Add a field, split a type, link two things — every change normally means rewriting a database schema and migrating every existing row. So the design churn that makes building fun becomes the thing that slows you down.

MorphDB makes a schema edit free and instant, and keeps the HTTP API frozen while the shape underneath changes. The agent stops fighting the database and just builds.

02 the mental model

Apps → types → objects.

Three nested ideas. The outermost one — the app — is the other half of the story: it's what makes MorphDB multi-tenant. Every site you ship is its own app, and they all live in one process behind one set of URLs.

appone per website · a hard isolation boundary
A single MorphDB process hosts thousands of apps (tenants). Each request carries an X-App-Key; that key — and nothing else — decides whose data you touch. No data, schema, or type name leaks across apps. One deploy backs every site you build.
typefields + relations
Your data model inside an app — like task, user. Just a set of fields (raw values) and relations (links to other types). The agent reshapes these constantly. Two different apps can both own a task type with totally different shapes.
objectthe instances · each has a _guid
The actual rows — one task, one user. Written and read by the site over generic endpoints. Relations ride along as fields on the object body.
your appcreated on first write
Persist an object in the demo above — it lands in your app and shows up right here.
someone else's appacme_store
loading a different tenant…
both panels call the exact same path — GET /objects/task — only the X-App-Key header differs

That's multi-tenancy, live. Same process, same URL, two apps that have never heard of each other. The key on the request is the only thing that picks a universe — which is exactly why a coding agent can spin up a fresh backend per project without standing up new infrastructure. Schema-fluid is how fast you build one site; multi-tenant is how one MorphDB backs all of them.

03 the big idea

Two audiences, one database.

MorphDB cleanly separates who changes the shape from who moves the data — and that separation is why the API can stay frozen.

you · the coding agent

Reshape the schema

Call tools to add a field, declare a relation, retype something. Instant, O(1), no rows rewritten.

acts on the schema, via mcp tools
add_fieldadd_relationdescribe_typeset_schema
the site · your frontend

Read & write data

Plain fetch against generic object endpoints. These URLs never change when the schema morphs.

acts on the data, over stable http
POST /objects/taskGET /objects/task?done=falsePATCH /objects/task/{id}
both meet at one SQLite-backed process — schema edits never touch the endpoints the site calls.
04 why it doesn't break

Lazy invalidation: rows are never migrated.

How can the shape change under live data without breaking it? Every object is stored as raw JSON and projected through the current schema on every read.

Add a field → old rows simply read it as empty (or its default). Drop a field → it vanishes from the view, the bytes stay put. Retype a field → a value that no longer fits reads as unset until rewritten. A schema edit is one tiny metadata write — zero row rewrites, whether you have ten objects or ten million.

05 relations

Declared once. Inverse for free. Filterable like a foreign key.

A relation is declared on one side; the inverse appears automatically on the other. Under the hood each link is a single row in an indexed edge table — so you can traverse both ways in one read, and filter a list by it like an ORM foreign key.

business name · score stage status · label stages ▸ ◂ business one business · many stages
filter a list by a relation
GET /objects/stage?business=<guid>&status=build
"All of this business's stages that are in build." The relation filter is resolved through the indexed edge table and composes with field filters, sort, and pagination — so you model a link as a relation, never a guid-bearing string field.
06 how claude plugs in

The agent reshapes schema over MCP — through a pipe, not a port.

Claude Code talks to MorphDB's tools through a tiny server, morphdb mcp. It's a stdio server: Claude launches it as a child process and exchanges newline-delimited JSON-RPC over its stdin/stdout — no network, no port. You never run or stop it; Claude owns its whole lifecycle.

mcp client
Claude Code
{ "method": "tools/call", "name": "add_relation" }stdin ▸
{ "result": { "ok": true } }◂ stdout
mcp server
morphdb mcp
a pneumatic tube between two programs — 11 tools: register_app · add_field · add_relation · describe_type · set_schema · query_objects · …
07 under the hood

Two files from the standard library. Nothing else.

MorphDB is deliberately tiny and dependency-free, so a coding agent can install and reason about all of it.

0 dependencies — just Python's http.server + sqlite3
Daemon
morphdb start — the HTTP backend, backgrounded.
CLI
start · stop · status · logs · dashboard · mcp
MCP server
stdio tools Claude spawns to edit schema.
Claude skill
teaches the agent the whole workflow.
Dashboard
read-only ER view of every app + type.