import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import type { CallToolResult, ToolAnnotations } from "@modelcontextprotocol/sdk/types.js"; import type { z } from "zod"; import { type ReadOnlyClass } from "./readOnly.js"; import { type ResourceIdentity, type ResponseControl, type ResponseState, type StateFace } from "./responseContract.js"; import type { Logger } from "./types/Logger.js"; /** * Discriminated union for delete confirmation results. * When confirmed is false, cancelledResponse is always present. */ export type ConfirmResult = { confirmed: true; } | { confirmed: false; cancelledResponse: CallToolResult; }; /** * Tool configuration metadata */ export interface ToolConfig { /** Unique tool identifier (e.g., "modyo-mcp-space-create") */ name: string; /** Human-readable description of what the tool does */ description: string; /** MCP annotations for the tool */ annotations?: ToolAnnotations; /** If false, re-throws errors instead of returning structured CallToolResult with isError: true (default: true) */ catchErrors?: boolean; } /** * Abstract base class for MCP tools * * Provides a structured, class-based approach to creating tools with: * - Type-safe parameter validation via Zod schemas * - Consistent error handling patterns * - Logging integration * - Repository access helpers * * @example * ```typescript * class CreateSpaceTool extends ToolBase { * protected config: ToolConfig = { * name: "space-create", * description: "Creates a new space", * annotations: { destructiveHint: true } * }; * * protected getParamsSchema() { * return z.object({ * name: z.string().min(2).max(100), * }); * } * * async execute(params: CreateSpaceParams): Promise { * const repo = await this.getRepository(SpacesRepository); * const response = await repo.createSpace(params); * return this.success(response); * } * } * ``` */ export declare abstract class ToolBase { protected server: McpServer; protected serverName: string; protected logger: Logger; /** * Tool configuration - must be defined by subclasses */ protected abstract config: ToolConfig; /** * Returns the Zod schema for validating tool parameters * Override to provide custom validation */ protected abstract getParamsSchema(): z.ZodType; /** * Executes the tool logic * @param params - Validated parameters * @returns MCP CallToolResult */ protected abstract execute(params: TParams): Promise; /** * Schema zod del `data` de esta tool, si ya está migrada al contrato de * respuesta (`docs/development/response-contract.md`). * * Devolver algo distinto de `null` es un **compromiso duro**: `ToolBase` * publica el `outputSchema` correspondiente, y el SDK falla toda respuesta * no-error que no traiga `structuredContent` * (`server/mcp.js`: "has an output schema but no structured content was * provided"). Por eso se declara en el mismo commit en que la tool empieza * a responder con `this.structured()`, nunca antes. * * La tool declara SOLO lo suyo: `platform`, `resource` y `state` los agrega * `envelopeSchema()` y son iguales para todo el catálogo. * * Las tools no migradas devuelven `null` (el default) y siguen con * `success()`. Las dos formas conviven sin problema durante el rollout. */ protected getOutputSchema(): z.ZodTypeAny | null; /** * Cara de `state` que publica esta tool: `"read"` para cobertura * (returned/total/searched/page/per_page/complete), `"write"` para * post-condición (status/published/live_version/pending/affected). * * Solo se consulta si `getOutputSchema()` devuelve algo. Publicar las dos * caras juntas le ofrecería al modelo campos que esa tool nunca emite, y * cuesta el doble en `tools/list`. */ protected getStateFace(): StateFace; /** * Initializes the tool with MCP server context * Called automatically during registration */ private initialize; /** * Registers this tool with the MCP server * Called by McpServerBase during setup */ registerTool(server: McpServer, serverName: string, logger: Logger): void; /** * Read-only classification (issue #154): "read" (declares readOnlyHint), * "gated" (multi-action with a read action / `manage`), or "mutation" * (single-purpose write/destroy). Used to skip mutation tools and to gate * gated tools per-action under MODYO_READ_ONLY. */ readOnlyClass(): ReadOnlyClass; /** * Returns a structured rejection if this invocation must be blocked in * read-only mode, or null to allow it. Only read actions (and a field-less * `manage`, which acts as a get) are allowed; everything else is denied * (fail-closed). */ private readOnlyGate; /** * Rechazo del gate read-only, por el envelope cuando la tool esta migrada. * Sin esto, toda tool `gated` con `outputSchema` fallaria con `-32602` en * modo read-only: el rechazo es `isError: false` y no traia * `structuredContent`. */ private readOnlyRejectionResponse; /** * Helper: Gets a repository instance using the configured platform * Uses SinglePlatformProvider to get platform config from environment variables * * @param RepositoryClass - Repository constructor * @returns Repository instance * @throws Error if platform not configured */ protected getRepository(RepositoryClass: new (url: string, token: string, logger: Logger) => T): Promise; /** * Respuesta bajo el contrato de `docs/development/response-contract.md`. * * Arma el envelope —`platform` siempre, `resource` y `state` cuando * aplican— y lo emite por los dos canales: * * - `structuredContent`, tipado por el `outputSchema` que publicó * `registerTool()`; * - `content[].text` con **el mismo payload**, serializado compacto. * * Que `content[].text` lleve el mismo payload y no un resumen es * deliberado: un resumen reintroduce por la ventana el `message` que #104 * saca por la puerta, y le da al modelo dos representaciones que pueden * discrepar. Compacto y no con `null, 2` porque la indentación es puro * relleno de tokens en payloads anidados. * * Solo puede usarla una tool que declare `getOutputSchema()`: sin * `outputSchema` publicado, `structuredContent` viajaría sin validar. */ protected structured(data: TData, extra?: { resource?: ResourceIdentity; state?: ResponseState; control?: ResponseControl; }): CallToolResult; /** * Desenlace de control: la llamada no se ejecuto y no es un error del * servidor (cancelacion del usuario, rechazo del gate read-only). * * Viaja por el mismo envelope que una respuesta normal —con `data` vacio y * el motivo en `control`— porque una tool que declara `outputSchema` no * puede devolver un resultado no-error sin `structuredContent`: el SDK lo * rechaza con `-32602`. Lo encontro el rollout, no el piloto. * * Las tools no migradas siguen recibiendo la forma heredada, para que la * convivencia durante el rollout no dependa del orden de migracion. */ protected controlResponse(control: ResponseControl): CallToolResult; /** * Respuesta plana, sin envelope. * * El parametro `message` que aceptaba —y que ninguna tool llegaba a usar— * se elimino con el rollout de #104 (categoria D de su auditoria). * * **Ninguna tool del catalogo la usa**: las 82 responden con * `structured()`. Queda solo como camino de compatibilidad interno para * una tool que todavia no declare `getOutputSchema()`, y una forcing * function impide que vuelva a aparecer en `src/tools/` * (`tests/forcing-functions/state-face-usage.test.ts`). */ protected success(data: unknown): CallToolResult; /** * Helper: Formats error response as MCP result * Returns a properly formatted error response without throwing */ protected errorResponse(error: unknown, context?: Record): CallToolResult; /** * Helper: Logs debug information */ protected debug(message: string, data?: unknown): void; /** * Helper: Logs info */ protected info(message: string, data?: unknown): void; /** * Helper: Logs error */ protected logError(message: string, data?: unknown): void; /** * Helper: Request user confirmation via MCP elicitation * * Use this for sensitive operations like password changes, email updates, etc. * Returns the user's response or null if client doesn't support elicitation. * * @param message - Message to display to the user * @param fields - Optional form fields to collect (for form-based elicitation) * @returns User response with action ('accept'|'decline'|'cancel') and optional content * * @example * ```typescript * // Simple confirmation * const result = await this.elicit("Are you sure you want to change the password?"); * if (result?.action !== 'accept') { * return this.errorResponse(new Error("Password change cancelled by user")); * } * * // With form fields * const result = await this.elicit("Confirm email change", { * confirm: { type: 'boolean', description: 'I confirm this change' } * }); * ``` */ protected elicit(message: string, fields?: Record): Promise<{ action: "accept" | "decline" | "cancel"; content?: Record; } | null>; /** * Helper: Request user confirmation before delete by typing resource name * * Use this for destructive delete operations. The user must type the exact * resource name to confirm deletion. * * @param resourceType - Type of resource being deleted (e.g., "variable", "template") * @param resourceName - Name/identifier of the resource to confirm * @returns Discriminated union: confirmed true, or confirmed false with cancelledResponse * * @example * ```typescript * const result = await this.confirmDelete("variable", variable.slug); * if (!result.confirmed) return result.cancelledResponse; * // proceed with deletion * ``` */ protected confirmDelete(resourceType: string, resourceName: string): Promise; /** * Helper: Request user confirmation before bulk delete * * @param resourceType - Type of resources being deleted (plural, e.g., "variables") * @param count - Number of items to delete * @returns Discriminated union: confirmed true, or confirmed false with cancelledResponse */ protected confirmBulkDelete(resourceType: string, count: number): Promise; /** * Extracts raw schema shape for MCP registration * Handles Zod object schemas */ private extractRawSchema; } //# sourceMappingURL=ToolBase.d.ts.map