Lagaam
OfficialLagaam is a read-only MCP server that lets an AI agent ground itself in allowed schema metadata and run single SELECT queries that are schema-checked, cost-priced from the engine's own plan, budget-gated before execution, and audited.
list_catalogs— discover every catalog, schema, and table the agent is granted access to (names outside the grant are never revealed).describe_table— get a table's exact columns, types, comments, and row estimate to ground SQL before writing it.query_data— run one read-onlySELECTand get rows back, with a hard cap on returned rows.Read-only enforcement — single statement only; no DDL/DML, no
SELECT *, no multi-statement injection or table-function passthrough; a missingLIMITis injected, and an oversized one is lowered to the row cap before it runs.Pre-execution cost gating — scans are priced from
EXPLAIN(scan bytes and widest-step row count), and a query over budget is refused with the number and a suggested fix rather than being allowed to stall the cluster.Verified results — warnings flag zero rows, truncated pages, and all-NULL columns so the agent isn't misled;
truncatedsignals more rows exist than were returned.Auditing — every tool call is written as a JSONL audit line recording who ran what, whether it was allowed or denied, and why.
Provides governed access to Trino, enabling schema grounding via list_catalogs and describe_table, pre-execution cost quotation and budget gating using Trino's EXPLAIN plans, read-only AST validation, query execution with row caps and timeouts, result verification, and audit logging.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Lagaamshow me total sales by region for last quarter"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.

One agent query shouldn't stall everyone's Trino. On a shared cluster, a missing partition filter or an accidental cross join doesn't cost you a bill — it costs everyone else their queries. Lagaam is an MCP server that sits between your AI agents and your lakehouse (Trino and Apache Pinot): every query is schema-grounded, priced from the engine's own plan before it runs, checked against a budget, and audited — and the bad one is refused before it runs, with a fix the agent can act on.

Real session, real Trino, nothing mocked — reproduce it with
uv run --project server python examples/demo.py.

Real session, real Pinot 1.5.1 realtime table, nothing mocked — reproduce it
with uv run --project server python examples/demo_pinot.py.
The problem
Agents write syntactically-valid, catastrophic SQL, and on a shared cluster
the damage isn't theirs alone. A missing partition filter turns into a full
scan that ties up every worker; an accidental cross join fills worker memory
while everyone else's queries wait behind it; a retry loop runs it again. A
SELECT * drags 40 columns into a context window that needed 2. The usual
fix is to not give agents database access at all.
Lagaam gives them access with reins on:
Cost is a quotation, not a bill. Every query is priced from the engine's own plan before execution —
EXPLAIN (TYPE IO)for the bytes it would scan,EXPLAIN (TYPE LOGICAL)for the widest row count any operator would build (the number a cross join blows and aLIMITcannot hide). Over budget → blocked, with the number and the fix.Un-estimable means no. No table statistics, a self-join that breaks the estimate, a passthrough the planner can't see — the gate fails safe instead of hoping.
Read-only, enforced in the AST. Single
SELECTonly. No DDL/DML, no multi-statement injection, noSELECT *, no table-function passthrough, and aLIMITis injected when missing. Validated SQL is re-rendered, so what runs is exactly what was checked.Agents ground themselves.
list_catalogsanddescribe_tablereturn exact names, types, and row estimates — scoped to the agent's table grant, so the agent never learns names it isn't allowed to touch.Results are verified before they're trusted. Zero rows, truncated pages, all-NULL columns — the agent gets a warning with a next action, not a silently misleading answer.
Every call is audited. One JSONL line per tool call: who, what, allowed or denied, and why.
Related MCP server: sql-steward
Doesn't Trino already limit this?
It limits a query once it is running. Lagaam refuses it before it starts. Keep both.
Acts | What the agent sees | Catches a cross join that reads little | |
during execution: terminated once the bytes are scanned | a query failure | no — it counts bytes read, not rows built | |
during execution: terminated after the time is spent | a query failure | only after it has held the cluster that long | |
Resource groups ( | on the next query: new queries queue once the group is over its share | its later queries wait | no — the running query continues |
Lagaam | before execution, from | a refusal with the number and the fix | yes — it prices the rows the widest step would build |
Measured on Trino 476: SELECT o.orderkey, l.partkey FROM tpch.tiny.orders o CROSS JOIN tpch.tiny.lineitem l LIMIT 10 reads 676,575 bytes by Trino's own
plan — any scan cap above 0.7 MB lets it run — and would build 902,625,000
rows. Lagaam refuses it before it runs: "This query would build 902,625,000
rows at its widest step, over your budget of 50,000,000 … a LIMIT will not
help — join on a column with more distinct values, or filter each side
before the join."
Resource groups stay your backstop for everything that does reach the cluster; Lagaam is the gate in front of it for agent traffic.
What about other database MCP servers?
They're good servers that do a different job: keep the agent read-only and bound what comes back. Lagaam does that too, and also prices the query first.
Read-only | Row cap | Timeout | Checks the plan before running | |
on by default | results truncated while fetching ( | during execution ( | no — | |
always on, parsed before execution |
| during execution ( | no | |
opt-in ( | opt-in ( | opt-in ( | no — opt-in | |
Lagaam | always on, parsed before execution | 1,000 by default; a bigger | during execution ( | yes — scan bytes and widest-step rows from |
Catch rate
11 queries an LLM agent plausibly writes — full scans, SELECT *, DDL,
injection attempts, oversized joins, out-of-grant reads. A raw MCP
wrapper submits all of them to the engine. Lagaam stops 11/11 before
execution while the well-scoped control query runs untouched.
Reproduce: benchmarks/catch_rate.py →
results.
Quickstart
Against the Trino you already have (TRINO_PORT / TRINO_USER if yours
aren't 8080 / lagaam):
TRINO_HOST=trino.internal LAGAAM_ALLOWED_TABLES=hive.sales.orders uvx lagaam # MCP server on stdioOr try it on a demo warehouse:
git clone https://github.com/lagaam-ai/lagaam && cd lagaam
docker compose -f examples/docker-compose.yml --profile trino up -d # demo warehouse
cd server && uv sync
LAGAAM_ALLOWED_TABLES=tpch.tiny.orders,tpch.tiny.lineitem \
uv run python -m lagaam # MCP server on stdioFor Pinot, --profile pinot brings up the batch quickstart and
--profile pinot-realtime up -d brings up a Kafka-fed streaming one —
run examples/pinot-realtime/bootstrap.sh after it to create the topics,
tables and feed. Then start the server with LAGAAM_ENGINE=pinot.
Wire it into any MCP client (Claude Code, Claude Desktop, or your own agent):
{
"mcpServers": {
"lagaam": {
"command": "uvx",
"args": ["lagaam"],
"env": {
"TRINO_HOST": "localhost",
"LAGAAM_MAX_SCAN_BYTES": "5368709120",
"LAGAAM_ALLOWED_TABLES": "hive.sales.orders,hive.sales.customers"
}
}
}
}In Claude Code, install it as a plugin instead:
/plugin marketplace add lagaam-ai/lagaam
/plugin install lagaam@lagaamIt asks for the tables to allow (LAGAAM_ALLOWED_TABLES) and your Trino
host, port and user (localhost / 8080 / lagaam if you leave them).
From a shell, pass them as flags:
claude plugin marketplace add lagaam-ai/lagaam
claude plugin install lagaam@lagaam \
--config allowed_tables=hive.sales.orders --config trino_host=trino.internalOn Pinot, pick engine=pinot and set the controller and broker URLs
(http://localhost:9000 / http://localhost:8000 if you leave them):
claude plugin install lagaam@lagaam --config engine=pinot \
--config allowed_tables=pinot.default.baseballStats \
--config pinot_controller_url=http://pinot.internal:9000 \
--config pinot_broker_url=http://pinot.internal:8099Other LAGAAM_* settings, and PINOT_USER / PINOT_PASSWORD for a Pinot
with auth, are read from the environment you start claude in.
The agent gets three tools — list_catalogs, describe_table,
query_data — and cannot reach the engine any other way.
Configuration
Env var | Meaning | Default |
| Comma list of | required |
|
| off |
| Identity stamped on the audit trail |
|
| Scan-bytes budget per query, pre-execution | 50 GiB |
| Scanned-row estimate budget per query | ungated |
| Rows the engine would build at its widest step — not rows returned, so a | 50,000,000 |
| Rows returned to the agent per query — unset, the server applies its own 1000-row cap, and a bigger |
|
| Wall-clock seconds per query |
|
| Metadata cache TTL, seconds |
|
| Audit JSONL file path | stderr |
| Trino coordinator |
|
The server will not start without LAGAAM_ALLOWED_TABLES. An agent that
can reach every table in every catalog is the thing this exists to prevent, so
that has to be asked for — set LAGAAM_ALLOW_ALL_TABLES=true if you mean it.
The budget dimensions above apply whether or not you set them. Leaving
LAGAAM_MAX_SCAN_BYTES and LAGAAM_MAX_INTERMEDIATE_ROWS unset gives you
their defaults, not an open gate — an unconfigured server refuses what it
cannot afford rather than waving it through. If queries are being denied and
you expected no limits, that is why; raise the dimension you mean to raise.
Cost quotes come from the engine's own plan estimates, and those need table
statistics: a table without stats cannot be priced, so its queries are refused
rather than guessed at. Run ANALYZE on your tables before pointing an agent
at them — a connector that has no statistics at all is effectively unusable
through the gate. That is deliberate: a query nobody can size is exactly the
kind that stalls a shared cluster.
How it works
A QueryEngine port with a Trino adapter and a native Pinot adapter —
grounding, execution, and a synthesised cost quotation for both OFFLINE and
REALTIME tables. Every query_data call walks one pipeline: validate
(sqlglot AST) → table allowlist → cost quotation → budget gate → execute
(row cap + timeout) → verify → audit.
On Trino, the quote is the engine's own plan, not a guess from the SQL text:
EXPLAIN (TYPE IO) prices the bytes a query would scan, and
EXPLAIN (TYPE LOGICAL) prices the widest row count any operator would
build — the number a cross join blows and a LIMIT cannot hide.
Pinot gives no such plan — every table scan reports the same placeholder row count whatever the table holds, and no endpoint reports bytes at all — so its quote is synthesised instead: from static segment metadata, the broker's own pruning oracle (how many segments survive the predicate), and the plan's join/union shape. A consuming (REALTIME) segment, which the controller reports as empty mid-flight, is priced at the stream's own flush threshold instead of zero. A join is charged its bound rather than the product wherever the catalog can prove the join key. See ADR 0008 and ADR 0009.
Details in docs/architecture.md; the longer story in docs/vision.md.
Status
v0.2.5 — on PyPI as lagaam, listed in the MCP Registry as io.github.lagaam-ai/lagaam, installable as a Claude Code plugin (lagaam@lagaam). Trino adapter, schema tools, plan-based cost guard, query
budgets, read-only enforcement, per-agent allowlists, result verification,
audit log. 1,189 unit + 212 integration tests (live Trino 476 and Pinot
1.5.1, batch and realtime), mypy strict. LAGAAM_ENGINE=pinot starts the
native Pinot adapter: grounding, execution and a quotation synthesised from
segment metadata and the broker's own pruning oracle, for OFFLINE and
REALTIME tables alike — a consuming segment is charged at the stream's
flush threshold, and a join is charged its bound rather than the product
wherever the catalog proves the key. On deck
(roadmap): post-execution actuals on the audit line,
then a Kubernetes control plane — agents as CRDs with token/dollar budgets
and kill switches.
Apache 2.0.
Available Tools
3 toolsdescribe_tableB
Get the exact columns and types of one table.
Always describe a table before querying it; column names you have not seen here are guesses.
| Name | Required | Description | Default |
|---|---|---|---|
| table | Yes | ||
| schema | Yes | ||
| catalog | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| table | Yes | |
| schema | Yes | |
| catalog | Yes | |
| columns | Yes | |
| comment | No | |
| row_estimate | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full behavioral burden. It says it returns columns and types and that it should precede queries, but does not disclose read-only status, permissions, side effects, or any other operational traits. This is a significant gap for a tool with zero annotation coverage.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, front-loaded with the purpose and then the usage rule. No wasted words, and the key constraint is placed prominently.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need not be explained. However, with no annotations and 0% parameter coverage, the description leaves behavioral and parameter semantics gaps. The parameter names are standard, but an agent still lacks explicit guidance on catalog/schema/table usage.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0% for all three required parameters (catalog, schema, table). The description does not mention any parameter or how to specify them, so it fails to compensate for the missing schema documentation.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource: 'Get the exact columns and types of one table.' It also implicitly distinguishes from query_data by instructing to describe before querying. An agent can tell what this tool does without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides explicit when-to-use guidance: 'Always describe a table before querying it.' It explains why (column names unseen are guesses) but does not name alternatives or state when not to use it, leaving that to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_catalogsA
List every catalog, schema, and table you are allowed to query.
Call this first to ground yourself before describing tables or writing SQL — table names you have not seen here are guesses.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| catalogs | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does well: it discloses that results are permission-filtered and that this is a grounding step rather than a data fetch. It stops short of covering pagination or result-size behavior, but the presence of an output schema covers return shape.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, both front-loaded: the capability first, then the sequencing instruction with its justification. No filler or restatement of the name.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a zero-parameter listing tool with an output schema, the description supplies everything the agent needs: what is returned, the permission scope, and the ordering relative to sibling tools. Return-value detail is correctly delegated to the output schema.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so the baseline of 4 applies; there is nothing for the description to disambiguate. No parameter-level detail is needed or missing.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ('List') over a precise, enumerated resource set (catalogs, schemas, tables), and the permission qualifier ('you are allowed to query') sharpens the scope. It is clearly distinguishable from describe_table and query_data without opening their schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly instructs 'Call this first to ground yourself before describing tables or writing SQL', naming both the trigger condition and the sibling activities it precedes. It also warns that unseen table names are guesses, giving a concrete when-not-to-skip rationale.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
query_dataA
Run a read-only SELECT and get the rows back.
Write a single SELECT in the engine's dialect. The query is checked
for safety, priced against your budget, and executed with a row cap —
so name the columns you need (no SELECT *), and add WHERE filters to
keep the scan small. If it is rejected, the message says what to fix.
Describe the tables first so column and table names are exact.
| Name | Required | Description | Default |
|---|---|---|---|
| sql | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| rows | Yes | |
| columns | Yes | |
| warnings | No | |
| row_count | Yes | |
| truncated | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does well: it discloses the read-only safety check, budget pricing, a row cap, and that rejections return actionable messages. It leaves out auth requirements and the concrete row-cap value, but the safety profile is substantially covered.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core action, then constraints and the prerequisite in descending priority. Every sentence adds a constraint or instruction, though the multi-line formatting is slightly more verbose than strictly needed.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists so return values need not be explained, and the description covers the key execution concerns (safety, budget, row cap, error feedback). Given one undocumented parameter, it is nearly complete, missing only auth and exact limits.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The single 'sql' parameter has 0% schema coverage, so the description must compensate, and it does: engine dialect, single SELECT only, no SELECT *, and add WHERE filters to bound the scan. It omits syntax specifics but adds real meaning beyond the bare string type.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Run a read-only SELECT and get the rows back') and implicitly distinguishes itself from the metadata siblings by telling the agent to describe tables first. An agent can tell this executes queries rather than listing catalogs or describing schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives a clear prerequisite ('Describe the tables first so column and table names are exact') which routes the agent to describe_table, and explains the rejection path. It stops short of explicitly naming the alternative tools or stating when-not to use it.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
3 tool updates
v0.2.4- First observed
describe_table - First observed
list_catalogs - First observed
query_data
TDQS
Scored across 3 tools
The three tools map to clearly distinct stages of a data workflow: discovery (list_catalogs), schema introspection (describe_table), and execution (query_data). There is no overlap in purpose, and the descriptions actively steer the agent between them.
All three names follow the same snake_case verb_noun pattern (list_catalogs, describe_table, query_data). The convention is predictable and self-explanatory.
Three tools is exactly right for a read-only SQL access server — each earns its place with no redundancy. Adding more would only fragment the discover-describe-query workflow.
The surface fully covers the read-only lifecycle: explore, inspect schema, then query. Write operations are absent, but that is a deliberate scope choice rather than a gap, leaving only minor omissions such as metadata or sample-value helpers.
Maintenance
Related MCP Connectors
Deterministic safety, correctness & cost gate that vets Postgres SQL before your AI agent runs it.
Safe, read-only Postgres and MySQL access for AI agents. Audit log + column-level controls.
See, price, and control every tool call your AI agents make: policy checks, cost, and audit tools.
The grounded data layer for any LLM: governed SQL, metrics, lineage and catalog over your data.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to understand and query your database safely by providing a semantic layer of metadata, with tools to search, explain, validate, and generate safe SQL.2MIT
- AlicenseAqualityFmaintenanceA governed SQL gateway that exposes typed tools to AI agents, compiling safe read-only queries from a semantic layer while blocking PII before execution, supporting SQL Server, Postgres, and SQLite.9MIT
- AlicenseNot gradedqualityCmaintenanceEnforces safety and governance for SQL queries executed by AI agents, providing read-only enforcement, cost estimation, and audit trails.Apache 2.0
- 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.16Apache 2.0