The MCP-native backend an AI agent stands up in minutes

Self-hostable, open source. Your agent defines tables at runtime and gets REST, GraphQL, and MCP for free — and domain blocks land as editable code in your repo, not a marketplace lock-in.

self-hosted · MCP-native · secure by default · blocks you own as editable code

Apache-2.0 · TypeScript · PostgreSQL 17 · Node 22+

~/my-app
$ npx @ion-drive/cli init my-app my-app scaffolded — server.ts + blocks/ are yours $ npm run dev API http://localhost:3000 · admin console /admin $ claude mcp add -t http ion-drive localhost:3000/api/v1/mcp agent connected — 17 tools: create_object, query_data, … $ npx ion-drive add crm crm installed — REST, GraphQL & MCP live, no restart $

Runtime schema

Define an object. Every API is already live.

Create tables through the admin console or one API call — no migrations, no deploys, no downtime. The moment it returns, the object is live on every surface:

  • REST — GET/POST/PATCH/DELETE /api/v1/data/contacts
  • GraphQL — contacts, create_contacts, …
  • MCP — tools for any connected LLM agent
  • OpenAPI — reflected in /api/v1/openapi.json

Data objects explains the model.

create a data object
curl -X POST http://localhost:3000/api/v1/schema/objects \
  -H 'content-type: application/json' \
  -d '{
    "name": "contacts",
    "displayName": "Contacts",
    "fields": [
      { "name": "full_name", "columnType": "text", "isRequired": true },
      { "name": "email",     "columnType": "email" },
      { "name": "status",    "columnType": "enum" }
    ]
  }'

Querying

One query language on every list endpoint

Full-text search, per-property operators, multi-sort, and two pagination styles — readable in a URL and identical across REST, GraphQL, and MCP. The zero-dependency client SDK builds these queries for you, Supabase-style.

The querying guide has the full operator table.

search + operators + pagination
GET /api/v1/data/contacts?search=acme
GET /api/v1/data/contacts?status[neq]=archived&age[gte]=21
GET /api/v1/data/contacts?sort=-created_at&page=1

{
  "data": [ /* records */ ],
  "pagination": { "page": 1, "pageSize": 25, "totalCount": 42, "hasNextPage": true }
}

Building blocks

Blocks are yours. The engine is a dependency.

Pulling in a building block applies its schema and drops its business logic into /blocks/<name> in your repo — like shadcn/ui, but for backend features. Core and the infrastructure plugins stay npm packages you upgrade; future block updates arrive as diffs you review, never overwrites.

Every artifact in the registry is sha256-digest-verified before it touches your project. Browse the registry or read how blocks work.

the ownership model
npx ion-drive list             # browse the registry catalog
npx ion-drive add crm          # schema-only: objects + APIs light up instantly
npx ion-drive add invoicing    # vendored logic lands in blocks/invoicing/

# edit blocks/invoicing/stripe.ts — it's your code, hot-reloaded

Agent-first

MCP is a first-class surface, not an add-on

The built-in MCP server at /api/v1/mcp gives any MCP-compatible agent schema introspection and CRUD with zero integration code — the same query semantics your application uses, because the same object definitions drive all three surfaces.

  • Schema tools — list_objects, create_object, add_field, …
  • Data tools — query_data, get_record, create_record, …
  • Block actions reflect as tools automatically

MCP server reference.

an agent queries your data
// POST /api/v1/mcp — tool: query_data
{
  "object_name": "contacts",
  "search": "acme",
  "filters": [{ "field": "status", "operator": "neq", "value": "archived" }],
  "sort": [{ "field": "created_at", "direction": "desc" }],
  "expand": ["company"]
}

Secure by default

Ship fast without shipping the classic holes

A fresh init enforces auth and RBAC on every surface — REST, GraphQL, and MCP all resolve the caller (session or role-bound API key) and check role permissions before touching data. The pitfalls of shipping fast are the platform's job to catch, not yours:

  • Production refuses to boot with auth off or wildcard credentialed CORS
  • Signup locks after the first admin; rate limiting is on out of the box
  • Secrets encrypted at rest (AES-256-GCM), never listed back; API keys stored as hashes
  • Outbound webhooks HMAC-signed; registry blocks digest-verified and sigstore-attested

Next on this front: user-scoped row-level policies and per-tenant data scoping — roadmap. Hardening knobs: the security checklist.

the platform refuses, so you don't leak
$ NODE_ENV=production node server.ts        # auth accidentally off?
✖ Refusing to boot: RBAC enforcement is disabled (ION_REQUIRE_AUTH is
  not set) in production, which leaves every endpoint anonymous …

$ ION_CORS_ORIGINS='*' node server.ts       # wildcard CORS + cookies?
✖ Refusing to boot: Unsafe CORS configuration …

POST /api/auth/sign-up/email                # second signup after bootstrap
→ 403 { "message": "Signup is disabled on this server" }

Quick start

Zero to an agent building, in five minutes

Node 22+ and Docker are the only prerequisites. What you own is deliberately small — a server.ts composition root and a /blocks directory; the platform arrives as npm dependencies.

Sign up in the admin console (the first user becomes admin), mint a role-bound API key, and point any MCP client at /api/v1/mcp. The full guide takes it from here.

terminal
npx @ion-drive/cli init my-app && cd my-app
docker compose up -d && npm install
npm run dev              # REST + GraphQL + MCP + admin console, live at :3000
npx ion-drive add crm    # domain blocks land as editable code in blocks/