Install the MoonDB MCP server in Cursor, Claude Code, or Windsurf — your agent gets
a native toolkit for managing projects, schema, and data, plus a built-in primer
(initialize.instructions) that briefs the model on MoonDB conventions
before the first call. No "ask the user for an API key" loop, no copy-pasted prompt.
Auth via your mk_… account key.
Cursor
.cursor/mcp.json
Drop this into .cursor/mcp.json at your project root.
# add the server
claude mcp add --transport http \
moondb https://moondb.ai/mcp \
--header"X-API-Key: mk_..."# your prompt:"Use moondb to spin up a backend for a blog — users, posts, and comments. Owner-scoped writes."
Data & AIqueryget_rowinsertupdate_rowdelete_rowai_call
Built for agents
Your agent already knows how to use this.
Context injection
Project-aware agent rules
# One endpoint. Full context.
GET /p/{id}/v1/llm-context
# Returns your exact schema,# endpoints, auth flow, types.# Paste into .cursorrules or# CLAUDE.md — zero hallucination.
Every project auto-generates agent instructions with your actual table names, column types, and API endpoints. No guessing.
Self-correcting
Errors that teach
400 {
"code": "FK_NOT_FOUND",
"message": "ref user_id: no row 'x'",
"suggestion": "Create the user
first with POST /p/.../api/users,
then retry."
}
Every error tells the agent exactly what went wrong and what to do next. Agents converge on a fix in one retry instead of spiraling.
Zero config
JSON in, backend out
# Agent sends this:
{ "tables": {
"todos": {
"title": "string required",
"done": "bool default false"
}
}
}
# REST API is live. Auth works.# No deploy. No config files.
The agent describes the data model in plain JSON. MoonDB handles tables, indexes, triggers, access control, and the full CRUD API automatically.
Capabilities
Everything a backend needs, nothing it doesn't.
{ }
Declarative schema
Send JSON. Get a database. The service handles diffing, migrations, and rollbacks. No SQL required.
/
Auto REST API
GETPOSTPATCHDELETE on every table. Filters, sort, pagination, joins built in.
@
Built-in auth
Email/password signup, JWT tokens, refresh flow. Owner-scoped access control per table.
^
File storage
R2-backed uploads. Mark a column type: "file" and multipart uploads just work.
!
Error suggestions
Every error includes a suggestion field with actionable fix instructions. Agents self-correct.
~
Edge-first
Runs on the edge in 300+ cities. Scales to zero. No cold starts. Sub-50ms latency worldwide.
Built-in AI
Add AI to your app with one schema field.
Define AI endpoints in your schema — pick a model, write a prompt template,
and call POST /ai/endpoint_name. No API keys, no proxy, no infra.
DB storage is total across all projects. Max 10 GB per individual project.
Ready to build?
Copy the prompt below, paste it into your AI agent, and have a working backend in under a minute.
The Prompt
Your agent's backend instructions.
Paste into your agent's system prompt, or download as a file.
Your agent will set up the entire backend itself.
Agent Prompt
# MoonDB Backend
This project uses MoonDB (https://moondb.ai) as its backend.
MoonDB is a declarative DBaaS — describe your data model in JSON, get a
REST API, auth, file storage, and AI endpoints automatically.
## Setup (do this once)
Preferred path — install the MCP server (Cursor, Claude Code, Windsurf):
Cursor: drop into .cursor/mcp.json
{ "mcpServers": { "moondb": {
"url": "https://moondb.ai/mcp",
"headers": { "X-API-Key": "mk_..." } } } }
Claude Code: claude mcp add --transport http moondb https://moondb.ai/mcp \
--header "X-API-Key: mk_..."
Windsurf: add to ~/.codeium/windsurf/mcp_config.json (same shape as Cursor,
with "serverUrl" instead of "url")
Docs: https://moondb.ai/mcp
With MCP installed you get 14 native tools (create_project, set_schema,
validate_schema, seed, query, get_row, insert, update_row, delete_row,
ai_call, get_reference, ...) plus an initialize.instructions primer that
briefs the model on MoonDB conventions. You can ignore the REST setup
below if MCP is available.
Otherwise (REST):
1. Get the API key (mk_...). Ask the user.
New account: POST https://moondb.ai/v1/accounts/signup { email, password } → api_key
Existing: https://moondb.ai/dashboard → Account.
2. Create a project:
POST https://moondb.ai/v1/projects
X-API-Key: mk_...
{ "name": "my-app" }
→ { data: { id, admin_key: "sk_...", public_key: "pk_..." } }
- save admin_key (sk_...) in .env as MOONDB_ADMIN_KEY — NEVER commit
- save public_key (pk_...) — safe for client-side code
3. Apply a schema:
PUT https://moondb.ai/p/{project_id}/v1/schema
X-Admin-Key: sk_...
{ "tables": { ... } }
4. Fetch the project-scoped reference (single source of truth, always in sync):
GET https://moondb.ai/p/{project_id}/v1/llm-context (human-readable, the agent prompt)
GET https://moondb.ai/p/{project_id}/v1/openapi.json (OpenAPI 3.0.3)
Re-fetch after every schema change. Replace this Setup section with the output.
Faster: pick a starter schema at https://moondb.ai/templates (todo / blog /
habit-tracker / bookmarks / recipes / team-chat) and POST it as the schema
in step 3 — one PUT, full backend.
## Schema definition
Column types: string, text, int, float, bool, date, datetime, json, enum, ref, file
Short-form: "string required unique", "int default 0", "ref users required", "bool default true"
Modifiers: required, unique, index, default <value>, min, max, max_length
Object form (advanced):
"status": { "type": "enum", "values": ["draft", "published"], "default": "draft" }
"task_id": { "type": "ref", "table": "tasks", "on_delete": "cascade", "required": true }
"avatar": { "type": "file", "accept": ["image/*"], "max_size_mb": 5 }
"score": { "type": "int", "min": 0, "max": 100 }
Table options:
"auth_table": true — auto-creates email + password_hash, enables /auth/*
endpoints (signup, login, refresh, me, change/forgot/reset)
"verify_email": true — sends verification email on signup (requires auth_table).
Auto-creates email_verified, verification_token,
verification_token_expires_at columns.
"access": { "read": "public", "create": "authenticated", "update": "owner", "delete": "owner" }
"owner_field": "user_id" — required when any access rule is "owner".
Must be a ref column pointing to the auth table.
MoonDB compares its value with the user id from
the JWT — users can only read/update/delete
their own rows. Server forces this field to
JWT.sub on INSERT and ignores client values on
UPDATE (non-admin). For auth_table itself use
"id" (user owns their own row).
"unique": [["col_a", "col_b"]] — composite unique constraints
Schema options (top-level): "timestamps": true (default), "soft_delete": false (default)
on_delete for refs: "cascade" | "restrict" (default) | "set_null"
Enum values must match [a-zA-Z0-9_-]. Adding values is non-destructive;
removing them requires "confirm_destructive": true.
Built-in columns (never define these):
id, created_at, updated_at, deleted_at (if soft_delete),
email (auth_table), password_hash (auth_table),
email_verified, verification_token, verification_token_expires_at (if verify_email),
reset_token, reset_token_expires_at (auth_table)
## Schema management
PUT https://moondb.ai/v1/schema { "tables": {...} } → apply (admin key)
PUT https://moondb.ai/v1/schema { "tables": {...}, "confirm_destructive": true }
GET https://moondb.ai/v1/schema → current schema (public key OK)
POST https://moondb.ai/v1/schema/validate { "tables": {...} } → dry-run (admin key)
GET https://moondb.ai/v1/schema/history → version history (admin key)
POST https://moondb.ai/v1/schema/seed { "users": [...], "habits": [...] } → seed rows (admin key)
Send the full schema every time — MoonDB diffs against the current version
and auto-migrates. Destructive changes (drop column, change type, narrow
enum, drop table) return a preview unless "confirm_destructive": true.
Seed supports cross-references via "@<table>.<index>" syntax (index is the
position in the seed array). For an auth_table, set "password" (string) on
each row — it's auto-hashed into password_hash. Example:
{
"users": [{ "email": "a@x.io", "password": "secret" },
{ "email": "b@x.io", "password": "secret" }],
"habits": [{ "user_id": "@users.0", "name": "Run" },
{ "user_id": "@users.1", "name": "Swim" }]
}
## REST API
GET https://moondb.ai/api/{table}?field=op.value&sort=field.desc&limit=N
GET https://moondb.ai/api/{table}/{id}
POST https://moondb.ai/api/{table} { ...fields }
PATCH https://moondb.ai/api/{table}/{id} { ...fields } — partial update
PUT https://moondb.ai/api/{table}/{id} { ...fields } — full replace (clears omitted non-default columns)
DELETE https://moondb.ai/api/{table}/{id}
POST https://moondb.ai/api/{table}/bulk [ {...}, {...} ] — atomic, up to 100 rows
Responses:
List: { "data": [...], "meta": { "total": N, "limit": N, "offset": N, "has_more": bool, "next_cursor": "eyJ..." | null } }
Single: { "data": { ...row } }
Error: { "error": { "code": "...", "message": "...", "suggestion": "..." } }
Filter operators: eq, neq, gt, gte, lt, lte, like, in, is_null, not_null
Examples: ?status=eq.active ?priority=in.high,medium ?name=like.foo%
?deleted_at=is_null ?score=gte.10
Sort: ?sort=created_at.desc Multiple: ?sort=col1.asc&sort=col2.desc
Pagination:
Offset (small tables, cheap totals): ?limit=20&offset=40
Cursor (large tables, keyset; use meta.next_cursor from the previous page):
?limit=20&sort=created_at.desc&cursor=eyJ... (opaque — never edit)
Select: ?select=id,name — return only specific fields
Include related: ?include=user_id — inline ref-target row (max 4; ref table
must have public/auth read access)
OpenAPI spec: GET https://moondb.ai/v1/openapi.json
Auto-generated from the current schema. Feed it to Postman / Swagger UI /
openapi-generator / any SDK generator. Cache-Control: 60s.
## Auth (when auth_table is set)
POST https://moondb.ai/auth/signup { email, password } → { data: { token, refresh_token, user } }
POST https://moondb.ai/auth/login { email, password } → { data: { token, refresh_token, user } }
POST https://moondb.ai/auth/refresh { refresh_token } → { data: { token, refresh_token } } (rotates)
GET https://moondb.ai/auth/me Authorization: Bearer {token} → { data: { user } }
PATCH https://moondb.ai/auth/me { display_name, ... } → { data: { user } }
POST https://moondb.ai/auth/logout Authorization: Bearer {token} (revokes refresh)
Token expires in 1 hour. Use refresh_token (30 days, rotates on every use).
Headers: Authorization: Bearer {token} for auth, X-Public-Key: pk_... for
public reads (browser code), X-Admin-Key: sk_... server-side only.
Password management:
POST https://moondb.ai/auth/change-password { current_password, new_password } (Bearer token)
POST https://moondb.ai/auth/forgot-password { email, redirect_url } → 200 always (no enum leak)
POST https://moondb.ai/auth/reset-password { token, new_password } → resets password
forgot-password requires redirect_url whose origin is in the project's
ALLOWED REDIRECT ORIGINS allowlist. Without configuration the endpoint 400s
with VALIDATION_REDIRECT_URL. Configure once per project:
GET https://moondb.ai/v1/redirect-origins (admin key)
PUT https://moondb.ai/v1/redirect-origins { "allowed_redirect_origins": ["https://myapp.com"] } (admin key)
Email verification (when verify_email: true on auth table):
On signup, a verification email is sent automatically (response has _warning).
GET https://moondb.ai/auth/verify?token=... → confirms email (link in email body)
POST https://moondb.ai/auth/resend-verification → resends (requires Bearer)
user.email_verified is 0 or 1.
External auth (Clerk, Auth0, Supabase, Firebase, Cognito, Kinde, WorkOS, Stytch):
PUT https://moondb.ai/v1/auth-config { "provider": "external", "jwks_url": "https://...", "user_id_claim": "sub", "audience": "..." }
DELETE https://moondb.ai/v1/auth-config → remove external auth
Validates RS256 JWTs via JWKS. Users auto-created on first request.
jwks_url is restricted to known-IdP host suffixes (https only, no userinfo).
audience defaults to project.id if not supplied — defends against cross-
project token replay when multiple projects share the same IdP tenant.
Key rotation:
POST https://moondb.ai/v1/rotate-keys → { admin_key?, public_key? } (admin key)
POST https://moondb.ai/v1/rotate-jwt-secret → { rotated: true } (admin key)
Rotating the JWT secret invalidates every outstanding end-user
access token. Users continue with their refresh token (which is
project-bound but lives in the registry, not signed by the secret)
— or re-login.
Changing a user's password (via change-password or reset-password) bumps a
per-row password version that invalidates all currently-issued access tokens
for that user. New JWTs after password change carry the new version.
## File storage (for `file` type columns)
POST https://moondb.ai/storage/upload multipart/form-data → { data: { id, url, size } }
Form fields: file (required), table, column (optional, validates against
schema file column constraints), row_id (optional, ties the file to a row).
When row_id is set the caller must own that row.
GET https://moondb.ai/storage/{file_id} → 302 to a presigned R2 URL (or direct stream)
DELETE https://moondb.ai/storage/{file_id} → { deleted: true }
Upload requires auth (Bearer or admin key). Downloads are served as
attachment with X-Content-Type-Options: nosniff for everything except a
whitelist of safe inline MIMEs (image/png|jpeg|gif|webp, video/mp4|webm,
audio/*) — defence against stored XSS via .html/.svg upload on the API
origin. After upload, store the returned url or id in the row's file
column via PATCH /api/{table}/{id}.
Admin-uploaded files (no Bearer at upload time) are admin-only on read.
## AI endpoints
Define endpoints in schema under "ai_endpoints" (same PUT /v1/schema call):
{ "tables": { ... }, "ai_endpoints": {
"summarize": { "model": "gemma", "prompt": "Summarize: {{text}}", "access": "auth" }
} }
Call: POST https://moondb.ai/ai/{name} { "param": "value" }
Text response: { "data": { "result": "...", "model": "...", "credits_used": N } }
Image response: { "data": { "image_base64": "<base64>", "content_type": "image/png", "model": "...", "credits_used": N } }
Image models return base64-encoded data, NOT a URL.
Text models: gemma (fast & cheap, vision), gpt-oss (most intelligent)
Image models: flux-schnell (fast), flux-dev (photorealistic)
Vision (image analysis): gemma supports image input. Pass "image" in the
request body as a base64 data URI or an https URL:
POST https://moondb.ai/ai/{name} { "text": "what is this?", "image": "data:image/png;base64,..." }
POST https://moondb.ai/ai/{name} { "text": "describe", "image": "https://example.com/photo.jpg" }
The prompt template handles {{text}}, the image is added automatically.
Access: "public" | "auth" (default) | "admin"
Credits: each plan includes free credits/month; check GET /v1/ai/balance
→ { data: { free_credits, free_credits_limit, purchased_credits, total, resets_at } }
Buy more: POST /v1/ai/topup. Balance never goes negative — over-budget
calls fail with AI_INSUFFICIENT_CREDITS (402) before the model is invoked.
## Conventions
- Never expose the admin key (sk_…) in client code. Store in .env as
MOONDB_ADMIN_KEY. Use it server-side only.
- Send Authorization: Bearer <token> once a user has logged in.
- Send X-Public-Key: pk_… for public-read endpoints from browser code.
- Every error response has a "suggestion" field — read it to self-correct.
Don't guess: the suggestion tells you the exact next call.
- Headers and bodies are JSON. CORS is open (no credentials).
- Rate limits are per-project (RPM by plan) and per-IP for auth endpoints.