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.

857 lines 118 kB
/** * SchemaDispatcher - Generic dispatcher for schema-driven operations * * This module provides automatic dispatch from operation schemas to handler methods. * It eliminates the need for manual switch statements in MCPAQLHandler by: * * 1. Looking up operation in schema * 2. Resolving handler from registry * 3. Building method arguments from params * 4. Calling the handler method * 5. Applying field selection/transformation to response (Issue #202) * * ARCHITECTURE: * - SchemaDispatcher.dispatch(operation, params, registry) → Promise<unknown> * - Uses argBuilder to determine how to pass parameters to handler * - Validates required parameters before dispatch * - Provides clear error messages for missing handlers/methods * * INPUT NORMALIZATION (Issue #251): * - Parameters can have multiple sources via `sources` field * - Resolution order: sources[0], sources[1], ..., params[key] * - Supports dot notation: 'input.elementType', 'params.type' * - Operations with `needsFullInput: true` have access to full OperationInput * * FIELD SELECTION (Issue #202): * - When `fields` param is provided, filters response to requested fields only * - Transforms field names for LLM consistency (name → element_name) * - Supports preset field sets: 'minimal', 'standard', 'full' * * @see Issue #247 - Schema-driven operation definitions * @see Issue #251 - ElementCRUD input normalization * @see Issue #202 - GraphQL field selection for response token optimization */ import yaml from 'js-yaml'; import { SecureYamlParser } from '../../security/secureYamlParser.js'; import { getOperationSchema, isSchemaOperation, } from './OperationSchema.js'; import { IntrospectionResolver } from './IntrospectionResolver.js'; import { NormalizerRegistry } from './normalizers/index.js'; import { ElementType } from '../../portfolio/types.js'; // Note: Field selection is now applied at MCPAQLHandler level (Issue #202) // ============================================================================ // Input Normalization (Issue #251) // ============================================================================ /** * Validate property path to prevent prototype pollution attacks. * Only allows alphanumeric characters, underscores, and dots. */ const SAFE_PATH_PATTERN = /^[a-zA-Z_$][a-zA-Z0-9_$.]*$/; const FORBIDDEN_PATHS = new Set(['__proto__', 'constructor', 'prototype']); /** * Resolve a value from a dot-notation path on an object. * Example: getNestedValue({ input: { elementType: 'persona' } }, 'input.elementType') => 'persona' * * Security: Validates path format and blocks prototype pollution vectors. */ function getNestedValue(obj, path) { // Validate path format to prevent injection attacks if (!SAFE_PATH_PATTERN.test(path)) { throw new Error(`Invalid property path format: ${path}`); } const parts = path.split('.'); // Check for prototype pollution attempts for (const part of parts) { if (FORBIDDEN_PATHS.has(part)) { throw new Error(`Forbidden property path segment: ${part}`); } } let current = obj; for (const part of parts) { if (current === null || current === undefined) { return undefined; } current = current[part]; } return current; } /** * Resolve a parameter value from multiple sources. * * The resolution order is: * 1. Check each source in `sources` array (if defined) * 2. Fall back to the direct parameter value * 3. Fall back to default value (if defined) * * @param key - Parameter name * @param def - Parameter definition with possible sources * @param context - Resolution context containing input, params, and raw params * @returns Resolved value or undefined */ function resolveParamValue(key, def, context) { // If sources are defined, check them in order if (def.sources && def.sources.length > 0) { for (const source of def.sources) { // Build the resolution context as a flat object for getNestedValue const resolveContext = { params: context.params, }; // Add input fields to the context if available if (context.input) { resolveContext.input = context.input; } const value = getNestedValue(resolveContext, source); if (value !== undefined) { return value; } } } // Fall back to direct param value const directValue = context.params[key]; if (directValue !== undefined) { return directValue; } // Fall back to default return def.default; } // ============================================================================ // Parameter Style Conversion (Issue #252) // ============================================================================ /** * Convert a snake_case string to camelCase. * * @example * snakeToCamel('dry_run') => 'dryRun' * snakeToCamel('max_results') => 'maxResults' * snakeToCamel('already_camel') => 'alreadyCamel' */ function snakeToCamel(str) { return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()); } /** * Apply param style conversion to a key based on the operation schema. * Individual `mapTo` overrides take precedence over style conversion. */ function applyParamStyle(key, def, paramStyle) { // Explicit mapTo takes precedence if (def.mapTo) { return def.mapTo; } // Apply style conversion if specified if (paramStyle === 'snakeToCamel') { return snakeToCamel(key); } // Default: use original key return key; } // ============================================================================ // Parameter Mapping // ============================================================================ /** * Map input parameters to handler arguments based on param schema. * Supports multi-source parameter resolution and param style conversion. */ function mapParams(params, schema, input, paramStyle) { const mapped = {}; const context = { input, params }; for (const [key, def] of Object.entries(schema)) { const value = resolveParamValue(key, def, context); const targetKey = applyParamStyle(key, def, paramStyle); if (value !== undefined) { mapped[targetKey] = value; } } return mapped; } /** * Validate required parameters are present. * * Uses multi-source resolution to check all possible sources for each * required parameter. Provides detailed error messages showing: * - Which parameter is missing * - Which sources were checked (in order) * - What was actually provided in params * * @param params - Parameters provided by the caller * @param schema - Parameter schema defining requirements * @param operation - Operation name for error context * @param input - Optional full OperationInput for source resolution * @throws Error with debugging context if required parameter is missing */ function validateRequiredParams(params, schema, operation, input) { const context = { input, params }; for (const [key, def] of Object.entries(schema)) { if (def.required) { const value = resolveParamValue(key, def, context); if (value === undefined) { // Build detailed error message for debugging const sourcesChecked = def.sources ? `Sources checked (in order): [${def.sources.join(' → ')}] → params.${key}` : `Source: params.${key}`; const providedParams = Object.keys(params).length > 0 ? `Provided params: {${Object.keys(params).join(', ')}}` : 'No params provided'; // Issue #290: Check both snake_case and camelCase for element_type const elementTypeValue = input?.element_type || input?.elementType; const hasElementType = elementTypeValue ? `input.element_type: '${elementTypeValue}'` : 'input.element_type: undefined'; throw new Error(`Missing required parameter '${key}' for operation '${operation}'. ` + `${sourcesChecked}. ${providedParams}. ${hasElementType}`); } } } } // ============================================================================ // Type Validation (Issue #255) // ============================================================================ /** * Get a human-readable type description for error messages */ function getActualType(value) { if (value === null) return 'null'; if (Array.isArray(value)) return 'array'; return typeof value; } /** * Validate a single parameter value against its type definition * * @param value - The parameter value to validate * @param def - The parameter definition from schema * @param key - The parameter name (for error messages) * @param operation - The operation name (for error messages) * @throws Error if type validation fails * * @see Issue #255 - Runtime type validation */ function validateParamType(value, def, key, operation) { // Skip validation for undefined values (handled by required check) if (value === undefined) return; const actualType = getActualType(value); switch (def.type) { case 'string': if (typeof value !== 'string') { throw new Error(`Parameter '${key}' for operation '${operation}' must be a string, got ${actualType}`); } break; case 'number': if (typeof value !== 'number' || Number.isNaN(value)) { throw new Error(`Parameter '${key}' for operation '${operation}' must be a number, got ${actualType}`); } break; case 'boolean': if (typeof value !== 'boolean') { throw new Error(`Parameter '${key}' for operation '${operation}' must be a boolean, got ${actualType}`); } break; case 'object': if (typeof value !== 'object' || value === null || Array.isArray(value)) { throw new Error(`Parameter '${key}' for operation '${operation}' must be an object, got ${actualType}`); } break; case 'array': if (!Array.isArray(value)) { throw new Error(`Parameter '${key}' for operation '${operation}' must be an array, got ${actualType}`); } break; case 'string[]': if (!Array.isArray(value)) { throw new Error(`Parameter '${key}' for operation '${operation}' must be a string array, got ${actualType}`); } // Validate array elements are strings for (let i = 0; i < value.length; i++) { if (typeof value[i] !== 'string') { throw new Error(`Parameter '${key}[${i}]' for operation '${operation}' must be a string, got ${getActualType(value[i])}`); } } break; case 'unknown': // Allow any type - no validation needed break; } } /** * Validate all parameter types against schema definitions * * @param params - The parameters to validate * @param schema - The parameter schema * @param operation - The operation name (for error messages) * @throws Error if any type validation fails * * @see Issue #255 - Runtime type validation */ function validateParamTypes(params, schema, operation) { for (const [key, def] of Object.entries(schema)) { const value = params[key]; validateParamType(value, def, key, operation); } } // ============================================================================ // Handler Resolution // ============================================================================ /** * Type guard to check if a value is a non-null object. * Used for safe property access without type assertions. */ function isObject(value) { return typeof value === 'object' && value !== null; } /** * Type guard to check if an object has a callable method. * Provides type-safe method lookup without unsafe assertions. * * @param obj - Object to check * @param methodName - Name of method to look for * @returns True if obj has a function property with the given name */ function hasMethod(obj, methodName) { return isObject(obj) && typeof obj[methodName] === 'function'; } /** * Get handler from registry with proper error handling. * * Resolves a handler by key from the registry, providing clear error messages * when handlers are missing. Differentiates between required and optional handlers. * * @param registry - Handler registry containing all configured handlers * @param handlerKey - Key identifying which handler to retrieve * @param operation - Operation name for error context * @param optional - Whether the handler is optional (affects error messaging) * @returns The resolved handler * @throws Error if handler is not configured */ function getHandler(registry, handlerKey, operation, optional) { const handler = registry[handlerKey]; if (!handler && !optional) { throw new Error(`Handler '${handlerKey}' is required for operation '${operation}' but not configured. ` + `Ensure the handler is registered in the HandlerRegistry.`); } if (!handler && optional) { throw new Error(`${handlerKey.charAt(0).toUpperCase() + handlerKey.slice(1)} operations not available: ` + `${handlerKey} not configured. This is an optional handler that may not be available in all configurations.`); } return handler; } /** * Get method from handler with type-safe lookup. * * Uses type guards instead of unsafe type assertions to verify the handler * has the expected method. Provides detailed error messages for debugging. * * @param handler - Handler object to get method from * @param methodName - Name of the method to retrieve * @param handlerKey - Handler key for error context * @param operation - Operation name for error context * @returns Bound method ready to call * @throws Error if method is not found or is not callable */ function getMethod(handler, methodName, handlerKey, operation) { // Use type guard instead of unsafe assertion if (!hasMethod(handler, methodName)) { const availableMethods = isObject(handler) ? Object.keys(handler).filter(k => typeof handler[k] === 'function') : []; throw new Error(`Method '${methodName}' not found on handler '${handlerKey}' for operation '${operation}'. ` + `Available methods: [${availableMethods.join(', ') || 'none'}]`); } // Now TypeScript knows handler[methodName] is a function const method = handler[methodName]; return method.bind(handler); } // ============================================================================ // Argument Building // ============================================================================ /** * Params that are handled at the dispatch level and should NOT be passed to handlers. * These are cross-cutting concerns processed by SchemaDispatcher itself. * * @see Issue #202 - fields is used for response filtering, not by handlers */ const DISPATCH_ONLY_PARAMS = new Set(['fields']); /** * Filter out dispatch-only params from a params object. * These params are handled at the dispatch level (e.g., field selection) * and should not be passed to handlers. */ function filterDispatchOnlyParams(params) { const filtered = {}; for (const [key, value] of Object.entries(params)) { if (!DISPATCH_ONLY_PARAMS.has(key)) { filtered[key] = value; } } return filtered; } /** * Build arguments for handler method based on argBuilder type * * - 'single': Pass params in schema order as positional args (default) * - 'spread': Pass query + remaining params * - 'named': Pass mapped params as named object * - 'namedWithType': Like 'named' but ensures 'type' is included from resolved sources * - 'typeWithParams': Pass resolved type + full params object * * Note: Params in DISPATCH_ONLY_PARAMS are filtered out before passing to handlers. */ function buildArgs(params, schema, mappedParams, input) { const builder = schema.argBuilder ?? 'single'; switch (builder) { case 'spread': // For handlers that take (query, options) return [params.query, filterDispatchOnlyParams(params)]; case 'named': { // For handlers that take a named params object // Filter out dispatch-only params const filtered = filterDispatchOnlyParams(mappedParams); return [filtered]; } case 'namedWithType': { // Like 'named' but ensures 'elementType' and 'elementName' are available // This handles the ElementCRUD pattern where type comes from input.element_type OR params.element_type // Issue #290: element_name maps to elementName, element_type maps to elementType const result = { ...mappedParams }; // If 'elementType' wasn't in mappedParams but is available from input.element_type (or legacy elementType), add it // Issue #290: Prefer snake_case element_type, fallback to camelCase elementType for backward compat if (!result.elementType && (input?.element_type || input?.elementType)) { result.elementType = input.element_type || input.elementType; } // Set 'type' from 'elementType' for handlers that expect it if (!result.type && result.elementType) { result.type = result.elementType; } // Set 'name' from 'elementName' for handlers that expect it (Issue #290) if (!result.name && result.elementName) { result.name = result.elementName; } // Issue #278: For ensembles, merge top-level elements into metadata // LLMs often pass elements at params level, not inside metadata // Issue #290: Prefer snake_case element_type, fallback to camelCase elementType const resolvedType = result.elementType || result.type || input?.element_type || input?.elementType; // Check for ensemble type (handles both plural constant and singular form) const isEnsemble = resolvedType === ElementType.ENSEMBLE || resolvedType === 'ensemble'; if (isEnsemble) { const elements = params.elements; const currentMetadata = result.metadata; if (elements && (!currentMetadata || !currentMetadata.elements)) { result.metadata = { ...currentMetadata, elements }; } } // Issue #722: Common fields (tags, triggers) — LLMs pass at params level for any element type. { let currentMeta = result.metadata; let updatedMeta = currentMeta; if (params.tags !== undefined && Array.isArray(params.tags) && (!updatedMeta || updatedMeta.tags === undefined)) { updatedMeta = { ...updatedMeta, tags: params.tags }; } if (params.triggers !== undefined && Array.isArray(params.triggers) && (!updatedMeta || updatedMeta.triggers === undefined)) { updatedMeta = { ...updatedMeta, triggers: params.triggers }; } if (updatedMeta !== currentMeta) { result.metadata = updatedMeta; } } const isTemplate = resolvedType === ElementType.TEMPLATE || resolvedType === 'template'; if (isTemplate && Array.isArray(params.variables)) { const currentMetadata = result.metadata; if (currentMetadata?.variables === undefined) { result.metadata = { ...currentMetadata, variables: params.variables }; } } // Agent V2 fields: goal, activates, tools, systemPrompt, autonomy, resilience const isAgent = resolvedType === ElementType.AGENT || resolvedType === 'agent'; if (isAgent) { const currentMetadata = result.metadata; let updatedMetadata = currentMetadata; if (params.goal !== undefined && (!updatedMetadata || updatedMetadata.goal === undefined)) { updatedMetadata = { ...updatedMetadata, goal: params.goal }; } if (params.activates !== undefined && (!updatedMetadata || updatedMetadata.activates === undefined)) { updatedMetadata = { ...updatedMetadata, activates: params.activates }; } if (params.tools !== undefined && (!updatedMetadata || updatedMetadata.tools === undefined)) { updatedMetadata = { ...updatedMetadata, tools: params.tools }; } // Issue #725: Accept both systemPrompt and system_prompt (LLMs commonly use snake_case) const systemPromptValue = params.systemPrompt ?? params.system_prompt; if (systemPromptValue !== undefined && (!updatedMetadata || updatedMetadata.systemPrompt === undefined)) { updatedMetadata = { ...updatedMetadata, systemPrompt: systemPromptValue }; } if (params.autonomy !== undefined && (!updatedMetadata || updatedMetadata.autonomy === undefined)) { updatedMetadata = { ...updatedMetadata, autonomy: params.autonomy }; } // Issue #722: resilience was missing from the agent V2 merge if (params.resilience !== undefined && (!updatedMetadata || updatedMetadata.resilience === undefined)) { updatedMetadata = { ...updatedMetadata, resilience: params.resilience }; } if (updatedMetadata !== currentMetadata) { result.metadata = updatedMetadata; } } // Issue #666: LLMs often pass gatekeeper at top-level params, not inside metadata. // Merge it into metadata for all element types. if (params.gatekeeper && typeof params.gatekeeper === 'object') { const currentMeta = result.metadata; if (!currentMeta || !currentMeta.gatekeeper) { console.debug(`[SchemaDispatcher] Merging top-level gatekeeper into metadata for element '${result.name ?? 'unknown'}'`); result.metadata = { ...currentMeta, gatekeeper: params.gatekeeper }; } } // Filter out dispatch-only params before returning return [filterDispatchOnlyParams(result)]; } case 'typeWithParams': { // Pass resolved type + full params object (minus dispatch-only params) // Used for operations like list_elements that need (type, paginationParams) // Issue #290: element_type maps to elementType, with backward compat for type // Prefer snake_case element_type, fallback to camelCase elementType const resolvedType = mappedParams.elementType ?? mappedParams.type ?? input?.element_type ?? input?.elementType; return [resolvedType, filterDispatchOnlyParams(params)]; } case 'single': default: { // Extract params in schema order, using resolved values if (!schema.params || Object.keys(schema.params).length === 0) { return []; } // For simple handlers, pass resolved params in schema order // Skip dispatch-only params (like 'fields') that are handled by SchemaDispatcher const args = []; for (const key of Object.keys(schema.params)) { // Skip dispatch-level params that shouldn't be passed to handlers if (DISPATCH_ONLY_PARAMS.has(key)) { continue; } // Use mapped value if available (handles source resolution) const targetKey = schema.params[key].mapTo ?? key; args.push(mappedParams[targetKey]); } return args; } } } // ============================================================================ // Special Operation Handlers // ============================================================================ /** * Handle introspection operation (uses IntrospectionResolver directly) */ async function handleIntrospection(params) { return IntrospectionResolver.resolve(params); } /** * Handle get_capabilities operation (uses IntrospectionResolver directly) * @see Issue #1760 - get_capabilities operation */ async function handleCapabilities(params) { return IntrospectionResolver.getCapabilities(params); } /** * Handle build info operation (formats output) */ async function handleBuildInfo(registry) { const service = registry.buildInfoService; if (!service) { throw new Error('BuildInfo operations not available: BuildInfoService not configured'); } const info = await service.getBuildInfo(); return { structuredContent: info, content: [{ type: 'text', text: service.formatBuildInfo(info), }], }; } /** * Handle cache budget report operation (formats output as markdown table) */ function handleCacheBudgetReport(registry) { const budget = registry.cacheMemoryBudget; if (!budget) { throw new Error('Cache budget not available: CacheMemoryBudget not configured'); } const report = budget.getReport(); const lines = [ '# Cache Memory Budget Report', '', `**Budget:** ${report.budgetMB} MB`, `**Used:** ${report.totalMemoryMB} MB (${report.utilizationPercent}%)`, `**Registered Caches:** ${report.caches.length}`, '', ]; if (report.caches.length > 0) { lines.push('| Cache | Entries | Memory (MB) | Hit Rate | Last Activity |'); lines.push('|-------|---------|-------------|----------|---------------|'); for (const c of report.caches) { const activity = c.lastActivityMs > 0 ? `${((Date.now() - c.lastActivityMs) / 1000).toFixed(0)}s ago` : 'never'; lines.push(`| ${c.name} | ${c.entries} | ${c.memoryMB} | ${(c.hitRate * 100).toFixed(1)}% | ${activity} |`); } } else { lines.push('_No caches registered._'); } return { content: [{ type: 'text', text: lines.join('\n'), }], }; } /** * Handle element export operation. * Returns an ExportPackage that can be used for import operations. */ async function handleExportElement(mappedParams, registry) { const handler = registry.elementCRUD; if (!handler) { throw new Error('ElementCRUD operations not available: handler not configured'); } // Get the exportable data from the element // Issue #290: Use mapped names (elementName, elementType) from schema mapTo const name = mappedParams.elementName; const type = mappedParams.elementType; const format = mappedParams.format || 'json'; // Use the element query service to get element details const elementDetails = await handler.getElementDetails(name, type); if (!elementDetails) { throw new Error(`Element not found: ${type}/${name}`); } // Build export package (matches MCPAQLHandler.handleExportElement format) const exportPackage = { exportVersion: '1.0', exportedAt: new Date().toISOString(), elementType: type, elementName: name, format, data: '', }; // Serialize to requested format if (format === 'yaml') { exportPackage.data = yaml.dump(elementDetails, { indent: 2, lineWidth: 120, noRefs: true, }); } else { exportPackage.data = JSON.stringify(elementDetails, null, 2); } return exportPackage; } /** * Handle element import operation. * Parses export package and delegates to ElementCRUD. * * Supports two export package formats: * 1. MCPAQLHandler format: { exportVersion, elementType, elementName, format, data } * 2. Legacy format: { version, element: { type, name, ... } } */ async function handleImportElement(mappedParams, registry) { const handler = registry.elementCRUD; if (!handler) { throw new Error('ElementCRUD operations not available: handler not configured'); } const data = mappedParams.data; const overwrite = mappedParams.overwrite; // Parse the export package (accept string or already-parsed object) let exportPackage; if (typeof data === 'string') { try { exportPackage = JSON.parse(data); } catch { throw new Error('Invalid export package: not valid JSON'); } } else if (typeof data === 'object' && data !== null) { exportPackage = data; } else { throw new Error('Invalid export package: data must be a string or object'); } // Determine element details based on package format let elementType; let elementName; let elementData; if (exportPackage.elementType && exportPackage.data) { // MCPAQLHandler format: { exportVersion, elementType, elementName, format, data } elementType = exportPackage.elementType; elementName = exportPackage.elementName; const format = exportPackage.format; // Parse the nested data field based on format const nestedData = exportPackage.data; if (typeof nestedData === 'string') { try { if (format === 'yaml') { // Use SecureYamlParser.parseRawYaml for safe YAML parsing (CORE_SCHEMA, size limits) elementData = SecureYamlParser.parseRawYaml(nestedData); } else { elementData = JSON.parse(nestedData); } } catch { throw new Error(`Invalid export package: data field is not valid ${format || 'JSON'}`); } } else if (typeof nestedData === 'object' && nestedData !== null) { elementData = nestedData; } else { throw new Error('Invalid export package: data field must be string or object'); } } else if (exportPackage.element) { // Legacy format: { version, element: { type, name, ... } } const element = exportPackage.element; elementType = element.type; elementName = element.name; elementData = element; } else { throw new Error('Invalid export package: missing element data'); } // Check if element already exists when overwrite is false if (!overwrite) { try { const existing = await handler.getElementDetails(elementName, elementType); if (existing) { throw new Error(`Element '${elementName}' already exists. Use overwrite: true to replace.`); } } catch (e) { // Element doesn't exist, which is what we want if (!(e instanceof Error) || !e.message.includes('not found')) { throw e; } } } // Create the element return handler.createElement({ name: elementData.name || elementName, type: elementType, description: elementData.description || '', content: elementData.content, instructions: elementData.instructions, metadata: elementData.metadata, }); } // ============================================================================ // Main Dispatcher // ============================================================================ /** * SchemaDispatcher - Dispatch operations using schema definitions * * This class provides the core dispatch logic for schema-driven operations. * It replaces the manual dispatch methods in MCPAQLHandler for operations * that are defined in the schema. */ export class SchemaDispatcher { /** * Check if an operation can be handled by schema dispatch */ static canDispatch(operation) { return isSchemaOperation(operation); } /** * Dispatch an operation using its schema definition * * @param operation - Operation name (e.g., 'browse_collection') * @param params - Operation parameters * @param registry - Handler registry with all configured handlers * @param input - Optional full OperationInput for source resolution * @returns Promise resolving to operation result * @throws Error if operation not found, handler missing, or params invalid */ static async dispatch(operation, params, registry, input) { // Get schema definition const schema = getOperationSchema(operation); if (!schema) { throw new Error(`No schema definition found for operation '${operation}'`); } // Apply normalizer if schema specifies one (Issue #243) // Normalizers transform raw input params before validation and dispatch if (schema.normalizer) { const normalizer = NormalizerRegistry.get(schema.normalizer); if (!normalizer) { throw new Error(`Normalizer '${schema.normalizer}' not found for operation '${operation}'. ` + `Registered normalizers: [${NormalizerRegistry.list().join(', ') || 'none'}]`); } // Build normalizer context for debugging and future extensibility const normalizerContext = { operation, endpoint: schema.endpoint, handler: schema.handler, method: schema.method, elementType: input?.elementType, }; // Normalize params - this may transform, validate, or enrich parameters const result = normalizer.normalize(params, normalizerContext); if (!result.success) { throw new Error(result.error); } // Use normalized params for the rest of dispatch params = result.params; } // Determine if we need full input context for source resolution const needsInput = schema.needsFullInput && input; // Handle special operations if (schema.method === '__introspect__') { return handleIntrospection(params); } if (schema.method === '__buildInfo__') { return handleBuildInfo(registry); } if (schema.method === '__cacheBudget__') { return handleCacheBudgetReport(registry); } if (schema.method === '__capabilities__') { return handleCapabilities(params); } // Map params according to schema (with input context and param style) // For operations with normalizers, the normalized params ARE the mapped params // (the normalizer already transforms input to handler-ready format) const mappedParams = schema.normalizer ? params // Normalizer already produced the correct output format : schema.params ? mapParams(params, schema.params, needsInput ? input : undefined, schema.paramStyle) : {}; // Validate params: required params first, then type validation (Issue #255) // Skip validation for normalized operations (normalizer handles validation) if (schema.params && !schema.normalizer) { validateRequiredParams(params, schema.params, operation, needsInput ? input : undefined); validateParamTypes(params, schema.params, operation); } // Handle special ElementCRUD operations if (schema.method === '__export__') { return handleExportElement(mappedParams, registry); } if (schema.method === '__import__') { return handleImportElement(mappedParams, registry); } // Get handler from registry const handler = getHandler(registry, schema.handler, operation, schema.optional ?? false); // Get method from handler const method = getMethod(handler, schema.method, schema.handler, operation); // Build arguments based on argBuilder type (with input context if needed) const args = buildArgs(params, schema, mappedParams, needsInput ? input : undefined); // Call the handler method // Note: Field selection is applied at MCPAQLHandler level (Issue #202) return method(...args); } } // ============================================================================ // Exports // ============================================================================ export { isSchemaOperation, getOperationSchema }; // Test exports for security boundary verification export const __test__ = { getNestedValue, SAFE_PATH_PATTERN, FORBIDDEN_PATHS, }; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU2NoZW1hRGlzcGF0Y2hlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9oYW5kbGVycy9tY3AtYXFsL1NjaGVtYURpc3BhdGNoZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBZ0NHO0FBRUgsT0FBTyxJQUFJLE1BQU0sU0FBUyxDQUFDO0FBQzNCLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLG9DQUFvQyxDQUFDO0FBQ3RFLE9BQU8sRUFDTCxrQkFBa0IsRUFDbEIsaUJBQWlCLEdBS2xCLE1BQU0sc0JBQXNCLENBQUM7QUFDOUIsT0FBTyxFQUFFLHFCQUFxQixFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFDbkUsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sd0JBQXdCLENBQUM7QUFJNUQsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLDBCQUEwQixDQUFDO0FBQ3ZELDJFQUEyRTtBQUUzRSwrRUFBK0U7QUFDL0UsbUNBQW1DO0FBQ25DLCtFQUErRTtBQUUvRTs7O0dBR0c7QUFDSCxNQUFNLGlCQUFpQixHQUFHLDZCQUE2QixDQUFDO0FBQ3hELE1BQU0sZUFBZSxHQUFHLElBQUksR0FBRyxDQUFDLENBQUMsV0FBVyxFQUFFLGFBQWEsRUFBRSxXQUFXLENBQUMsQ0FBQyxDQUFDO0FBRTNFOzs7OztHQUtHO0FBQ0gsU0FBUyxjQUFjLENBQUMsR0FBNEIsRUFBRSxJQUFZO0lBQ2hFLG9EQUFvRDtJQUNwRCxJQUFJLENBQUMsaUJBQWlCLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUM7UUFDbEMsTUFBTSxJQUFJLEtBQUssQ0FBQyxpQ0FBaUMsSUFBSSxFQUFFLENBQUMsQ0FBQztJQUMzRCxDQUFDO0lBRUQsTUFBTSxLQUFLLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUU5Qix5Q0FBeUM7SUFDekMsS0FBSyxNQUFNLElBQUksSUFBSSxLQUFLLEVBQUUsQ0FBQztRQUN6QixJQUFJLGVBQWUsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQztZQUM5QixNQUFNLElBQUksS0FBSyxDQUFDLG9DQUFvQyxJQUFJLEVBQUUsQ0FBQyxDQUFDO1FBQzlELENBQUM7SUFDSCxDQUFDO0lBRUQsSUFBSSxPQUFPLEdBQVksR0FBRyxDQUFDO0lBRTNCLEtBQUssTUFBTSxJQUFJLElBQUksS0FBSyxFQUFFLENBQUM7UUFDekIsSUFBSSxPQUFPLEtBQUssSUFBSSxJQUFJLE9BQU8sS0FBSyxTQUFTLEVBQUUsQ0FBQztZQUM5QyxPQUFPLFNBQVMsQ0FBQztRQUNuQixDQUFDO1FBQ0QsT0FBTyxHQUFJLE9BQW1DLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDdkQsQ0FBQztJQUVELE9BQU8sT0FBTyxDQUFDO0FBQ2pCLENBQUM7QUFFRDs7Ozs7Ozs7Ozs7O0dBWUc7QUFDSCxTQUFTLGlCQUFpQixDQUN4QixHQUFXLEVBQ1gsR0FBYSxFQUNiLE9BR0M7SUFFRCw4Q0FBOEM7SUFDOUMsSUFBSSxHQUFHLENBQUMsT0FBTyxJQUFJLEdBQUcsQ0FBQyxPQUFPLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRSxDQUFDO1FBQzFDLEtBQUssTUFBTSxNQUFNLElBQUksR0FBRyxDQUFDLE9BQU8sRUFBRSxDQUFDO1lBQ2pDLG1FQUFtRTtZQUNuRSxNQUFNLGNBQWMsR0FBNEI7Z0JBQzlDLE1BQU0sRUFBRSxPQUFPLENBQUMsTUFBTTthQUN2QixDQUFDO1lBRUYsK0NBQStDO1lBQy9DLElBQUksT0FBTyxDQUFDLEtBQUssRUFBRSxDQUFDO2dCQUNsQixjQUFjLENBQUMsS0FBSyxHQUFHLE9BQU8sQ0FBQyxLQUFLLENBQUM7WUFDdkMsQ0FBQztZQUVELE1BQU0sS0FBSyxHQUFHLGNBQWMsQ0FBQyxjQUFjLEVBQUUsTUFBTSxDQUFDLENBQUM7WUFDckQsSUFBSSxLQUFLLEtBQUssU0FBUyxFQUFFLENBQUM7Z0JBQ3hCLE9BQU8sS0FBSyxDQUFDO1lBQ2YsQ0FBQztRQUNILENBQUM7SUFDSCxDQUFDO0lBRUQsa0NBQWtDO0lBQ2xDLE1BQU0sV0FBVyxHQUFHLE9BQU8sQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDeEMsSUFBSSxXQUFXLEtBQUssU0FBUyxFQUFFLENBQUM7UUFDOUIsT0FBTyxXQUFXLENBQUM7SUFDckIsQ0FBQztJQUVELHVCQUF1QjtJQUN2QixPQUFPLEdBQUcsQ0FBQyxPQUFPLENBQUM7QUFDckIsQ0FBQztBQUVELCtFQUErRTtBQUMvRSwwQ0FBMEM7QUFDMUMsK0VBQStFO0FBRS9FOzs7Ozs7O0dBT0c7QUFDSCxTQUFTLFlBQVksQ0FBQyxHQUFXO0lBQy9CLE9BQU8sR0FBRyxDQUFDLE9BQU8sQ0FBQyxXQUFXLEVBQUUsQ0FBQyxDQUFDLEVBQUUsTUFBTSxFQUFFLEVBQUUsQ0FBQyxNQUFNLENBQUMsV0FBVyxFQUFFLENBQUMsQ0FBQztBQUN2RSxDQUFDO0FBRUQ7OztHQUdHO0FBQ0gsU0FBUyxlQUFlLENBQ3RCLEdBQVcsRUFDWCxHQUFhLEVBQ2IsVUFBMkI7SUFFM0Isa0NBQWtDO0lBQ2xDLElBQUksR0FBRyxDQUFDLEtBQUssRUFBRSxDQUFDO1FBQ2QsT0FBTyxHQUFHLENBQUMsS0FBSyxDQUFDO0lBQ25CLENBQUM7SUFFRCxzQ0FBc0M7SUFDdEMsSUFBSSxVQUFVLEtBQUssY0FBYyxFQUFFLENBQUM7UUFDbEMsT0FBTyxZQUFZLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDM0IsQ0FBQztJQUVELDRCQUE0QjtJQUM1QixPQUFPLEdBQUcsQ0FBQztBQUNiLENBQUM7QUFFRCwrRUFBK0U7QUFDL0Usb0JBQW9CO0FBQ3BCLCtFQUErRTtBQUUvRTs7O0dBR0c7QUFDSCxTQUFTLFNBQVMsQ0FDaEIsTUFBK0IsRUFDL0IsTUFBbUIsRUFDbkIsS0FBc0IsRUFDdEIsVUFBMkI7SUFFM0IsTUFBTSxNQUFNLEdBQTRCLEVBQUUsQ0FBQztJQUMzQyxNQUFNLE9BQU8sR0FBRyxFQUFFLEtBQUssRUFBRSxNQUFNLEVBQUUsQ0FBQztJQUVsQyxLQUFLLE1BQU0sQ0FBQyxHQUFHLEVBQUUsR0FBRyxDQUFDLElBQUksTUFBTSxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDO1FBQ2hELE1BQU0sS0FBSyxHQUFHLGlCQUFpQixDQUFDLEdBQUcsRUFBRSxHQUFHLEVBQUUsT0FBTyxDQUFDLENBQUM7UUFDbkQsTUFBTSxTQUFTLEdBQUcsZUFBZSxDQUFDLEdBQUcsRUFBRSxHQUFHLEVBQUUsVUFBVSxDQUFDLENBQUM7UUFFeEQsSUFBSSxLQUFLLEtBQUssU0FBUyxFQUFFLENBQUM7WUFDeEIsTUFBTSxDQUFDLFNBQVMsQ0FBQyxHQUFHLEtBQUssQ0FBQztRQUM1QixDQUFDO0lBQ0gsQ0FBQztJQUVELE9BQU8sTUFBTSxDQUFDO0FBQ2hCLENBQUM7QUFFRDs7Ozs7Ozs7Ozs7Ozs7R0FjRztBQUNILFNBQVMsc0JBQXNCLENBQzdCLE1BQStCLEVBQy9CLE1BQW1CLEVBQ25CLFNBQWlCLEVBQ2pCLEtBQXNCO0lBRXRCLE1BQU0sT0FBTyxHQUFHLEVBQUUsS0FBSyxFQUFFLE1BQU0sRUFBRSxDQUFDO0lBRWxDLEtBQUssTUFBTSxDQUFDLEdBQUcsRUFBRSxHQUFHLENBQUMsSUFBSSxNQUFNLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUM7UUFDaEQsSUFBSSxHQUFHLENBQUMsUUFBUSxFQUFFLENBQUM7WUFDakIsTUFBTSxLQUFLLEdBQUcsaUJBQWlCLENBQUMsR0FBRyxFQUFFLEdBQUcsRUFBRSxPQUFPLENBQUMsQ0FBQztZQUNuRCxJQUFJLEtBQUssS0FBSyxTQUFTLEVBQUUsQ0FBQztnQkFDeEIsNkNBQTZDO2dCQUM3QyxNQUFNLGNBQWMsR0FBRyxHQUFHLENBQUMsT0FBTztvQkFDaEMsQ0FBQyxDQUFDLGdDQUFnQyxHQUFHLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsY0FBYyxHQUFHLEVBQUU7b0JBQzVFLENBQUMsQ0FBQyxrQkFBa0IsR0FBRyxFQUFFLENBQUM7Z0JBRTVCLE1BQU0sY0FBYyxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUMsTUFBTSxHQUFHLENBQUM7b0JBQ25ELENBQUMsQ0FBQyxxQkFBcUIsTUFBTSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEdBQUc7b0JBQ3hELENBQUMsQ0FBQyxvQkFBb0IsQ0FBQztnQkFFekIsbUVBQW1FO2dCQUNuRSxNQUFNLGdCQUFnQixHQUFHLEtBQUssRUFBRSxZQUFZLElBQUksS0FBSyxFQUFFLFdBQVcsQ0FBQztnQkFDbkUsTUFBTSxjQUFjLEdBQUcsZ0JBQWdCO29CQUNyQyxDQUFDLENBQUMsd0JBQXdCLGdCQUFnQixHQUFHO29CQUM3QyxDQUFDLENBQUMsK0JBQStCLENBQUM7Z0JBRXBDLE1BQU0sSUFBSSxLQUFLLENBQ2IsK0JBQStCLEdBQUcsb0JBQW9CLFNBQVMsS0FBSztvQkFDcEUsR0FBRyxjQUFjLEtBQUssY0FBYyxLQUFLLGNBQWMsRUFBRSxDQUMxRCxDQUFDO1lBQ0osQ0FBQztRQUNILENBQUM7SUFDSCxDQUFDO0FBQ0gsQ0FBQztBQUVELCtFQUErRTtBQUMvRSwrQkFBK0I7QUFDL0IsK0VBQStFO0FBRS9FOztHQUVHO0FBQ0gsU0FBUyxhQUFhLENBQUMsS0FBYztJQUNuQyxJQUFJLEtBQUssS0FBSyxJQUFJO1FBQUUsT0FBTyxNQUFNLENBQUM7SUFDbEMsSUFBSSxLQUFLLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQztRQUFFLE9BQU8sT0FBTyxDQUFDO0lBQ3pDLE9BQU8sT0FBTyxLQUFLLENBQUM7QUFDdEIsQ0FBQztBQUVEOzs7Ozs7Ozs7O0dBVUc7QUFDSCxTQUFTLGlCQUFpQixDQUN4QixLQUFjLEVBQ2QsR0FBYSxFQUNiLEdBQVcsRUFDWCxTQUFpQjtJQUVqQixtRUFBbUU7SUFDbkUsSUFBSSxLQUFLLEtBQUssU0FBUztRQUFFLE9BQU87SUFFaEMsTUFBTSxVQUFVLEdBQUcsYUFBYSxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBRXhDLFFBQVEsR0FBRyxDQUFDLElBQUksRUFBRSxDQUFDO1FBQ2pCLEtBQUssUUFBUTtZQUNYLElBQUksT0FBTyxLQUFLLEtBQUssUUFBUSxFQUFFLENBQUM7Z0JBQzlCLE1BQU0sSUFBSSxLQUFLLENBQ2IsY0FBYyxHQUFHLG9CQUFvQixTQUFTLDJCQUEyQixVQUFVLEVBQUUsQ0FDdEYsQ0FBQztZQUNKLENBQUM7WUFDRCxNQUFNO1FBRVIsS0FBSyxRQUFRO1lBQ1gsSUFBSSxPQUFPLEtBQUssS0FBSyxRQUFRLElBQUksTUFBTSxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDO2dCQUNyRCxNQUFNLElBQUksS0FBSyxDQUNiLGNBQWMsR0FBRyxvQkFBb0IsU0FBUywyQkFBMkIsVUFBVSxFQUFFLENBQ3RGLENBQUM7WUFDSixDQUFDO1lBQ0QsTUFBTTtRQUVSLEtBQUssU0FBUztZQUNaLElBQUksT0FBTyxLQUFLLEtBQUssU0FBUyxFQUFFLENBQUM7Z0JBQy9CLE1BQU0sSUFBSSxLQUFLLENBQ2IsY0FBYyxHQUFHLG9CQUFvQixTQUFTLDRCQUE0QixVQUFVLEVBQUUsQ0FDdkYsQ0FBQztZQUNKLENBQUM7WUFDRCxNQUFNO1FBRVIsS0FBSyxRQUFRO1lBQ1gsSUFBSSxPQUFPLEtBQUssS0FBSyxRQUFRLElBQUksS0FBSyxLQUFLLElBQUksSUFBSSxLQUFLLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUM7Z0JBQ3hFLE1BQU0sSUFBSSxLQUFLLENBQ2IsY0FBYyxHQUFHLG9CQUFvQixTQUFTLDRCQUE0QixVQUFVLEVBQUUsQ0FDdkYsQ0FBQztZQUNKLENBQUM7WUFDRCxNQUFNO1FBRVIsS0FBSyxPQUFPO1lBQ1YsSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQztnQkFDMUIsTUFBTSxJQUFJLEtBQUssQ0FDYixjQUFjLEdBQUcsb0JBQW9CLFNBQVMsMkJBQTJCLFVBQVUsRUFBRSxDQUN0RixDQUFDO1lBQ0osQ0FBQztZQUNELE1BQU07UUFFUixLQUFLLFVBQVU7WUFDYixJQUFJLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDO2dCQUMxQixNQUFNLElBQUksS0FBSyxDQUNiLGNBQWMsR0FBRyxvQkFBb0IsU0FBUyxpQ0FBaUMsVUFBVSxFQUFFLENBQzVGLENBQUM7WUFDSixDQUFDO1lBQ0Qsc0NBQXNDO1lBQ3RDLEtBQUssSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsR0FBRyxLQUFLLENBQUMsTUFBTSxFQUFFLENBQUMsRUFBRSxFQUFFLENBQUM7Z0JBQ3RDLElBQUksT0FBTyxLQUFLLENBQUMsQ0FBQyxDQUFDLEtBQUssUUFBUSxFQUFFLENBQUM7b0JBQ2pDLE1BQU0sSUFBSSxLQUFLLENBQ2IsY0FBYyxHQUFHLElBQUksQ0FBQyxxQkFBcUIsU0FBUywyQkFBMkIsYUFBYSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQ3pHLENBQUM7Z0JBQ0osQ0FBQztZQUNILENBQUM7WUFDRCxNQUFNO1FBRVIsS0FBSyxTQUFTO1lBQ1osd0NBQXdDO1lBQ3hDLE1BQU07SUFDVixDQUFDO0FBQ0gsQ0FBQztBQUVEOzs7Ozs7Ozs7R0FTRztBQUNILFNBQVMsa0JBQWtCLENBQ3pCLE1BQStCLEVBQy9CLE1BQW1CLEVBQ25CLFNBQWlCO0lBRWpCLEtBQUssTUFBTSxDQUFDLEdBQUcsRUFBRSxHQUFHLENBQUMsSUFBSSxNQUFNLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUM7UUFDaEQsTUFBTSxLQUFLLEdBQUcsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQzFCLGlCQUFpQixDQUFDLEtBQUssRUFBRSxHQUFHLEVBQUUsR0FBRyxFQUFFLFNBQVMsQ0FBQyxDQUFDO0lBQ2hELENBQUM7QUFDSCxDQUFDO0FBRUQsK0VBQStFO0FBQy9FLHFCQUFxQjtBQUNyQiwrRUFBK0U7QUFFL0U7OztHQUdHO0FBQ0gsU0FBUyxRQUFRLENBQUMsS0FBYztJQUM5QixPQUFPLE9BQU8sS0FBSyxLQUFLLFFBQVEsSUFBSSxLQUFLLEtBQUssSUFBSSxDQUFDO0FBQ3JELENBQUM7QUFFRDs7Ozs7OztHQU9HO0FBQ0gsU0FBUyxTQUFTLENBQ2hCLEdBQVksRUFDWixVQUFrQjtJQUVsQixPQUFPLFFBQVEsQ0FBQyxHQUFHLENBQUMsSUFBSSxPQUFPLEdBQUcsQ0FBQyxVQUFVLENBQUMsS0FBSyxVQUFVLENBQUM7QUFDaEUsQ0FBQztBQUVEOzs7Ozs7Ozs7Ozs7R0FZRztBQUNILFNBQVMsVUFBVSxDQUNqQixRQUF5QixFQUN6QixVQUFzQixFQUN0QixTQUFpQixFQUNqQixRQUFpQjtJQUVqQixNQUFNLE9BQU8sR0FBRyxRQUFRLENBQUMsVUFBbUMsQ0FBQyxDQUFDO0lBRTlELElBQUksQ0FBQyxPQUFPLElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQztRQUMxQixNQUFNLElBQUksS0FBSyxDQUNiLFlBQVksVUFBVSxnQ0FBZ0MsU0FBUyx3QkFBd0I7WUFDdkYsMERBQTBELENBQzNELENBQUM7SUFDSixDQUFDO0lBRUQsSUFBSSxDQUFDLE9BQU8sSUFBSSxRQUFRLEVBQUUsQ0FBQztRQUN6QixNQUFNLElBQUksS0FBSyxDQUNiLEdBQUcsVUFBVSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQyxXQUFXLEVBQUUsR0FBRyxVQUFVLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyw2QkFBNkI7WUFDeEYsR0FBRyxVQUFVLCtGQUErRixDQUM3RyxDQUFDO0lBQ0osQ0FBQztJQUVELE9BQU8sT0FBTyxDQUFDO0FBQ2pCLENBQUM7QUFFRDs7Ozs7Ozs7Ozs7O0dBWUc7QUFDSCxTQUFTLFNBQVMsQ0FDaEIsT0FBZ0IsRUFDaEIsVUFBa0IsRUFDbEIsVUFBa0IsRUFDbEIsU0FBaUI7SUFFakIsNkNBQTZDO0lBQzdDLElBQUksQ0FBQyxTQUFTLENBQUMsT0FBTyxFQUFFLFVBQVUsQ0FBQyxFQUFFLENBQUM7UUFDcEMsTUFBTSxnQkFBZ0IsR0FBRyxRQUFRLENBQUMsT0FBTyxDQUFDO1lBQ3hDLENBQUMsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLE9BQU8sT0FBTyxDQUFDLENBQUMsQ0FBQyxLQUFLLFVBQVUsQ0FBQztZQUNwRSxDQUFDLENBQUMsRUFBRSxDQUFDO1FBRVAsTUFBTSxJQUFJLEtBQUssQ0FDYixXQUFXLFVBQVUsMkJBQTJCLFVBQVUsb0JBQW9CLFNBQVMsS0FBSztZQUM1Rix1QkFBdUIsZ0JBQWdCLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLE1BQU0sR0FBRyxDQUNoRSxDQUFDO0lBQ0osQ0FBQztJQUVELHlEQUF5RDtJQUN6RCxNQUFNLE1BQU0sR0FBRyxPQUFPLENBQUMsVUFBVSxDQUFDLENBQUM7SUFDbkMsT0FBTyxNQUFNLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBNkMsQ0FBQztBQUMxRSxDQUFDO0FBRUQsK0VBQStFO0FBQy9FLG9CQUFvQjtBQUNwQiwrRUFBK0U7QUFFL0U7Ozs7O0dBS0c7QUFDSCxNQUFNLG9CQUFvQixHQUFHLElBQUksR0FBRyxDQUFDLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQztBQUVqRDs7OztHQUlHO0FBQ0gsU0FBUyx3QkFBd0IsQ0FDL0IsTUFBK0I7SUFFL0IsTUFBTSxRQUFRLEdBQTRCLEVBQUUsQ0FBQztJQUM3QyxLQUFLLE1BQU0sQ0FBQyxHQUFHLEVBQUUsS0FBSyxDQUFDLElBQUksTUFBTSxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDO1FBQ2xELElBQUksQ0FBQyxvQkFBb0IsQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQztZQUNuQyxRQUFRLENBQUMsR0FBRyxDQUFDLEdBQUcsS0FBSyxDQUFDO1FBQ3hCLENBQUM7SUFDSCxDQUFDO0lBQ0QsT0FBTyxRQUFRLENBQUM7QUFDbEIsQ0FBQztBQUVEOzs7Ozs7Ozs7O0dBVUc7QUFDSCxTQUFTLFNBQVMsQ0FDaEIsTUFBK0IsRUFDL0IsTUFBb0IsRUFDcEIsWUFBcUMsRUFDckMsS0FBc0I7SUFFdEIsTUFBTSxPQUFPLEdBQUcsTUFBTSxDQUFDLFVBQVUsSUFBSSxRQUFRLENBQUM7SUFFOUMsUUFBUSxPQUFPLEVBQUUsQ0FBQztRQUNoQixLQUFLLFFBQVE7WUFDWCwwQ0FBMEM7WUFDMUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUUsd0JBQXdCLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQztRQUUxRCxLQUFLLE9BQU8sQ0FBQyxDQUFDLENBQUM7WUFDYiwrQ0FBK0M7WUFDL0Msa0NBQWtDO1lBQ2xDLE1BQU0sUUFBUSxHQUFHLHdCQUF3QixDQUFDLFlBQVksQ0FBQyxDQUFDO1lBQ3hELE9BQU8sQ0FBQyxRQUFRLENBQUMsQ0FBQztRQUNwQixDQUFDO1FBRUQsS0FBSyxlQUFlLENBQUMsQ0FBQyxDQUFDO1lBQ3JCLHlFQUF5RTtZQUN6RSx1R0FBdUc7WUFDdkcsaUZBQWlGO1lBQ2pGLE1BQU0sTUFBTSxHQUFHLEVBQUUsR0FBRyxZQUFZLEVBQUUsQ0FBQztZQUVuQyxtSEFBbUg7WUFDbkgsb0dBQW9HO1lBQ3BHLElBQUksQ0FBQyxNQUFNLENBQUMsV0FBVyxJQUFJLENBQUMsS0FBSyxFQUFFLFlBQVksSUFBSSxLQUFLLEVBQUUsV0FBVyxDQUFDLEVBQUUsQ0FBQztnQkFDdkUsTUFBTSxDQUFDLFdBQVcsR0FBRyxLQUFLLENBQUMsWUFBWSxJQUFJLEtBQUssQ0FBQyxXQUFXLENBQUM7WUFDL0QsQ0FBQztZQUNELDREQUE0RDtZQUM1RCxJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksSUFBSSxNQUFNLENBQUMsV0FBVyxFQUFFLENBQUM7Z0JBQ3ZDLE1BQU0sQ0FBQyxJQUFJLEdBQUcsTUFBTSxDQUFDLFdBQVcsQ0FBQztZQUNuQyxDQUFDO1lBQ0QseUVBQXlFO1lBQ3pFLElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxJQUFJLE1BQU0sQ0FBQyxXQUFXLEVBQUUsQ0FBQztnQkFDdkMsTUFBTSxDQUFDLElBQUksR0FBRyxNQUFNLENBQUMsV0FBVyxDQUFDO1lBQ25DLENBQUM7WUFFRCxvRUFBb0U7WUFDcEUsZ0VBQWdFO1lBQ2hFLGdGQUFnRjtZQUNoRixNQUFNLFlBQVksR0FBRyxNQUFNLENBQUMsV0FBVyxJQUFJLE1BQU0sQ0FBQyxJQUFJLElBQUksS0FBSyxFQUFFLFlBQVksSUFBSSxLQUFLLEVBQUUsV0FBVyxDQUFDO1lBQ3BHLDJFQUEyRTtZQUMzRSxNQUFNLFVBQVUsR0FBRyxZQUFZLEtBQUssV0FBVyxDQUFDLFFBQVEsSUFBSSxZQUFZLEtBQUssVUFBVSxDQUFDO1lBQ3hGLElBQUksVUFBVSxFQUFFLENBQUM7Z0JBQ2YsTUFBTSxRQUFRLEdBQUcsTUFBTSxDQUFDLFFBQVEsQ0FBQztnQkFDakMsTUFBTSxlQUFlLEdBQUcsTUFBTSxDQUFDLFFBQStDLENBQUM7Z0JBQy9FLElBQUksUUFBUSxJQUFJLENBQUMsQ0FBQyxlQUFlLElBQUksQ0FBQyxlQUFlLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQztvQkFDaEUsTUFBTSxDQUFDLFFBQVEsR0FBRyxFQUFFLEdBQUcsZUFBZSxFQUFFLFFBQVEsRUFBRSxDQUFDO2dCQUNyRCxDQUFDO1lBQ0gsQ0FBQztZQUVELCtGQUErRjtZQUMvRixDQUFDO2dCQUNDLElBQUksV0FBV