Skip to main content
Glama
agentic-eng

a2db

Official
by agentic-eng

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?)

  • Read-only enforced — SQLGlot AST parsing blocks all write operations

  • All drivers bundledpip 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

pip install a2db

Claude Code (with pre-configured connection):

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):

claude mcp add -s user a2db -- a2db-mcp

Claude Desktop / Cursor / any MCP client (.mcp.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:

{
  "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

# 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):

{
  "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, ...):

{
  "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):

{
  "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 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.

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 outputlist_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)

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

FROM python:3.12-slim
RUN pip install a2db
CMD ["a2db-mcp", "--register", "myapp/prod/main", "postgresql://user:${DB_PASSWORD}@host/mydb"]
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

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

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


-
security - not tested
A
license - permissive license
-
quality - not tested

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/agentic-eng/a2db'

If you have feedback or need assistance with the MCP directory API, please join our Discord server