MCP Fusion/Core concepts/ModelとPresenter

ModelとPresenter

VinkiusについてAIに質問

defineModelでドメインを一度だけ宣言し、createPresenterでエージェントが認識する内容を形づくります:スキーマ、rules、マスキング、UIブロック、アフォーダンス、制限。

ModelsとPresentersは、the MVA patternのMとVに相当します。Modelはドメインを一度だけ宣言し、Presenterはそのドメインをエージェントがどう認識するかを形づくります。この分離には意図があります。同じModelに対して、対象やタスクごとに複数のPresenterを持てるのです。

Models

typescript
import { defineModel } from '@mcpfusion/core';

const InvoiceModel = defineModel('Invoice', (m) => {
  m.casts({ amount_cents: 'number' });
  m.hidden(['internal_notes', 'cost_basis']);
  m.guarded(['id', 'created_at']);
  m.fillable({
    create: ['customer_id', 'amount_cents', 'due_date'],
    update: ['status'],
  });
});
  • .casts(): 読み取り時に適用される型変換
  • .hidden(): サーバーの外に決して出ないフィールド。デバッグ出力にも現れません
  • .guarded(): エージェントが書き込めないフィールド
  • .fillable(): 各操作が書き込める唯一のフィールド

宣言していないものは、エージェントにとって単純に存在しません。ModelはZodスキーマにコンパイルされるため、InvoiceModel.schemaが入力を検証し、typeof InvoiceModel.inferがTypeScriptの型を提供します。

フィールドの.describe()アノテーションは自動的に抽出され、エージェントへのシステムルールになります。フィールドの意味がフィールドと一緒に運ばれる仕組みです。

Presenters

typescript
import { createPresenter, t, suggest, ui } from '@mcpfusion/core';

const InvoicePresenter = createPresenter('Invoice')
  .schema({
    id: t.string,
    customer: t.string,
    amount_cents: t.number.describe('CENTS, divide by 100'),
    status: t.enum('draft', 'paid', 'overdue'),
  })
  .rules([
    'CRITICAL: amount_cents is in CENTS. Divide by 100.',
  ])
  .redactPII(['customer.email', 'customer.ssn'])
  .ui((inv) => [
    ui.table(['Field', 'Value'], [
      ['Invoice', inv.id],
      ['Amount', inv.amount_cents / 100],
      ['Status', inv.status],
    ]),
  ])
  .suggest((inv) => [
    inv.status === 'overdue'
      ? suggest('billing.remind', 'Send a payment reminder')
      : null,
  ])
  .limit(50);

ビルダーメソッド

メソッド役割
.schema()エージェントに存在するフィールド。tの型と説明を定義します
.rules()このPresenterのすべての応答に注入されるシステムプロンプトの行
.redactPII()シリアライズ前に除去されるフィールドパス。出口ファイアウォールです
.ui()テーブルなどの描画ブロック。そのデータは設計上、マスキングをバイパスします
.suggest()アフォーダンス:エージェントが検討できる次のアクション
.limit()返却項目数の上限。自己修復的な切り詰めメッセージ付き
.embed()関連エンティティのためのネストされたPresenter

rulesはコメントではなく契約

.rules()の行はシステムコンテキストに注入されるため、モデルはコードコメントを無視するようには無視できません。単位、通貨、ステータス、そしてモデルが繰り返し間違える事柄を記述するのに適した場所です。

マスキングは意図的に後段で実行される

DLPエンジンは.redactPII()のパスを、Presenterごとにキャッシュされる最適化されたマスキング関数へコンパイルします。マスキングはUIブロックの描画後、シリアライズの直前に実行されるため、ダッシュボードは実際の値を保ちながら、モデルのコンテキストはクリーンなまま保たれます。

.ui()のデータは設計上、マスキングをバイパスします。人間のオペレーター向けのテーブルには実際の値が表示されることがあります。人間が唯一の閲覧者でない限り、機密情報を.ui()ブロックに入れないでください。

1つのエンティティ、複数の対象読者

typescript
const AdminPresenter = createPresenter('Invoice').schema({ /* everything */ });
const SupportPresenter = createPresenter('Invoice')
  .schema({ id: t.string, status: t.enum('draft', 'paid', 'overdue') })
  .limit(10);

ロールごとの認識には追加モジュールが不要です。ミドルウェアでテナントやロールに応じてPresenterを選択すれば、同じツールが両方の対象を安全に扱えます。CredentialsGovernanceを参照してください。

次のステップ

  • Tools: .returns()でPresenterを接続
  • Routing: ファイルベースのツール検出
  • Testing: エージェントが受け取る内容を検証