Skip to main content
Glama
warehouse-mcp

warehouse-mcp

by diviteja3

warehouse-mcp

CI

An MCP server that lets AI agents query a SQL database without being able to change it, stall it, or leak personal data from it.

When a team puts an agent in front of its real data, the first question is "what stops it from running DELETE, or dumping every customer's email?". This server answers that with layered controls you can explain to a security reviewer, plus an audit log they can read.

agent ──MCP──▶ list_tables / describe_table / run_query
                         │
          ┌──────────────▼──────────────┐
          │ 1. SQL guard (parsed AST)   │  one SELECT only; no writes, locks, INTO,
          │                             │  side-effect functions, system catalogs
          │ 2. Row cap                  │  wraps the query: LIMIT max_rows + 1
          ├─────────────────────────────┤
          │ 3. Read-only transaction    │  enforced by the database itself
          │ 4. Statement timeout        │  stopped by the database itself
          ├─────────────────────────────┤
          │ 5. PII masking              │  by column, through aliases, and by value
          │ 6. Audit log (JSONL)        │  every call; never the result data
          └─────────────────────────────┘

Why each layer exists

Layer

Stops

Why it isn't enough on its own

SQL guard: parses with sqlglot and rejects anything but one read-only query

DELETE, stacked statements (SELECT 1; DROP …), data-modifying CTEs, SELECT INTO, FOR UPDATE, pg_sleep, nextval, pg_read_file, dblink, catalog snooping

Parsers have edge cases

Read-only transaction: SET TRANSACTION READ ONLY (Postgres), PRAGMA query_only (SQLite)

Any write the guard missed. The tests bypass the guard entirely to prove this.

Doesn't limit cost

Statement timeout: SET LOCAL statement_timeout (Postgres), progress handler (SQLite)

Runaway joins and recursive CTEs

Doesn't limit result size

Row cap: fetches max_rows + 1 and reports truncated

Agents pulling a whole table into context

—

PII masking

SELECT email AS contact (traced through the alias), upper(full_name), and emails, SSNs or card numbers inside free-text columns

Pattern-based; see limitations

Audit log

"What did the agent look at?"

—

Rejections come back as tool errors that say why (for example Query rejected: DELETE is not allowed in a read-only query.), so the agent can fix its query instead of retrying the same one.

Related MCP server: Governor SQL MCP

Quickstart

pip install -e ".[postgres]"          # drop [postgres] for SQLite only
python examples/seed_demo.py          # demo e-commerce DB with fake PII
WAREHOUSE_URL=sqlite:///examples/demo.db warehouse-mcp

Connect it to an MCP client

Claude Desktop, Cursor, VS Code and other MCP clients use the same shape:

{
  "mcpServers": {
    "warehouse": {
      "command": "warehouse-mcp",
      "env": {
        "WAREHOUSE_URL": "postgresql+psycopg://readonly_agent@db.internal:5432/analytics",
        "WAREHOUSE_ALLOWED_SCHEMAS": "analytics,reporting",
        "WAREHOUSE_MAX_ROWS": "200"
      }
    }
  }
}

Example

Agent: Which product categories sold the most units, delivered orders only?

// run_query
{"sql": "SELECT p.category, SUM(o.quantity) AS units FROM orders o JOIN products p ON p.id = o.product_id WHERE o.status = 'delivered' GROUP BY p.category ORDER BY units DESC"}
// → {"columns": ["category","units"], "rows": [...], "row_count": 6, "truncated": false, "masked_columns": [], "elapsed_ms": 4}
// run_query
{"sql": "SELECT id, email AS contact, region FROM customers LIMIT 3"}
// → {"columns": ["id","contact","region"], "rows": [[1,"***","EU"], ...], "masked_columns": ["contact"]}
// run_query
{"sql": "WITH d AS (DELETE FROM orders RETURNING *) SELECT * FROM d"}
// → error: Query rejected: DELETE is not allowed in a read-only query.

Configuration

Variable

Default

WAREHOUSE_URL

(required)

Any SQLAlchemy URL. Tested with PostgreSQL 16 and SQLite.

WAREHOUSE_ALLOWED_SCHEMAS

all

Comma-separated allow list

WAREHOUSE_MAX_ROWS

200

Row cap per query

WAREHOUSE_TIMEOUT_MS

5000

Statement timeout

WAREHOUSE_PII_COLUMNS

built-in list

Extra column-name patterns to mask, added to the defaults (email, phone, ssn, full_name, address, …)

WAREHOUSE_AUDIT_LOG

warehouse_audit.jsonl

Empty string disables it

In production, also connect as a role with only SELECT grants. This server is defense in depth, not a replacement for database permissions.

Tests

pip install -e ".[dev]"
pytest -q

There are 45 tests: guard attack cases, PII tracing through aliases, value-level masking, end-to-end calls through a real MCP client, and audit-log contents. Read-only and timeout behaviour are tested against the database with the guard bypassed. CI runs everything against a real PostgreSQL 16 on Python 3.10, 3.11 and 3.12.

Limitations

  • PII detection is name- and pattern-based. It won't recognise a column called notes that holds home addresses, so add those columns with WAREHOUSE_PII_COLUMNS.

  • Masking follows aliases and expressions but not every path. For example, string_agg over a PII column is masked, while a join that reconstructs PII from non-PII columns isn't detected.

  • Snowflake, BigQuery and other SQLAlchemy dialects will connect, but only Postgres and SQLite have read-only and timeout enforcement at the database level today.

Roadmap

  • Snowflake and BigQuery read-only session enforcement

  • Per-table row-level filters (for example region = 'EU' for an EU-only agent)

  • Query cost estimate from EXPLAIN before execution, rejecting full scans over a threshold

  • OpenTelemetry spans per tool call

  • Streamable HTTP transport with OAuth for shared deployments

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    A
    maintenance
    Enables AI agents to query PostgreSQL databases securely with read-only defaults, PII masking, rate limiting, and human-approved, expiring mutation tokens.
    9
    1
    Apache 2.0
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents to safely query SQL databases through read-only tools for describing entities, reading records, and aggregating records, with view-based access and fail-closed per-user row-level security.
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI agents to query Postgres, BigQuery and DuckDB read-only through policy-checked SQL, with AST-based guards that refuse writes and dangerous functions, column masking on the way out, and an audit log of every query. Also exposes schema browsing and query-planning tools so agents can see allowed tables, columns and costs before running anything.
    MIT