MCP Fusion/Security and governance/マルチテナントコネクタ
マルチテナントコネクタ
1 つのコネクタで複数のテナントを扱えます。特別なモジュールは不要です。リクエストごとのテナント解決、タグでフィルターされた機能の可視性、ロールベースの Presenter により、各テナントが見てよい内容だけを表示します。
1 つのコネクタで複数の顧客に対応することは、後から追加する機能ではありません。コンテキスト、可視性、認識がどのように構築されるかという性質です。MCP Fusion にテナンシーモジュールがないのは、既に使っている部品が 1 つに組み合わさるためです。このページではその方法を説明します。
1. リクエストごとにテナントを解決する
すべてのリクエストは新しい ctx から始まるため、ハンドラーの前にテナントを一度だけ決定します。
interface AppContext {
db: PrismaClient;
tenantId: string;
role: 'viewer' | 'admin';
}
registry.attachToServer(server, {
contextFactory: async (extra) => {
const claims = await verify(extra.session?.authToken);
return {
db: pool.forTenant(claims.tenant_id),
tenantId: claims.tenant_id,
role: claims.role,
};
},
});安全にするルールは 2 つあります。ファクトリーはリクエストごとに新しいオブジェクトを返すため、あるリクエストが別のリクエストのコンテキストを観測できません。ミドルウェアはプロトタイプ汚染対策付きでそのオブジェクトに書き込み、悪意のある引数名が __proto__ に到達できないようにします。ハンドラーはデータへの唯一の入口として ctx.db または ctx.tenantId を使い、テナント ID がツール引数から来ることはありません。
2. タグで機能をスコープする
テナントが同じ面を持つことはほとんどありません。タグとフィルターで、誰に何が存在するかを決めます。
const filterFor = (role: string) =>
role === 'admin'
? { anyTag: ['public', 'admin'] }
: { tags: ['public'], exclude: ['internal'] };
attachToServer(server, { filter: filterFor(ctx.role) });この形では、無料デプロイメントは公開ツールを、エンタープライズデプロイメントは管理ツールを公開し、ハンドラーのコードベースは同一です。フィルターは、リクエスト自身のコンテキストの下でツールリストが構築される場所にて評価されます。Tool exposition を参照してください。
3. ロールで認識を形作る
可視性はロールが見るツールを決め、認識はツールが表示する内容を決めます。確認済みのパターンは、ステップ 2 のフィルターが選択する 1 つのハンドラー上の 2 つのツールです。
const listPeople = async (input, ctx) => ctx.db.people.findMany();
f.query('people.list_public')
.describe('List people in the tenant, without contact details')
.tags('public')
.returns(ViewerPresenter) // schema without email or phone
.handle(listPeople);
f.query('people.list_admin')
.describe('List people in the tenant with full records')
.tags('admin')
.returns(AdminPresenter)
.handle(listPeople); // same handler functionviewer ロールのエージェントには people.list_public だけが届き、連絡先フィールドは見えません。admin エージェントには完全な Presenter とともに両方が届きます。フィールドルールとレダクションは対象者ごとに 1 か所にあり、コントラクトダイジェストは両方の面を記録します。(data, ctx) => string[] として書くコンテキストルールは、ロールに応じたガイダンスを追加します。Models and Presenters を参照してください。
4. テナントごとにクレデンシャルを分離する
ベンダーキーもテナント間で共有してはいけません。defineCredentials で宣言してリクエストごとに読み取ります。Vinkius Cloud ではランタイムが購入者ごとのシークレットを各リクエストに注入し、ローカルでは同じアクセサーが環境を読み取ります。Credentials を参照してください。
5. コストとトラフィックを帰属させる
すべての呼び出しを帰属させられます。ctx.tenantId をレート制限キーとして渡し、あるテナントが別のテナントの予算を使い切らないようにします。また、監査イベントとテレメトリにテナント ID を出力します。Vinkius コンソールでは同じコネクタがサーバーごとのコストと信頼性を表示します。AI spend と Tool reliability に記載されています。
パターンを 1 つの表に
| Concern | Mechanism | Where it lives |
|---|---|---|
| Identity | JWT, API key or OAuth token | contextFactory + auth middleware |
| Data isolation | per-tenant client from the context | contextFactory |
| Capability visibility | tags + filter | tool list construction |
| Field isolation | role-based Presenters | Presenter per audience |
| Secret isolation | BYOC credentials | runtime injection |
| Fair use | rate-limit key from ctx | rate limiter middleware |
| Proof | contract digest + audit events | lockfile and audit sink |
忘れてしまうランタイムスイッチも、誤設定するクロステナントキャッシュもありません。明示的に共有しない限り何も共有されないためです。これが、テナント列を持つだけのシングルテナントコネクタとマルチテナントコネクタの違いです。
次のステップ
- Authentication: ID の出所
- Middleware and context: 導出と分離の仕組み
- Security pipeline: 各リクエストが通過するレイヤー
