mcpField
A
AKKO MCP server for Trino

AKKO MCP server for Trino

ローカル
akko-ai.comデータベース42 週間前に更新

AKKO MCP server for Trino について

akko-mcp-trinoは、Trinoを照会するAIエージェント向けの読み取り専用MCPサーバーです。実行前にSQLを解析して書き込みを拒否し、デフォルトでは検証済みの呼び出し元として照会し、Trinoの既存ポリシーで読み取り権限を管理します。

主な機能

  1. 01カタログ、スキーマ、テーブルの一覧表示
  2. 02テーブル情報の取得とクエリの説明
  3. 03SQLを解析して書き込みを拒否
  4. 04呼び出し元の権限でクエリを実行
  5. 05結果行数と実行時間の上限設定
  6. 06呼び出しごとの監査記録

活用シーン

  1. 01既存のTrino権限でエージェントにデータを照会させる
  2. 02書き込みを許可せずにレイクハウスデータを分析
  3. 03ツール呼び出しとクエリ実行状況を追跡

MCP 対応

このサーバーは、stdio、SSE、streamable HTTPを通じて、Trinoのクエリおよびメタデータ用ツールを6種類MCPクライアントに提供します。Trinoへのアクセス前に呼び出し元のトークンを検証し、設定されたIDモードで実行します。

設定

ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。

mcp.json · 共通設定
{
"mcpServers": {
"ab2dridi-akko-mcp-trino": {
"command": "uvx",
"args": [
"akko-mcp-trino"
],
"env": {
"TRINO_HOST": "<TRINO_HOST>",
"MCP_JWKS_URL": "<MCP_JWKS_URL>",
"MCP_OIDC_ISSUER": "<MCP_OIDC_ISSUER>",
"MCP_USER_TOKEN": "<MCP_USER_TOKEN>"
}
}
}
}
起動コマンドuvx akko-mcp-trino
設定が必要な環境変数
TRINO_HOSTTrino coordinator host
MCP_JWKS_URLJWKS endpoint of the OIDC provider
MCP_OIDC_ISSUERExpected token issuer
MCP_USER_TOKENシークレットThe user's bearer token (stdio transport)

ローカルサーバーにはマシン上の実行環境(Node.js 18 以上など)が必要です。設定ファイルの場所はクライアントごとに異なります。

パッケージ
pypiakko-mcp-trino0.5.3

ツール

ツール一覧はまだ取得されていません

ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。

詳細

CI テスト カバレッジ バージョン Python Trino MCP ライセンス Ruff ステータス

akko-mcp-trino

English · Français

AI エージェントに Trino への読み取り専用アクセスのみを提供します。

akko-mcp-trino は、Trino 用の MCP サーバーです。 エージェントはカタログ、スキーマ、テーブルを一覧表示し、テーブルを説明し、 クエリを解説して実行できます。一方、受け取った書き込み要求はすべて拒否します。 各ステートメントを解析し、Trino に送信する前に、そこに含まれる書き込みを拒否します。 デフォルトでは、すべてのクエリはエージェントが代理するユーザーとして実行されます。 サーバーは、このサーバー用に発行されたユーザーのトークンを検証し、ID プロバイダーから Trino 用の別のトークンを取得します(OAuth 2.0 トークン交換)。ユーザー自身のトークンが サーバーの外に出ることはありません。その後、Trino のアクセス制御(OPA、Ranger、または Trino 独自のルール)が、そのユーザーに許可される読み取り対象を決定します。結果は行数と 実行時間の上限によって制限され、各呼び出しについて監査ログが 1 行記録されます。

0.5 からアップグレードしますか? 0.6.0 ではデフォルトが変更されています。呼び出し元の 検証が必須になり、jwt モードでは呼び出し元のトークンをそのまま渡すのではなく交換します。 MIGRATING.md をご覧ください。

なぜ

データレイクハウスを読み取るエージェントには、小さく予測可能なインターフェースが必要です。 モデルが理解できる説明を備えた少数のツール、際限なく実行されない応答、そしてプロンプト インジェクションによって質問が DROP TABLE に変えられることはないという保証です。 このサーバーは、そのためのインターフェースだけを提供します。それ以外の機能はありません。 アクセス権限を決めるのはこのサーバーではありません。Trino が既存のルールに基づいてユーザーごとに 判断します。または、service モードでは、指定した単一の ID に基づいて判断します。

主な特長

  • このサーバーを通じて、設計上読み取り専用です。 SQL は AST に解析され、CTE やサブクエリを含め、ツリー内のどこかに書き込みがあれば拒否されます。テーブル関数(TABLE(...)。JDBC コネクターの system.query など)と system カタログも拒否されます。セッションカタログが system の場合は、カタログを指定しない名前を通じたアクセスも拒否されます。ほかの手段で Trino 上でユーザーが実行できることについては、Trino のルールが適用されます。読み取り専用の範囲をご覧ください。
  • デフォルトで安全な、ユーザー自身の権限です。 デフォルトの jwt モードでは、サーバーが呼び出し元のトークン(Trino 用ではなく、このサーバー用に発行されたもの)を検証し、ID プロバイダーで対象が Trino のトークンに交換します(RFC 8693)。Trino はそのユーザーとしてクエリを実行します。呼び出し元のトークンが転送されることはありません。有効なトークンがないリクエストは 401 で拒否されます。
  • トークン交換を行わないフォールバックです。 impersonation モードでは、サーバーは自身のアカウント(パスワードまたはクライアント証明書)で Trino に認証し、Trino は、そのアカウントにのみ適用される偽装ルールに基づいて、検証済みの呼び出し元としてクエリを実行します。
  • 指定された場合に限り、単一の ID を使用します。 service モードでは、すべてのクエリが TRINO_USER として実行されます。デフォルトで選択されることはなく、サーバーは起動時と、初期化手順を通じて各ホストにその旨を伝えます。
  • デフォルトで制限されています。 truncated フラグ付きの行数上限(100)、Trino 内でキャンセルされるクエリごとの実行時間上限(60 秒)、ツール呼び出しごとの実行時間上限(120 秒)を設定します。
  • 呼び出しごとに監査ログを 1 行記録します。 X-Request-Id をキーとして、トークンや SQL テキストは記録しません。誰がどのツールを呼び出し、どのような結果になり、どれくらい時間がかかったかを記録します。
  • ツールは 6 つのみです。 実際に可能な操作を注釈で示します。execute_query が読み取り専用として注釈されるのは、TRINO_READ_ONLY が true の場合に限られます。
  • 保護された入口です。 任意の OIDC プロバイダーの JWKS に照らしてベアラートークンを検証します(発行者、このサーバーに紐付けられたオーディエンス、有効期限、クロック許容範囲)。Trino 用に発行されたトークンは拒否されます。保護されたリソースメタデータ(RFC 9728)と Host ヘッダーフィルターも備えています。
  • フォークせずに拡張できるよう設計されています。 ツールプラグインと拡張機能は、起動時に指定する Python パッケージです。ID モード、入口のガード、監査フィールド、ルート、ツールを拡張できます。拡張機能ができるのはコアが許可した操作を拒否することだけで、許可範囲を広げることはできません。
  • あらゆる MCP ホスト、あらゆるモデルに対応します。 Cursor、Claude Desktop、VS Code、Python SDK などのほか、Mistral や、サンプルエージェント経由の OpenAI 互換モデルに対応しています。
  • コンパクトで実証済みです。 377 件のテストで、行カバレッジと分岐カバレッジはともに 100%です。3 種類すべてのトランスポートについて、実際の SDK を使ったインプロセスのテストスイート、CI での Keycloak および Trino に対するライブ実行(integration/)、製品に依存しないガードが含まれます。

ドキュメント

目次

  1. 仕組み
  2. 互換性
  3. 前提条件
  4. インストールと実行
  5. MCP ホストの接続
  6. エージェントからの使用
  7. ツール
  8. 読み取り専用の範囲
  9. 拡張機能
  10. リクエストの処理
  11. 設定
  12. 運用
  13. 開発
  14. 設計上の注意点
  15. コントリビューション
  16. AKKO について

仕組み

flowchart LR
    Host["MCP host<br/>(Cursor, Claude, VS Code, an agent)"]
    IdP["Identity provider<br/>(OIDC, JWKS, token exchange)"]
    Server["akko-mcp-trino"]
    Trino["Trino"]
    Policy["Policy engine<br/>(OPA, Ranger, built-in)"]
    Data[("Data sources")]

    Host -- "1. tool call + Bearer token<br/>(audience: this server)" --> Server
    Server -. "2. verify signature, issuer,<br/>audience, expiry (JWKS)" .-> IdP
    Server -. "3. exchange it for a token<br/>whose audience is Trino (RFC 8693)" .-> IdP
    Server -- "4. read-only SQL, with that token" --> Trino
    Trino -- "5. may this user read this?" --> Policy
    Trino -- "6. rows, as that user may read them" --> Server
    Server -- "7. result" --> Host
    Trino --> Data

ホストはユーザーのアクセストークンを使ってツールを呼び出します。サーバーはトークンを検証し(プロバイダーの JWKS に対する署名、発行者、このサーバーを指定するオーディエンス、有効期限)、有効なトークンがないリクエストを 401 で拒否します。Trino 用に発行されたトークンも拒否します。その後、サーバーはステートメントを確認し(読み取り専用、識別子、テーブル関数、system カタログ)、TRINO_IDENTITY_MODE が指定する ID モードで Trino 上で実行します。Trino はポリシーエンジンに問い合わせ、その ID が読み取り可能なデータを、行数と時間の上限内で返します。

モードTrino から見えるユーザーTrino が認識する方法使用するタイミング
jwt(デフォルト)呼び出し元サーバーがトークン交換(RFC 8693)によって取得した Trino 用トークン。Trino は JWT 認証器で検証しますID プロバイダーが OAuth 2.0 トークン交換(RFC 8693)に対応している場合。Keycloak 26 でテスト済み
impersonation呼び出し元サーバー自身のアカウント(パスワードまたはクライアント証明書)が、呼び出し元としてクエリを実行するよう Trino に要求します。Trino は、そのアカウントだけに適用される偽装ルールに基づいて許可しますID プロバイダーがトークンを交換できない場合
serviceすべての呼び出し元で共通の TRINO_USERローカルで、パスワード、クライアント証明書、または認証なし明示的に指定した場合のみ。全員に同じ ID を使い、起動時とすべてのホストに告知します

どのモードでも、呼び出し元自身のトークンがサーバーの外に出ることはありません。MCP の認可仕様では、トークンをそのまま引き渡すこと(「トークンパススルー」)が禁止されています。呼び出し元の名前は、常に検証済みトークンから取得され、呼び出し元が記述したヘッダーから取得されることはありません。呼び出し元として実行するどちらのモードも、有効なトークンを必須とするドアが設定されていない場合は起動を拒否します。拡張機能(拡張機能を参照)で、ほかの ID モードを追加できます。

互換性

対応テスト済み
Python3.12、3.133.12、3.13(CI)
Trino351 以降。jwt では JWT 認証器を使う https、impersonation ではパスワード認証器または証明書認証器と偽装ルールを使う https、service では http(ローカル)または httpsJWT 認証器と証明書認証器を備えた最近のリリース。OPA およびファイルベースのルールの配下で、行フィルターと列マスクを使用
ポリシーエンジンOPA(trino-opa)、Ranger(Trino プラグイン)、Trino のファイルベースのアクセス制御行フィルターと列マスクを使用した OPA とファイルベースのルール
ID プロバイダーJWKS を公開する任意の OIDC プロバイダー(ドア)。OAuth 2.0 トークン交換、RFC 8693(jwt モード)Keycloak 26(標準トークン交換)
MCP公式の mcp SDK 2.2 がネゴシエートするプロトコル(2025-11-25 までのバージョン)。トランスポートは streamable-http、sse、stdio3 種類すべて。公式 SDK クライアントを CI でテスト
MCP ホストリモート:ベアラーヘッダーを送信するもの。ローカル:任意の stdio ホストPython SDK、サンプルエージェント
モデル任意。サーバーはモデルと通信しませんツールを操作する Mistral Small 3.2(OpenRouter 経由)

前提条件

必要なもの理由備考
Python 3.12 以降実行環境pip と仮想環境
接続可能な Trino コーディネーターエンジン最近のバージョンであればどれでも使用できます。jwt と impersonation を使う場合、およびシークレットを送信する場合は https が必要です
JWKS エンドポイントを備えた OIDC プロバイダー認証の入口Keycloak、Entra ID、Okta、Dex など。トークンには iss、このサーバーを指定する aud、exp、およびサブジェクト(preferred_username または sub)が含まれている必要があります
jwt:プロバイダーでのトークン交換と、Trino の JWT 認証器各ユーザーが自身の権限で読み取りますこのサーバー用の機密クライアントが必要です。このサーバー向けに発行されたトークンを、Trino 向けのオーディエンスを持つトークンと交換できる必要があります。http-server.authentication.type=JWT を設定し、プロバイダーの JWKS と、そのオーディエンスに設定した required-audience を指定します
または impersonation:このサーバー用のアカウントと偽装ルールトークン交換なしで、各ユーザーが自身の権限で読み取りますTRINO_PASSWORD を設定した TRINO_USER、またはクライアント証明書が必要です。そのアカウントだけがユーザーを偽装できるよう、Trino のルールを設定します
または service:Trino の単一の IDすべての呼び出し元に適用される権限TRINO_USER と、TRINO_PASSWORD またはクライアント証明書が必要です。すべての呼び出し元が読み取れるものに限って、読み取り権限を付与します
Trino 用のポリシーエンジンガバナンスOPA(opa.policy.uri)、Ranger、または Trino のファイルベースのルール
MCP ホストクライアントCursor、Claude Desktop、VS Code、または mcp SDK を使用して構築された任意のクライアント

任意:/metrics をスクレイプする Prometheus。

インストールと実行

pip install akko-mcp-trino        # or: pipx install akko-mcp-trino
uvx akko-mcp-trino --version      # run it without installing, the way most MCP hosts do

または、ソースからインストールします。

git clone https://github.com/AKKO-p/akko-mcp-trino.git
cd akko-mcp-trino
python -m venv .venv && source .venv/bin/activate
pip install -e .

このパッケージをインストールすると、Python モジュール(akko_mcp_trino)とコマンド(akko-mcp-trino)が利用可能になります。python -m akko_mcp_trino も同じエントリーポイントです。

Trino と ID プロバイダーを指定して、サーバーを起動します。デフォルトは安全な設定です。各クエリは呼び出し元として実行され(jwt)、有効なトークンのないリクエストは拒否されます。

export TRINO_HOST=trino.example.internal
export TRINO_PORT=8443
export TRINO_HTTP_SCHEME=https          # required: a token never travels over http
export TRINO_CATALOG=iceberg

# The door: tokens issued for this server, verified against the provider's JWKS
export MCP_JWKS_URL=https://idp.example.com/realms/data/protocol/openid-connect/certs
export MCP_OIDC_ISSUER=https://idp.example.com/realms/data
export MCP_OIDC_AUDIENCE=akko-mcp        # the audience the provider gives tokens for this server
export MCP_RESOURCE_URL=https://mcp.example.com/mcp   # optional: this server's canonical URL

# A token for Trino, by token exchange (RFC 8693), never the caller's own
export TRINO_TOKEN_EXCHANGE_URL=https://idp.example.com/realms/data/protocol/openid-connect/token
export TRINO_TOKEN_EXCHANGE_CLIENT_ID=akko-mcp
export TRINO_TOKEN_EXCHANGE_CLIENT_SECRET=...
export TRINO_TOKEN_EXCHANGE_AUDIENCE=trino

python -m akko_mcp_trino

起動前に akko-mcp-trino --check を実行すると、有効な設定(シークレットは表示されません)と警告が出力されます。起動を拒否する問題がある場合は、終了コード 2 で終了します。stdio では MCP_USER_TOKEN が存在することを確認しますが、有効性は検証しません(実際の起動時にトークンを検証します)。次に:

INFO:__main__:serving transport=streamable-http port=3000 auth=True strict=True identity=jwt extensions=none

稼働していて、Trino に接続できることを確認します。

curl -s localhost:3001/health   # {"status":"ok"}       never touches Trino
curl -s localhost:3001/ready    # {"status":"ready"}    jwt: Trino's /v1/info; impersonation and service: a bounded SELECT 1 as the server's account
curl -s localhost:3001/metrics  # Prometheus exposition
各ユーザーが自身の権限を使用する場合(デフォルトの jwt)

呼び出し元のトークンは、このサーバー向けに発行されます。オーディエンスは MCP_OIDC_AUDIENCE(または MCP_RESOURCE_URL)です。サーバーはプロバイダーでそのトークンを、オーディエンスが TRINO_TOKEN_EXCHANGE_AUDIENCE のトークンと交換し、そのトークンを有効期限が近づくまでメモリに保持して Trino に送信します。Trino のオーディエンスを持つトークンは入口で拒否され、そのようなトークンを受け入れる設定では起動が拒否されます。

プロバイダーでは、このサーバーにトークン交換が許可された機密クライアントを割り当て、Trino のオーディエンスを利用できるようにします。Keycloak 26 の場合:サーバーのクライアントで Standard token exchange を有効にし、Trino クライアント用のオーディエンスマッパーを追加します。また、MCP ホストのクライアントが取得するトークンのオーディエンスにサーバーのクライアントを含めるよう設定します(こちらにもオーディエンスマッパーを設定します)。integration/ フォルダーには、この設定を含むレルムがあり、すべてのプルリクエストでテストされています。

Trino 側では、http-server.authentication.type=JWT を設定し、http-server.authentication.jwt.key-file にプロバイダーの JWKS URL を指定します。http-server.authentication.jwt.principal-field には、サーバーが読み取るものと同じクレーム(preferred_username または sub)を指定し、required-issuer と required-audience でトークンを自分のプロバイダーと Trino のオーディエンスに限定します。同じ質問をする 2 人のユーザーにも、それぞれ自身の権限で許可された回答が返されます。サーバー自身の ID を使って問い合わせることはできないため、/ready は Trino の公開 /v1/info に問い合わせます。

トークン交換なし(impersonation)

プロバイダーがトークンを交換できない場合、サーバーは自身のアカウントで Trinoに接続し、Trinoは検証済みの呼び出し元として各クエリを実行します。

export TRINO_IDENTITY_MODE=impersonation
export TRINO_USER=mcp-service
export TRINO_CLIENT_CERT=/certs/mcp-service.crt   # mTLS; or TRINO_PASSWORD over https
export TRINO_CLIENT_KEY=/certs/mcp-service.key

Trinoでは、そのアカウントを認証し(ユーザーが使用するものとは別の証明書または パスワード認証器を使用します)、そのアカウントに限ってユーザーの 代理実行を許可する必要があります。ファイルベースのアクセス制御では、次のように設定します。

{"impersonation": [{"original_user": "mcp-service", "new_user": ".*", "allow": true}]}

new_user は、エージェントが代理実行できるユーザーに限定してください。ほかの ユーザーは代理実行できません。別のユーザー名を指定するヘッダーを送信したユーザーはTrinoに 拒否されます。また、サーバーが呼び出し元の名前をヘッダーから取得することはなく、 検証済みトークンからのみ取得します。このアカウントの認証情報はサービス用の 認証情報と同様に管理してください。これを所持する人は、ルールで許可された任意の ユーザーとして操作できます。

すべての呼び出し元で同じIDを使用(名前が指定された場合のみのservice)
export TRINO_IDENTITY_MODE=service
export TRINO_USER=mcp-reader
export TRINO_PASSWORD=...               # or TRINO_CLIENT_CERT and TRINO_CLIENT_KEY; https only

誰が呼び出しても、すべてのクエリはTRINO_USERとして実行されます。このIDには、 このサーバーのすべての呼び出し元に認められる権限だけを付与し、それ以上の権限は 付与しないでください。また、読み取り専用の権限のみを付与してください。サーバーは 起動時に警告をログに記録し、--checkでも表示します。また、すべてのホストに対し、 MCPの初期化時のinstructionsにも警告を送信します。ドアはデフォルトで有効のままです。 ローカルで認証なしで実行するには、MCP_AUTH_ENABLED=falseと MCP_AUTH_REQUIRED=falseも設定してください。警告にはその旨が表示されます。

コンテナー

リポジトリにはDockerfileが含まれています(非root、ヘルスチェック、 OCIラベル)。同じ環境変数を設定し、ポート3000(MCP)と3001(ヘルスチェック)を 公開して実行してください。

MCPホストに接続する

ホストは、このサーバー向けに発行されたIDプロバイダーのユーザーのアクセストークンを 送信します。サーバーはこれを検証し、監査ログに呼び出し元を記録します。MCP認可に対応した ホストは、MCP_RESOURCE_URLが設定されている場合にサーバーが公開する保護された リソースのメタデータから、プロバイダーを検出します。

Cursor、Claude Desktop、VS Code(mcp.json):

{
  "mcpServers": {
    "trino": {
      "url": "http://localhost:3000/mcp",
      "headers": {"Authorization": "Bearer <the user's access token>"}
    }
  }
}

公式SDKを使用してPythonから接続する場合:

import anyio
from mcp import ClientSession
from mcp.client.streamable_http import create_mcp_http_client, streamable_http_client


async def main(token: str):
    http = create_mcp_http_client(headers={"Authorization": f"Bearer {token}"})
    url = "http://localhost:3000/mcp"
    async with streamable_http_client(url, http_client=http) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            result = await session.call_tool("execute_query", {"sql": "SELECT 1"})
            print(result.content[0].text)


anyio.run(main, "<token>")

streamable-http(/mcp)がデフォルトで、現在のホストが想定している方式です。 古いホストではMCP_TRANSPORT=sse(/sse)を設定してください。どちらも同じ 認証ガードを使用します。

stdio経由でローカルホストに接続する

Claude Desktop、Cursor、Mistral Vibeはサーバーを直接起動できます。この場合、リクエストは ないため、呼び出し元は環境から取得されます。つまり、このサーバー向けに発行された ユーザー自身のアクセストークンを、ベアラーヘッダーの場合とまったく同じように検証し、 プロセスに紐付けます。デフォルトの厳格モードでは、トークンがなければ起動を拒否します。 jwtモード(httpの場合と同様に、トークンをTrino用のトークンに交換します)と impersonationモードでは、ユーザーとしてクエリを実行します。serviceモードでは、 TRINO_USERとして実行します。

{
  "mcpServers": {
    "trino": {
      "command": "uvx",
      "args": ["akko-mcp-trino"],
      "env": {
        "MCP_TRANSPORT": "stdio",
        "TRINO_HOST": "trino.example.internal", "TRINO_PORT": "8443", "TRINO_HTTP_SCHEME": "https",
        "MCP_JWKS_URL": "https://idp.example.com/realms/data/protocol/openid-connect/certs",
        "MCP_OIDC_ISSUER": "https://idp.example.com/realms/data",
        "MCP_OIDC_AUDIENCE": "akko-mcp",
        "TRINO_TOKEN_EXCHANGE_URL": "https://idp.example.com/realms/data/protocol/openid-connect/token",
        "TRINO_TOKEN_EXCHANGE_CLIENT_ID": "akko-mcp",
        "TRINO_TOKEN_EXCHANGE_CLIENT_SECRET": "...",
        "TRINO_TOKEN_EXCHANGE_AUDIENCE": "trino",
        "MCP_USER_TOKEN": "<the user's access token>"
      }
    }
  }
}

エージェントから使用する

examples/agent.pyは80行で構成された完全なエージェントです。 OpenAI互換モデル(La Plateforme上のMistral、OpenRouterまたはLiteLLM経由、あるいは その他のプロバイダー)、MCPツール、ユーザーのトークンを使用します。モデルがトークンを 見ることはありません。サーバーは各ツール呼び出しでトークンを受け取り、検証します。

pip install akko-mcp-trino openai
export LLM_BASE_URL=https://api.mistral.ai/v1
export LLM_API_KEY=...
export LLM_MODEL=mistral-small-latest
export MCP_URL=http://localhost:3000/mcp
export USER_TOKEN=<the user's access token>
python examples/agent.py "Which catalogs can I see, and what is in them?"

詳細はexamplesフォルダーを参照してください。

ツール

ツール引数戻り値
list_catalogsなし{"catalogs": [...], "truncated": bool}:Trinoに表示されるカタログ
list_schemascatalog{"schemas": [...], "truncated": bool}:そのカタログ内のスキーマ
list_tablescatalog, schema{"tables": [...], "truncated": bool}:そのスキーマ内のテーブルとビュー
describe_tablecatalog, schema, table, sample_rows(0〜20)列、型、コメント、truncated;要求された場合はガバナンスが適用されたサンプル;拡張機能の記述ツールが追加する情報
explain_querysql{"plan": "..."}:読み取り専用ステートメントを実行せずに、Trinoがどのように実行するかを示します
execute_querysql, max_rows(省略可能。サーバーの上限より小さい値){"columns": [...], "rows": [...], "row_count": n, "truncated": bool}
一覧表示ツールを含め、すべてのツールはアイデンティティモードで Trino を実行します。メタデータもデータであり、Trino はそのアイデンティティが参照できるものだけを一覧表示します。すべての説明はモデル向けに書かれており、ツールが返す内容を記載しています。describe_table と execute_query の説明には、マスクされた値や行が見つからない場合はアクセス・ポリシーによるものであり、再試行すべきエラーではないことも記載されています。

すべての識別子は、SQL に含める前に検証されます([A-Za-z_][A-Za-z0-9_-]*)。execute_query は、Trino が受け付けるあらゆる SQL を受け付けますが、読み取りのみです。ステートメントは AST に解析され、複数のステートメントが含まれる場合、またはツリー内のどこか(CTE、サブクエリ、EXPLAIN が説明するステートメント内を含む)に INSERT、UPDATE、DELETE、MERGE、CREATE、DROP、ALTER、GRANT、CALL、SET のいずれかが含まれる場合は拒否されます。EXPLAIN ANALYZE は、説明対象を実行するため拒否されます。テーブル関数と system カタログは拒否され、system を指定する SHOW も拒否されます。カタログを省略した名前はセッションカタログ(デフォルトでは TRINO_CATALOG、system)で読み取られます。セッションカタログが system の場合、カタログを省略したテーブルは拒否されます(CTE はテーブルではありません)。通過できるのは SHOW CATALOGS と、FROM にカタログを含む完全な名前を指定した SHOW SCHEMAS、SHOW TABLES、SHOW COLUMNS のみです。そのため、TRINO_CATALOG にはデータカタログを設定してください。エンジンのポリシーでも system を拒否する必要があります。このガードは二重の防御策です。

結果は TRINO_MAX_ROWS(デフォルトは 100、最小値は 1)で上限が設定されます。上限より 1 行多く読み取るため、truncated に回答が切り詰められたかどうかが示され、残りのクエリは Trino でキャンセルされます。すべての一覧表示にも同じことが明記されています。TRINO_QUERY_TIMEOUT_SECONDS(デフォルトは 60 秒)を超えて実行されるクエリは Trino でキャンセルされ、その旨がエージェントに伝えられます。上限はサーバーによって適用され、セッションプロパティとして設定されるものではありません。セッションプロパティの設定をユーザーに禁止するポリシーエンジンでは、そうしないとすべてのクエリが拒否されてしまいます。MCP_CALL_TIMEOUT_SECONDS(デフォルトは 120 秒)では、実行するすべてのクエリを含め、ツール呼び出し全体に時間制限を設けます。制限時間を過ぎると、新しいクエリは開始されず、実行中のクエリはキャンセルされ(Trino がクエリを認識する前に制限時間に達した場合も同様です)、エージェントには {"error": "the tool call exceeded its time budget…"} が返されます。それまでに呼び出しが収集した内容や、期限後に届いた回答が返されることはありません。独自のバックエンドを呼び出すプラグインは、そのリクエストに akko_mcp_trino.audit.time_allowed(timeout) を使って上限を設定します。プラグイン独自のタイムアウトと、呼び出しの残り時間のうち、短い方が適用されます。

デフォルト値はバッチジョブではなく、エージェント向けに設定されています。エージェントが尋ねる質問には 1 分以内に回答が返るべきです。それ以上かかるクエリは通常、エージェントが対象を絞るべき全件スキャンであり、返されるエラーにもその旨が示されます。呼び出しごとに 2 分あれば、2、3 個のクエリを実行するツールにも余裕があり、1 つのツールでエージェントのターンが長時間止まることもありません。いずれも 0 に設定すると制限が解除されます(その場合、クエリは Trino 独自の query.max-execution-time が許す限り実行されます)。--check では両方の値が表示されます。

execute_query に readOnlyHint: true の注釈が付くのは、TRINO_READ_ONLY が true の場合に限られます。TRINO_READ_ONLY=false の場合、その注釈は書き込みの可能性を示す内容になり(readOnlyHint: false、destructiveHint: true)、説明にもその旨が記載されます。そのためホストは、読み取り専用ツールなら省略する確認を求めます。残りの 5 つのツールは、設定にかかわらず読み取りのみです。

エラーは Trino のメッセージ全体を含む {"error": "..."} としてエージェントに返されます。Trino による権限拒否は通常のエラーであり、クラッシュではありません。監査ログの行に記録されるのはエラーコードのみです(監査を参照)。

別のパッケージからツールを追加する

このサーバーを基盤に構築された製品は、フォークしなくても独自のツールを追加できます。パッケージは、エントリーポイントグループ akko_mcp_trino.tools にレジストラー (mcp, client) -> None または (mcp, client, tools) -> None を登録し、運用担当者がその名前を MCP_TOOL_PLUGINS に指定します。

[project.entry-points."akko_mcp_trino.tools"]
my-tools = "my_package.tools:register"
from akko_mcp_trino.identity import current_subject


def register(mcp, client):
    @mcp.tool()
    def my_tool(text: str) -> str:
        return client.query("SELECT ...", user=current_subject())

指定されていないものは読み込まれず、不明な名前が指定されると起動に失敗します。この方法で追加されたツールは組み込みツールと同じクライアントを経由するため、同じ認証モードが適用されます。また、レジストラーが mcp.tool() または mcp.add_tool() を通じて追加したすべてのツールについて、プラグインを変更することなく、同じ監査ログが記録されます。Python から build_server(extra_tool_registrars=...) を呼び出した場合も同様です。

第 3 引数を受け取るレジストラーには ToolContext が渡されます。tools.tool(title, governed=..., open_world=...) は MCP アノテーション付きのツールを登録し、tools.run(sql) は認証モードを通じて Trino にクエリを実行し、tools.note(...) は呼び出しの内容(実行した SQL、テーブル、行数、結果が切り詰められたかどうか)を記録します。また、tools.add_describer(fn) は describe_table の応答に説明関数を追加します。

import json


def register(mcp, client, tools):
    @tools.tool("Ask the data", governed=True, idempotent=False)
    def ask_data(question: str) -> str:
        answer = my_service.ask(question)  # runs its SQL through tools.run
        tools.note(sql=answer["sql"], tables=answer["tables"], rows=len(answer["rows"]))
        return json.dumps(answer)

読み取り専用でカバーされる範囲

読み取り専用ガードが対象とするのは、このサーバーを経由する処理です。エージェントが送信するすべてのステートメントは、書き込み処理がどこかに含まれている場合、Trino に渡される前に拒否されます。これは、ユーザーが別の手段で Trino を利用する場合に何ができるかを保証するものではありません。

jwt モードと impersonation モードでは、Trino は各クエリをユーザーとして実行し、そのユーザーにポリシーで与えられた権限が適用されます。そのポリシーでユーザーに書き込みが許可されている場合、ユーザーは Trino 用のトークンをプロバイダーから直接受け取って、CLI、ノートブック、BI ツールなど、ほかの Trino クライアントを通じて書き込みを行えます。サーバーが交換によって取得するトークンはメモリ内に保持され、エージェントにもホストにも返されませんが、Trino でのユーザー自身の権限は変わりません。service モードでは、認証情報を持つ人に対して TRINO_USER にも同じことが当てはまります。

エンドツーエンドで読み取り専用にするには、エンジン側でも制限を徹底してください。

  • service:TRINO_USER に読み取り権限のみを付与します。ファイルベースのアクセス制御の場合は {"catalogs": [{"user": "mcp-reader", "catalog": ".*", "allow": "read-only"}]} を設定します。OPA または Ranger の場合は、その ID に SELECT のみを許可するポリシーを設定します。
  • impersonation:エージェントが代理で操作できるユーザーに限定して偽装ルールを設定します。また、そうしたユーザーがほかの場所で書き込み可能な場合は、サーバーのアカウントには SELECT 1 以外の権限を一切与えないでください。
  • jwt:このサーバーのクライアントだけが交換によって Trino のオーディエンス向けトークンを取得できるようにします。これにより、エージェントのホストが保持するトークンで Trino にアクセスできなくなります。また、ポリシーエンジンで、エージェントがアクセスできるカタログについて、エージェント経由で到達するユーザーに読み取り専用ルールを設定します。Trino は、どのクライアントから送信されたクエリであっても同じルールを適用します。そのため、この経路を含むすべての経路でユーザーの権限を読み取り専用にするには、ポリシーを適切に設定してください。

ガードはあくまで第 2 の防御線であり、唯一の防御策ではありません。エンジンのポリシーでもテーブル関数と system カタログを拒否する必要があります(設計上の注意点を参照してください)。

拡張機能

ツールプラグインはツールを追加します。拡張機能はコアの周囲に機能を追加できますが、コアに取って代わることはありません。パッケージはエントリポイントグループ akko_mcp_trino.extensions にファクトリー (config, env) -> Extension を登録し、運用者は MCP_EXTENSIONS に名前を指定します(カンマ区切り、指定順):

[project.entry-points."akko_mcp_trino.extensions"]
my-extension = "my_package.extension:create"
from akko_mcp_trino.extensions import Extension, Refusal


class MyExtension(Extension):
    name = "my-extension"

    def after_auth(self, admission):
        if admission.principal is not None and admission.principal.subject.startswith("svc-"):
            return Refusal("service_accounts_refused", status=403)
        return None


def create(config, env):
    return MyExtension()

指定されていないものは読み込まれません。不明な名前があると起動を拒否します。また、対応できない設定に対して ValueError を発生させるファクトリーがある場合も同様です。--check はどちらも報告します。akko_mcp_trino.extensions.Extension が基底クラスです。すべてのフックは何もしないため、拡張機能は追加する機能だけをオーバーライドします。

フック追加するもの
check_lines()--check が出力する行。シークレットは出力しません
identity_modes()TRINO_IDENTITY_MODE の名前で指定する ID モード。それぞれ IdentityMode(describe、connection(user)、ping)です。コアのモード(jwt、impersonation、service)と同じ名前を指定すると、そのモードを置き換えます
audiences()MCP_OIDC_AUDIENCE および MCP_RESOURCE_URL に加えて受け入れるトークンのオーディエンス。Trino のものは対象外です
before_auth(admission)トークンを確認する前に拒否する理由、または None
after_auth(admission)コアがトークンを検証した後(またはトークンがないと判明した後)に拒否する理由、または None
public_documents()リクエストパスごとの、トークンなしで提供する JSON ドキュメント。コアのドキュメントより優先されます
challenge(refusal)拒否時の WWW-Authenticate 値。コアの値より優先されます
routes()提供されるアプリに追加する Starlette ルート。ID ガードの背後に配置されます
wrap_audit(audit)サーバーが書き込む監査シンク。通常なら書き込む監査シンクが渡されます
client_tags()これから実行するクエリのクライアントタグ(X-Trino-Client-Tags)
describe_table_doc()describe_table の説明に追加するテキスト
tool_registrars()ツールプラグインのものと同様に呼び出されるツール登録関数。プラグインより前に呼び出されます

ガードは Refusal(reason, status=401, error="", headers={}, message="") を返します。reason は X-Reason と本文に入り、error はチャレンジに含められる RFC 6750 のコードです。headers はレスポンスに追加され(Retry-After など)、message が設定されている場合は、本文でクライアントに対応方法を伝えます。ガードは Admission を読み取ります。これはリクエストヘッダー、リクエストが提示したトークン、検証済みの principal(認証前またはトークンがない場合は None)、stdio(stdio プロセスの唯一の admission の場合は true)を含みます。admission.bind(var, value) はリクエスト中にコンテキスト変数を設定し、リクエスト終了後にリセットします。

ルール:拡張機能ができるのは拒否だけで、許可範囲を広げることはできません。 拡張機能のガードはコアのトークン検証の前後で実行され、検証の代わりにはなりません。厳格モードでは、ガードがどのように応答しても、検証済み ID のないリクエストは拒否されます。また、SQL の読み取り専用ガードはすべてのクエリに適用されます。ID モードは、Trino に対するサーバーの ID をクエリごとに決定します(akko_mcp_trino.trino_client.resolve_identity は、コアと拡張機能の中から TRINO_IDENTITY_MODE が指定するモードを選び、不明なモードは拒否します。service へのフォールバックはありません)。呼び出し元のトークンを Trino に送るモードは、Trino 用のトークンを取得しなければなりません。呼び出し元のトークンを転送してはいけません。ここが JwtIdentity.trino_token(bearer) フックです。呼び出し元に代わって独自のバックエンドを呼び出すプラグインは、検証済みの bearer を akko_mcp_trino.identity.current_bearer() で取得し、同様に交換してから送信しなければなりません。呼び出し元のトークンをそのまま送信してはいけません。読み取り可能なものは Trino がその ID に基づいて決定します。

リクエスト処理の流れ

sequenceDiagram
    participant H as MCP host
    participant G as Guard (ASGI middleware)
    participant T as Tool
    participant Tr as Trino

    H->>G: POST /mcp  Authorization, X-Request-Id
    G->>G: request id: honoured or generated
    opt an extension's guard before the token
        G-->>H: its refusal  X-Reason
    end
    G->>G: verify JWT (JWKS, iss, aud = this server, exp) → Principal(subject)
    alt no valid token (none, forged, expired, issued for Trino)
        G-->>H: 401  X-Reason: unauthenticated, what to send
    end
    opt an extension's guard after the token
        G-->>H: its refusal  X-Reason
    end
    G->>T: Principal in a ContextVar
    T->>T: validate identifiers · refuse writes
    opt jwt mode, no valid exchanged token in memory
        T->>T: exchange the caller's token at the provider (RFC 8693)
    end
    T->>Tr: SQL as the caller (jwt: exchanged token; impersonation: server account), source
    Tr-->>T: rows (masked, filtered by the policy engine)
    T->>G: audit line {request_id, tool, subject, ok, error, duration_ms}
    G-->>H: result  X-Request-Id

設定

すべて環境変数から読み込みます。ハードコードされた設定はありません。拡張機能は同じ環境から独自の変数を読み込みます。

Trino
変数意味デフォルト
TRINO_HOST, TRINO_PORTコーディネーターlocalhost, 8080
TRINO_USERサーバー自身の Trino アカウントです。service モードではすべてのクエリがこのアカウントとして実行され、impersonation モードでは呼び出し元を認証し、なりすましますtrino
TRINO_PASSWORDBasic 認証で送信するパスワードです。http では拒否されます。--check では設定されているかどうかのみが示され、内容は表示されませんなし
TRINO_CLIENT_CERT, TRINO_CLIENT_KEY代わりにアカウントの認証に使用するクライアント証明書とそのキー(PEM パス)です(mTLS)。https のみなし
TRINO_CATALOGセッションのカタログです。カタログ名なしの名前を読み取る場所です。データカタログに設定してください(system に設定すると、そのような名前は拒否されます)system
TRINO_READ_ONLYexecute_query での書き込みを拒否しますtrue
TRINO_MAX_ROWS結果の上限です。最小値は 1 です。回答が truncated かどうかを示すため、さらに 1 行読み取ります100
TRINO_QUERY_TIMEOUT_SECONDS1 つのクエリの制限時間です。超過すると、サーバーは Trino でクエリをキャンセルします。0 の場合、制限は Trino に委ねられます60
MCP_CALL_TIMEOUT_SECONDS1 回のツール呼び出しの制限時間です。呼び出しに含まれるすべてのクエリが対象です。超過すると、新しいクエリは開始されず、実行中のクエリはキャンセルされ、エージェントにはエラーが返されます。0 の場合、制限は設定されません120
TRINO_SOURCETrino が各クエリに記録するソース(X-Trino-Source)サーバー名
TRINO_HTTP_SCHEMEhttp または https。パスワードは http では送信されませんhttp
TRINO_VERIFYTLS 検証です。true、false、または Trino の TLS チェーンの照合に使用する CA バンドルのパスを指定しますtrue
TRINO_REQUEST_TIMEOUT_SECONDSTrino クライアントのリクエストごとのタイムアウトです30
TRINO_IDENTITY_MODETrino に対するサーバーの ID です。jwt(トークン交換で取得した Trino 用トークンを使い、すべてのクエリを呼び出し元として実行)、impersonation(TRINO_USER になりすまし、すべてのクエリを呼び出し元として実行)、または service(すべてのクエリを TRINO_USER として実行し、警告を通知)を指定します。最初の 2 つには https と、有効なトークンを要求するゲートウェイが必要です。拡張機能でほかのモードを追加できます。不明なモードでは起動が拒否されますjwt
TRINO_TOKEN_EXCHANGE_URLjwt:プロバイダーのトークンエンドポイント(https のみ)なし(jwt では必須)
TRINO_TOKEN_EXCHANGE_CLIENT_ID, TRINO_TOKEN_EXCHANGE_CLIENT_SECRETjwt:プロバイダーに登録された、このサーバーの機密クライアントです。Basic 認証で送信されます。--check でシークレットが表示されることはありませんなし(jwt では必須)
TRINO_TOKEN_EXCHANGE_AUDIENCEjwt:Trino 用に要求するトークンのオーディエンスです。Trino の required-audience が指定する値であり、このサーバーが受け入れるオーディエンスではありませんなし(jwt では必須)
TRINO_TOKEN_EXCHANGE_SCOPEjwt:トークンとともに要求するスコープです。スペース区切りで指定しますなし
TRINO_TOKEN_EXCHANGE_VERIFYトークンエンドポイントの TLS 検証です。true、false、または CA バンドルのパスを指定しますtrue
ID
変数意味デフォルト
MCP_AUTH_ENABLED呼び出し元のベアラートークンを検証します(ドア)true
MCP_AUTH_REQUIRED有効なトークンがないリクエストを 401 で拒否します。jwt と impersonation は、これが無効だと起動を拒否しますtrue
MCP_JWKS_URLプロバイダーの JWKS エンドポイントなし(認証が有効な場合は必須)
MCP_OIDC_ISSUERトークンに含まれている必要のある発行者なし
MCP_OIDC_AUDIENCEトークンに含まれている必要のある対象者:プロバイダーがこのサーバー向けに発行するトークンに指定する対象者(RFC 8707)。Trino の対象者は指定しないでくださいなし(認証が有効な場合、これまたは MCP_RESOURCE_URL が必須)
MCP_RESOURCE_URLこのサーバーの正規 URL。例:https://mcp.example.com/mcp。対象者として受け入れられ、サーバーが公開する保護されたリソースのメタデータ(RFC 9728)および 401 のチャレンジに記載されますなし
MCP_JWT_LEEWAY_SECONDS発行者とこのサーバーの間の時刻のずれに対する exp/nbf/iat の許容範囲30
MCP_ALLOWED_HOSTSHost ヘッダーのフィルター(SDK の DNS リバインディング保護):host または host:port をカンマ区切りで指定します。任意のポートには host:* を指定します(唯一のワイルドカード)。フィルターが有効な場合、Origin ヘッダーを含むリクエストは拒否されます(403)。空の場合は任意の Host を受け入れますなし(無効)

JWKS URL、またはこのサーバー自身の対象者を指定せずに認証を有効にすると、起動が拒否されます。何も検証できない認証レイヤーが、検証できるかのように振る舞ってはならないためです。検証できないトークンを必須にするドア(MCP_AUTH_REQUIRED=true かつ MCP_AUTH_ENABLED=false)も同様です。

プラグインと拡張機能
変数意味デフォルト
MCP_TOOL_PLUGINSエントリーポイントグループ akko_mcp_trino.tools にインストールされたレジストラー名をカンマ区切りで指定しますなし
MCP_EXTENSIONSエントリーポイントグループ akko_mcp_trino.extensions にインストールされた拡張機能名をカンマ区切りで指定しますなし
サーバーの実行
変数意味デフォルト
MCP_TRANSPORTstreamable-http、sse または stdiostreamable-http
MCP_PORT、MCP_HEALTH_PORTリッスンポート3000、3001
MCP_SERVER_NAMEホストに通知する名前trino-mcp
MCP_USER_TOKENstdio 専用:ベアラーヘッダーと同じ方法で検証されるユーザーのアクセストークンなし(厳格モードでは必須)

運用

拒否

拒否応答にはすべて X-Reason とリクエスト ID が含まれますが、トークンは含まれません。コア機能は、有効なトークンがないリクエスト(トークンなし、偽造、期限切れ、別の対象者向けに発行されたもの。Trino 向けも含む)を 401 unauthenticated で拒否し、本文で送信すべき内容を示します。

{"error": "unauthenticated", "message": "this server requires a bearer token issued for it by the identity provider: send Authorization: Bearer <access token>"}

MCP_RESOURCE_URL が設定されている場合、サーバーは /.well-known/oauth-protected-resource(および URL のパス配下。例:/.well-known/oauth-protected-resource/mcp)で保護されたリソースのメタデータを公開し、リソースと MCP_OIDC_ISSUER を認可サーバーとして指定します。また、MCP の認可仕様に従って、401 に WWW-Authenticate: Bearer resource_metadata="…" が含まれます。拡張機能のガードは独自の理由を追加します。そのドキュメントとチャレンジは、コア機能のものより優先されます。

警告

設定によって何かが未保護のままになる場合、その旨は3回通知されます。起動時の WARNING 行、--check、および初期化時にすべての MCP ホストへ送信される instructions です。コア機能は、service モードと、有効なトークンを必須としないドアについて警告します。

監査

MCP エンドポイントからのすべてのレスポンスには、X-Request-Id が含まれます。サーバーの前段にあるゲートウェイから送信された値が単純なトークン(英字、数字、.、_、:、- のみで、128 文字以下)の場合はその値を使用し、それ以外の場合はサーバーが生成した値を使用します。HTTP リクエスト外(stdio)でのツール呼び出しには、個別の ID が割り当てられます。プラグインによる呼び出しも含め、すべてのツール呼び出しは、その ID をキーとして mcp.audit ロガーに JSON を 1 行出力します。

{"request_id":"edge-42","tool":"execute_query","subject":"alice","ok":false,"error":"USER_ERROR:PERMISSION_DENIED","duration_ms":84}

デフォルトのロギング設定では、行の先頭に INFO:mcp.audit: が付きます。純粋な JSON を想定するコレクターは、このプレフィックスを取り除きます。

フィールド意味
request_id、tool、subjectどの呼び出しか、どのツールか、誰のための呼び出しかを示します。この行には、ベアラートークンや SQL テキストを格納できるフィールドはありません
ok、error呼び出しの結果を示します。error はコードであり、メッセージではありません。Trino のメッセージには、行から読み取った値が含まれる可能性があるためです(Cannot cast '<value>' to INT)。Trino のエラーの場合は Trino のエラー型と名前(USER_ERROR:INVALID_CAST_ARGUMENT)、それ以外の例外の場合はそのクラス(TimeoutError、呼び出しの予算時間を超過した CallTimeout)、ツールが独自の拒否に付けたコード(read_only、invalid_argument)、または note(error=...) を使用するプラグインが指定したコードを記録し、それ以外は tool_error になります。エージェントはメッセージ全体を読み取ります
duration_ms呼び出しにかかった時間

呼び出しで実行された処理(SQL の SHA-256、テーブル、行、データが切り詰められたかどうか)は、実行中の記録(akko_mcp_trino.audit.note)に保持されます。コアのログ行には出力されませんが、拡張機能では出力できます(wrap_audit)。

ヘルスチェックとメトリクス

ヘルスポートの /health は Trino にアクセスしないため、Trino の応答が遅くても、生存確認プローブがサーバーを停止させることはありません。/ready は認証モードを確認します。jwt モードでは Trino の公開 /v1/info を確認し、impersonation モードと service モードでは、他のユーザーになりすまさずに TRINO_USER として、時間制限付きの SELECT 1 を実行します。/metrics は mcp_trino_queries_total、mcp_trino_query_errors_total、mcp_trino_query_duration_seconds を公開します。

開発

pip install -e ".[dev]"
ruff check akko_mcp_trino tests examples && ruff format akko_mcp_trino tests examples
ruff check akko_mcp_trino --select D100,D101,D102,D103,D105,D107   # every public name documented
mypy akko_mcp_trino           # the package ships py.typed and type-checks clean
pip-audit                     # no known vulnerability in the dependency tree
bash lint-vendor-neutral.sh   # fails if the package imports anything product-specific
pytest                        # 377 tests; line or branch coverage below 100 % fails the run
integration/run.sh            # live: Keycloak, Trino and three servers in Docker, then removed
python -m build && twine check dist/*
akko-mcp-trino --check        # effective configuration, no secrets; exit 2 if it would not start

tests/test_core_alone.py には、コアが保持するもの(ツール、モジュール、設定、監査フィールド)が列挙されており、何かが追加されたのにリストに記載されていない場合はテストが失敗します。

akko_mcp_trino/ 内の各モジュールには、それぞれ 1 つの責務と、それを対象とするテストが tests/ にあります。

モジュール責務
config.py環境変数の読み取り、中立的なデフォルト値
auth.pyJWKS による JWT の検証、Principal
identity.pyContextVar に保持される、検証済みの呼び出し元
middleware.pyガード:トークン、リクエスト ID、拡張機能のガードとドキュメント
extensions.py拡張ポイントとその読み込み
transport_security.pyHost ヘッダーのフィルター(MCP_ALLOWED_HOSTS)
audit.py監査ログ行、リクエスト ID、呼び出しの記録と時間予算
sql_guard.py識別子の検証、AST に基づく読み取り専用判定
tools.py、plugins.py6 つのツールと ToolContext、ツールプラグインの読み込み
trino_client.pyTrino 接続、ID モード(コアの jwt、impersonation、service)、時間予算
token_exchange.pyOAuth 2.0 Token Exchange(RFC 8693):呼び出し元のトークンと引き換えに Trino 用のトークンを取得
health.py、metrics.py/health、/ready、/metrics、Prometheus カウンター
server.py、app.py、__main__.py組み立て、トランスポート、エントリーポイント、--check

README もテストされます。ここに記載されているすべての変数は config.py によって読み取られ、ここに記載されているデフォルト値はコード内のデフォルト値と一致します。

設計上の注意点

ガードは純粋な ASGI ミドルウェアであり、BaseHTTPMiddleware ではありません。 後者は下流の処理を別のタスクで実行するため、ContextVar の伝播を妨げます。検証済みの呼び出し元情報がツールに届かなくなります。

マウント方法はトランスポートに依存しません。 トランスポートが何であっても、1 つのコードパスでトランスポートアプリを構築し、ガードをマウントします。以前、ガードはデフォルトのトランスポートが通らない分岐にしかありませんでしたが、現在は両方でガードが適用されることをテストで確認しています。

execute_query だけでなく、すべてのツールで ID モードを使用します。 カタログ、スキーマ、テーブルの一覧表示はメタデータの読み取りです。すべてのツールを調べ、同じ方法で Trino に到達することをテストで確認しています。

読み取り専用かどうかは、ツリー内のどこにあるかを問わず AST で判定します。 先頭のキーワードだけを確認する方法では、WITH w AS (DELETE FROM t) SELECT 1 が通過してしまいます。ルートノードだけを確認する方法でも同様でしたが、実環境での敵対的な検証によって問題が見つかりました。現在、ガードはツリー全体を走査します。

テーブル関数は拒否されます。 TABLE(pg.system.query(query => '...')) は単純な SELECT として解析されますが、コネクター自身のアカウントでソースに生の SQL を送信するため、エンジンの行フィルターも列マスクも適用されません。ガードはすべてのテーブル関数と、system.runtime.queries に他のユーザーの SQL が表示される system カタログを拒否します。エンジンのポリシーでも拒否する必要があります。ガードは二重の防御であり、唯一の防御ではありません。

トークンのパススルーはありません。 0.5 までは、jwt モードで検証後の呼び出し元自身のトークンを Trino に送信していました。MCP の認可仕様ではこれが禁止されています。あるサービス向けに発行されたトークンを別のサービスに送信してはなりません。そのサービスが本来そのトークンの対象ではないにもかかわらず、信頼してしまう可能性があるためです(「Confused Deputy」)。0.6 以降、サーバーは自身を対象者とするトークンのみを受け入れ、Trino 向けに発行されたトークンを拒否します。また、OAuth 2.0 Token Exchange(RFC 8693)によって、Trino 向けのトークンを独自に取得します。プロバイダーがトークン交換に対応していない場合は、明示的なフォールバックとして impersonation を使用します。黙って選択されることはありません。

使用されるのは検証済みの ID であり、呼び出し元が指定した ID ではありません。 呼び出し元の名前は常に、検証済みのトークンから取得されます。Trino ユーザーを指定するヘッダーを呼び出し元が送信しても、何も変わりません。サーバーが Trino へのリクエストを独自に構築します。パスワード、トークン、クライアント証明書が平文の http で送信されることはありません。また、コアが認識しない ID モードが指定された場合は、フォールバックせずに起動を拒否します。

デフォルトで安全です。 0.5 までは、すべてのクエリを TRINO_USER として実行し、トークンのないリクエストも処理していました。0.6 以降は、すべてのクエリを呼び出し元として実行し、有効なトークンのないリクエストを拒否します。service モードや認証なしでのアクセスも引き続き可能ですが、名前を指定して選択し、警告として通知されます。行数と時間の上限にもデフォルト値があります。無制限を選んだ人はいませんでした。

拡張機能ができるのは拒否することだけです。 拡張機能のガードはトークン検証の前後で実行され、検証の代わりにはなりません。また、読み取り専用ガードは拡張機能の応答に関係なく適用されます。

アクセスを決定するのはサーバーではありません。 呼び出し元ごと、または設定された ID に対して、Trino が決定します。サーバーにポリシーを設定すると、他のすべてのクライアントに対してエンジンがすでに適用しているポリシーと重複し、いずれ矛盾することになります。

コントリビューション

コントリビューションを歓迎します。基本ルール(テストを先に行う、カバレッジ 100 %、製品に依存しない、フェイルクローズ)についてはCONTRIBUTING.mdを、脆弱性を非公開で報告する方法についてはSECURITY.mdをご覧ください。 変更はCHANGELOG.mdで追跡されています。

AKKO について

akko-mcp-trino は、AKKO が構築、保守しています。AKKO はフランスのプロジェクトで、組織がすでに運用しているエンジンと ID プロバイダー上で、企業データへの AI エージェントのアクセスを適切に管理する取り組みを進めています。

保守担当: AKKO([email protected]) · 課題: github.com/AKKO-p/akko-mcp-trino/issues

ライセンス

Apache 2.0。LICENSEをご覧ください。

コメント

MCP は初めてですか?

プロトコルの概要から最初のサーバーの導入まで、よくある疑問にまとめて答えます。

AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。