Lagaam について
Lagaamは、共有データクラスター上のクエリを制御する、TrinoとApache Pinot向けのMCPサーバーです。実行前にテーブル権限とクエリコストを確認し、呼び出しを監査します。
主な機能
- 01実行前のクエリコスト確認
- 02テーブル単位のアクセス許可
- 03読み取り専用SQLの検証
- 04結果の検証と件数制限
- 05JSONL監査ログの記録
活用シーン
- 01高コストなスキャンや結合の防止
- 02エージェントの許可テーブルへの制限
- 03監査ログでのクエリ判定確認
MCP 対応
MCPクライアントに list_catalogs、describe_table、query_data のツールを公開し、これら以外の方法でエージェントがデータベースへアクセスできないようにします。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
uvx lagaamLAGAAM_ALLOWED_TABLESComma list of catalog.schema.table grants; the server will not start without itLAGAAM_ALLOW_ALL_TABLEStrue to run with no grant at all: an explicit opt-out, off by defaultLAGAAM_ENGINEWhich adapter to runTRINO_HOSTTrino coordinator hostTRINO_PORTTrino coordinator portTRINO_USERTrino userPINOT_CONTROLLER_URLPinot controller URLPINOT_BROKER_URLPinot broker URLPINOT_USERPinot user; unset means no authPINOT_PASSWORDシークレットPinot password; unset means no authLAGAAM_AGENT_NAMEIdentity stamped on the audit trailLAGAAM_MAX_SCAN_BYTESScan-bytes budget per query, checked before it runs (default 50 GiB)LAGAAM_MAX_ROWSScanned-row estimate budget per query; unset means ungatedLAGAAM_MAX_INTERMEDIATE_ROWSRows the engine would build at its widest step, not rows returned, so a LIMIT doesn't lower itLAGAAM_MAX_RETURNED_ROWSRows returned to the agent per query, capped at 100000; a bigger LIMIT in the query is lowered to itLAGAAM_QUERY_TIMEOUTWall-clock seconds per queryLAGAAM_METADATA_TTLMetadata cache TTL, secondsLAGAAM_AUDIT_LOGAudit JSONL file path; unset means stderrローカルサーバーにはマシン上の実行環境(Node.js 18 以上など)が必要です。設定ファイルの場所はクライアントごとに異なります。
ツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細

エージェントのクエリひとつで、共有の Trino を止めてはいけません。 クラスターを共有している場合、パーティションフィルターの付け忘れや誤ったクロス結合による損害は、請求額にとどまりません。他のユーザーのクエリにも影響します。Lagaam は、AI エージェントとデータレイクハウス(Trino と Apache Pinot)の間に配置する MCP サーバーです。すべてのクエリはスキーマに基づいて作成され、エンジン独自の実行計画から実行前にコストが算出され、予算と照合されて監査されます。問題のあるクエリは実行前に拒否され、エージェントが対処できる修正案が提示されます。

実際のセッション、実際の Trino、モックなし — uv run --project server python examples/demo.py で再現できます。

実際のセッション、実際の Pinot 1.5.1 リアルタイムテーブル、モックなし — uv run --project server python examples/demo_pinot.py で再現できます。
問題
エージェントは構文的には正しくても、壊滅的な結果を招く SQL を作成します。共有クラスターでは、その被害を受けるのはエージェントだけではありません。パーティションフィルターを付け忘れると全件スキャンになり、すべてのワーカーを占有します。誤ったクロス結合はワーカーのメモリを使い果たし、その間、他のユーザーのクエリは待たされます。リトライループがあれば、同じクエリが再び実行されます。SELECT * は、2列あれば足りるコンテキストウィンドウに40列を読み込みます。通常の解決策は、エージェントにデータベースへのアクセスを一切許可しないことです。
Lagaam なら、制御を効かせたうえでアクセスを許可できます。
- コストは請求額ではなく、事前の見積もりです。 すべてのクエリは、実行前にエンジン独自の実行計画からコストが算出されます。スキャンするバイト数には
EXPLAIN (TYPE IO)を、オペレーターが構築する行数の最大値(クロス結合で膨らみ、LIMITでは隠せない数値)にはEXPLAIN (TYPE LOGICAL)を使用します。予算超過の場合は、数値と修正案を示してブロックします。 - 推定できないクエリは許可されません。 テーブル統計がない場合、自己結合によって推定が崩れる場合、プランナーが把握できないパススルーがある場合は、推測に頼らず安全側に倒してゲートを失敗させます。
- AST によって読み取り専用を徹底します。 許可されるのは単一の
SELECTのみです。DDL/DML、複数ステートメントのインジェクション、SELECT *、テーブル関数のパススルーは許可されず、LIMITがない場合は挿入されます。検証済みの SQL は再レンダリングされるため、実行されるのは検証された内容と完全に一致します。 - エージェントは自分で情報を確認できます。
list_catalogsとdescribe_tableは、正確な名前、型、行数の推定値を返します。エージェントに許可されたテーブルの範囲に限定されるため、アクセスを許可されていない名前を知ることはありません。 - 結果は信頼する前に検証されます。 0件の結果、切り詰められたページ、すべての値が NULL の列がある場合、エージェントには誤解を招く回答を黙って返すのではなく、次の対応を示す警告が提示されます。
- すべての呼び出しが監査されます。 ツール呼び出しごとに、実行者、内容、許可または拒否の結果、その理由を記録した JSONL 行が1行出力されます。
Trino ですでに制限されているのでは?
Trino はクエリの実行開始後に制限します。Lagaam は実行開始前に拒否します。 両方を併用してください。
| 動作 | エージェントに表示される内容 | 読み取り量が少ないクロスジョインも検出するか | |
|---|---|---|---|
query.max-scan-physical-bytes | 実行中:スキャンしたバイト数が上限に達すると終了 | クエリの失敗 | いいえ — 読み取ったバイト数を数えるもので、生成された行数ではありません |
query.max-execution-time | 実行中:指定時間が経過すると終了 | クエリの失敗 | その時間だけクラスターを占有した後に限ります |
リソースグループ(hardPhysicalDataScanLimit、softMemoryLimit、…) | 次のクエリから:グループの割り当て分を超えると、新しいクエリはキューに入ります | 後続のクエリは待機します | いいえ — 実行中のクエリは継続します |
| Lagaam | 実行前:EXPLAIN に基づきます | 数値と対処方法を添えた拒否 | はい — 最も幅の広いステップで生成される行数を見積もります |
Trino 476 での測定結果:SELECT o.orderkey, l.partkey FROM tpch.tiny.orders o CROSS JOIN tpch.tiny.lineitem l LIMIT 10 は、Trino 独自のプランで 676,575 バイトを読み取ります。そのため、スキャン上限が 0.7 MB を超えていれば実行され、902,625,000 行を生成することになります。Lagaam は実行前にこれを拒否します:「このクエリは最も幅の広いステップで 902,625,000 行を生成します。上限の 50,000,000 を超えています……LIMIT では解決しません。個別の値の種類がより多い列で結合するか、結合前に各テーブルを絞り込んでください。」
クラスターに到達したクエリすべてに対する最後の防御策として、リソースグループは引き続き利用できます。Lagaam はエージェントからのトラフィックをその前段で制御します。
ほかのデータベース MCP サーバーはどうですか?
優れたサーバーですが、役割が異なります。エージェントを読み取り専用にし、返されるデータ量を制限します。Lagaam も同じことを行い、さらにクエリを事前に見積もります。
| 読み取り専用 | 行数の上限 | タイムアウト | 実行前にプランを確認 | |
|---|---|---|---|---|
| tuannvm/mcp-trino | デフォルトで有効 | 取得中に結果を切り詰めます(TRINO_MAX_ROWS) | 実行中(TRINO_QUERY_TIMEOUT) | いいえ — explain_query はエージェントにプランを表示しますが、execute_query の実行を制御しません |
| startreedata/mcp-pinot | 常に有効。実行前に解析します | 実行前に LIMIT を書き換え、結果をページ分割します | 実行中(PINOT_QUERY_TIMEOUT、60 s) | いいえ |
| bytebase/dbhub | オプトイン(readonly):キーワードのチェックと、データベース独自の読み取り専用トランザクション | オプトイン(max_rows)。LIMIT/TOP を挿入します | オプトイン(query_timeout)。実行中 | いいえ — オプトインの explain_sql はプランを表示しますが、execute_sql の実行を制御しません |
| Lagaam | 常に有効。実行前に解析します | デフォルトで 1,000。これを超える LIMIT は実行前に引き下げられます | 実行中(LAGAAM_QUERY_TIMEOUT、300 s) | はい — EXPLAIN に基づくスキャンバイト数と最も幅の広いステップの行数を確認し、上限超過時は拒否します |
検出率
LLM エージェントが現実的に生成しうる 11 個のクエリです。全件スキャン、SELECT *、DDL、インジェクションの試行、過大な結合、許可範囲外の読み取りが含まれます。MCP の単純なラッパーは、これらすべてをエンジンに送信します。Lagaam は、適切な範囲に収まる対照クエリはそのまま実行しつつ、11 件すべてを実行前に停止します。
再現方法:benchmarks/catch_rate.py →
結果。
クイックスタート
すでに利用している Trino に接続する場合(TRINO_PORT/TRINO_USER が 8080/lagaam ではない場合):
TRINO_HOST=trino.internal LAGAAM_ALLOWED_TABLES=hive.sales.orders uvx lagaam # MCP server on stdio
デモ用データウェアハウスで試す場合:
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 stdio
Pinot の場合、--profile pinot でバッチ版のクイックスタートが起動し、--profile pinot-realtime up -d で Kafka からデータを取り込むストリーミング版が起動します。その後、examples/pinot-realtime/bootstrap.sh を実行してトピック、テーブル、データフィードを作成してください。次に、LAGAAM_ENGINE=pinot を指定してサーバーを起動します。
任意の MCP クライアント(Claude Code、Claude Desktop、または独自のエージェント)に接続します:
{
"mcpServers": {
"lagaam": {
"command": "uvx",
"args": ["lagaam"],
"env": {
"TRINO_HOST": "localhost",
"LAGAAM_MAX_SCAN_BYTES": "5368709120",
"LAGAAM_ALLOWED_TABLES": "hive.sales.orders,hive.sales.customers"
}
}
}
}
Claude Code では、代わりにプラグインとしてインストールします:
/plugin marketplace add lagaam-ai/lagaam
/plugin install lagaam@lagaam
アクセスを許可するテーブル(LAGAAM_ALLOWED_TABLES)と、Trino のホスト、ポート、ユーザーを指定するよう求められます(変更しない場合は localhost/8080/lagaam です)。シェルからは、フラグとして渡します:
claude plugin marketplace add lagaam-ai/lagaam
claude plugin install lagaam@lagaam \
--config allowed_tables=hive.sales.orders --config trino_host=trino.internal
Pinot の場合は、engine=pinot を選択し、コントローラーとブローカーの URL を設定します(変更しない場合は http://localhost:9000/http://localhost:8000 です):
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
その他の LAGAAM_* 設定と、認証を有効にした Pinot 用の PINOT_USER/PINOT_PASSWORD は、claude を起動した環境から読み込まれます。
エージェントが利用できるツールは list_catalogs、describe_table、query_data の3つで、それ以外の方法でエンジンにアクセスすることはできません。
設定
| 環境変数 | 意味 | デフォルト |
|---|---|---|
LAGAAM_ALLOWED_TABLES | 許可する catalog.schema.table のカンマ区切りリスト | 必須 |
LAGAAM_ALLOW_ALL_TABLES | true にすると、許可リストなしで実行します | 無効 |
LAGAAM_AGENT_NAME | 監査ログに記録される ID | anonymous |
LAGAAM_MAX_SCAN_BYTES | クエリごとの実行前のスキャンバイト数の上限 | 50 GiB |
LAGAAM_MAX_ROWS | クエリごとのスキャン行数の推定上限 | 制限なし |
LAGAAM_MAX_INTERMEDIATE_ROWS | エンジンが最大幅の処理ステップで 生成する 行数。返される行数ではないため、LIMIT では減りません | 50,000,000 |
LAGAAM_MAX_RETURNED_ROWS | クエリごとにエージェントへ返す行数。未設定の場合、サーバーが独自に1000行の上限を適用し、クエリ内の LIMIT がこれを超える場合は実行前に引き下げます | 1000(最大 100000) |
LAGAAM_QUERY_TIMEOUT | クエリごとの実時間のタイムアウト(秒) | 300 |
LAGAAM_METADATA_TTL | メタデータキャッシュの TTL(秒) | 300 |
LAGAAM_AUDIT_LOG | 監査 JSONL ファイルのパス | stderr |
TRINO_HOST/TRINO_PORT/TRINO_USER | Trino コーディネーター | localhost/8080/lagaam |
LAGAAM_ALLOWED_TABLES が設定されていないと、サーバーは起動しません。 すべてのカタログ内のすべてのテーブルにアクセスできるエージェントを防ぐことが、この仕組みの目的です。そのため、許可を明示する必要があります。全テーブルへのアクセスを許可する場合は、LAGAAM_ALLOW_ALL_TABLES=true を設定してください。
上記の予算上限は、設定の有無にかかわらず適用されます。 LAGAAM_MAX_SCAN_BYTES と LAGAAM_MAX_INTERMEDIATE_ROWS を未設定にしても、デフォルト値が適用され、制限がなくなるわけではありません。設定されていないサーバーは、処理能力を超えるクエリを通すのではなく拒否します。クエリが拒否され、制限がないはずだと思っていた場合は、それが理由です。引き上げたい上限を設定してください。
コストの見積もりはエンジン独自のプラン推定に基づき、推定にはテーブル統計が必要です。統計のないテーブルはコストを算出できないため、そのテーブルへのクエリは推測で通すのではなく拒否されます。エージェントからアクセスさせる前に、テーブルに対して ANALYZE を実行してください。統計情報が一切ないコネクターは、このゲートを通じて実質的に利用できません。共有クラスターを停止させるのは、コストを見積もれないクエリにほかならないため、これは意図した動作です。
動作の仕組み
Trino アダプターとネイティブ Pinot アダプターを備えた QueryEngine ポートです。OFFLINE と REALTIME の両テーブルを対象に、根拠付け、実行、推定コスト見積もりを行います。すべての query_data 呼び出しは、次のパイプラインを通ります。検証(sqlglot AST)→ テーブル許可リスト → コスト見積もり → 予算ゲート → 実行(行数上限+タイムアウト)→ 検証 → 監査。
Trino では、見積もりは SQL テキストからの推測ではなく、エンジン自身のプランに基づきます。EXPLAIN (TYPE IO) はクエリがスキャンするバイト数を見積もり、EXPLAIN (TYPE LOGICAL) はいずれかのオペレーターが生成する最大行数を見積もります。これは、クロス結合によって膨れ上がり、LIMIT では隠せない数値です。
Pinot にはそのようなプランがありません。すべてのテーブルスキャンは、テーブルのサイズにかかわらず同じプレースホルダーの行数を報告し、バイト数を報告するエンドポイントもありません。そのため、見積もりは静的なセグメントメタデータ、ブローカー独自のプルーニングオラクル(述語を通過するセグメント数)、プランの結合/ユニオン構造から合成されます。コントローラーが処理中に空として報告する処理中(REALTIME)セグメントには、ゼロではなくストリーム独自のフラッシュしきい値が見積もりとして適用されます。カタログで結合キーを証明できる場合、結合には直積ではなく上限値が課金されます。ADR 0008 と ADR 0009 をご覧ください。
詳細は docs/architecture.md、全体像は docs/vision.md をご覧ください。
ステータス
v0.2.5 — PyPI では lagaam として公開され、MCP Registry には io.github.lagaam-ai/lagaam として登録されています。Claude Code プラグイン(lagaam@lagaam)としてインストールできます。Trino アダプター、スキーマツール、プランベースのコストガード、クエリ予算、読み取り専用の強制、エージェントごとの許可リスト、結果検証、監査ログを備えています。単体テスト 1,189 件と統合テスト 212 件(ライブ Trino 476 および Pinot 1.5.1、バッチとリアルタイム)を実施済みで、mypy strict に対応しています。LAGAAM_ENGINE=pinot を設定すると、ネイティブ Pinot アダプターが起動します。OFFLINE と REALTIME の両テーブルを対象に、根拠付け、実行、セグメントメタデータとブローカー独自のプルーニングオラクルから合成した見積もりを提供します。処理中セグメントにはストリームのフラッシュしきい値が課金され、カタログがキーを証明できる場合、結合には直積ではなく上限値が課金されます。準備中の項目(ロードマップ):監査ログへの実行後の実績値の追加、続いて Kubernetes コントロールプレーン — エージェントを CRD として管理し、トークン/ドル予算とキルスイッチを備えます。
Apache 2.0。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。