コンテンツにスキップ

RPC アクション: 関数のコンパイル

Kozou はスキーマの 名詞 — テーブルとビュー — を忠実な REST / OpenAPI / MCP / Admin UI サーフェスにコンパイルします。v1.4 以降は 動詞 もコンパイルできます。COMMENT でタグ付けされた Postgres 関数が、 同じ 4 つのサーフェスすべてで呼び出し可能な アクション になります。

これが重要なのは、Kozou が推奨するセキュリティモデル — JWT → SET LOCAL ROLE → 行レベルセキュリティ + 列 GRANT — が、意味のある書き込み (注文を承認、入金を記録、公開) を関数に集約し、テーブルレベルの書き込み権限を revoke するためです。これらの動詞は、本来であればすべての生成サーフェスの 外側に落ちてしまいます。RPC アクションはそれらを 同じ 認可機構の上に取り戻し ます。新しい認証モデルは不要で、どの role が各関数を実行できるかはデータベースが 施行します。

Kozou の出力の中で RPC がどこに位置するかは @kozou/api REST レイヤーCOMMENT 規約 を参照してください。

デフォルトでは何も露出されません。関数は、その COMMENT@expose: rpc タグを持つときにのみアクションへコンパイルされます。

COMMENT ON FUNCTION approve_order(order_id uuid) IS
'注文を承認し在庫を確保する。
@ai: 冪等ではない — 再呼び出し前に注文ステータスを確認すること。
@policy: 承認できるのはマネージャーのみ。
@expose: rpc';

このタグ 1 つで、関数は 4 つのサーフェスすべて にコンパイルされます。

  • RESTPOST /rpc/<schema>.<fn>。名前付き引数の JSON ボディを取ります (例: POST /rpc/public.approve_order { "order_id": "…" })。
  • OpenAPIPOST /rpc/<schema>.<fn> operation (operationId: rpc.<schema>.<fn>)。@ai / @policy の advisory は x-kozou-ai / x-kozou-policy として付与されます。
  • MCPdescribe_functions ツールが各アクションのシグネチャと advisory を 返すので、AI エージェントはどんなアクションが存在しどう呼ぶかを把握できます。
  • Admin UI — “Actions” ページが引数フォーム (テーブルフォームと同じ widget 推論 — enum-select、relation-select など) を描画し、結果を表示します。

引数は名前付き (スカラ / enum / FK 型) で、DEFAULT を持つ引数は省略できます。 戻り値の wire マッピング:

Postgres の戻りWire
スカラ (integertextuuid など)その値
単一 composite / 表の 1 行オブジェクト
SETOF / RETURNS TABLE(...)配列 — 行型なら オブジェクトの配列、スカラ集合なら 値の配列
void204 No Content (REST)、MCP call 経由では { ok: true, note: … }

@expose: rpc タグは「この動詞をサーフェスに出す」という意味であり、誰が 実行できるか を決めるものではありません。それを施行するのは、リクエストの role (JWT クレーム由来、SET LOCAL ROLE 経由) における PostgreSQL の EXECUTE 権限です。EXECUTE を持たない呼び出し元は 42501 となり、Kozou はこれを汎用的な ボディとともに 403 Forbidden にマッピングします。関数は role の トランザクション内で実行されるので、関数自身のチェックと RLS が効きます。

そのため、露出にはガードレールがあります。いずれかを満たさないタグ付き関数は 露出されず、ビルドがそれを大きく報告します (黙って消えることはありません)。

CREATE FUNCTION はデフォルトで EXECUTEPUBLIC に付与します。この付与が 残ったままのタグ付き関数は、誰でも — 匿名 role を含め — エンドポイントを 呼べてしまうため、skip されます。まず締めてください。

REVOKE EXECUTE ON FUNCTION approve_order(uuid) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION approve_order(uuid) TO app_manager;

意図的に public 呼び出し可能にしたい関数 (例: 匿名検索) は、明示的に宣言します — COMMENT に @expose: rpc public、または api.rpc.allowPublicExecute に列挙します。

SECURITY DEFINER は二重 opt-in が必要

Section titled “SECURITY DEFINER は二重 opt-in が必要”

SECURITY DEFINER 関数は 所有者 権限で実行され、呼び出し元の RLS を バイパスしうる — 最も強力で footgun になりやすいケースです。次の 両方 を 満たすときにのみ露出されます。

  1. api.rpc.allowDefiner に列挙されて いること (migration 作成者のタグとは別の、operator による deploy 時の opt-in)。かつ

  2. owner-safe な search_path を宣言していること — 各要素がその所有者しか オブジェクトを作成できないスキーマ (例: pg_catalog) で、末尾に pg_temp を 置くこと。

    CREATE FUNCTION settle_invoice(invoice_id uuid) RETURNS void
    LANGUAGE sql SECURITY DEFINER
    SET search_path = pg_catalog, pg_temp
    AS $$ … $$;

    書き込み可能なスキーマを含む search_path (別の role が、所有者が未修飾で 解決するオブジェクトを仕込めてしまう) は拒否されます。

オーバーロード名 (同一 schema.name が複数)、VARIADIC / polymorphic / 無名引数、マッピング不能な戻り型はビルド issue とともに skip されるので、 タグ付けしたのに現れない関数は必ず理由がわかります。

MCP エージェントからアクションを実行する

Section titled “MCP エージェントからアクションを実行する”

describe_functions はエージェントにアクションを 学ばせcall ツールは それを 実行 させます — describe → act の「act」側です。これは opt-in でデフォルト OFF: オペレーターが実行を有効化するまで、MCP サーバーは describe 専用です。

kozou.config.yamlserver.mcp.execution で有効化します。

server:
mcp:
execution:
enabled: true
role: kozou_mcp_agent # call の実行 role (server.mcp.http.auth なしの場合は必須)
# claims: { tenant_id: acme } # 任意。RLS 向けに publish される
# allow: [public.approve_order] # 任意の allowlist。省略 = 露出アクション全部

実行を有効化すると、bundled kozou mcp サーバーは { "function": "<schema>.<fn>", "args": { … } } を取る call ツールを列挙します。 関数は REST サーフェスと同じ施行エンベロープ (SET LOCAL ROLE + publish した claims をトランザクション内で) で実行されるので、関数の EXECUTE 権限とそれ自身の RLS が適用されます。call が到達できるのは describe_functions が既に広告している 関数 (および任意の allow リストとの積集合) だけで、それ以外は「unknown function」 として扱われます (露出されていない関数は存在しない関数と区別できません)。

デフォルトは単一サービス role — OAuth で per-user に

Section titled “デフォルトは単一サービス role — OAuth で per-user に”

REST サーフェスは リクエストごと の JWT を運ぶので、各呼び出しはそのユーザーの role で実行されます。server.mcp.http.auth なしでは MCP にエンドユーザーの トークンがありません: call はすべてのリクエストで 1 つの オペレーター設定 role として実行され、エージェントは role や claims を 選べません (選べれば self-elevate できてしまうため)。これは意図的に粗い設計です。

実行 role の EXECUTE 付与と関数の RLS が境界のすべてなので、call には 専用の least-privilege role を与えてください — DB owner や superuser は避け、 エージェントが実行してよい関数にだけ GRANT EXECUTE します。

CREATE ROLE kozou_mcp_agent NOLOGIN;
GRANT kozou_mcp_agent TO app_login; -- 接続のログイン role
GRANT EXECUTE ON FUNCTION approve_order(uuid) TO kozou_mcp_agent;

SECURITY DEFINER が最も鋭い縁です: definer 関数は 所有者 として実行され、 実行 role の RLS をバイパスするので、call の有効化と definer アクションが揃うと エージェントは所有者権限の動詞を起動できます。definer 関数はそもそも露出に allowDefiner の二重 opt-in を要します — 実行を有効化する前にレビューしてください。

失敗した呼び出しは 汎用的な メッセージの MCP エラー結果を返します — permission denied、制約カテゴリ、RAISE の場合は “the function call failed”。生の DB エラー文は エージェントに返しません (サーバーログにのみ出します)。

MCP の HTTP トランスポートはデフォルトでは無認証です: call 有効時、ポートに到達 できる誰もが実行 role としてアクションを実行できるため、サーバーは非 loopback bind 時に より強い起動警告を出します。127.0.0.1 (デフォルト) のままにするか、前段に auth/proxy を置くか、OAuth resource-server モード (server.mcp.http.auth) を有効に してください — 有効時は毎リクエストに検証済み bearer トークンが必要になり、call は 固定 role ではなくトークンごとの role として実行されます (リモート MCP と OAuth)。call の wire 形は RPC サーフェスの他と同様、 Kozou v1.6 時点で安定した契約です。env-only の standalone @kozou/mcp CLI は describe 専用のままで、実行は bundled kozou mcp の機能です。

2 つの RPC allowlist は kozou.config.yamlapi.rpc 配下にあり、デフォルトは空です (これらが無くても、PUBLIC EXECUTE を revoke した invoker 関数は露出されます)。

api:
rpc:
allowDefiner:
- public.approve_order # schema 修飾
allowPublicExecute:
- public.search

関数を提供するのは内製の @kozou/api バックエンド (デフォルト) だけです。 外部 PostgREST opt-out では Admin UI は Actions サーフェスを隠し、PostgREST 自身の /rpc/ を使います。