UNPKG

@dollhousemcp/mcp-server

Version:

DollhouseMCP - A Model Context Protocol (MCP) server that enables dynamic AI persona management from markdown files, allowing Claude and other compatible AI assistants to activate and switch between different behavioral personas.

653 lines 27.3 kB
/** * MCPAQLHandler - Unified handler for all MCP-AQL operations * * ARCHITECTURE: * - Thin resolver pattern: validates → routes → dispatches * - SOLID principles: depends on abstractions (HandlerRegistry interface) * - Defense in depth: Gatekeeper validates endpoint/operation matching and enforces policies * - Dispatch pattern: routes handler references to actual method calls * * OPERATION FLOW: * 1. Validate input structure (type guards) * 2. Validate permissions (Gatekeeper) * 3. Route operation (OperationRouter) * 4. Dispatch to handler (resolveHandlerReference) * 5. Return standardized result (OperationResult) * * ERROR HANDLING: * All errors are caught and returned as OperationFailure with: * - success: false * - error: human-readable message * - data: never (discriminated union enforces this) */ import { Gatekeeper } from './Gatekeeper.js'; import { OperationResult, BatchResult } from './types.js'; import type { DangerZoneEnforcer } from '../../security/DangerZoneEnforcer.js'; import type { VerificationStore } from '@dollhousemcp/safety'; import type { IVerificationNotifier } from '../../services/VerificationNotifier.js'; import type { ElementCRUDHandler } from '../ElementCRUDHandler.js'; import type { MemoryManager } from '../../elements/memories/MemoryManager.js'; import type { AgentManager } from '../../elements/agents/AgentManager.js'; import type { TemplateRenderer } from '../../utils/TemplateRenderer.js'; import type { ElementQueryService } from '../../services/query/ElementQueryService.js'; import type { CollectionHandler } from '../CollectionHandler.js'; import type { PortfolioHandler } from '../PortfolioHandler.js'; import type { GitHubAuthHandler } from '../GitHubAuthHandler.js'; import type { ConfigHandler } from '../ConfigHandler.js'; import type { EnhancedIndexHandler } from '../EnhancedIndexHandler.js'; import type { PersonaHandler } from '../PersonaHandler.js'; import type { SyncHandler } from '../SyncHandlerV2.js'; import type { BuildInfoService } from '../../services/BuildInfoService.js'; import type { MemoryLogSink } from '../../logging/sinks/MemoryLogSink.js'; import type { PerformanceMonitor } from '../../utils/PerformanceMonitor.js'; import type { OperationMetricsTracker } from '../../metrics/OperationMetricsTracker.js'; import type { GatekeeperMetricsTracker } from '../../metrics/GatekeeperMetricsTracker.js'; import type { AutonomyMetricsSnapshot } from '../../elements/agents/autonomyEvaluator.js'; /** * Verification metrics tracker following DangerZoneEnforcer.getMetrics() pattern. * Tracks success/failure/expiry rates and time-to-verify. */ export interface VerificationMetrics { /** Total verify_challenge calls since startup */ totalAttempts: number; /** Successful verifications */ totalSuccesses: number; /** Failed verifications (wrong code) */ totalFailures: number; /** Expired challenge attempts */ totalExpired: number; /** Invalid format attempts (rejected before store lookup) */ totalInvalidFormat: number; /** Rate-limited attempts */ totalRateLimited: number; /** Average time from challenge creation to successful verify (ms) */ averageTimeToVerifyMs: number; /** Current failures in rate-limit window */ failuresInCurrentWindow: number; } /** * Handler registry interface for dependency injection. * Abstracts the concrete handler types for better testability and decoupling. */ export interface HandlerRegistry { elementCRUD: ElementCRUDHandler; memoryManager: MemoryManager; agentManager: AgentManager; templateRenderer: TemplateRenderer; elementQueryService: ElementQueryService; collectionHandler?: CollectionHandler; portfolioHandler?: PortfolioHandler; authHandler?: GitHubAuthHandler; configHandler?: ConfigHandler; enhancedIndexHandler?: EnhancedIndexHandler; personaHandler?: PersonaHandler; syncHandler?: SyncHandler; buildInfoService?: BuildInfoService; cacheMemoryBudget?: import('../../cache/CacheMemoryBudget.js').CacheMemoryBudget; gatekeeper: Gatekeeper; dangerZoneEnforcer?: DangerZoneEnforcer; verificationStore?: VerificationStore; verificationNotifier?: IVerificationNotifier; memorySink?: MemoryLogSink; metricsSink?: import('../../metrics/sinks/MemoryMetricsSink.js').MemoryMetricsSink; performanceMonitor?: PerformanceMonitor; operationMetricsTracker?: OperationMetricsTracker; gatekeeperMetricsTracker?: GatekeeperMetricsTracker; } /** * MCPAQLHandler - Unified entry point for all MCP-AQL operations * * This handler implements the thin resolver pattern: * 1. Validates that operations are called via correct CRUD endpoints * 2. Routes operations to their handler references * 3. Dispatches to actual handler methods * 4. Returns standardized OperationResult * * DESIGN RATIONALE: * - Single Responsibility: Only orchestrates, delegates actual work * - Dependency Inversion: Depends on HandlerRegistry interface * - Interface Segregation: HandlerRegistry exposes only what's needed * - Open/Closed: New operations added to router, not this class */ /** * Minimal interface for correlation ID retrieval. * Keeps coupling loose — only requires what MCPAQLHandler actually needs. * Issue #301: Request correlation support. */ export interface CorrelationIdProvider { getCorrelationId(): string | undefined; } export declare class MCPAQLHandler { private readonly handlers; private readonly contextTracker?; private readonly gatekeeper; /** * Issue #656: Per-memory save debounce timers. * When addEntry is called rapidly, we coalesce saves — only the latest * state is written to disk after the debounce window expires. * Key: normalized memory name, Value: { timer, memory, manager } */ private readonly pendingSaves; /** Issue #656: Debounce metrics — tracks saves coalesced vs actually written. */ private readonly debounceMetrics; /** * Issue #657: Per-memory save frequency tracker. * Sliding window counter: tracks addEntry calls per memory within the monitor window. * Logs warnings at configurable thresholds to catch runaway loops early. */ private readonly saveFrequencyCounters; /** Issue #625 Phase 4: Rate limiter for permission_prompt evaluations (configurable via env) */ private readonly permissionPromptLimiter; /** Issue #625 Phase 4: Rate limiter for CLI approval record creation (configurable via env) */ private readonly cliApprovalLimiter; /** * Build a standardized rate-limit deny response for permission_prompt. */ private buildRateLimitDeny; /** Issue #142: Rate limiter for verify_challenge attempts (max 10 failures per 60s window) */ private readonly verificationRateLimiter; /** Issue #142: Metrics tracker for verification operations */ private readonly verificationMetrics; /** * Tracks agents currently in an execution loop for Gatekeeper policy enforcement. * * **Lifecycle:** Entries are added in `dispatchExecute()` on `execute_agent` and * removed on `complete_execution`. Only agents with a gatekeeper policy (explicit * or synthesized from `tools` config) are tracked. * * **Policy resolution:** Entries are included in `getActiveElements()` so the * Gatekeeper evaluates agent policies alongside persona/skill/ensemble policies. * The standard priority applies: deny > scope_restriction > confirm > allow. * * **Memory safety:** The Map is bounded by concurrently executing agents. If a * session ends without `complete_execution`, the Map is garbage collected with * the MCPAQLHandler instance. * * Issue #449 */ private readonly executingAgents; /** * Set of aborted goalIds. Once a goalId is aborted, all further execution * operations (record_execution_step, complete_execution, continue_execution) * for that goalId are rejected at the dispatch layer. * * Issue #249: Abort/cancellation infrastructure. */ private readonly abortedGoals; constructor(handlers: HandlerRegistry, contextTracker?: CorrelationIdProvider | undefined); /** * Get verification metrics for monitoring/diagnostics. * Follows the same pattern as DangerZoneEnforcer.getMetrics(). */ getVerificationMetrics(): VerificationMetrics; /** * Get autonomy evaluation metrics for monitoring/diagnostics. * Issue #391: Follows the same pattern as getVerificationMetrics(). */ getAutonomyMetrics(): AutonomyMetricsSnapshot; /** * Gather currently active elements for Gatekeeper policy evaluation. * * Queries PersonaManager, SkillManager, and EnsembleManager for active elements, * then appends any currently executing agents with gatekeeper policies. All * elements are mapped to the {@link ActiveElement} interface expected by * `Gatekeeper.enforce()`. * * Issue #452: Provides element context for Layer 2 (element policy resolution). * Issue #449: Includes executing agents alongside personas/skills/ensembles. * * @returns Array of active elements with their gatekeeper policies, or empty * array if gathering fails (fail-open: only route policies apply). */ private getActiveElements; private getPolicyReportElements; private copyGatekeeperDiagnostics; /** * Handle CREATE operations (additive, non-destructive) * * CREATE endpoint operations: * - create_element: Create new elements * - import_element: Import elements from exported data * - addEntry: Add entries to memory elements * * Supports batch operations when input contains `operations` array. * * @param input - Operation input with operation name and params, or BatchRequest * @returns OperationResult with success/failure status, or BatchResult for batch operations */ handleCreate(input: unknown): Promise<OperationResult | BatchResult>; /** * Handle READ operations (read-only, safe) * * READ endpoint operations: * - list_elements: List elements with filtering * - get_element: Get element by name * - get_element_details: Get detailed element information * - search_elements: Full-text search * - query_elements: Query with pagination * - get_active_elements: Get active elements * - validate_element: Validate element * - render: Render template * - export_element: Export element * - activate_element: Activate elements for use * - deactivate_element: Deactivate element * * Supports batch operations when input contains `operations` array. * * @param input - Operation input with operation name and params, or BatchRequest * @returns OperationResult with success/failure status, or BatchResult for batch operations */ handleRead(input: unknown): Promise<OperationResult | BatchResult>; /** * Handle UPDATE operations (modifying existing state) * * UPDATE endpoint operations: * - edit_element: Modify existing elements * * Supports batch operations when input contains `operations` array. * * @param input - Operation input with operation name and params, or BatchRequest * @returns OperationResult with success/failure status, or BatchResult for batch operations */ handleUpdate(input: unknown): Promise<OperationResult | BatchResult>; /** * Handle DELETE operations (destructive actions) * * DELETE endpoint operations: * - delete_element: Delete elements * - clear: Clear memory entries * - clear_github_auth: Remove GitHub authentication * * Supports batch operations when input contains `operations` array. * * @param input - Operation input with operation name and params, or BatchRequest * @returns OperationResult with success/failure status, or BatchResult for batch operations */ handleDelete(input: unknown): Promise<OperationResult | BatchResult>; /** * Handle EXECUTE operations (runtime execution lifecycle) * * EXECUTE endpoint operations: * - execute_agent: Start execution of an agent or executable element * - get_execution_state: Query current execution state * - record_execution_step: Record execution progress or findings * - complete_execution: Signal execution finished successfully * - continue_execution: Resume execution from saved state * - abort_execution: Cancel an ongoing execution * * Unlike CRUD operations (which are idempotent), EXECUTE operations manage * runtime state and are inherently non-idempotent. Calling execute twice * creates two separate executions. * * Supports batch operations when input contains `operations` array. * * @param input - Operation input with operation name and params, or BatchRequest * @returns OperationResult with success/failure status, or BatchResult for batch operations */ handleExecute(input: unknown): Promise<OperationResult | BatchResult>; /** * Core execution logic shared by all CRUD endpoints. * Implements the thin resolver pattern: validate → route → dispatch → return. * * OPERATION FLOW: * 1. Validate input structure * 2. Validate permissions (PermissionGuard) * 3. Route operation (OperationRouter) * 4. Dispatch to handler * 5. Return standardized result * * @param input - Raw input to validate and process * @param endpoint - CRUD endpoint being called * @returns Standardized OperationResult */ private executeOperation; /** * Execute a batch of operations sequentially. * Operations are executed in order, and failures do not stop execution. * * EXECUTION SEMANTICS: * - Operations run sequentially (in order) * - Each operation is validated independently * - Failed operations don't stop the batch * - All results are collected and returned * * @param batch - BatchRequest with array of operations * @param endpoint - CRUD endpoint being called * @returns BatchResult with all operation results and summary */ private executeBatch; /** * Dispatch a handler reference to the actual handler method. * * Handler reference format: "Module.method" * Examples: * - "ElementCRUD.create" → this.handlers.elementCRUD.createElement(...) * - "Memory.addEntry" → this.handlers.memoryManager.addEntry(...) * - "Agent.execute" → this.handlers.agentManager.executeAgent(...) * * DISPATCH STRATEGY (Issue #247): * 1. Check if operation is schema-driven → use SchemaDispatcher * 2. Otherwise fall through to legacy module-based dispatch * * Schema-driven operations benefit from: * - Declarative configuration (no switch statements) * - Auto-generated parameter validation * - Single source of truth for operation metadata * * @param handlerRef - Handler reference in "Module.method" format * @param input - Validated operation input * @returns Promise resolving to operation-specific data * @throws Error if handler reference is unknown or method fails */ private dispatch; /** * Dispatch ElementCRUD operations to ElementCRUDHandler */ private dispatchElementCRUD; /** * Handle element import operation. * Parses export package, deserializes element data, and creates the element. * * @param params - Operation parameters containing data and optional overwrite flag * @returns Promise resolving to created element * @throws Error if export package is invalid or element already exists (when overwrite is false) */ private handleImportElement; /** * Export an element to JSON or YAML format * * @param name - Element name to export * @param type - Element type (persona, skill, template, agent, memory, ensemble) * @param format - Export format: 'json' or 'yaml' (default: 'json') * @returns ExportPackage with serialized element data */ private handleExportElement; /** * Dispatch Memory operations to MemoryManager * * Memory operations work on individual Memory instances: * 1. Get the Memory by name from MemoryManager * 2. Call the operation on the Memory instance */ /** * Issue #656: Debounce memory saves to prevent file descriptor exhaustion. * Coalesces rapid addEntry calls — only writes the latest state after the window expires. */ private debouncedMemorySave; /** * Issue #657: Track addEntry call frequency per memory and log warnings at thresholds. * * Uses a sliding window counter to detect runaway save loops before they exhaust resources. * Counters are bounded: max 500 tracked memories, oldest-first eviction when exceeded. * Warning/critical flags auto-reset when frequency drops below thresholds. * * @param memoryName - Name of the memory being written to */ private trackSaveFrequency; /** * Issue #656: Flush all pending debounced saves on shutdown. * Called by DI container dispose chain to ensure no data is lost. */ dispose(): Promise<void>; /** * Issue #656: Flush all pending debounced saves immediately. */ flushPendingSaves(): Promise<void>; private dispatchMemory; /** * Dispatch Agent operations to AgentManager */ private dispatchAgent; /** * Dispatch Template operations to TemplateRenderer */ private dispatchTemplate; /** * Dispatch Activation operations (cross-cutting concern) */ private dispatchActivation; /** * Dispatch Search operations to ElementQueryService */ private dispatchSearch; /** * Dispatch Introspection operations to IntrospectionResolver */ private dispatchIntrospection; /** * Search elements across one or all element types * * Implements simple case-insensitive substring matching on: * - metadata.name * - metadata.description * - content (if available) * * @param input - Operation input with optional elementType and search params * @returns Search results with matched elements and relevance info */ private handleSearchElements; /** * Dispatch Collection operations to CollectionHandler */ private dispatchCollection; /** * Dispatch Portfolio operations to PortfolioHandler */ private dispatchPortfolio; /** * Dispatch Auth operations to GitHubAuthHandler */ private dispatchAuth; /** * Dispatch Config operations to ConfigHandler and BuildInfoService */ private dispatchConfig; /** * Dispatch EnhancedIndex operations to EnhancedIndexHandler */ private dispatchEnhancedIndex; /** * Dispatch Persona operations to PersonaHandler */ private dispatchPersona; private challengeIsForDeadlockRelief; private issueDeadlockReliefChallenge; private completeDeadlockRelief; /** * Dispatch Gatekeeper operations for confirmation management. * * Issue #452: Handles the confirm_operation flow where users approve * operations that require confirmation per Gatekeeper policy. * * Flow: * 1. Gatekeeper.enforce() returns confirmationPending: true * 2. LLM calls confirm_operation with the operation name * 3. This method records the confirmation in the session * 4. LLM retries the original operation, which now passes via Layer 3 */ private dispatchGatekeeper; /** * Dispatch Logging operations (Issue #528 - CRUDE migration) * * Routes query_logs through the unified CRUDE pipeline instead of * a standalone MCP tool, providing operation routing, gatekeeper * policy enforcement, and structured response format. */ private dispatchLogging; /** * Dispatch Metrics operations. * * Routes query_metrics through the unified CRUDE pipeline, providing * operation routing, gatekeeper policy enforcement, and structured response format. */ private dispatchMetrics; /** * Dispatch Browser operations (Issue #774: open portfolio browser) */ /** * Dispatch Browser operations. * * Starts the portfolio web server (if not running) and opens the * system browser. Returns the URL and any warnings if the browser * could not be opened automatically. * * @param method - The browser method to dispatch ('open') * @returns MCP response with URL and optional warning * @see Issue #774 */ /** * Extract URL query parameters from operation params. * Serializes all params except 'tab' as string key-value pairs. * @see Issue #1765 - URL parameter support for portfolio browser */ private static extractUrlParams; /** Serialize a param value to a URL-safe string. */ private static serializeParamValue; private dispatchBrowser; /** * Look up the CRUDE endpoint for a given operation name. * Used by dispatchGatekeeper to determine the correct endpoint context. */ private getEndpointForOperation; /** * Build a human-readable summary of an operation for confirmation prompts. * Issue #748: Natural language confirmation summaries. * * Translates raw operation names and params into plain language so users * understand what they're approving without parsing machine-readable payloads. */ private buildOperationSummary; /** * Score the risk of an MCP-AQL operation for auto-confirm audit trails. * * Returns a score from 0-100 based on operation characteristics: * - DELETE endpoint operations: base 80 (destructive, data loss) * - EXECUTE endpoint operations: base 60 (unpredictable side effects) * - UPDATE endpoint operations: base 40 (modifies existing data) * - CREATE endpoint operations: base 20 (additive, low risk) * * Modifiers: * - Operations with canBeElevated: false get +10 (structurally dangerous) * - Operations targeting gatekeeper fields get +10 (privilege escalation vector) * * @param operation - The operation name * @param endpoint - The CRUDE endpoint * @param params - Operation parameters (for field inspection) * @returns Risk score 0-100 */ private scoreOperationRisk; /** * Dispatch Execute operations for execution lifecycle management (Issue #244) * * Execute operations handle runtime state of executable elements: * - execute: Start a new execution * - getState: Query current execution state * - updateState: Record progress, findings, step completion * - complete: Signal successful completion * - continue: Resume from saved state * - abort: Cancel an ongoing execution * * These operations are non-idempotent by nature (unlike CRUD operations). * Currently routes to AgentManager, but designed for future extensibility * to workflows, pipelines, and other executable element types. */ private dispatchExecute; /** Maximum recent blocks tracked per agent to prevent unbounded growth */ private static readonly MAX_RECENT_BLOCKS; /** * Record a gatekeeper block against all currently executing agents. * Called when Gatekeeper.enforce() denies an operation, so that * record_execution_step can report it as a notification. * * @since v2.1.0 - Agent Notification System * @private */ private recordGatekeeperBlockForAgents; /** * Collect notifications for an executing agent from all sources: * 1. Unreported gatekeeper blocks → permission_pending * 2. Autonomy pause (continue=false) → autonomy_pause * 3. DangerZone blocked agents (system-wide broadcast) → danger_zone * * Gatekeeper blocks are marked as "reported" after first collection * so they don't repeat on subsequent calls. DangerZone and autonomy * notifications are always emitted while the condition persists. * * @since v2.1.0 - Agent Notification System * @private */ private collectNotifications; /** * Evaluate resilience policy after a step result and potentially override * the autonomy directive to enable auto-continuation or retry. * * Returns a modified step result if resilience kicks in, or null to use * the original result (default behavior). * * @since v2.1.0 - Agent Execution Resilience (Issue #526) * @private */ private evaluateResilience; /** * Get active goal IDs for an agent (Issue #249). * Queries the agent's state to find in-progress goals. * @private */ private getActiveGoalIds; /** * Get the current correlation ID from the underlying ContextTracker. * Exposed for use by UnifiedEndpoint and other wrappers. * Issue #301. */ getCorrelationId(): string | undefined; /** * Build response metadata with correlation ID and timing. * Issue #301: Request IDs and distributed tracing support. */ private buildMeta; /** * Create a successful operation result */ private success; /** * Create a failed operation result */ private failure; /** * Apply field selection and name transformation to handler response. * * Processes the `fields` parameter to filter response data: * - If fields is an array, filter to those fields only * - If fields is a preset string ('minimal', 'standard', 'full'), use preset * - If fields is not provided, transform names only (name → element_name) * * For responses with `results` or `items` arrays, field selection is applied * to each item in the array rather than filtering the container object. * * @param result - The raw handler result * @param params - Original params containing optional `fields` * @returns Transformed result with field selection applied */ private applyFieldSelection; /** * Transform response with awareness of nested arrays. * Applies name transformation to items within `results` or `items` arrays. */ private transformWithArrayAwareness; /** * Filter response with awareness of nested arrays. * Applies field filtering to items within `results` or `items` arrays. */ private filterWithArrayAwareness; /** * Transform top-level fields of an object, excluding specified keys. * Used to preserve container metadata (total, query, etc.) while filtering nested arrays. */ private transformTopLevel; /** * Check if result is an MCP response format (with content array). * These responses contain formatted text and shouldn't be transformed. */ private isMCPResponse; } //# sourceMappingURL=MCPAQLHandler.d.ts.map