Skip to main content
Glama
FrankieSoltero

sqlite-explorer-mcp

README.md
# sqlite-explorer-mcp

A production-grade [Model Context Protocol](https://modelcontextprotocol.io) server that exposes a
**read-only** SQLite database to any MCP client (Claude Code, Claude Desktop, …). It ships both
transports, real input/output bounds, structured logging, auth, tests, CI, and a container.

Built on the official `@modelcontextprotocol/sdk` (v1.29.x).

## Design

| Capability | MCP primitive | Why |
|------------|---------------|-----|
| Discover tables / columns | **Resource** (`schema://tables`, `schema://table/{name}`) | Read-only context the model reads; cacheable, side-effect-free |
| Run a query | **Tool** (`query`) | An action with cost/risk: validated input, can fail, auditable |

The server is defined **once** (`src/server.ts`) and exposed over two transports from shared code:

- `src/stdio.ts` — for **local** clients (the client launches it as a subprocess).
- `src/http.ts` — Streamable HTTP for **remote** clients on other machines.

## Safety

- **The read-only boundary is the engine, not a string check.** The DB is opened
  `{ readonly: true }` with `PRAGMA query_only = ON`; SQLite rejects any write regardless of the SQL
  sent. The single-statement / SELECT-only gate in `src/db.ts` only produces clearer errors.
- **Output is bounded** — `MAX_ROWS` cap + `busy_timeout` so a runaway scan can't exhaust memory.
- **Secrets via Zod-validated env** (`src/env.ts`); nothing hardcoded. HTTP requires a 32+ char
  `MCP_BEARER_TOKEN`, checked with a constant-time compare and fail-closed.
- **The stdout rule** — on stdio, stdout is the JSON-RPC channel. All logging goes to **stderr**
  (`src/logger.ts`); an ESLint `no-console` rule enforces it.

## Quick start

```bash
npm install
npm run seed          # creates data.db (sample customers + orders)
npm test              # unit + in-memory integration tests
npm run build
```

### Run locally (stdio) + connect to Claude Code

```bash
npx @modelcontextprotocol/inspector node build/stdio.js   # debug standalone first
claude mcp add --transport stdio sqlite -- node "$(pwd)/build/stdio.js"
```

### Run remotely (Streamable HTTP)

```bash
export MCP_BEARER_TOKEN=$(openssl rand -hex 32)
npm run start:http
# from any machine (behind TLS in production):
claude mcp add --transport http sqlite https://your-host/mcp \
  --header "Authorization: Bearer $MCP_BEARER_TOKEN"
```

Confirm with `claude mcp list` and `/mcp` inside a session.

### Docker

```bash
docker build -t sqlite-explorer-mcp .
docker run --rm -p 8080:8080 \
  -e MCP_BEARER_TOKEN=$(openssl rand -hex 32) \
  -e BIND_HOST=0.0.0.0 \
  -v "$(pwd)/data.db:/app/data.db:ro" \
  sqlite-explorer-mcp
```

Terminate TLS at a reverse proxy in front of the container; never expose the plain HTTP port
publicly. For per-user identity/revocation, Streamable HTTP also supports OAuth 2.0 — ship the
bearer token first and add OAuth only when compliance requires named-user attribution.

## Configuration

All via environment (see `.env.example`): `SQLITE_PATH`, `PORT`, `BIND_HOST`, `MCP_BEARER_TOKEN`,
`MAX_ROWS`, `QUERY_TIMEOUT_MS`, `LOG_LEVEL`.

## License

MIT

TDQS

A3.9/5.0

Scored across 2 tools

Disambiguation5/5

list_tables and query have clearly distinct purposes: one lists table names, the other executes SQL queries. There is no ambiguity or overlap between them.

Naming Consistency4/5

Both tool names are verbs, but list_tables follows a verb_noun pattern while query is a standalone verb. This is a minor deviation, not a major inconsistency.

Tool Count3/5

With only two tools, the server feels thin at first glance. However, the tools cover the core functionality for a read-only SQLite explorer, making the count borderline but not excessive.

Completeness4/5

The tool set provides list_tables for discovery and query for arbitrary read-only SQL, which covers most exploration needs. There is no dedicated schema tool, but query can access sqlite_master, so gaps are minor and workable.

Maintenance

ActivityInactive
ResponsivenessNo issues