/** * StateMachineGate — Temporal Anti-Hallucination Engine * * Zero-hallucination tool ordering via finite state machines. * Tools bound to FSM states are **physically removed** from * `tools/list` when the current state doesn't match — the LLM * cannot call what doesn't exist in its reality. * * Powered by XState v5 (optional peer dependency, lazy-loaded). * * @example * ```typescript * const gate = new StateMachineGate({ * id: 'checkout', * initial: 'empty', * states: { * empty: { on: { ADD_ITEM: 'has_items' } }, * has_items: { on: { CHECKOUT: 'payment', CLEAR: 'empty' } }, * payment: { on: { PAY: 'confirmed', CANCEL: 'has_items' } }, * confirmed: { on: { RESET: 'empty' } }, * }, * }); * * gate.bindTool('cart_add_item', ['empty', 'has_items'], 'ADD_ITEM'); * gate.bindTool('cart_checkout', ['has_items'], 'CHECKOUT'); * gate.bindTool('cart_pay', ['payment'], 'PAY'); * ``` * * ## Architecture * * ``` * ┌──────────────────────────────────────────────────────┐ * │ Boot: StateMachineGate(config) │ * │ │ * │ XState createMachine() → createActor() → .start() │ * │ │ │ * │ ▼ │ * │ tools/list request │ * │ │ │ * │ ▼ │ * │ gate.getVisibleTools(allTools) │ * │ → filter by current FSM state │ * │ → return only allowed tools │ * │ │ │ * │ ▼ │ * │ tools/call succeeds │ * │ → gate.transition(event) │ * │ → FSM advances → list_changed notification │ * └──────────────────────────────────────────────────────┘ * ``` * * @module */ /** * Configuration for a finite state machine definition. * * Uses the same shape as XState v5 `createMachine()` config, * but only the subset needed for tool gating. */ export interface FsmConfig { /** Unique identifier for this state machine */ id?: string; /** Initial state when a new session starts */ initial: string; /** State definitions with event transitions */ states: Record; /** Set to `'final'` to mark a terminal state */ type?: 'final'; }>; } /** * External state store for serverless/edge deployments. * * When MCP runs over Streamable HTTP (Vercel, Cloudflare Workers), * there is no persistent process — FSM state must be externalized. * The `sessionId` comes from the `Mcp-Session-Id` header. * * @example * ```typescript * const fsmStore: FsmStateStore = { * load: async (sessionId) => { * const data = await redis.get(`fsm:${sessionId}`); * return data ? JSON.parse(data) : undefined; * }, * save: async (sessionId, snapshot) => { * await redis.set(`fsm:${sessionId}`, JSON.stringify(snapshot), { EX: 3600 }); * }, * }; * ``` */ export interface FsmStateStore { /** Load persisted FSM state for a session. Returns `undefined` for new sessions. */ load(sessionId: string): Promise; /** Save FSM state after a transition. */ save(sessionId: string, snapshot: FsmSnapshot): Promise; } /** * Serializable FSM state snapshot for persistence. */ export interface FsmSnapshot { /** Current FSM state value */ state: string; /** Timestamp of last transition */ updatedAt: number; } /** * Result of a state transition attempt. */ export interface TransitionResult { /** Whether the FSM state actually changed */ changed: boolean; /** The FSM state before the transition */ previousState: string; /** The FSM state after the transition */ currentState: string; } /** * Reset the XState module cache so that `loadXState()` will * re-attempt the dynamic import on next call. * * Intended for test environments where `xstate` availability * may change between test suites via dynamic mocking. * * @public */ export declare function resetXStateCache(): void; /** * Pre-load `xstate` at boot time (optional optimization). * * Call during server startup to avoid the first-use dynamic import latency. * Returns `true` if xstate is available, `false` otherwise. * * @example * ```typescript * import { initFsmEngine } from '@vinkius-core/mcp-fusion'; * const available = await initFsmEngine(); * if (!available) console.warn('xstate not installed — FSM gating disabled'); * ``` */ export declare function initFsmEngine(): Promise; /** * Temporal Anti-Hallucination Engine. * * Wraps an XState v5 finite state machine and controls which MCP tools * are visible to the LLM based on the current workflow state. * * **Hard constraint**: Tools not bound to the current state are removed * from `tools/list` entirely — the LLM physically cannot call them. * * **Soft constraint**: `suggestActions` (HATEOAS) continues to recommend * the best next action within the visible set. Zero conflict. */ export declare class StateMachineGate { private readonly _config; private readonly _bindings; private readonly _transitionCallbacks; private _actor; private _currentState; private _initialized; /** * @param config - FSM definition (states, transitions, initial state) */ constructor(config: FsmConfig); /** * Initialize the XState actor (lazy-loaded). * * Called automatically on first use. Can be called explicitly * at boot time for eager initialization. * * @returns `true` if XState is available and the actor started */ init(): Promise; /** * Bind a tool to specific FSM states. * * The tool will only appear in `tools/list` when the FSM * is in one of the specified states. * * @param toolName - MCP tool name (flat: `cart_add_item`, grouped: `cart`) * @param allowedStates - FSM states where this tool is visible * @param transitionEvent - Event to send on successful execution (optional) * @returns `this` for chaining * * @example * ```typescript * gate.bindTool('cart_add_item', ['empty', 'has_items'], 'ADD_ITEM'); * gate.bindTool('cart_checkout', ['has_items'], 'CHECKOUT'); * ``` */ bindTool(toolName: string, allowedStates: string[], transitionEvent?: string): StateMachineGate; /** * Get the current FSM state. */ get currentState(): string; /** * Check if a specific tool is allowed in the current FSM state. * * Tools **not** registered via `bindTool()` are always visible * (ungated — they don't participate in FSM gating). * * @param toolName - MCP tool name to check * @returns `true` if the tool should appear in `tools/list` */ isToolAllowed(toolName: string): boolean; /** * Filter a list of tool names by the current FSM state. * * @param toolNames - All registered tool names * @returns Only the tools allowed in the current state */ getVisibleToolNames(toolNames: string[]): string[]; /** * Get the transition event for a tool (if any). * * @param toolName - MCP tool name * @returns The event string, or `undefined` if no transition is bound */ getTransitionEvent(toolName: string): string | undefined; /** * Check if any tools are bound to this FSM gate. * * @returns `true` if at least one tool is state-gated */ get hasBindings(): boolean; /** * Send an event to the FSM, potentially triggering a state transition. * * @param eventType - The event to send (e.g. `'ADD_ITEM'`, `'CHECKOUT'`) * @returns Result indicating whether the state changed */ transition(eventType: string): Promise; /** * Manual state transition fallback when XState is not installed. * * Reads the FSM config directly to determine the next state. * This provides basic FSM gating even without `xstate` installed, * though without XState's guards, actions, and advanced features. * * @internal */ private _transitionManual; /** * Register a callback that fires when the FSM state changes. * * Used by `ServerAttachment` to emit `notifications/tools/list_changed` * when a state transition makes tools appear or disappear. * * @param callback - Function to call on state change * @returns Unsubscribe function */ onTransition(callback: () => void): () => void; /** * Create a serializable snapshot of the current FSM state. * * Used with `FsmStateStore` for serverless deployments where * FSM state must survive across request boundaries. * * @returns Serializable snapshot */ snapshot(): FsmSnapshot; /** * Restore FSM state from a persisted snapshot. * * Resets the XState actor (if running) so the next `transition()` * re-initializes the machine starting from the restored state. * This ensures restore → transition works correctly in * serverless/edge deployments (Vercel, Cloudflare Workers). * * @param snap - Previously saved snapshot */ restore(snap: FsmSnapshot): void; /** * Create a lightweight clone of this gate with the same config * and bindings but independent mutable state. * * Used in serverless/edge deployments where concurrent requests * must not share `_currentState`. Each request gets its own clone, * restores session state into it, transitions, and saves — without * interfering with other concurrent requests. * * The clone starts **uninitialized** (no XState actor) so the first * `transition()` call will create a fresh actor from the cloned state. * * @returns A new `StateMachineGate` with identical config and bindings */ clone(): StateMachineGate; /** * Stop the XState actor and release resources. */ dispose(): void; } //# sourceMappingURL=StateMachineGate.d.ts.map