/** * GroupedToolBuilder — Fluent API for MCP Tool Construction * * The primary entry point for building grouped MCP tools. Consolidates * multiple related actions behind a single discriminator field, reducing * tool count and improving LLM routing accuracy. * * @example * ```typescript * import { createTool, success, error } from '@vinkius-core/mcp-fusion'; * import { z } from 'zod'; * * const projects = createTool('projects') * .description('Manage workspace projects') * .commonSchema(z.object({ * workspace_id: z.string().describe('Workspace identifier'), * })) * .action({ * name: 'list', * readOnly: true, * schema: z.object({ status: z.enum(['active', 'archived']).optional() }), * handler: async (ctx, args) => { * const projects = await ctx.db.projects.findMany({ * where: { workspaceId: args.workspace_id, status: args.status }, * }); * return success(projects); * }, * }) * .action({ * name: 'delete', * destructive: true, * schema: z.object({ project_id: z.string() }), * handler: async (ctx, args) => { * await ctx.db.projects.delete({ where: { id: args.project_id } }); * return success('Deleted'); * }, * }); * ``` * * @see {@link createTool} for the recommended factory function * @see {@link ToolRegistry} for registration and server attachment * @see {@link ActionGroupBuilder} for hierarchical group configuration * * @module */ import { type ZodObject, type ZodRawShape } from 'zod'; import { type Tool as McpTool } from '@modelcontextprotocol/sdk/types.js'; import { type ToolResponse, type ToolBuilder, type ActionMetadata, type InternalAction, type MiddlewareFn, type ActionConfig, type StateSyncHint } from '../types.js'; import { type DebugObserverFn } from '../../observability/DebugObserver.js'; import { type TelemetrySink } from '../../observability/TelemetryEvent.js'; import { type MiddlewareDefinition } from '../middleware/ContextDerivation.js'; import { type FusionTracer } from '../../observability/Tracing.js'; import { type ProgressSink } from '../execution/ProgressHelper.js'; import { type ConcurrencyConfig } from '../execution/ConcurrencyGuard.js'; import { type SandboxConfig } from '../../sandbox/SandboxEngine.js'; import { type GroupConfigurator } from './ActionGroupBuilder.js'; export { ActionGroupBuilder } from './ActionGroupBuilder.js'; export type { GroupConfigurator } from './ActionGroupBuilder.js'; /** * Create a new grouped tool builder. * * This is the **recommended entry point** for building MCP tools. * Equivalent to `new GroupedToolBuilder(name)` but more * concise and idiomatic. * * @typeParam TContext - Application context type passed to every handler. * Use `void` (default) if your handlers don't need context. * * @param name - Tool name as it appears in the MCP `tools/list` response. * Must be unique across all registered tools. * * @returns A new {@link GroupedToolBuilder} configured with the given name. * * @example * ```typescript * // Simple tool (no context) * const echo = createTool('echo') * .action({ * name: 'say', * schema: z.object({ message: z.string() }), * handler: async (_ctx, args) => success(args.message), * }); * * // With application context * const users = createTool('users') * .description('User management') * .use(requireAuth) * .action({ * name: 'list', * readOnly: true, * handler: async (ctx, _args) => success(await ctx.db.users.findMany()), * }); * * // With hierarchical groups * const platform = createTool('platform') * .tags('core') * .group('users', 'User management', g => { * g.action({ name: 'list', readOnly: true, handler: listUsers }); * }) * .group('billing', 'Billing operations', g => { * g.action({ name: 'refund', destructive: true, schema: refundSchema, handler: issueRefund }); * }); * ``` * * @see {@link GroupedToolBuilder} for the full builder API * @see {@link ToolRegistry.register} for tool registration */ export declare function createTool(name: TName): GroupedToolBuilder, TName>; /** * Fluent builder for creating consolidated MCP tools. * * Groups multiple related operations behind a single discriminator field * (default: `"action"`), producing one MCP tool definition with a * union schema and auto-generated descriptions. * * @typeParam TContext - Application context passed to every handler * @typeParam TCommon - Shape of the common schema (inferred automatically) * @typeParam TName - Tool name literal (inferred by createTool) * @typeParam TRouterMap - Accumulated action entries for InferRouter (phantom type) * * @see {@link createTool} for the recommended factory function */ export declare class GroupedToolBuilder = Record, TName extends string = string, TRouterMap extends Record = Record> implements ToolBuilder { private readonly _name; private _description?; private _discriminator; private _annotations?; private _tags; private _commonSchema?; private _middlewares; private _actions; private _hasFlat; private _hasGroup; private _toonMode; private _selectEnabled; private _frozen; private _debug?; private _tracer?; private _telemetry?; private _concurrencyGuard?; private _egressMaxBytes?; private _sandboxConfig?; private _mutationSerializer?; private readonly _stateSyncHints; private _fsmStates?; private _fsmTransition?; private _cachedTool?; private _executionContext?; constructor(name: string); /** * Set the discriminator field name. * * The discriminator is the field the LLM uses to select which action * to execute. Defaults to `"action"`. * * @param field - Field name for the discriminator enum * @returns `this` for chaining * * @example * ```typescript * // Custom discriminator * const builder = createTool('projects') * .discriminator('operation') * .action({ name: 'list', handler: listProjects }); * // LLM sends: { operation: 'list' } * ``` * * @defaultValue `"action"` */ discriminator(field: string): this; /** * Set the tool description. * * Appears as the first line in the auto-generated tool description * that the LLM sees. * * @param desc - Human-readable description of what this tool does * @returns `this` for chaining * * @example * ```typescript * createTool('projects') * .description('Manage workspace projects') * ``` */ description(desc: string): this; /** * Set MCP tool annotations. * * Manual override for tool-level annotations. If not set, * annotations are automatically aggregated from per-action properties. * * @param a - Annotation key-value pairs * @returns `this` for chaining * * @example * ```typescript * createTool('admin') * .annotations({ openWorldHint: true, returnDirect: false }) * ``` * * @see {@link https://modelcontextprotocol.io/specification/2025-03-26/server/tools#annotations | MCP Tool Annotations} */ annotations(a: Record): this; /** * Set capability tags for selective tool exposure. * * Tags control which tools the LLM sees via * {@link ToolRegistry.attachToServer}'s `filter` option. * Use tags to implement per-session context gating. * * @param tags - One or more string tags * @returns `this` for chaining * * @example * ```typescript * const users = createTool('users').tags('core'); * const admin = createTool('admin').tags('admin', 'internal'); * * // Expose only 'core' tools to the LLM: * registry.attachToServer(server, { filter: { tags: ['core'] } }); * ``` * * @see {@link ToolRegistry.getTools} for filtered tool retrieval */ tags(...tags: string[]): this; /** * Set a common schema shared by all actions. * * Fields from this schema are injected into every action's input * and marked as `(always required)` in the auto-generated description. * The return type narrows to propagate types to all handlers. * * @typeParam TSchema - Zod object schema type (inferred) * @param schema - A `z.object()` defining shared fields * @returns A narrowed builder with `TCommon` set to `TSchema["_output"]` * * @example * ```typescript * createTool('projects') * .commonSchema(z.object({ * workspace_id: z.string().describe('Workspace identifier'), * })) * .action({ * name: 'list', * handler: async (ctx, args) => { * // ✅ args.workspace_id is typed as string * const projects = await ctx.db.projects.findMany({ * where: { workspaceId: args.workspace_id }, * }); * return success(projects); * }, * }); * ``` */ commonSchema>(schema: TSchema): GroupedToolBuilder; /** * Enable TOON-formatted descriptions for token optimization. * * Uses TOON (Token-Oriented Object Notation) to encode action metadata * in a compact tabular format, reducing description token count by ~30-50%. * * @returns `this` for chaining * * @example * ```typescript * createTool('projects') * .toonDescription() // Compact descriptions * .action({ name: 'list', handler: listProjects }) * ``` * * @see {@link toonSuccess} for TOON-encoded responses */ toonDescription(): this; /** * Declare glob patterns invalidated when this tool succeeds. * * Eliminates manual `stateSync.policies` configuration — * the framework auto-collects hints from all builders. * * @param patterns - Glob patterns (e.g. `'sprints.*'`, `'tasks.*'`) * @returns `this` for chaining * * @example * ```typescript * createTool('tasks') * .invalidates('tasks.*', 'sprints.*') * .action({ name: 'update', handler: updateTask }); * ``` * * @see {@link StateSyncConfig} for centralized configuration */ invalidates(...patterns: string[]): this; /** * Mark this tool's data as immutable (safe to cache forever). * * Use for reference data: countries, currencies, ICD-10 codes. * Equivalent to `cacheControl: 'immutable'` in manual policies. * * @returns `this` for chaining * * @example * ```typescript * createTool('countries') * .cached() * .action({ name: 'list', readOnly: true, handler: listCountries }); * ``` */ cached(): this; /** * Mark this tool's data as volatile (never cache). * * Equivalent to `cacheControl: 'no-store'` in manual policies. * Use for dynamic data that changes frequently. * * @returns `this` for chaining */ stale(): this; /** @internal */ private _setCacheDirective; /** * Enable `_select` reflection for context window optimization. * * When enabled, actions that use a Presenter with a Zod schema * expose an optional `_select` parameter in the input schema. * The AI can send `_select: ['status', 'amount']` to receive * only the specified top-level fields in the data payload, * reducing context window usage without developer effort. * * **Disabled by default** — opt-in to avoid changing existing * tool schemas. * * **Late Guillotine**: UI blocks, system rules, and action * suggestions are always computed with the **full** validated * data. Only the wire-facing data block is filtered. * * **Shallow (top-level only)**: Nested objects are returned * whole. If the AI selects `'user'`, it gets the entire `user` * object. No recursive GraphQL-style traversal. * * @returns `this` for chaining * * @example * ```typescript * createTool('invoices') * .enableSelect() // Expose _select in input schema * .action({ * name: 'get', * returns: InvoicePresenter, * handler: async (ctx, args) => ctx.db.invoices.findUnique(args.id), * }); * // AI sends: { action: 'get', id: '123', _select: ['status'] } * // Returns: { status: 'paid' } instead of full invoice * ``` * * @see {@link Presenter.getSchemaKeys} for introspection */ enableSelect(): this; /** * Set concurrency limits for this tool (Semaphore + Queue pattern). * * Prevents thundering-herd scenarios where the LLM fires N * concurrent calls in the same millisecond. Implements a * semaphore with backpressure queue and load shedding. * * When all active slots are occupied, new calls enter the queue. * When the queue is full, calls are immediately rejected with * a self-healing `SERVER_BUSY` error. * * **MCP Spec Compliance**: The MCP specification requires servers * to rate-limit tool invocations. This method fulfills that requirement. * * **Zero overhead** when not configured — no semaphore exists. * * @param config - Concurrency configuration * @returns `this` for chaining * * @example * ```typescript * createTool('billing') * .concurrency({ maxActive: 5, maxQueue: 20 }) * .action({ name: 'process_invoice', handler: processInvoice }); * // 5 concurrent executions, 20 queued, rest rejected * ``` * * @see {@link ConcurrencyConfig} for configuration options */ concurrency(config: ConcurrencyConfig): this; /** * Set maximum payload size for tool responses (Egress Guard). * * Prevents oversized responses from crashing the Node process * with OOM or overflowing the LLM context window. * * When a response exceeds the limit, the text content is truncated * and a system intervention message is injected, forcing the LLM * to use pagination or filters. * * This is a **brute-force safety net**. For domain-aware truncation * with guidance, use Presenter `.agentLimit()` instead. * * **Zero overhead** when not configured. * * @param bytes - Maximum payload size in bytes * @returns `this` for chaining * * @example * ```typescript * createTool('logs') * .maxPayloadBytes(2 * 1024 * 1024) // 2MB * .action({ name: 'search', handler: searchLogs }); * ``` * * @see {@link Presenter.agentLimit} for domain-level truncation */ maxPayloadBytes(bytes: number): this; /** * Enable zero-trust sandboxed execution for this tool. * * Stores the sandbox configuration so that tools built with * `.sandboxed()` on the FluentToolBuilder can propagate it. * * @param config - Sandbox configuration (timeout, memory, output size) * @returns `this` for chaining * * @example * ```typescript * createTool('analytics') * .sandbox({ timeout: 5000, memoryLimit: 128 }) * .action({ name: 'compute', handler: computeHandler }); * ``` * * @see {@link SandboxConfig} for configuration options * @see {@link SandboxEngine} for the execution engine */ sandbox(config: SandboxConfig): this; /** * Get the sandbox configuration (if any). * * **Important**: This is metadata only — it does NOT auto-create a * `SandboxEngine` nor inject it into the execution pipeline. * The developer must create the engine manually (e.g. via `f.sandbox()`). * This accessor exists for introspection, testing, and contract tooling. * * @returns The stored `SandboxConfig`, or `undefined` if `.sandbox()` was not called */ getSandboxConfig(): SandboxConfig | undefined; /** * Bind this tool to specific FSM states. * * When a `StateMachineGate` is configured, this tool is only * visible in `tools/list` when the FSM is in one of the specified states. * * @param states - FSM state(s) where this tool is visible * @param transition - Event to send on successful execution * @returns `this` for chaining */ bindState(states: string[], transition?: string): this; /** * Get the FSM binding metadata (if any). * Used by `ToolRegistry` and `ServerAttachment` for FSM gating. */ getFsmBinding(): { states: string[]; transition?: string; } | undefined; /** * Get the tool name. * Used by framework internals for tool routing and FSM binding. */ getToolName(): string; /** * Add middleware to the execution chain. * * Middleware runs in **registration order** (first registered = outermost). * Chains are pre-compiled at build time — zero runtime assembly cost. * * Accepts both `MiddlewareDefinition` from `f.middleware()` and * raw `MiddlewareFn` functions. * * @param mw - Middleware function or MiddlewareDefinition * @returns `this` for chaining * * @example * ```typescript * const requireAuth: MiddlewareFn = async (ctx, args, next) => { * if (!ctx.user) return error('Unauthorized'); * return next(); * }; * * createTool('projects') * .use(requireAuth) // Runs on every action * .action({ name: 'list', handler: listProjects }); * ``` * * @see {@link MiddlewareFn} for the middleware signature * @see {@link ActionGroupBuilder.use} for group-scoped middleware */ use(mw: MiddlewareFn | MiddlewareDefinition>): this; /** * Register a flat action. * * Flat actions use simple keys (e.g. `"list"`, `"create"`). * Cannot be mixed with `.group()` on the same builder. * * When a `schema` is provided, the handler args are fully typed as * `TSchema["_output"] & TCommon` — no type assertions needed. * * @param config - Action configuration * @returns `this` for chaining * * @example * ```typescript * createTool('projects') * .action({ * name: 'list', * description: 'List all projects', * readOnly: true, * schema: z.object({ status: z.enum(['active', 'archived']).optional() }), * handler: async (ctx, args) => { * // args: { status?: 'active' | 'archived' } — fully typed * return success(await ctx.db.projects.findMany({ where: args })); * }, * }) * .action({ * name: 'delete', * destructive: true, * schema: z.object({ id: z.string() }), * handler: async (ctx, args) => { * await ctx.db.projects.delete({ where: { id: args.id } }); * return success('Deleted'); * }, * }); * ``` * * @see {@link ActionConfig} for all configuration options * @see {@link GroupedToolBuilder.group} for hierarchical grouping */ action, TOmit extends keyof TCommon = never>(config: { name: TActionName; description?: string; schema: TSchema; destructive?: boolean; idempotent?: boolean; readOnly?: boolean; omitCommon?: TOmit[]; handler: (ctx: TContext, args: TSchema["_output"] & Omit) => Promise; }): GroupedToolBuilder; }>; /** Register a flat action (untyped: no schema, args default to Record) */ action(config: ActionConfig & { name: TActionName; }): GroupedToolBuilder ? Record : TCommon; }>; /** * Register a group of actions under a namespace. * * Group actions use compound keys (e.g. `"users.create"`, `"billing.refund"`). * Cannot be mixed with `.action()` on the same builder. * * @param name - Group name (must not contain dots) * @param configure - Callback that receives an {@link ActionGroupBuilder} * @returns `this` for chaining * * @example * ```typescript * createTool('platform') * .group('users', 'User management', g => { * g.use(requireAdmin) // Group-scoped middleware * .action({ name: 'list', readOnly: true, handler: listUsers }) * .action({ name: 'ban', destructive: true, schema: banSchema, handler: banUser }); * }) * .group('billing', g => { * g.action({ name: 'refund', destructive: true, schema: refundSchema, handler: issueRefund }); * }); * // Discriminator enum: "users.list" | "users.ban" | "billing.refund" * ``` * * @see {@link ActionGroupBuilder} for group-level configuration * @see {@link GroupedToolBuilder.action} for flat actions */ group(name: string, configure: GroupConfigurator): this; group(name: string, description: string, configure: GroupConfigurator): this; /** * Generate the MCP Tool definition. * * Compiles all actions into a single MCP tool with auto-generated * description, union schema, and aggregated annotations. Caches * the result and permanently freezes the builder. * * Called automatically by {@link execute} if not called explicitly. * * @returns The compiled MCP Tool object * @throws If no actions are registered * * @example * ```typescript * const tool = builder.buildToolDefinition(); * console.log(tool.name); // "projects" * console.log(tool.description); // Auto-generated * console.log(tool.inputSchema); // Union of all action schemas * ``` */ buildToolDefinition(): McpTool; /** * Enable debug observability for this tool. * * When enabled, structured {@link DebugEvent} events are emitted at * each step of the execution pipeline. * * When disabled (the default), there is **zero runtime overhead** — * no conditionals, no timing, no object allocations in the hot path. * * @param observer - A {@link DebugObserverFn} created by `createDebugObserver()` * @returns `this` for chaining * * @example * ```typescript * import { createTool, createDebugObserver, success } from '@vinkius-core/mcp-fusion'; * * const debug = createDebugObserver(); * * const tool = createTool('users') * .debug(debug) // ← enable observability * .action({ name: 'list', handler: async () => success([]) }); * ``` */ debug(observer: DebugObserverFn): this; /** * Enable out-of-band telemetry emission for Inspector TUI. * * When set, `validate`, `middleware`, `presenter.slice`, and * `presenter.rules` events are emitted to the TelemetrySink * (Shadow Socket IPC), enabling real-time monitoring in the * Inspector dashboard. * * **Zero overhead** when not configured — no conditionals in * the hot path. * * @param sink - A {@link TelemetrySink} from `startServer()` or `TelemetryBus` * @returns `this` for chaining */ telemetry(sink: TelemetrySink): this; /** * Enable OpenTelemetry-compatible tracing for this tool. * * When enabled, each `execute()` call creates a single span with * structured events for each pipeline step (`mcp.route`, `mcp.validate`, * `mcp.middleware`, `mcp.execute`). * * **Zero overhead** when disabled — the fast path has no conditionals. * * **OTel direct pass-through**: The `FusionTracer` interface is a * structural subtype of OTel's `Tracer`, so you can pass an OTel * tracer directly without any adapter: * * ```typescript * import { trace } from '@opentelemetry/api'; * * const tool = createTool('projects') * .tracing(trace.getTracer('mcp-fusion')) * .action({ name: 'list', handler: listProjects }); * ``` * * **Error classification**: * - Validation failures → `SpanStatusCode.UNSET` + `mcp.error_type` attribute * - Handler exceptions → `SpanStatusCode.ERROR` + `recordException()` * * **Context propagation limitation**: Since MCP Fusion does not depend * on `@opentelemetry/api`, it cannot call `context.with(trace.setSpan(...))`. * Auto-instrumented downstream calls (Prisma, HTTP, Redis) inside handlers * will appear as siblings, not children, of the MCP span. * * @param tracer - A {@link FusionTracer} (or OTel `Tracer`) instance * @returns `this` for chaining * * @see {@link FusionTracer} for the interface contract * @see {@link SpanStatusCode} for status code semantics */ tracing(tracer: FusionTracer): this; /** * Route a tool call to the correct action handler. * * Pipeline: `parseDiscriminator → resolveAction → validateArgs → runChain` * * Auto-calls {@link buildToolDefinition} if not called yet. * If a debug observer is active, structured events are emitted * at each pipeline step with timing information. * * @param ctx - Application context * @param args - Raw arguments from the LLM (includes discriminator) * @param progressSink - Optional callback for streaming progress notifications. * When attached via `attachToServer()`, this is automatically wired to * MCP `notifications/progress`. When omitted, progress events are silently consumed. * @param signal - Optional AbortSignal from the MCP SDK protocol layer. * Fired when the client sends `notifications/cancelled` or the connection drops. * The framework checks this signal before handler execution and during * generator iteration, aborting zombie operations immediately. * @returns The handler's {@link ToolResponse} * * @example * ```typescript * // Direct execution (useful in tests) * const result = await builder.execute(ctx, { * action: 'list', * workspace_id: 'ws_123', * }); * ``` */ execute(ctx: TContext, args: Record, progressSink?: ProgressSink, signal?: AbortSignal): Promise; /** * Internal: execute with the appropriate observability path. * Extracted to keep the concurrency/egress guards clean. */ private _executeWithObservability; /** * Pipeline hooks for observability instrumentation. * * Each hook is called at the corresponding pipeline step. * The fast path passes `undefined` (zero overhead). * Debug and traced paths supply their hooks via factory methods. */ private _executePipeline; /** * Build debug hooks: lightweight event emission. */ private _buildDebugHooks; /** * Build traced hooks: OpenTelemetry-compatible span creation. * * Creates ONE span per tool call with events for pipeline steps. * Uses wrapResponse for leak-proof span closure. * AI errors → UNSET, system errors → ERROR. */ private _buildTracedHooks; /** * Build telemetry hooks: Shadow Socket event emission for Inspector TUI. * * Emits `validate`, `middleware`, and `execute` TelemetryEvents * to the IPC sink so that `fusion inspect` shows real pipeline data. */ private _buildTelemetryHooks; /** Get the tool name. */ getName(): string; /** Get a copy of the capability tags. */ getTags(): string[]; /** Get all registered action keys (e.g. `["list", "create"]` or `["users.list", "users.ban"]`). */ getActionNames(): string[]; /** Get the discriminator field name (e.g. `"action"`). Used by the Exposition Compiler. */ getDiscriminator(): string; /** * Get all registered internal actions. * Used by the Exposition Compiler for atomic tool expansion. * @returns Read-only array of internal action definitions */ getActions(): readonly InternalAction[]; /** * Get the common schema shared across all actions. * Used by the Exposition Compiler for schema purification. * @returns The common Zod schema, or undefined if not set */ getCommonSchema(): ZodObject | undefined; /** Check if `_select` reflection is enabled. Used by the Exposition Compiler. */ getSelectEnabled(): boolean; /** Get per-action state sync hints for auto-policy generation. */ getStateSyncHints(): ReadonlyMap; /** * Preview the exact MCP protocol payload that the LLM will receive. * * Builds the tool definition if not already built, then renders * a human-readable preview of the complete tool including: * - Tool name and description * - Input schema (JSON) * - Annotations (if any) * - Approximate token count (~4 chars per token, GPT-5.2 heuristic) * * Call this from your dev environment to optimize token usage * and verify the LLM-facing prompt without starting an MCP server. * * @returns Formatted string showing the exact MCP payload + token estimate * * @example * ```typescript * const projects = defineTool('projects', { ... }); * console.log(projects.previewPrompt()); * * // Output: * // ┌─────────────────────────────────────────┐ * // │ MCP Tool Preview: projects │ * // ├─────────────────────────────────────────┤ * // │ Name: projects │ * // │ Actions: 3 (list, create, delete) │ * // │ Tags: api, admin │ * // ├─── Description ─────────────────────────┤ * // │ Manage workspace projects. ... │ * // ├─── Input Schema ────────────────────────┤ * // │ { "type": "object", ... } │ * // ├─── Annotations ─────────────────────────┤ * // │ readOnlyHint: false │ * // │ destructiveHint: true │ * // ├─── Token Estimate ──────────────────────┤ * // │ ~342 tokens (1,368 chars) │ * // └─────────────────────────────────────────┘ * ``` * * @see {@link buildToolDefinition} for the raw MCP Tool object */ previewPrompt(): string; /** * Get metadata for all registered actions. * * Useful for programmatic documentation, compliance audits, * dashboard generation, or runtime observability. * * @returns Array of {@link ActionMetadata} objects * * @example * ```typescript * const meta = builder.getActionMetadata(); * for (const action of meta) { * console.log(`${action.key}: destructive=${action.destructive}, fields=${action.requiredFields}`); * } * ``` * * @see {@link ActionMetadata} for the metadata shape */ getActionMetadata(): ActionMetadata[]; private _assertNotFrozen; } //# sourceMappingURL=GroupedToolBuilder.d.ts.map