localdb-agent
Provides tools for interacting with SQLite databases, enabling schema inspection, read-only queries, safe previews on an in-memory clone, transactional execution of mutations, and snapshot-based rollback.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@localdb-agentWhat tables are in demo.db and how many rows does each have?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
LocalDB Agent
Git for AI database operations. A local-first AI database operator that connects to SQLite databases through the Model Context Protocol (MCP) and lets you manage them safely with natural language.
Type "create a users table with id, name, email and 10 sample rows" or "delete every cancelled order" and instead of blindly running SQL, the app runs an agentic pipeline:
Understand → Inspect → Plan → Safety Check → Preview → Confirm → Execute → Verify → CompleteEvery step streams into an animated timeline. You see the generated SQL as it's drafted, the affected tables and rows, a risk score, the real before/after schema and row diff (computed on a throwaway clone — see Sandbox preview), verification query results, and execution timing. Destructive changes require an explicit confirmation, run inside a single transaction, and are protected by an automatic snapshot so any operation — including a branch merge — can be rolled back with one click.
It is not a text-to-SQL chatbot.
Contents
Related MCP server: MCP-Server-data
Why
Letting an LLM run SQL against a real database is a bad trade: one mis-scoped UPDATE or a
DROP TABLE and the data is gone. The usual "text-to-SQL" tools generate a statement and
hope. LocalDB Agent instead treats each request the way a careful engineer would:
read the schema first so the plan is grounded in what actually exists,
run the plan on a disposable copy and show you exactly what it does — real row counts, real diffs, real failures — before touching your data,
gate anything destructive behind a confirmation with those real numbers in front of you,
wrap execution in a transaction and snapshot first, so undo is always available,
and expose the whole capability layer as an MCP server so the same guarantees apply whether you drive it from the web UI or from Claude Desktop / Cursor / any MCP client.
The result feels like version control for database changes rather than a chat window that occasionally corrupts your database.
Highlights
Agentic workflow | 9-stage pipeline with streamed reasoning, streamed SQL, and a live timeline |
Database branching | fork the DB into a named branch, let the agent run wild, diff branch vs. main, then merge the good version back or discard it — like |
Truthful preview | every plan runs first against a throwaway in-memory clone — you see the real result, real schema/row diff, real verification outcomes, real failures — before anything touches your database |
Safe by construction | static SQL analysis, risk scoring, hard-blocked statements, parameterised reads, transaction boundaries, read/write separation |
Snapshots & undo | online-backup checkpoint before every mutation; one-click rollback — a merge is undoable too |
Schema intelligence | explorer with tables / columns / row counts / indexes / FKs, and structural before/after diffs |
Command palette | ⌘K / Ctrl-K for connect, switch DB, new conversation, re-run, theme |
MCP-native | the database capability layer is a standalone MCP server — external clients get the same guarantees |
Single- or multi-user | zero-auth by default; flip |
Free by default | offline rule-based planner needs no API key; optional Ollama / Anthropic / OpenAI |
Polished UI | Next.js + shadcn-style components + Framer Motion, dark/light, responsive |
Stack
Next.js 15 (App Router) · React 19 · TypeScript · Tailwind + shadcn-style UI · Framer Motion ·
@modelcontextprotocol/sdk · better-sqlite3 · Drizzle ORM (query builder) · Zod ·
Server-Sent Events · Vitest
Quick start
npm install
cp .env.example .env # optional — the defaults work with zero config
npm run seed # builds databases/demo.db (customers / products / orders)
npm run dev # http://localhost:3000Open the app, connect demo.db, and try any of these:
Read / explore
list all tables · describe orders · how many customers do we have · show all customers in Berlin · show orders where status = 'shipped'
Schema
create a suppliers table with id, name, email and 10 sample rows · add a column phone to customers · rename table products to catalog · rename column status to state in orders · drop the suppliers table
Data
insert 25 sample rows into products · set status to shipped in orders where id = 3 · delete from orders where status = 'cancelled' ← asks for confirmation
Raw SQL (passed through verbatim, still safety-checked and previewed)
SELECT product_id, COUNT(*) FROM orders GROUP BY product_id ORDER BY 2 DESC
Production build
npm run build
npm start # = node .next/standalone/server.js (PORT, default 3000)The app-metadata database is created and kept current automatically on first run — there is no separate migration step.
Docker
Build it yourself:
docker compose up --build # http://localhost:3000Or pull the image built by CI on every push (package page):
docker run -p 3000:3000 \
-v "$PWD/databases:/databases" \
-v localdb-data:/data \
ghcr.io/madhurv01/localsqllitemcp:latest./databases is bind-mounted; app metadata + snapshots persist in the localdb-data
volume.
A request, start to finish
What you actually see when you send "create a suppliers table with id, name, email and insert 5 sample suppliers":
Stage | What happens | What you see |
Understand | the request is echoed back | ✓ Understood |
Inspect |
| Found 3 tables — customers (12), orders (30), products (8) |
Plan | the planner (offline rules, or an LLM) turns the request into concrete SQLite statements + verification queries | reasoning streams in; each SQL statement appears as it's drafted |
Safety Check | the statement splitter classifies each statement, scores risk, checks the hard-block list | Risk: LOW — 0 destructive statements |
Preview | the whole plan runs on an in-memory clone of the DB | 1 table changed · +5 −0 rows · checks 2/2 with a real schema diff + sample inserted rows |
Confirm | (destructive / high-risk plans only) the pipeline stops and persists an | Apply to database / Cancel, with the real impact numbers |
Execute | the identical SQL runs on the real DB in one transaction; a snapshot is taken first | Executed 2 statements in 0.9 ms |
Verify | the plan's verification | ✓ "suppliers" has 5 rows |
Complete | the operation is recorded; schema diff + result are shown | Undo / rollback button; follow-up suggestion chips |
If the plan would fail (a foreign-key violation, a bad column name, a typo) the sandbox catches it at Preview — the pipeline stops, nothing is written, and no mutating operation is created.
What the built-in planner handles
With LLM_PROVIDER=heuristic (the default) there is no API key and no network call —
a rule-based intent router maps natural language to real SQLite. It recognises:
Intent | Example phrasings |
list tables / count tables / schema overview | list all tables, what tables are there, how many tables, show me the schema |
describe a table | describe orders, columns of customers, the ddl for orders |
count rows | how many rows in orders, count the records in customers, how many customers do we have |
select rows | show all users, list orders where status = 'x', show customers in Berlin (guesses the column), first 20 products |
create table (+ optional seed) | create me a table named test_env with two fields name, dob, fill 10 rows of dummy data |
add / drop / rename column | add a column age to users, drop column city from customers, rename column status to state in orders |
rename / drop table | rename table products to catalog, rename products table to catalog, drop the suppliers table |
insert rows | insert 20 sample rows into products (sample values are realistic and collision-free with existing rows) |
update rows | set status to shipped in orders where id = 3, update orders set quantity = 1 |
delete rows | delete from orders where status = 'cancelled', delete every row from logs |
raw SQL | anything starting with a real |
Anything it can't map is surfaced honestly ("try phrasing it as an operation…") rather than
guessed. For open-ended requests set LLM_PROVIDER=ollama (free, local) or anthropic /
openai. If a configured LLM is unreachable the app automatically falls back to the offline
planner and tells you in the UI.
Database branching
Completes the "Git for database operations" metaphor. Everything still runs through the
pipeline — branching just changes which .db file is active.
Fork — copies the active branch's file (
VACUUM INTO) intodata/branches/, instantly, and records the parent's schema at fork time. The parent is untouched.Switch — the branch switcher in the header swaps the file the agent operates on. History, undo, schema and operations are all branch-scoped.
Compare — branch vs. parent: schema diff, per-table row-count deltas, and the list of operations that ran on the branch.
Merge — checks the parent hasn't diverged structurally since the fork (else it flags a conflict), previews the combined statements on a sandbox copy of the parent, then on confirm replays them onto the parent in one transaction with a snapshot — so the merge itself is undoable. You land back on the parent branch.
Discard — deletes the branch file and its operations;
mainnever knew it existed.
Try: fork a branch → "delete half the customers" → Compare shows
customers: 12 → 6→ Merge or Discard.
Sandbox preview
src/lib/sqlite/sandbox.ts makes the Preview stage truthful instead of a static guess:
clone the database with
VACUUM INTOa scratch file,load the bytes into a private in-memory SQLite instance, delete the scratch file,
run the plan in a transaction on the clone,
capture the real
StatementResults, a realSchemaDiff, a row-level diff (added / removed / changed rows keyed onrowid, with samples), and real verification results,discard the clone. Nothing is written back.
If the plan errors, the pipeline stops at Preview and never creates a mutating operation.
Databases larger than LOCALDB_SANDBOX_MAX_MB (default 200) skip the clone and fall back to
static analysis, with a note in the UI.
Safety model
Hard-blocked regardless of confirmation:
ATTACH/DETACH DATABASE,load_extension,PRAGMA writable_schema,VACUUM INTO.Risk scoring:
safe → low → moderate → high → critical.UPDATE/DELETEwithout aWHEREjump tohigh;DROPiscritical.Confirmation gate: any destructive statement or
high+ risk stops the pipeline and persists anawaiting_confirmationoperation until you approve (or setLOCALDB_REQUIRE_CONFIRM_ALL=trueto gate every mutation).Transactions: every batch runs in one
better-sqlite3transaction — any failure rolls the whole batch back, nothing is committed.Snapshots: a physical WAL-truncated file copy is taken before mutations; Undo closes connections, restores the file, and marks the snapshot consumed.
Reads are parameterised and run on a
readonlyconnection, separate from the read/write pool.Path safety: user-supplied paths are confined to the workspace root; traversal, absolute paths outside it, non-
.db/.sqliteextensions and URIs are rejected.
Multi-user
Default is AUTH_MODE=single — no login, one shared local workspace, exactly the current
behaviour. Set AUTH_MODE to turn on isolation:
Mode | Identity source | Setup |
| none (user | nothing |
| a trusted reverse-proxy header ( | put Cloudflare Access / oauth2-proxy / nginx |
| GitHub / Google via Auth.js |
|
In header / oauth mode:
each user gets a private directory
$LOCALDB_DB_ROOT/u/<id>/and only ever sees and operates their own databases — every API route is ownership-checked (cross-user access returns404),users upload a
.db/.sqlitefrom their computer (validated by SQLite magic bytes, size-capped) or create one,per-user quotas —
LOCALDB_MAX_DBS_PER_USER(10),LOCALDB_MAX_DB_MB(50),LOCALDB_MAX_USER_MB(200) — and an agent-request rate limit (LOCALDB_AGENT_RATE/_WINDOW_S) are enforced,the header shows the signed-in identity and workspace usage.
Run one instance — the connection pool and local files are per-process and
better-sqlite3 is synchronous; scale up, not out.
Full recipes (Cloudflare Access, oauth2-proxy, Auth.js) are in DEPLOYMENT.md.
Configuration
All optional — see .env.example.
Variable | Default | Purpose |
|
| workspace root; paths outside it are rejected |
|
| app metadata DB, snapshots, branch files |
|
|
|
|
| free local LLM planning |
| — | enable hosted planners |
|
| cap on rows returned per read |
|
| skip the clone above this DB size (static preview only) |
|
| sample changed rows shown from a preview |
|
| require confirmation for every mutating op |
|
|
|
| (4 common headers) | comma-separated headers to check in |
| — |
|
|
| per-user quotas |
|
| per-user agent-request rate limit |
|
|
|
Deploying
The build output (.next/standalone/server.js) is a self-contained Node HTTP server. It
needs a long-lived Node process + a persistent writable volume + a streaming HTTPS proxy +
auth — so not Vercel / Netlify / Lambda (better-sqlite3 is native and it writes real
files).
Docker / VM / systemd — DEPLOYMENT.md §1–3
Fly.io — a ready
fly.tomlis included:fly launch --no-deploy→fly volumes create localdb_data --size 1→fly deploy→fly scale count 1VPS + Caddy — auto-HTTPS + basic auth in ~6 lines; with
AUTH_MODE=headereach Caddy-authenticated user gets an isolated workspaceFree & 24/7, no cloud bill — run it on a device you own (Raspberry Pi, old laptop, mini-PC) and expose it with Tailscale Funnel (
tailscale funnel 3000) or a Cloudflare Tunnel — free, stable public HTTPS URL, no card. (Cloud "always-free" tiers all require a payment card; the free PaaS tiers wipe your SQLite files on restart.)
Full walkthrough — including the scaling and security notes — in DEPLOYMENT.md.
Using the MCP server standalone
The database capability layer is also a standalone MCP server, so any MCP client gets the exact same validation, sandboxing and safety guarantees the web app uses.
npm run mcp:stdio{
"mcpServers": {
"localdb-agent": {
"command": "npx",
"args": ["tsx", "src/bin/mcp-stdio.ts"],
"env": { "LOCALDB_DB_ROOT": "/absolute/path/to/databases" }
}
}
}Tool | Reads/writes | Description |
| read | tables + views in the database |
| read | columns, indexes, foreign keys, row count for one table |
| read | the whole schema + row counts as a structured object |
| read-only | run |
| none | static analysis only — statement kinds, risk, destructive count, affected tables |
| none (clone) | run the SQL on a throwaway in-memory clone; returns real statement results, schema diff, row-level changes, verification outcomes — the real DB is untouched |
| write | run SQL in one transaction; auto-snapshots before mutations; |
| write | restore a captured snapshot over the database (undo) |
Architecture
Modular services, each independently testable:
src/lib/
config.ts env + paths + per-user roots
auth.ts / auth-oauth.ts pluggable identity (single | header | oauth)
quota.ts per-user database / disk / rate limits
db/ Drizzle query-builder schema + app metadata SQLite (isolated from user DBs)
sqlite/
path-safety.ts path-traversal / extension / URI rejection; per-user confinement
connection-manager.ts one isolated pooled connection per file (ro / rw split)
introspect.ts schema + row-count capture
diff.ts structural before/after diff
safety.ts statement splitter, classifier, risk model, hard blocks
executor.ts parameterised run + single-transaction batch
snapshot.ts online-backup checkpoint + restore
sandbox.ts in-memory clone for the truthful preview
clone.ts VACUUM INTO file / in-memory copy helpers
branching.ts fork / switch / compare / merge / discard branches
mcp/
tools.ts the canonical capability layer (Zod-validated)
server.ts MCP server wrapper
client.ts in-process client used by the agent
agent/
providers/ heuristic | ollama | anthropic | openai (+ automatic fallback)
prompts.ts system/user prompt + schema serialisation
orchestrator.ts the 9-stage pipeline as an async event generator
repo.ts metadata CRUD, ownership-scoped
sse.ts AsyncGenerator<AgentEvent> → text/event-streamThe UI (src/components/*) consumes SSE and never touches SQLite directly. The agent
orchestrator calls mcp/client.ts, which calls the exact same mcp/tools.ts registry the
standalone MCP server exposes — so external MCP clients inherit every guarantee.
The app-metadata schema lives in src/lib/db/app-db.ts (BOOTSTRAP_SQL +
runInlineMigrations) and is created / upgraded automatically on boot;
src/lib/db/schema.ts is the Drizzle view of the same tables used by the query builder.
Data & files
Path | Contents |
| user |
| metadata: databases, branches, conversations, messages, operations, snapshots |
| pre-mutation file checkpoints (one per mutating op) |
| each branch's own |
| transient — scratch files for the preview clone, deleted immediately |
Backups: copy LOCALDB_DB_ROOT and LOCALDB_DATA_DIR. Prune old snapshots with
find "$LOCALDB_DATA_DIR/snapshots" -name '*.db' -mtime +30 -delete.
Scripts & tests
Script | Description |
| Next.js |
| build |
| standalone MCP server (stdio) |
| Vitest |
| TypeScript / ESLint |
npm testCovers: the string/comment-aware statement splitter, statement classifier, risk model and hard blocks; path-traversal and per-user path confinement; header-mode identity parsing; cross-user isolation and quotas; the sandbox row-diff and failure capture; the offline planner's intent routing; and an end-to-end plan → preview → execute → verify → rollback and branch fork → diff → merge cycle on temp databases.
See CONTRIBUTING.md for the pre-PR checklist and ground rules.
FAQ
Does it work fully offline?
Yes — with LLM_PROVIDER=heuristic (the default) there are no network calls at all. The one
build-time exception is a webfont fetched by next/font; it's cached after the first build.
Where is my data? On disk, next to the app — see Data & files. Nothing is sent anywhere unless you opt into a hosted LLM provider.
Can I undo a merge? Yes. A merge runs as a normal operation with a pre-mutation snapshot, so it shows up in Operations with an Undo button like any other.
Can multiple people use one instance?
Yes, with AUTH_MODE=header or oauth — each gets a private, isolated workspace. See
Multi-user.
Is there a size limit on databases?
The agent works on any size, but the truthful preview clone is skipped above
LOCALDB_SANDBOX_MAX_MB (falls back to static analysis). In multi-user mode uploads and
total disk are quota-capped.
Which SQL is never allowed?
ATTACH / DETACH DATABASE, load_extension, PRAGMA writable_schema, VACUUM INTO —
blocked regardless of confirmation.
Does it modify my database while I'm just asking questions?
No. Read-only requests (list tables, show orders, how many customers) never create a
snapshot, never open a write connection, and never appear as an undoable operation — only
plans with at least one mutating statement do.
What happens if the server crashes mid-execution?
Nothing partial is ever committed: every batch runs inside one better-sqlite3
transaction, so a crash or error rolls the whole statement group back atomically, and the
pre-mutation snapshot means even a fully-applied change can still be reversed afterward.
Using it on your own machine
Everything above talks to LocalDB Agent through its web UI. The exact same
capability layer is also a local MCP server (npm run mcp:stdio), so any MCP-aware
tool already on your machine can operate your local .db files too — with the identical
safety, sandbox-preview and snapshot guarantees. No web app, no port, no browser: it's a
process your AI client spawns and talks to over stdio.
Pick a database root — a real, existing folder on disk, e.g.
C:\Users\you\sqlite-projects(Windows) or~/sqlite-projects(macOS/Linux). This is the sandbox boundary; the server can only read/write.dbfiles inside it.Point your client at this repo's stdio entrypoint, passing that folder as
LOCALDB_DB_ROOT:{ "mcpServers": { "localdb-agent": { "command": "npx", "args": ["tsx", "M:/LocalSqlLiteMCP/src/bin/mcp-stdio.ts"], "env": { "LOCALDB_DB_ROOT": "C:/Users/you/sqlite-projects" } } } }Claude Desktop — Settings → Developer → Edit Config, add the block above to
claude_desktop_config.json, restart the app.Claude Code (CLI) —
claude mcp add localdb-agent -- npx tsx M:/LocalSqlLiteMCP/src/bin/mcp-stdio.ts(or add the same JSON undermcpServersin.claude/settings.jsonfor one project).Cursor / Windsurf — Settings → MCP → Add server, same command/args/env.
VS Code (Claude/Copilot MCP extensions) — add to the extension's
mcp.jsonthe same way.
Ask it to operate a file in that folder — from the client's normal chat: "using localdb-agent, list the tables in sales.db" or "create a
notes.dbwith a title and body column". The client callsdry_run/preview/executeon your behalf; you still get transactional writes and an automatic pre-mutation snapshot for every change.Review or undo it visually whenever you like — run
npm run devonce, open it against the sameLOCALDB_DB_ROOT, and check the Operations tab. The web app and the stdio server share the sameLOCALDB_DATA_DIRmetadata, so every change made from Claude Desktop, Cursor, or any other client shows up there with a one-click Undo.
Because the stdio server and the web app read the same LOCALDB_DB_ROOT /
LOCALDB_DATA_DIR, you can freely mix the two on one machine: fork a branch and run a risky
change from Claude Desktop, then open the web UI to review the diff, merge, or roll it back.
This server cannot be deployed
Maintenance
Related MCP Connectors
An agent-native database over MCP: shared, validated, structured records in every AI chat.
Analytical memory for AI agents: a real Postgres queried in plain English over MCP. One command.
AI agents propose database changes as reviewable requests — no direct write access.
- mcpOAuthcom.gibsonai
GibsonAI MCP server: manage your databases with natural language
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceAn MCP server that enables AI agents to interact with SQLite databases by querying schemas, executing SQL, and inspecting table metadata. It supports safe database access through configurable read-only modes, query timeouts, and dry-run execution plans.MIT
- FlicenseNot gradedqualityBmaintenanceEnables AI agents to query a SQLite database using natural language through the Model Context Protocol (MCP). Includes security guardrails that block destructive SQL operations.-
- AlicenseNot gradedqualityBmaintenanceEnables natural language querying of SQLite databases through a secure MCP server that writes, runs, and explains SQL with a three-layer read-only guarantee.MIT
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to safely work with SQLite databases by enforcing read/write separation, dry-run writes with confirmation, automatic backups, and an audit trail.MIT