sqlens-mcp
by dicoy
README.md
# sqlens-mcp
[](https://github.com/dicoy/sqlens-mcp/actions/workflows/ci.yml)
An MCP server that gives Claude read-only access to your local development databases — inspect schemas, run queries, and explain query plans across Postgres, MySQL, and SQLite without leaving the conversation.
Built with the [Model Context Protocol](https://modelcontextprotocol.io/) TypeScript SDK and a dialect-agnostic provider pattern. Only SELECT statements are permitted; SQLite connections open in `readonly` mode at the driver level.
---
## Tools
| Tool | What it answers |
|---|---|
| `list_connections` | What databases are configured? (credentials masked) |
| `list_tables` | What tables and views exist? How many rows? How large on disk? |
| `describe_table` | What are the columns, types, nullability, defaults, indexes, and foreign keys? |
| `run_query` | Run a SELECT and get results as a formatted table (max 500 rows, default 50). |
| `explain_query` | What query plan does the engine choose? (`EXPLAIN ANALYZE` on Postgres, `EXPLAIN QUERY PLAN` on SQLite). |

---
## Installation
### Via npm (recommended)
```bash
npm install -g sqlens-mcp
```
Or use it without installing — `npx` will fetch and run it on demand (see Claude config below).
### From source
```bash
git clone https://github.com/dicoy/sqlens-mcp.git
cd sqlens-mcp
npm install
npm run build
```
### Add to Claude Code
```bash
claude mcp add sqlens -- npx -y sqlens-mcp
```
### Add to Claude Desktop
Add to `~/Library/Application Support/Claude/claude_desktop_config.json` on macOS:
```json
{
"mcpServers": {
"sqlens": {
"command": "npx",
"args": ["-y", "sqlens-mcp"],
"env": {
"DEVDB_URL": "postgres://localhost/myapp"
}
}
}
}
```
---
## Configuration
Connections are configured with environment variables. No config files.
### Single connection
```bash
DEVDB_URL=postgres://localhost/myapp
```
### Multiple named connections
Any `DEVDB_<NAME>` variable registers a named connection. The suffix is lowercased and underscores become hyphens.
```bash
DEVDB_URL=postgres://localhost/myapp # "default"
DEVDB_STAGING=mysql://staging.internal/myapp # "staging"
DEVDB_LOCAL=sqlite:///absolute/path/to/dev.db # "local"
```
Claude selects a connection by name: `run_query({ sql: "...", connection: "staging" })`. If no connection is specified, the default is used.
### Supported dialects
| Dialect | URL prefix | Example |
|---|---|---|
| PostgreSQL | `postgres://` or `postgresql://` | `postgres://user:pass@localhost:5432/mydb` |
| MySQL | `mysql://` | `mysql://user:pass@localhost:3306/mydb` |
| SQLite | `sqlite://` or `.db` / `.sqlite` path | `sqlite:///Users/you/dev.db` |
### Claude Desktop: multiple connections
```json
{
"mcpServers": {
"sqlens": {
"command": "npx",
"args": ["-y", "sqlens-mcp"],
"env": {
"DEVDB_URL": "postgres://localhost/myapp",
"DEVDB_ANALYTICS": "postgres://localhost/analytics",
"DEVDB_LOCAL": "sqlite:///Users/you/local.db"
}
}
}
}
```
---
## Safety
- **SELECT only** — every query is validated before execution. Anything other than `SELECT` or `WITH` is rejected with a typed error before it reaches the database.
- **SQLite readonly mode** — SQLite connections use `readonly: true` at the `better-sqlite3` level. Writes are blocked by the OS, not just by the check above.
- **Credential masking** — `list_connections` shows URLs with passwords replaced by `****`. Credentials never appear in tool output.
- **Row cap** — `run_query` returns at most 500 rows; default is 50.
---
## Architecture
```
src/
├── providers/
│ ├── db.ts # IDbProvider interface + shared types
│ ├── postgres.ts # PostgresProvider — pg.Pool, information_schema + pg_index
│ ├── mysql.ts # MySqlProvider — mysql2/promise, information_schema
│ ├── sqlite.ts # SqliteProvider — better-sqlite3 (readonly: true), PRAGMAs
│ └── connection-config.ts # env parsing, createProvider() factory, maskCredentials()
├── errors/
│ └── index.ts # DevDbError hierarchy (ConnectionNotFoundError, ReadOnlyViolationError, …)
├── tools/ # One directory per tool: schema.ts + handler.ts + handler.test.ts
└── registry/
└── tool-registry.ts # resolveProvider(), per-call provider lifecycle
```
**Design principles:**
- **Single interface, three dialects** — `IDbProvider` exposes `listTables`, `describeTable`, `runQuery`, `explainQuery`, and `close`. Tool handlers never import a concrete provider class.
- **Connection-per-call** — each tool call opens a fresh provider and closes it in a `finally` block. No shared state between calls, no connection leaks.
- **One Zod schema per tool** — the same schema drives both MCP input validation and TypeScript types. No duplication.
- **Typed error hierarchy** — `ConnectionNotFoundError`, `ReadOnlyViolationError`, `TableNotFoundError`, and others. The registry catches `DevDbError` and formats each one as a clear message for Claude rather than a stack trace.
---
## Development
```bash
npm run dev # build in watch mode
npm run typecheck # tsc --noEmit
npm run lint # biome check
npm run lint:fix # biome check --write
npm run test # vitest run
npm run test:watch # vitest (interactive)
npm run ci # typecheck + lint + test + build
npm run demo # run the demo script (Node 20+ required)
```
### Adding a new dialect
1. Implement `IDbProvider` in `src/providers/<dialect>.ts`
2. Add the URL pattern to `detectDialect()` in `connection-config.ts`
3. Add the case to `createProvider()` in `connection-config.ts`
### Adding a new tool
1. Create `src/tools/your-tool/schema.ts` — Zod input schema
2. Create `src/tools/your-tool/handler.ts` — pure function, injected `IDbProvider`
3. Create `src/tools/your-tool/handler.test.ts` — mock `IDbProvider`, not a real database
4. Register in `src/registry/tool-registry.ts`
---
## Tech stack
| | |
|---|---|
| Runtime | Node.js 20+ |
| MCP SDK | `@modelcontextprotocol/sdk` |
| Validation | `zod` |
| PostgreSQL | `pg` |
| MySQL | `mysql2` |
| SQLite | `better-sqlite3` |
| Build | `tsup` |
| Tests | `vitest` |
| Lint + format | `biome` |
TDQS
A4.1/5.0
Scored across 5 tools
Disambiguation5/5
Each tool has a distinct purpose: schema description, query explanation, connection listing, table metadata, and query execution. No overlapping functionality.
Naming Consistency5/5
All tool names follow a consistent verb_noun pattern in snake_case (e.g., describe_table, list_tables). No deviations.
Tool Count5/5
5 tools is well-scoped for a SQL analysis server, covering essential database introspection and querying operations without excess.
Completeness5/5
The tool set provides a complete surface for read-only SQL analysis: schema exploration, query execution, execution plans, and connection management. No obvious gaps.
Maintenance
ActivityInactive
ResponsivenessNo issues