A small, production-minded Model Context Protocol server that safely exposes a SQL (SQLite) database to Claude and other MCP clients.
It's the pattern most teams actually want first — "let our AI read (and carefully write to) our database" — done with the guardrails that make that safe: schema introspection, validated read-only queries, and fully-parameterized guarded writes.
| Tool | Description |
|---|---|
list_tables |
List all tables and views in the database. |
describe_table |
Column schema for a table/view — name, type, nullability, PK, default. |
query |
Run a single read-only statement (SELECT / WITH … SELECT). Writes are rejected. Results capped at 200 rows. |
insert_row |
Insert one row via fully-parameterized values. Disabled in read-only mode. |
- Reads can't write.
querycompiles the statement and checks better-sqlite3'sStatement.readerflag, so anything that mutates data (INSERT/UPDATE/DELETE/DROP) is refused — no fragile keyword regex. - Side-effecting statements blocked even when they return rows.
PRAGMA,ATTACH,DETACH, andVACUUMare rejected by leading keyword, because some (e.g.PRAGMA journal_mode=WAL) do return a row and would otherwise pass the reader check while mutating database state. - No SQL injection on writes.
insert_rowvalidates the table againstsqlite_master, whitelists columns fromPRAGMA table_info, quotes identifiers, and binds every value as a parameter. - Single-statement only. Batched/stacked statements are rejected — and the check ignores comments and string-literal contents, so
SELECT ';' AS xis allowed whileSELECT …; DROP …is not. - Bounded output. Query results are capped and flagged when truncated.
- Read-only mode. Start with
SQLITE_READONLY=1to disable all writes.
npm install
npm run build
npm run seed # creates a demo database at ./data/sample.db
npm start # runs the server on stdioThe smoke test drives the server through a real MCP client:
node test/smoke.mjs| Env var | Default | Purpose |
|---|---|---|
SQLITE_DB_PATH |
./data/sample.db (auto-seeded) |
Path to the SQLite database file. |
SQLITE_READONLY |
0 |
1/true disables insert_row. |
Add to claude_desktop_config.json:
{
"mcpServers": {
"sqlite": {
"command": "node",
"args": ["/absolute/path/to/sqlite-mcp-server/dist/index.js"],
"env": { "SQLITE_DB_PATH": "/absolute/path/to/your.db" }
}
}
}claude mcp add sqlite -- node /absolute/path/to/sqlite-mcp-server/dist/index.jsAsk Claude: "Which customers have spent the most?" → it calls list_tables, describe_table, then:
SELECT c.name, SUM(o.amount_cents) AS cents
FROM customers c JOIN orders o ON o.customer_id = c.id
GROUP BY c.id ORDER BY cents DESC;TypeScript · @modelcontextprotocol/sdk · better-sqlite3 · zod
MIT © Tim McVicker