MCP Fusion/Core concepts/ミドルウェアとコンテキスト
ミドルウェアとコンテキスト
型付きコンテキスト導出で認証、テナント分離、レート制限を組み合わせます。defineMiddleware の 1 回の呼び出しで下流のすべてのハンドラーの ctx が拡張され、ビルド時に O(1) でコンパイルされます。
ミドルウェアは、コネクターが関数の集合からサービスへ変わる場所です。アイデンティティを解決し、テナントを分離し、トラフィックを制限し、すべての呼び出しを監査します。MCP Fusion のミドルウェアモデルは小さく、エンドツーエンドで型付けされ、最初のリクエストの前にコンパイルされます。
2 つの形、1 つの概念
コンテキスト導出が慣用的な形です。ミドルウェアは ctx に追加する部分を返します。
import { initMCPFusion } from '@mcpfusion/core';
interface AppContext {
db: PrismaClient;
user?: { id: string; role: 'viewer' | 'admin' };
}
const f = initMCPFusion<AppContext>();
const withUser = f.middleware(async (ctx) => {
const token = ctx.headers?.authorization;
const user = await verify(token);
if (!user) throw f.error('UNAUTHORIZED', 'Sign in first');
return { user };
});ツールでの利用で接続します。
f.query('billing.list_invoices')
.use(withUser)
.handle(async (input, ctx) => {
return ctx.db.invoices.findMany({
where: { userId: ctx.user.id },
});
});型システムは導出結果を引き継ぎます。.use(withUser) によってビルダーのコンテキスト型は AppContext & { user } に変わるため、ハンドラー内の ctx.user はコンパイルされ、ミドルウェアを削除するとビルドが失敗します。本番で初めて見つかる実行時キャストはありません。
従来の形も利用できます
前後で処理したり、途中で短絡したりする必要があるミドルウェアは (ctx, args, next) を使います。
import type { MiddlewareFn } from '@mcpfusion/core';
const audit: MiddlewareFn<AppContext> = async (ctx, args, next) => {
const started = Date.now();
const result = await next();
ctx.audit?.({ tool, args, ms: Date.now() - started });
return result;
};ミドルウェアから ToolResponse を返すと短絡します。next() は呼び出されず、そのレスポンスが回答になります。これにより inputFirewall と rateLimit はハンドラーに触れずに呼び出しを拒否できます。return next() を忘れると、すべての開発者が一度はその間違いをするため、コンソールに一度だけ警告が表示されます。
例外を投げる方法も使えます。throw toolError('NOT_FOUND', ...) はコードと復旧情報を保ったままパイプラインを通過します。それ以外の throw 値は INTERNAL_ERROR としてラップされます。
順序とスコープ
3 つのスコープがアクションごとに 1 つのチェーンへ組み合わされます。
| スコープ | 宣言場所 | 位置 |
|---|---|---|
| グローバル | f.middleware() とレジストリレベル | 最外部 |
| グループ | ActionGroupBuilder.use() | 中間 |
| ツールまたはアクション | ビルダーの .use() | 最内部 |
グローバルミドルウェアが最初に実行され、認証がすべてに先行します。アクション単位のミドルウェアは最後に実行され、ハンドラーに最も近い位置になります。チェーンは buildToolDefinition() でアクションごとの 1 つのクロージャへコンパイルされるため、リクエストパスは配列をループするのではなく呼び出し 1 回です。
組み込み機能
フレームワークには 3 つのミドルウェアが付属します。特別な魔法ではなく、通常のミドルウェアです。
inputFirewall({ judge }): 引数に対する LLM-as-Judge、fail-closed でINPUT_REJECTEDを返しますrateLimit({ windowMs, limit, keyFn }): タイムスタンプによるスライディングウィンドウ。拒否されたリクエストは記録されないため、悪用するクライアントが自分のロックアウトを延長することはできませんauditTrail({ sink, hashArgs }): 呼び出しごとに引数の SHA-256 ダイジェストを含むイベントを出力します。引数そのものは出力しません
認証ミドルウェアは専用パッケージの requireJwt、requireApiKey、requireAuth から提供されます。認証をご覧ください。
構造による分離
contextFactory はリクエストごとに実行され、新しいオブジェクトを返します。ミドルウェアはそのオブジェクトに導出したキーを書き込みますが、__proto__、constructor、prototype は保護機構によってスキップされます。そのため、同じ長寿命プロセスに到達する場合でも、並行する 2 つのリクエストがテナント ID、ユーザー、データベースハンドルを共有することはありません。ステートレスなトランスポートではリクエストごとに新しいサーバーが割り当てられ、stdio ではリクエストごとのコンテキストオブジェクトによって同じ保証が得られます。
これが マルチテナントコネクター の基盤です。分離はハンドラーが覚えておく規律ではなく、ランタイムの性質です。
次のステップ
- 認証: JWT、API キー、OAuth デバイスフロー
- マルチテナントコネクター: 1 つのコネクターで多数のテナント
- ランタイムアーキテクチャ: パイプライン内でミドルウェアが位置する場所
