warehouse-context-mcp
Provides governed, read-only context and query access for Databricks. It reads Unity Catalog comments and PII column tags, merges them with a curated catalog, and uses a USE and SELECT-only principal while exposing tools for table discovery, lineage, freshness, masked sampling, and bounded read-only queries.
Provides governed, read-only context and query access for a DuckDB data warehouse using a YAML catalog. Agents can list and describe tables, inspect lineage and freshness, sample PII-masked rows, and run bounded SELECT queries against a read-only DuckDB file.
Provides governed, read-only context and query access for Snowflake. It reads native table and column comments and PII object tags, merges them with a curated catalog, and uses a SELECT-only role while exposing tools for table discovery, lineage, freshness, masked sampling, and bounded read-only queries.
This server does not currently have a deployable release on Glama. Follow the repository's setup instructions to install and configure it for your MCP client, including any required credentials and local dependencies.
warehouse-context-mcp
An MCP server that gives AI agents trustworthy, governed context about a data warehouse: what each table means, where it comes from, how fresh it is, and a way to query it that cannot write, cannot run away, and cannot leak personal data.
Coding and analytics agents are good at SQL and bad at knowing which table to trust. Schema alone does not tell them that fct_voyage_economics is the governed source for TCE, that stg_voyages is staging, that contact_email is personal data, or that a table stopped updating two days ago. This server puts that context one tool call away, and puts guardrails around everything else.
Tools
Tool | What the agent gets |
| Every table with its documented purpose and owner |
| Columns, types, descriptions, PII flags, grain, row count, undocumented columns |
| Upstream sources and downstream consumers, cycle-safe, depth-limited |
| Last update, age, and whether it meets the documented expectation |
| A few example rows, PII masked |
| One read-only SELECT, row-limited, with PII protected |
Related MCP server: Lagaam
Backends
Backend | Status | Native context it reads | Read-only connection layer |
DuckDB | v0.1 | none (YAML catalog only) | file opened |
Snowflake | v0.2 | table/column comments, | a SELECT-only role ( |
Databricks | v0.2 | Unity Catalog comments, | a principal with only |
Comments and tags stored in the warehouse are merged with the curated YAML catalog: curated values win, native ones fill the gaps, and a PII flag from either source sticks.
Guardrails
Read-only is enforced in three independent layers, each tested on its own:
Parsing, not string matching. SQL is parsed with sqlglot. It must be exactly one statement whose root is a SELECT or a set operation, and no node in the tree may be a write, DDL,
SELECT … INTO, or an unmodelled command (COPY,ATTACH,PRAGMA,SET).Read-only connection. DuckDB opens the file read-only; Snowflake and Databricks connect with a SELECT-only role or principal, so a guardrail bug still cannot write.
Bounded results. A row limit is always applied (tightened if the query asks for more) and queries are interrupted after a timeout.
Scope: queries may only touch tables in the configured schema. Anything qualified with another database or schema is refused, and unqualified names are rewritten to fully qualified ones, so a query never depends on the session's default schema.
PII: columns flagged pii: true in the catalog can be counted (COUNT(DISTINCT email)) but never read, filtered on or grouped by, including through aliases and subqueries. SELECT * is allowed; PII values come back masked.
See docs/adr/0001-read-only-by-construction.md for the reasoning.
Quick start
Requires uv and Python 3.14 or 3.15 (uv installs it if missing).
uv sync # creates .venv from uv.lock
uv run examples/build_demo.py examples/demo.duckdb # fully synthetic shipping data
uv run pytestConnect it to Claude Code:
claude mcp add warehouse -- uv run --directory "$PWD" warehouse-context-mcp \
--duckdb examples/demo.duckdb --catalog examples/catalog.yamlThen ask: "Which vessel class had the best average TCE last quarter, and is that table fresh?"
Snowflake or Databricks
uv sync --extra snowflake # or --extra databricks, or --all-extras
cp .env.example .env # fill in your own values; .env is git-ignored
# run examples/snowflake_demo.sql (or databricks_demo.sql) once in the warehouse
uv run warehouse-context-mcp --backend snowflake --catalog examples/catalog.yamlSettings come from SNOWFLAKE_* / DATABRICKS_* environment variables or ./.env; real environment variables win, and blank values count as unset. Key-pair auth for Snowflake: uv run scripts/snowflake_keypair.py prints the one ALTER USER statement to run.
Live tests run the demo expectations against a real warehouse and are skipped otherwise:
WCM_LIVE=snowflake uv run pytest -m liveOn Windows the same commands work in PowerShell; set the variable with $env:WCM_LIVE = "snowflake".
On the Databricks Free Edition the personal access token belongs to the workspace admin, so the read-only connection layer is not available there; the query policy still applies. In a real workspace, use a service principal with only USE and SELECT grants.
Python versions
3.14 | 3.15 | |
Core, DuckDB | ✓ | ✓ |
Databricks extra | ✓ | ✓ (lz4 and thrift build from source) |
Snowflake extra | ✓ | not yet: no 3.15 wheels, and the source build fails; the extra is skipped by an environment marker |
The catalog
Meaning lives in a YAML file reviewed in Git like code (example):
tables:
fct_voyage_economics:
description: Time-charter-equivalent economics per voyage.
owner: freight-analytics
grain: voyage_id
upstream: [stg_voyages]
freshness_column: computed_at
expected_freshness_hours: 12
columns:
tce_usd_per_day: { description: TCE in USD per day. }In production it would be generated from dbt docs, Unity Catalog comments or a data catalog.
Architecture
src/warehouse_context_mcp/
├── domain/ catalog, result models, namespace, errors (no I/O beyond YAML)
├── guardrails/ QueryPolicy: parse, scope, PII rules, row limit; result masking
├── warehouses/ Warehouse protocol + DuckDB, Snowflake, Databricks adapters
├── service.py WarehouseContext: combines the three; no MCP dependency
├── server.py MCP transport: six read-only tools with typed inputs and outputs
├── settings.py pydantic-settings for connection config and secrets
└── cli.py entry pointDependencies point inwards: the transport knows the service, the service knows the domain, guardrails and the Warehouse protocol, and nothing in the domain knows about MCP or any driver.
Typed end to end. Tool results are frozen pydantic models, so MCP clients receive a JSON schema for every output. Tool arguments carry their bounds (
depth1–5,max_rows≤ 1000) in the input schema. DB-API drivers are reached through aCursor[P]protocol generic over the driver's paramstyle, so binding positional parameters to a named-style driver is a type error.Errors as data. Guardrail refusals come back as
{"ok": false, "error": ...}so the agent can repair its query; programming errors and unknown tables raise.Warehouse-agnostic core. Adapters only describe where metadata lives; fetching, caching, allow-listing and catalog assembly are shared in
DbApiWarehouse.
See docs/adr for the decisions behind the guardrails and the toolchain.
Development
uv run ruff format # format
uv run ruff check # lint: every ruff rule, with a short documented ignore list
uv run ty check # type check, strict rules
uv run pytest --cov # tests with branch coverageCI runs the same on Python 3.14 and 3.15 (Linux) and 3.14 (Windows), and builds and smoke-tests the wheel.
Roadmap
v0.1: DuckDB adapter, catalog, six tools, three-layer guardrails, tests, CI
v0.2: Snowflake and Databricks Unity Catalog adapters, native comments and PII tags, schema scoping
v0.3: Python 3.14+, uv, ruff and ty; layered package; typed tool inputs and outputs; table names qualified by the policy
Catalog import from dbt
manifest.jsonEvaluation: 20 analyst questions answered with and without the server, scored for correctness and table choice
Per-tool audit log
License
MIT
This server cannot be deployed
Maintenance
Related MCP Connectors
Safe, read-only Postgres and MySQL access for AI agents. Audit log + column-level controls.
Query 40 databases from Claude, ChatGPT, or Cursor — on any device. Read-only, encrypted, audited.
Query your org's data in natural language — read-only MCP access to SQL, NoSQL, files & warehouses.
The grounded data layer for any LLM: governed SQL, metrics, lineage and catalog over your data.
Related MCP Servers
- AlicenseNot gradedqualityAmaintenanceEnables AI agents to query governed data warehouses through plain language, returning grounded answers with SQL, confidence grades, and signed receipts while enforcing access, testing, and audit controls.279 PyPI17Apache 2.0
- AlicenseAqualityAmaintenanceEnables AI agents to query Trino and Apache Pinot lakehouses under enforced governance, where every SQL statement is AST-validated, table-allowlisted, priced from the engine's own plan before it runs, and blocked or admitted against scan-byte and intermediate-row budgets. It also grounds agents with schema discovery tools, returns verified results with warnings instead of misleading answers, and records every tool call on an audit trail.31Apache 2.0
- AlicenseNot gradedqualityCmaintenanceLets an AI assistant explore and query Postgres, DuckDB, or Snowflake with read-only SQL, table and schema discovery, sampling, and explainable guardrails.MIT
- AlicenseNot gradedqualityBmaintenanceEnables 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