database-mcp 0.6.0

A single-binary MCP server for MySQL, MariaDB, PostgreSQL, and SQLite
# CLAUDE.md

A single-binary MCP (Model Context Protocol) server for MySQL, MariaDB, PostgreSQL, and SQLite. Built in Rust with zero runtime dependencies.

## Build & Test Commands

```bash
cargo build                                    # debug build
cargo build --release                          # release build
cargo run -- --db-backend mysql --db-user root # stdio mode (default)
cargo run -- http --db-backend mysql --db-user root --host 127.0.0.1 --port 9001

cargo test                          # all tests
cargo test --workspace --lib --bins # unit tests only
cargo test <name>             # single test
cargo test -- --nocapture     # with stdout

cargo clippy --workspace --tests -- -D warnings   # lint (warnings = errors)
cargo fmt --check             # check formatting
cargo fmt                     # apply formatting

./tests/run.sh                # integration tests (Docker required)
./tests/run.sh --filter mysql # filter by backend
```

## Architecture

Cargo workspace: root binary (`database-mcp`) + 7 library crates under `crates/`. Workspace members use `"crates/*"` glob.

- **`src/`** — Binary crate. `commands/root.rs` owns CLI parsing (clap with subcommands), tracing init, config construction, backend dispatch via `Handler` enum. `commands/http.rs` and `commands/stdio.rs` handle transport modes.
- **`crates/config/`** (`database-mcp-config`) — `Config`, `DatabaseConfig`, `HttpConfig` structs, `DatabaseBackend` enum (`Mysql`, `Mariadb`, `Postgres`, `Sqlite` via `clap::ValueEnum`). `Config::validate()` accumulates errors into `Result<(), Vec<ConfigError>>`.
- **`crates/backend/`** (`database-mcp-backend`) — Shared `AppError` type, SQL read-only validation (`validation` module), identifier quoting/validation (`identifier` module), and request/response types (`types` module).
- **`crates/server/`** (`database-mcp-server`) — Shared MCP tool implementations (`tools` module) and `server_info()`. Reused by all three database handler crates.
- **`crates/mysql/`** (`database-mcp-mysql`) — MySQL/MariaDB backend: connection pooling, query operations, schema introspection, MCP handler via `rmcp::tool_router`.
- **`crates/postgres/`** (`database-mcp-postgres`) — PostgreSQL backend: per-database connection pool cache (moka), query operations, schema introspection, MCP handler.
- **`crates/sqlite/`** (`database-mcp-sqlite`) — SQLite backend: single-file connection, query operations, schema introspection, MCP handler.
- **`crates/sqlx-to-json/`** (`sqlx-to-json`) — `RowExt` trait for type-safe row-to-JSON conversion. Per-backend implementations for `SqliteRow`, `PgRow`, and `MySqlRow`.
- **Transport**: `stdio` (default, for Claude Desktop/Cursor) and `http` (Streamable HTTP with CORS via axum + tower-http).

## Configuration

**Precedence**: CLI flags > environment variables > defaults

- Env vars are set by the MCP client (via `env` or `envFile` in server config)
- Run `cargo run -- --help` for the full list of flags and env var mappings
- `MCP_READ_ONLY` defaults to `true` — write operations blocked unless explicitly disabled

## Code Style

This project follows the [Microsoft Pragmatic Rust Guidelines](https://microsoft.github.io/rust-guidelines/agents/all.txt).

- **M-MODULE-DOCS**: Every module MUST have `//!` documentation
- **M-CANONICAL-DOCS**: Public items MUST have `///` docs with `# Errors`, `# Panics` sections when applicable
- **M-FIRST-DOC-SENTENCE**: Summary sentence MUST be under 15 words
- **M-APP-ERROR**: Use `thiserror` for error type derivation
- **M-MIMALLOC-APPS**: Use mimalloc as global allocator

See [CONTRIBUTING.md](CONTRIBUTING.md) for commit conventions, PR process, and detailed testing guide.

## Security Constraints

- **Read-only mode**: ONLY allow SQL prefixed with SELECT, SHOW, DESC, DESCRIBE, USE. **ALWAYS** block `LOAD_FILE()`, `SELECT INTO OUTFILE/DUMPFILE`. Strip comments and string contents before validation.
- **MULTI_STATEMENTS**: **NEVER** enable the MULTI_STATEMENTS client flag. The connection MUST clear it to prevent multi-statement SQL injection.
- **Parameterized queries**: **NEVER** interpolate user values into SQL strings. Use parameterized queries exclusively.
- **Identifier validation**: **ALWAYS** validate database/table names (alphanumeric and underscore). Never use string interpolation for identifiers — use proper quoting per backend.

## Never Do

- **NEVER** use `unwrap()` — use `expect("reason")` or propagate with `?`
- **NEVER** interpolate user input into SQL — use parameterized queries or sqlx bind
- **NEVER** skip identifier validation for database/table names — always validate then quote
- **NEVER** enable `MULTI_STATEMENTS` on any database connection
- **NEVER** add features, refactor, or "improve" code beyond what was explicitly asked
- **NEVER** duplicate content from `CONTRIBUTING.md` — reference it instead
- **ALWAYS** use `thiserror` for new error types, not manual `impl Display`

## Before You're Done

ALWAYS run before considering any task complete:

```bash
cargo fmt                           # format
cargo clippy --workspace --tests -- -D warnings         # lint
cargo test --workspace --lib --bins # unit tests
./tests/run.sh                # integration tests (Docker required)
```

ALWAYS verify for new/modified code:
- Every new module has `//!` doc comment
- Every public item has `///` doc comment with `# Errors`/`# Panics` where applicable
- The first doc sentence is under 15 words