io.github.agentic-eng/a2db
Safe, read-only database access for AI agents. 5 databases, batch queries, SQLGlot.
Open source Open in the app JSON README (API)
About
Safe, read-only database access for AI agents. 5 databases, batch queries, SQLGlot.
Details
- Kind
- MCP servers
- Topic
- Databases
- Publisher
- agentic-eng
- Origin
- official
- Category
- ferramentas
- Transport
- local
- Version
- 0.2.1
- Stars
- 2
- Last push
- 2026-04-13T01:51:01Z
- Repository state
- ativo
- Language
- Python
- License
- Apache-2.0
- Added
- 2026-08-29 03:02:23
- Updated
- 2026-08-29 03:02:23
- Origin id
io.github.agentic-eng/a2db
README
<p align="center">
<h1 align="center">๐๏ธ a2db</h1>
<p align="center">
<em>Agent-to-Database</em>
</p>
<p align="center">
<strong>Give AI agents safe, read-only access to your databases. One call, multiple queries, clean results.</strong>
</p>
<p align="center">
5 databases · batch queries · pre-configured connections · SQLGlot read-only
</p>
<p align="center">
<a href="https://pypi.org/project/a2db/"><img src="https://img.shields.io/pypi/v/a2db.svg" alt="PyPI"></a>
<a href="https://pypi.org/project/a2db/"><img src="https://img.shields.io/pypi/pyversions/a2db.svg" alt="Python versions"></a>
<a href="https://github.com/yoselabs/a2db/blob/main/LICENSE"><img src="https://img.shields.io/github/license/yoselabs/a2db.svg" alt="License"></a>
<a href="https://github.com/yoselabs/a2db/actions"><img src="https://github.com/yoselabs/a2db/actions/workflows/publish.yml/badge.svg" alt="CI"></a>
<a href="https://registry.modelcontextprotocol.io/servers/io.github.yoselabs/a2db"><img src="https://img.shields.io/badge/MCP-registry-blue" alt="MCP Registry"></a>
</p>
<p align="center">
<a href="#quick-start">Quick Start</a> ·
<a href="#mcp-tools">MCP Tools</a> ·
<a href="#security">Security</a> ·
<a href="#comparison">Comparison</a> ·
<a href="#setup-by-environment">Setup</a>
</p>
</p>
---
```
Agent: "Show me active users and their recent orders"
โ
a2db execute โ 2 queries, 1 call, structured results
โ
Agent: "Got it โ 847 active users, avg order $42.50"
```
## Why a2db?
Most database MCP servers make you run one query at a time, repeat connection details on every call, and return results double-encoded inside JSON strings. a2db fixes all of that:
- **Pre-configured connections** โ define databases in `.mcp.json` with `--register`, agent queries immediately
- **Batch queries** โ run multiple named queries in a single tool call
- **Default connection** โ set connection once, use it across all queries in a batch
- **Clean output** โ structured JSON envelope with compact TSV data and per-query timing (see [why TSV?](#why-tsv))
- **Read-only enforced** โ SQLGlot AST parsing blocks all write operations
- **All drivers bundled** โ `pip install a2db` and you're done
- **Secrets stay in env** โ `${DB_PASSWORD}` in DSNs, expanded only at connection time
## Supported Databases
| Database | Driver | Async |
|----------|--------|-------|
| PostgreSQL | asyncpg | native |
| SQLite | aiosqlite | native |
| MySQL / MariaDB | mysql-connector-python | wrapped |
| Oracle | oracledb | wrapped |
| SQL Server | pymssql | wrapped |
## Quick Start
```bash
pip install a2db
```
### As an MCP Server (recommended)
**Claude Code** (with pre-configured connection):
```bash
claude mcp add -s user a2db -- a2db-mcp \
--register myapp/prod/main 'postgresql://user:${DB_PASSWORD}@host/mydb'
```
**Claude Code** (minimal โ agent calls `login` on demand):
```bash
claude mcp add -s user a2db -- a2db-mcp
```
**Claude Desktop / Cursor / any MCP client** (`.mcp.json`):
```json
{
"mcpServers": {
"a2db": {
"command": "uvx",
"args": [
"a2db-mcp",
"--register", "myapp/prod/main", "postgresql://user:${DB_PASSWORD}@host/mydb"
],
"env": {
"DB_PASSWORD": "your-password-here"
}
}
}
}
```
**Multiple databases:**
```json
{
"args": [
"a2db-mcp",
"--register", "myapp/prod/main", "postgresql://user:${DB_PASSWORD}@host/maindb",
"--register", "myapp/prod/analytics", "postgresql://user:${DB_PASSWORD}@host/analytics"
]
}
```
`--register` pre-registers connections at server startup โ the agent can query immediately. Passwords use `${ENV_VAR}` syntax and are expanded at connection time, never stored in plaintext.
### As a CLI
```bash
# Save a connection (validates immediately)
a2db login -p myapp -e prod -d main 'postgresql://user:${DB_PASSWORD}@localhost/mydb'
# Query
a2db query -p myapp -e prod -d main "SELECT * FROM users LIMIT 10"
# JSON output
a2db query -p myapp -e prod -d main -f json "SELECT * FROM users LIMIT 10"
# Explore schema
a2db schema -p myapp -e prod -d main tables
a2db schema -p myapp -e prod -d main columns -t users
# List / remove connections
a2db connections
a2db logout -p myapp -e prod -d main
```
## MCP Tools
| Tool | Description |
|------|-------------|
| `login` | Save a connection โ validates by connecting first |
| `logout` | Remove a saved connection |
| `list_connections` | List connections (no secrets exposed) |
| `execute` | Run named batch queries with pagination |
| `search_objects` | Explore schema โ tables, columns, with detail levels |
### `execute` โ the core tool
**Named dict with default connection (preferred):**
```json
{
"connection": {"project": "myapp", "env": "prod", "db": "main"},
"queries": {
"active_users": {"sql": "SELECT id, name FROM users WHERE active = true"},
"recent_orders": {"sql": "SELECT id, total FROM orders ORDER BY created_at DESC LIMIT 5"}
}
}
```
**List format (auto-named q1, q2, ...):**
```json
{
"connection": {"project": "myapp", "env": "prod", "db": "main"},
"queries": [
{"sql": "SELECT COUNT(*) AS cnt FROM users"},
{"sql": "SELECT AVG(total) AS avg_order FROM orders"}
]
}
```
**Response (TSV format โ default):**
```json
{
"active_users": {
"data": "id\tname\n1\tAlice\n2\tBob\n3\tCharlie",
"rows": 3,
"truncated": false,
"time_ms": 12
},
"recent_orders": {
"data": "id\ttotal\n501\t129.00\n500\t49.99",
"rows": 2,
"truncated": false,
"time_ms": 8
}
}
```
No `::text` casts needed โ integers, floats, timestamps, arrays, NULLs all work natively.
### Error context
When a query fails with a column error, a2db enriches the message:
```
column "nme" does not exist
Did you mean: name?
Available columns: id (integer), name (text), email (text), active (integer)
```
### Why TSV?
LLM context windows are expensive. JSON row data is verbose โ every row repeats every column name, adds braces, commas, and quotes. TSV is a flat grid: one header row, then just values separated by tabs.
For a 100-row, 5-column result set, TSV typically uses **40-60% fewer tokens** than JSON row format. The structured JSON envelope still gives you metadata (row count, truncation status) โ only the row payload is TSV.
Set `format="json"` if you need full structured output with column names on every row.
## Security
### Read-Only Enforcement
Every query is parsed by [SQLGlot](https://github.com/tobymao/sqlglot) before execution:
- **Blocked:** INSERT, UPDATE, DELETE, DROP, TRUNCATE, ALTER, CREATE, GRANT, REVOKE
- **Bypass-resistant:** multi-statement attacks and comment-wrapped writes are caught at the AST level, not just keyword matching
- **Allowed:** SELECT, UNION, EXPLAIN, SHOW, DESCRIBE, PRAGMA
This is defense-in-depth โ you should also use a read-only database user, but a2db won't let writes through even if the user has write permissions.
**Write support** is implemented in the core but not yet exposed via MCP. Planned: per-connection write permissions, explicitly enabled by the human operator โ not the agent. See [TODO.md](TODO.md).
### Credential Storage
Connections are saved in `~/.config/a2db/connections/` as TOML files.
- **`${DB_PASSWORD}` syntax** โ environment variable references are stored literally and expanded only at connection time. Secrets stay in your environment, not on disk.
- **No secrets in list output** โ `list_connections` shows project/env/db and database type, never DSNs or passwords
- Connection files are local to your machine and outside any repository
### Deployment Scope
a2db currently runs as a **local stdio MCP server**. It inherits environment variables from the process that launches it (your shell, Claude Code, Docker). This is the standard model for local MCP servers โ the same approach used by DBHub, Google Toolbox, and others.
**Planned:** remote HTTP transport with OAuth 2.1 per the MCP spec. For now, if running in Docker, inject secrets via environment variables at container runtime.
## Comparison
| Feature | a2db | DBHub | Google Toolbox | PGMCP | Supabase MCP |
|---------|------|-------|----------------|-------|--------------|
| **Databases** | 5 (PG, SQLite, MySQL, Oracle, MSSQL) | 5 (PG, MySQL, MSSQL, MariaDB, SQLite) | 40+ (cloud + OSS) | PG only | PG (Supabase) |
| **Batch queries** | Named dict + list | Semicolon-separated | No | No | No |
| **Default connection** | Set once, use for all | Per-query | N/A | Single DB | Single project |
| **Read-only** | SQLGlot AST (enforced) | Keyword check (config) | Hint/annotation | Read-only tx + regex | Config flag |
| **Write support** | Planned (per-connection) | Config flag | Via tool definition | No | Config flag |
| **Output** | JSON + TSV data | Structured text | MCP protocol | Table / JSON / CSV | JSON |
| **Schema discovery** | 3 detail levels | Dedicated tool | Prebuilt tools | Via NL-to-SQL | Dedicated tools |
| **Pre-configured** | `--register` in MCP config | Config file | YAML config | Env var | Cloud-managed |
| **Credentials** | `${ENV_VAR}` in DSN | DSN strings | Env vars + GCP IAM | Env var | OAuth 2.1 |
| **Drivers bundled** | All included | All included | Varies | Built-in | Managed |
| **CLI** | Yes | No | Yes | Yes | No |
| **Error context** | Column suggestions + types | No | No | No | No |
| **License** | Apache 2.0 | MIT | Apache 2.0 | Apache 2.0 | Apache 2.0 |
**When to use what:**
- **a2db** โ multi-DB batch queries with clean output, agent-first design, fast setup
- **DBHub** โ custom tools via TOML config, web workbench UI
- **Google Toolbox** โ GCP ecosystem, IAM integration, 40+ sources
- **PGMCP** โ natural-language-to-SQL for PostgreSQL (requires OpenAI key)
- **Supabase MCP** โ full Supabase platform management (edge functions, branching, storage)
## Setup by Environment
### Local (macOS / Linux)
```bash
pip install a2db
# CLI
a2db login -p myapp -e dev -d main 'postgresql://user:pass@localhost/mydb'
# Or add as MCP server (see Quick Start)
```
### Docker
```dockerfile
FROM python:3.12-slim
RUN pip install a2db
CMD ["a2db-mcp", "--register", "myapp/prod/main", "postgresql://user:${DB_PASSWORD}@host/mydb"]
```
```bash
docker run -e DB_PASSWORD=secret -i my-a2db-image
```
Secrets are injected as environment variables at runtime โ never baked into the image.
### CI / Automation
```bash
pip install a2db
# Pre-configured โ no login needed
a2db-mcp --register myapp/ci/main "postgresql://ci_user:${CI_DB_PASSWORD}@db-host/mydb"
# Or use CLI directly
a2db login -p myapp -e ci -d main "postgresql://ci_user:${CI_DB_PASSWORD}@db-host/mydb"
a2db query -p myapp -e ci -d main "SELECT COUNT(*) FROM migrations"
```
## Development
```bash
make bootstrap # Install deps + hooks
make check # Lint + test + security (full gate)
make test # Tests with coverage (90% minimum)
make lint # Lint only (never modifies files)
make fix # Auto-fix + lint
```
## License
Apache 2.0
---
<p align="center">
<sub>๐๏ธ Agent-first database access since 2025.</sub>
</p>
<p align="center">
<sub>Built by <a href="https://github.com/iorlas">Denis Tomilin</a></sub>
</p>
<!-- mcp-name: io.github.yoselabs/a2db -->