Lagaam

io.github.lagaam-ai由 GitHub 帳號 lagaam-ai 發布v1.0.0更新於 Nov 7, 2026

Stops an agent's Trino or Pinot query before it runs if it costs too much or reads outside its grant

概覽

AI 產生的概覽

受控管的 MCP 閘道,讓 AI 代理查詢 Trino 或 Apache Pinot,並在查詢超出成本預算或越出資料表授權時拒絕執行。

可以做什麼
  • 列出 catalog 並描述資料表結構,範圍限定在代理的資料表授權內
  • 對 Trino 或 Pinot 執行唯讀 SELECT 查詢,附帶回傳列數上限與逾時
  • 依引擎執行計畫為每筆查詢估價,超出掃描位元組或最寬步驟列數預算時在執行前攔下
  • 拒絕 DDL/DML、多語句注入、SELECT * 與資料表函式透傳,缺少 LIMIT 時自動補上
  • 對零列、被截斷的分頁、全為 NULL 的欄位等可疑結果提出警告
  • 每次工具呼叫寫一行 JSONL 稽核紀錄,把報價與實際讀取量並列
適用情境
適合需要讓 AI 代理存取共用 Trino 或 Pinot 叢集,又不希望漏寫分割區篩選、笛卡兒積或 SELECT * 拖垮其他人查詢的情境。也適合需要依代理限定資料表授權、執行前成本閘門與稽核留痕的團隊。
執行需求
以 PyPI 套件 lagaam 在本機透過 stdio 執行(Python,可用 uvx 啟動)。必須設定 LAGAAM_ALLOWED_TABLES,否則伺服器無法啟動。需要可連線的 Trino 協調節點(TRINO_HOST、TRINO_PORT、TRINO_USER),或設定 LAGAAM_ENGINE=pinot 並提供 Pinot controller 與 broker 位址。選用登入憑證為 TRINO_PASSWORD 或 TRINO_JWT、PINOT_USER 與 PINOT_PASSWORD,私有 CA 用 TRINO_VERIFY。資料表需有統計資訊才能估價。
安裝前請注意
它會持有資料庫憑證:TRINO_PASSWORD、TRINO_JWT 與 PINOT_PASSWORD 從環境變數讀取,同時設定密碼與 JWT 會導致啟動失敗。LAGAAM_ALLOW_ALL_TABLES=true 會完全取消資料表授權限制。預算即使未設定也會生效,查詢可能被意外拒絕。沒有統計資訊的資料表會被拒絕。每個行程只服務一個代理,僅支援 stdio,沒有 HTTP 傳輸或逐請求的身分。

安裝

在 SourceWeft 中

  1. 開啟 儀表板中的 Lagaam,將其新增到工作區。
  2. 為需要使用其工具的對話啟用該服務。

Desktop only,透過 STDIO。 STDIO 服務會啟動本機處理程序,因此需要 SourceWeft 桌面主機。

其他 MCP 客戶端

參照 儲存庫 中的啟動說明。

README

[Lagaam — every query priced before it runs]

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.

[Lagaam demo: SELECT * rejected, an oversized join blocked pre-execution, a scoped query running]

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

[Lagaam demo: a consuming Pinot segment priced at its flush threshold, a keyless join blocked, the same join admitted on a proven upsert key]

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 a LIMIT cannot 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 SELECT only. No DDL/DML, no multi-statement injection, no SELECT *, no table-function passthrough, and a LIMIT is injected when missing. Validated SQL is re-rendered, so what runs is exactly what was checked.
  • Agents ground themselves. list_catalogs and describe_table return 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. A query's line puts the quote next to what the engine says it actually read, so you can see how far off the estimate was before you loosen or tighten a budget.

Doesn't Trino already limit this?

It limits a query once it is running. Lagaam refuses it before it starts. Keep both.

ActsWhat the agent seesCatches a cross join that reads little
query.max-scan-physical-bytesduring execution: terminated once the bytes are scanneda query failureno — it counts bytes read, not rows built
query.max-execution-timeduring execution: terminated after the time is spenta query failureonly after it has held the cluster that long
Resource groups (hardPhysicalDataScanLimit, softMemoryLimit, …)on the next query: new queries queue once the group is over its shareits later queries waitno — the running query continues
Lagaambefore execution, from EXPLAINa refusal with the number and the fixyes — 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-onlyRow capTimeoutChecks the plan before running
tuannvm/mcp-trinoon by defaultresults truncated while fetching (TRINO_MAX_ROWS)during execution (TRINO_QUERY_TIMEOUT)no — explain_query shows the agent a plan; it doesn't gate execute_query
startreedata/mcp-pinotalways on, parsed before executionLIMIT rewritten before execution, paged resultsduring execution (PINOT_QUERY_TIMEOUT, 60 s)no
bytebase/dbhubopt-in (readonly): keyword check plus the database's own read-only transactionopt-in (max_rows), injected as LIMIT/TOPopt-in (query_timeout), during executionno — opt-in explain_sql shows a plan; it doesn't gate execute_sql
Lagaamalways on, parsed before execution1,000 by default; a bigger LIMIT is lowered before it runsduring execution (LAGAAM_QUERY_TIMEOUT, 300 s)yes — scan bytes and widest-step rows from EXPLAIN; over budget is refused

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

bash
TRINO_HOST=trino.internal LAGAAM_ALLOWED_TABLES=hive.sales.orders uvx lagaam   # MCP server on stdio

Or try it on a demo warehouse:

bash
git clone https://github.com/lagaam-ai/lagaam && cd lagaamdocker compose -f examples/docker-compose.yml --profile trino up -d   # demo warehousecd server && uv syncLAGAAM_ALLOWED_TABLES=tpch.tiny.orders,tpch.tiny.lineitem \  uv run python -m lagaam                                             # MCP server on stdio

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

json
{  "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@lagaam

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

bash
claude plugin marketplace add lagaam-ai/lagaamclaude plugin install lagaam@lagaam \  --config allowed_tables=hive.sales.orders --config trino_host=trino.internal

On Pinot, pick engine=pinot and set the controller and broker URLs (http://localhost:9000 / http://localhost:8000 if you leave them):

bash
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:8099

Other LAGAAM_* settings, the Trino login (TRINO_PASSWORD or TRINO_JWT, plus TRINO_VERIFY for a private CA), and PINOT_USER / PINOT_PASSWORD for a Pinot with auth, are read from the environment you start claude in, so a password never sits in the plugin's config.

The agent gets three tools — list_catalogs, describe_table, query_data — and cannot reach the engine any other way.

Configuration

Env varMeaningDefault
LAGAAM_ALLOWED_TABLESComma list of catalog.schema.table grantsrequired
LAGAAM_ALLOW_ALL_TABLEStrue to run with no grant at alloff
LAGAAM_AGENT_NAMEIdentity stamped on the audit trailanonymous
LAGAAM_MAX_SCAN_BYTESScan-bytes budget per query, pre-execution50 GiB
LAGAAM_MAX_ROWSScanned-row estimate budget per queryungated
LAGAAM_MAX_INTERMEDIATE_ROWSRows the engine would build at its widest step — not rows returned, so a LIMIT doesn't lower it50,000,000
LAGAAM_MAX_RETURNED_ROWSRows returned to the agent per query — unset, the server applies its own 1000-row cap, and a bigger LIMIT in the query is lowered to it before it runs1000 (max 100000)
LAGAAM_QUERY_TIMEOUTWall-clock seconds per query300
LAGAAM_METADATA_TTLMetadata cache TTL, seconds300
LAGAAM_AUDIT_LOGAudit JSONL file pathstderr
LAGAAM_ENGINEpinot to run the native Pinot adaptertrino
PINOT_CONTROLLER_URL / PINOT_BROKER_URLPinot controller and brokerhttp://localhost:9000 / http://localhost:8000
PINOT_USER / PINOT_PASSWORDBasic-auth login for Pinotunset (no login)
TRINO_HOST / TRINO_PORT / TRINO_USERTrino coordinatorlocalhost / 8080 / lagaam
TRINO_PASSWORDPassword for TRINO_USER, for a cluster that wants a loginunset (no login)
TRINO_JWTJWT to log in with instead of a password — set one or the other, not bothunset
TRINO_HTTP_SCHEMEhttp or httpshttps once a password or JWT is set, else http
TRINO_VERIFYCheck the cluster's TLS cert: true, false, or the path of your CA bundle (PEM) for a private CAtrue

A shared Trino with a login works the same way. Set TRINO_PASSWORD (or TRINO_JWT) and lagaam talks https, since Trino won't take a password over plain http; set TRINO_PORT to the cluster's https port too (often 443 or 8443). A login the cluster refuses tells the agent so, without the password, and a password and a JWT both set stop the server at startup rather than guessing which one you meant. Try it locally against a Trino that wants both: examples/trino-auth/setup.sh, then docker compose -f examples/docker-compose.yml --profile trino-auth up -d.

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.

What it doesn't do

  • One agent per server process, over stdio. Identity and grant come from the environment at startup; there is no HTTP transport or per-request token. Run one lagaam per agent.
  • No stats, no answer. A table without statistics can't be priced, so its queries are refused (see above).
  • Per query, not per day. Budgets bound each query; there is no running total across an agent's session.
  • Trino and Pinot only. Other engines would need their own adapter behind the same QueryEngine port.
  • Actuals are recorded, never enforced. The audit line shows what a query really read next to its quote; nothing is cut off mid-run on those numbers — that is what LAGAAM_QUERY_TIMEOUT and your engine's own limits are for.

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

v1.0.0 — 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 with post-execution actuals, https + password/JWT login for a shared Trino. 1,243 unit + 219 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.

The MCP server is feature-complete: what's here is what v1 is, and new work is bug fixes and engine compatibility. The Kubernetes control plane in the roadmap is not scheduled.

Apache 2.0.

來源:README.md,提交 b176ec5

工具

0
SourceWeft 尚未在沙箱中啟動過該軟體包,因此其工具未知。

版本歷史

3
  1. v1.0.0最新Nov 7, 2026
  2. v0.2.5Oct 10, 2026
  3. v0.2.4Sep 29, 2026

評分

已滿足適用檢查項的 50%

該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。

  • 近 30 天內可連通25 分

    請保持端點能夠回應 MCP 握手;SourceWeft 每 30 天探測一次。

  • 已宣告認證方式10 分

    請在註冊中心條目中宣告憑證請求標頭,或保持端點能夠回應探測,以便判定其認證方式。

  • 已列出工具15 分

    請讓伺服器在無憑證時回應 tools/list,或發布可在沙箱中無憑證啟動的軟體包。

  • 描述必需15 分
  • 圖示10 分
  • 文件15 分
  • 授權條款10 分