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.

834 lines 124 kB
#!/usr/bin/env node // Load environment variables from .env files BEFORE anything else // This ensures .env.local and .env are loaded for all modules import { env } from './config/env.js'; import * as path from 'path'; import { realpathSync } from 'node:fs'; import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { ErrorHandler } from "./utils/ErrorHandler.js"; import { logger } from "./utils/logger.js"; import { DollhouseContainer } from "./di/Container.js"; import { ElementType } from "./portfolio/PortfolioManager.js"; import { PACKAGE_VERSION } from "./generated/version.js"; import { ConfigManager } from "./config/ConfigManager.js"; import { FileOperationsService } from "./services/FileOperationsService.js"; import { FileLockManager } from "./security/fileLockManager.js"; import * as os from "os"; // Defensive error handling for npx/CLI execution process.on('uncaughtException', (error) => { logger.error('Unhandled exception detected', { error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined }); console.error('[DollhouseMCP] Server startup failed'); process.exit(1); }); process.on('unhandledRejection', (reason, promise) => { logger.error('Unhandled promise rejection detected', { reason: reason instanceof Error ? reason.message : String(reason), stack: reason instanceof Error ? reason.stack : undefined, promise }); console.error('[DollhouseMCP] Server startup failed'); process.exit(1); }); // Only log execution environment in debug mode if (process.env.DOLLHOUSE_DEBUG) { console.error('[DollhouseMCP] Debug mode enabled'); } export class DollhouseMCPServer { server; personasDir; currentUser = null; isInitialized = false; initializationPromise = null; container; toolRegistry; enhancedIndexHandler; personaHandler; elementCRUDHandler; collectionHandler; portfolioHandler; githubAuthHandler; displayConfigHandler; identityHandler; configHandler; syncHandler; resourceHandler; /** * Create a new DollhouseMCPServer instance * * @param container DollhouseContainer instance for dependency injection. * Use `new DollhouseContainer()` for production or * `createIntegrationContainer().container` for tests. */ constructor(container) { // Build capabilities object conditionally based on configuration // Resources are disabled by default (advertise_resources: false) const capabilities = { tools: {}, }; // Check if resources should be advertised // This is a future-proof implementation - resources are opt-in try { // Initialize ConfigManager to check resource settings // Note: Config may not be fully initialized yet, so we check synchronously // If config is not initialized, defaults (advertise_resources: false) apply const fileLockManager = new FileLockManager(); const fileOperations = new FileOperationsService(fileLockManager); const configManager = new ConfigManager(fileOperations, os); const resourcesConfig = configManager.getSetting('elements.enhanced_index.resources'); if (resourcesConfig?.advertise_resources === true) { capabilities.resources = {}; logger.info('[DollhouseMCP] MCP Resources capability advertised (enabled via config)'); } else { logger.info('[DollhouseMCP] MCP Resources capability NOT advertised (disabled by default)'); } } catch (error) { // Config not initialized yet - use safe default (no resources) const errorMessage = error instanceof Error ? error.message : String(error); logger.debug(`[DollhouseMCP] Config not initialized yet, resources capability disabled by default: ${errorMessage}`); } this.server = new Server({ name: "dollhousemcp", version: PACKAGE_VERSION, }, { capabilities, }); this.personasDir = null; this.currentUser = process.env.DOLLHOUSE_USER || null; this.container = container; } async initializePortfolio() { await this.container.preparePortfolio(); this.personasDir = this.container.getPersonasDir(); } /** * Complete initialization after portfolio is ready * FIX #610: This was previously in a .then() callback in the constructor * Now called synchronously from run() to prevent race condition */ async completeInitialization() { // Create handlers with server instance - all state managed by services const handlers = await this.container.createHandlers(this.server); this.personaHandler = handlers.personaHandler; this.elementCRUDHandler = handlers.elementCrudHandler; this.collectionHandler = handlers.collectionHandler; this.portfolioHandler = handlers.portfolioHandler; this.githubAuthHandler = handlers.githubAuthHandler; this.displayConfigHandler = handlers.displayConfigHandler; this.identityHandler = handlers.identityHandler; this.configHandler = handlers.configHandler; this.syncHandler = handlers.syncHandler; this.toolRegistry = handlers.toolRegistry; this.enhancedIndexHandler = handlers.enhancedIndexHandler; // Initialize and register resource handlers // Resources are disabled by default and require explicit configuration await this.initializeResourceHandlers(); this.isInitialized = true; } /** * Initialize MCP Resources handlers if enabled in configuration * This is separate from other handlers because it requires dynamic import * and conditional registration based on configuration */ async initializeResourceHandlers() { try { const { ResourceHandler } = await import('./handlers/ResourceHandler.js'); const configManager = this.container.resolve('ConfigManager'); this.resourceHandler = new ResourceHandler(configManager); await this.resourceHandler.initialize(this.server); } catch (error) { // Resources are optional - don't fail server startup if they can't be initialized logger.warn('[DollhouseMCP] Failed to initialize resource handlers, continuing without resources'); logger.debug(`Resource initialization error: ${error}`); } } /** * Ensure server is initialized before any operation * FIX #610: Added for test compatibility - tests don't call run() */ async ensureInitialized() { if (this.isInitialized) { return; } // If initialization is already in progress, wait for it if (this.initializationPromise) { await this.initializationPromise; return; } // Start initialization this.initializationPromise = (async () => { try { await this.initializePortfolio(); await this.completeInitialization(); logger.info("Portfolio and personas initialized successfully (lazy)"); } catch (error) { ErrorHandler.logError('DollhouseMCPServer.ensureInitialized', error); throw error; } })(); await this.initializationPromise; } // Tool handler methods - now public for access from tool modules async listPersonas() { await this.ensureInitialized(); return this.personaHandler.listPersonas(); } // Use activateElement(name, 'persona'), deactivateElement(name, 'persona'), // getActiveElements('persona'), and getElementDetails(name, 'persona') instead. // These were removed to normalize persona handling through the generic element API. async reloadPersonas() { await this.ensureInitialized(); return this.personaHandler.reloadPersonas(); } // ===== Element Methods (Generic for all element types) ===== async listElements(type) { try { const normalizedType = this.elementCRUDHandler.normalizeElementType(type); if (normalizedType === ElementType.PERSONA) { return this.listPersonas(); } return this.elementCRUDHandler.listElements(normalizedType); } catch (error) { ErrorHandler.logError('DollhouseMCPServer.listElements', error, { type }); return { content: [{ type: "text", text: `❌ Failed to list ${type}: ${ErrorHandler.getUserMessage(error)}` }] }; } } async activateElement(name, type, context) { try { // FIX: Issue #281 - Route all element types through elementCRUDHandler // PersonaHandler.activatePersona is deprecated; PersonaActivationStrategy handles personas const normalizedType = this.elementCRUDHandler.normalizeElementType(type); return this.elementCRUDHandler.activateElement(name, normalizedType, context); } catch (error) { ErrorHandler.logError('DollhouseMCPServer.activateElement', error, { type, name }); return { content: [{ type: "text", text: `❌ Failed to activate ${type} '${name}': ${ErrorHandler.getUserMessage(error)}` }] }; } } async getActiveElements(type) { try { // FIX: Issue #281 - Route all element types through elementCRUDHandler // PersonaHandler.getActivePersona is deprecated; PersonaActivationStrategy handles personas const normalizedType = this.elementCRUDHandler.normalizeElementType(type); return this.elementCRUDHandler.getActiveElements(normalizedType); } catch (error) { ErrorHandler.logError('DollhouseMCPServer.getActiveElements', error, { type }); return { content: [{ type: "text", text: `❌ Failed to get active ${type}: ${ErrorHandler.getUserMessage(error)}` }] }; } } async deactivateElement(name, type) { try { // FIX: Issue #281 - Route all element types through elementCRUDHandler // PersonaHandler.deactivatePersona is deprecated; PersonaActivationStrategy handles personas const normalizedType = this.elementCRUDHandler.normalizeElementType(type); return this.elementCRUDHandler.deactivateElement(name, normalizedType); } catch (error) { ErrorHandler.logError('DollhouseMCPServer.deactivateElement', error, { type, name }); return { content: [{ type: "text", text: `❌ Failed to deactivate ${type} '${name}': ${ErrorHandler.getUserMessage(error)}` }] }; } } async getElementDetails(name, type) { // FIX: Issue #276 - Route all element types through elementCRUDHandler for consistent error handling // PersonaHandler.getPersonaDetails is deprecated; PersonaActivationStrategy handles personas const normalizedType = this.elementCRUDHandler.normalizeElementType(type); return this.elementCRUDHandler.getElementDetails(name, normalizedType); } async reloadElements(type) { await this.ensureInitialized(); return this.elementCRUDHandler.reloadElements(type); } // Element-specific methods async renderTemplate(name, variables) { await this.ensureInitialized(); return this.elementCRUDHandler.renderTemplate(name, variables); } async executeAgent(name, parameters) { await this.ensureInitialized(); return this.elementCRUDHandler.executeAgent(name, parameters); } /** * Create a new element in the portfolio. * @param args.elements - For ensembles: array of element references (Issue #278) */ async createElement(args) { await this.ensureInitialized(); // FIX: Issue #20 - Remove persona special case, route all element creation through ElementCRUDHandler // This ensures consistent duplicate checking and error handling for all element types // FIX: Issue #278 - Support elements parameter for ensembles const normalizedType = this.elementCRUDHandler.normalizeElementType(args.type); return this.elementCRUDHandler.createElement({ ...args, type: normalizedType }); } async editElement(args) { await this.ensureInitialized(); // FIX: Issue #276 - Route all element types through elementCRUDHandler for consistent error handling // PersonaHandler.editPersona is deprecated; elementCRUDHandler handles personas via PersonaActivationStrategy const normalizedType = this.elementCRUDHandler.normalizeElementType(args.type); return this.elementCRUDHandler.editElement({ ...args, type: normalizedType }); } async validateElement(args) { await this.ensureInitialized(); // FIX: Issue #276 - Route all element types through elementCRUDHandler for consistent error handling // PersonaHandler.validatePersona is deprecated; elementCRUDHandler handles personas const normalizedType = this.elementCRUDHandler.normalizeElementType(args.type); return this.elementCRUDHandler.validateElement({ ...args, type: normalizedType }); } async deleteElement(args) { await this.ensureInitialized(); // FIX: Issue #276 - Route all element types through elementCRUDHandler for consistent error handling // PersonaHandler.deletePersona is deprecated; elementCRUDHandler handles personas via dedicated delete path const normalizedType = this.elementCRUDHandler.normalizeElementType(args.type); return this.elementCRUDHandler.deleteElement({ ...args, type: normalizedType }); } async browseCollection(section, type) { await this.ensureInitialized(); return this.collectionHandler.browseCollection(section, type); } async searchCollection(query) { await this.ensureInitialized(); return this.collectionHandler.searchCollection(query); } async searchCollectionEnhanced(query, options = {}) { await this.ensureInitialized(); return this.collectionHandler.searchCollectionEnhanced(query, options); } async getCollectionContent(path) { await this.ensureInitialized(); return this.collectionHandler.getCollectionContent(path); } async installContent(inputPath) { await this.ensureInitialized(); return this.collectionHandler.installContent(inputPath); } async submitContent(contentIdentifier) { await this.ensureInitialized(); return this.collectionHandler.submitContent(contentIdentifier); } async getCollectionCacheHealth() { await this.ensureInitialized(); return this.collectionHandler.getCollectionCacheHealth(); } // User identity management - delegated to IdentityHandler async setUserIdentity(username, email) { await this.ensureInitialized(); return this.identityHandler.setUserIdentity(username, email); } async getUserIdentity() { await this.ensureInitialized(); return this.identityHandler.getUserIdentity(); } async clearUserIdentity() { await this.ensureInitialized(); return this.identityHandler.clearUserIdentity(); } getCurrentUserForAttribution() { return this.identityHandler.getCurrentUserForAttribution(); } // GitHub authentication management async setupGitHubAuth() { await this.ensureInitialized(); return this.githubAuthHandler.setupGitHubAuth(); } async checkGitHubAuth() { await this.ensureInitialized(); return this.githubAuthHandler.checkGitHubAuth(); } async getOAuthHelperStatus(verbose = false) { await this.ensureInitialized(); return this.githubAuthHandler.getOAuthHelperStatus(verbose); } async clearGitHubAuth() { await this.ensureInitialized(); return this.githubAuthHandler.clearGitHubAuth(); } // OAuth configuration management async configureOAuth(client_id) { await this.ensureInitialized(); return this.githubAuthHandler.configureOAuth(client_id); } /** * Configure indicator settings (delegated to DisplayConfigHandler) */ async configureIndicator(config) { await this.ensureInitialized(); return this.displayConfigHandler.configureIndicator(config); } /** * Get current indicator configuration (delegated to DisplayConfigHandler) */ async getIndicatorConfig() { await this.ensureInitialized(); return this.displayConfigHandler.getIndicatorConfig(); } /** * Configure collection submission settings (delegated to CollectionHandler) */ async configureCollectionSubmission(autoSubmit) { await this.ensureInitialized(); return this.collectionHandler.configureCollectionSubmission(autoSubmit); } /** * Get collection submission configuration (delegated to CollectionHandler) */ async getCollectionSubmissionConfig() { await this.ensureInitialized(); return this.collectionHandler.getCollectionSubmissionConfig(); } /** * Export a single persona */ async exportPersona(personaName) { await this.ensureInitialized(); return this.personaHandler.exportPersona(personaName); } /** * Export all personas */ async exportAllPersonas(includeDefaults = true) { await this.ensureInitialized(); return this.personaHandler.exportAllPersonas(includeDefaults); } /** * Import a persona */ async importPersona(source, overwrite = false) { await this.ensureInitialized(); return this.personaHandler.importPersona(source, overwrite); } /** * Portfolio management methods */ /** * Check portfolio status including repository existence and sync information */ async portfolioStatus(username) { await this.ensureInitialized(); return this.portfolioHandler.portfolioStatus(username); } /** * Initialize a new GitHub portfolio repository */ async initPortfolio(options) { await this.ensureInitialized(); return this.portfolioHandler.initPortfolio(options); } /** * Configure portfolio settings */ async portfolioConfig(options) { await this.ensureInitialized(); return this.portfolioHandler.portfolioConfig(options); } /** * Sync portfolio with GitHub */ async syncPortfolio(options) { await this.ensureInitialized(); return this.portfolioHandler.syncPortfolio(options); } /** * Handle configuration operations - delegates to ConfigHandler */ async handleConfigOperation(options) { await this.ensureInitialized(); return this.configHandler.handleConfigOperation(options); } /** * Handle sync operations - delegates to SyncHandler */ async handleSyncOperation(options) { await this.ensureInitialized(); return this.syncHandler.handleSyncOperation(options); } async dispose() { await this.container.dispose(); } /** * Search local portfolio using the metadata index system * This provides fast, comprehensive search across all element types */ async searchPortfolio(options) { await this.ensureInitialized(); return this.portfolioHandler.searchPortfolio(options); } /** * Search across all sources (local, GitHub, collection) using UnifiedIndexManager * This provides comprehensive search with duplicate detection and version comparison */ async searchAll(options) { await this.ensureInitialized(); return this.portfolioHandler.searchAll(options); } /** * Find semantically similar elements using Enhanced Index */ async findSimilarElements(options) { return this.enhancedIndexHandler.findSimilarElements(options); } /** * Get all relationships for a specific element */ async getElementRelationships(options) { return this.enhancedIndexHandler.getElementRelationships(options); } /** * Search for elements by action verb */ async searchByVerb(options) { return this.enhancedIndexHandler.searchByVerb(options); } /** * Get statistics about Enhanced Index relationships */ async getRelationshipStats() { return this.enhancedIndexHandler.getRelationshipStats(); } async run() { logger.debug("DollhouseMCPServer.run() started"); logger.info("Starting DollhouseMCP server..."); const timer = this.container.resolve('StartupTimer'); // Issue #706: Critical path only — get to connect() as fast as possible // Non-critical work (memory auto-load, activation restore, etc.) is // deferred to completeDeferredSetup() which runs post-connect. try { timer.startPhase('portfolio_prepare', true); await this.initializePortfolio(); timer.endPhase('portfolio_prepare'); timer.startPhase('handler_creation', true); await this.completeInitialization(); timer.endPhase('handler_creation'); // Initialize operational telemetry (async, non-blocking, never throws) const operationalTelemetry = this.container.resolve('OperationalTelemetry'); operationalTelemetry.initialize().catch(() => { // Telemetry errors are logged internally, safe to ignore here }); logger.info("Portfolio and personas initialized successfully"); if (!env.DOLLHOUSE_GATEKEEPER_ENABLED) { logger.warn("⚠️ Gatekeeper is DISABLED (DOLLHOUSE_GATEKEEPER_ENABLED=false). All permission checks are bypassed."); } logger.info("DollhouseMCP server ready - waiting for MCP connection on stdio"); logger.debug("DollhouseMCPServer.run() completed initialization"); } catch (error) { ErrorHandler.logError('DollhouseMCPServer.run.initialization', error); throw error; // Re-throw to prevent server from starting with incomplete initialization } const transport = new StdioServerTransport(); // Set up graceful shutdown handlers const cleanup = async () => { logger.info("Shutting down DollhouseMCP server..."); try { await this.container.dispose(); logger.info("Cleanup completed"); } catch (error) { logger.error("Error during cleanup", { error }); } process.exit(0); }; // Register shutdown handlers process.on('SIGINT', cleanup); process.on('SIGTERM', cleanup); process.on('SIGHUP', cleanup); // Connect ASAP — tools are registered, server can accept requests timer.startPhase('mcp_connect', true); await this.server.connect(transport); timer.endPhase('mcp_connect'); timer.markConnect(); // Mark that MCP is now connected - no more console output allowed logger.setMCPConnected(); // Issue #706 Phase 3: Emit READY sentinel for bridge clients process.stderr.write('DOLLHOUSEMCP_READY\n'); logger.info("DollhouseMCP server running on stdio"); // Issue #706 Phase 2: Deferred setup — runs post-connect, non-blocking // Pattern encryption, background validator, memory auto-load, activation // restore, log hooks, danger zone init all move here. const deferredPromise = this.container.completeDeferredSetup(); deferredPromise.catch(err => logger.warn('[Startup] Deferred setup error:', err)); // Issue #706 Phase 4: Wire deferred promise into ServerSetup for request buffering const serverSetup = this.container.resolve('ServerSetup'); serverSetup.setDeferredSetupPromise(deferredPromise); // Log startup timing after deferred setup completes deferredPromise.then(async () => { const report = timer.getReport(); logger.info(`[Startup] Full report: connect at ${report.connectAtMs}ms, ` + `critical ${report.criticalPathMs}ms, deferred ${report.deferredMs}ms, ` + `total ${report.totalMs}ms`); }).catch(() => { }); } } // Export is already at class declaration // Only start the server if this file is being run directly (not imported by tests) // Handle different execution methods (direct, npx, CLI) // // Bug fix: npx creates symlinks in .bin/ (e.g. .bin/mcp-server → dist/index.js). // Node.js keeps the symlink path in process.argv[1], so without resolving it, // isDirectExecution missed the dist/index.js suffix and the server never started. const rawScriptPath = process.argv?.[1] ?? ''; let scriptPath = rawScriptPath ? path.normalize(rawScriptPath) : ''; try { scriptPath = realpathSync(scriptPath); } catch { if (process.env.DOLLHOUSE_DEBUG) { console.error(`[DEBUG] Symlink resolution failed for ${rawScriptPath} — using original path`); } } const isDirectExecution = scriptPath.endsWith(`${path.sep}dist${path.sep}index.js`) || scriptPath.endsWith(`${path.sep}src${path.sep}index.ts`); // Modern npm (v7+) runs npx as "npm exec" — npm_execpath may point to // npm-cli.js instead of npx-cli.js. Detect both legacy and modern npx. const isNpxExecution = process.env.npm_execpath?.includes('npx') || process.env.npm_command === 'exec'; // Match all registered bin entry names from package.json "bin" field const binName = path.basename(rawScriptPath); const isCliExecution = binName === 'dollhousemcp' || binName === 'mcp-server'; const isTest = process.env.JEST_WORKER_ID; // This is set when Jest runs tests const isTestMode = process.env.TEST_MODE === 'true'; // Check for TEST_MODE environment variable const dollhouseDebugFlag = process.env.DOLLHOUSE_DEBUG?.toLowerCase(); const isDebugStartupLogging = dollhouseDebugFlag === 'true' || dollhouseDebugFlag === '1'; // Progressive startup with retries for npx/CLI execution const STARTUP_DELAYS = [10, 50, 100, 200]; // Progressive delays in ms async function startServerWithRetry(retriesLeft = STARTUP_DELAYS.length) { if (isDebugStartupLogging) { console.error("DEBUG: startServerWithRetry called."); } try { const container = new DollhouseContainer(); const server = new DollhouseMCPServer(container); await server.run(); } catch (error) { if (retriesLeft > 0 && (isNpxExecution || isCliExecution)) { // Try again with a longer delay const delayIndex = STARTUP_DELAYS.length - retriesLeft; const delay = STARTUP_DELAYS[delayIndex]; await new Promise(resolve => setTimeout(resolve, delay)); return startServerWithRetry(retriesLeft - 1); } // Final failure - minimal error message for security // Note: Using console.error here is intentional as it's the final error before exit console.error("[DollhouseMCP] Server startup failed", process.env.DOLLHOUSE_DEBUG ? error : error.message || 'unknown error'); process.exit(1); } } /** * Resolve the console port from config file, with range validation. * Used by standalone --web mode when no CLI --port flag is provided. * Returns undefined if the config file is missing or the port is invalid. */ async function resolvePortFromConfig() { try { const { readFile } = await import('node:fs/promises'); const configPath = path.join(os.homedir(), '.dollhouse', 'config.yml'); const raw = await readFile(configPath, 'utf8'); if (raw.length > 64 * 1024) { logger.debug('[PortConfig] Config file exceeds 64KB — skipping'); return undefined; } const { ConfigManager, validatePort } = await import('./config/ConfigManager.js'); const configPort = validatePort(ConfigManager.readPortFromYaml(raw)); if (configPort) { logger.debug(`[PortConfig] Resolved port ${configPort} from config file`); return configPort; } logger.debug('[PortConfig] No valid port in config file — using env/default'); return undefined; } catch { logger.debug('[PortConfig] Config file not found — using env/default'); return undefined; } } if ((isDirectExecution || isNpxExecution || isCliExecution) && (!isTest || isTestMode)) { // Issue #704: --web flag starts the portfolio web UI instead of MCP server const isWebMode = process.argv.includes('--web'); if (isWebMode) { // Issue #796: Bootstrap DI container for web-only mode so API routes // go through MCPAQLHandler (validated, cached, gatekeeper-checked) // // Suppress terminal output in --web mode unless DOLLHOUSE_DEBUG is set. // All logs are still captured in MemoryLogSink and visible in the Logs tab — // the terminal only needs the console URL banner, not a wall of startup noise. if (!process.env.DOLLHOUSE_DEBUG && !process.env.ENABLE_DEBUG) { logger.setMinLevel('error'); } (async () => { const portfolioDir = path.join(os.homedir(), '.dollhouse', 'portfolio'); // CLI flag parsed early; config file resolved after container bootstrap (#1840) const portArg = process.argv.find(a => a.startsWith('--port=')); const cliPort = portArg ? Number.parseInt(portArg.split('=')[1], 10) : undefined; const noBrowser = process.argv.includes('--no-open'); // Pre-flight: kill any stale DollhouseMCP process squatting on our port // BEFORE any container/server setup. This is the definitive fix for #1850 — // clear the port first, then start cleanly. // Race condition note: a new process could grab the port between kill and // our bind, but recoverStalePort's TOCTOU mitigation (500ms lock file // re-read) and bindAndListen's own recovery handle that edge case. const targetPort = cliPort || env.DOLLHOUSE_WEB_CONSOLE_PORT; try { const { recoverStalePort } = await import('./web/console/StaleProcessRecovery.js'); const recovered = await recoverStalePort(targetPort); if (recovered) { console.error(` Cleared stale process from port ${targetPort}\n`); } } catch (err) { // Non-fatal — bindAndListen will handle EADDRINUSE as a fallback. // Log so operators can diagnose recovery failures. console.error(`[DollhouseMCP] Pre-flight port recovery failed: ${err instanceof Error ? err.message : String(err)}`); } let container; let mcpAqlHandler; let memorySink; let metricsSink; try { container = new DollhouseContainer(); await container.preparePortfolio(); const bundle = await container.bootstrapHandlers(); // Wire sinks, hooks, collectors, and security — skip only leader election // and permission server. Standalone --web mode IS the server (#1866). await container.completeSinkSetup(); // Sweep stale port files (normally done in completeConsoleSetup) try { const { sweepStalePortFiles } = await import('./web/portDiscovery.js'); await sweepStalePortFiles(); } catch { /* non-fatal */ } mcpAqlHandler = bundle.mcpAqlHandler; try { memorySink = container.resolve('MemoryLogSink'); } catch { /* not registered */ } try { metricsSink = container.resolve('MemoryMetricsSink'); } catch { /* not registered */ } } catch (err) { console.error("[DollhouseMCP] Container bootstrap failed — web routes will use direct filesystem access."); console.error("[DollhouseMCP] Reason:", err.message || err); console.error("[DollhouseMCP] This may indicate a corrupt portfolio or missing dependencies."); } // Fallback sinks if container bootstrap failed entirely — // standalone --web mode still needs working logs and metrics tabs if (!memorySink) { const { MemoryLogSink } = await import('./logging/sinks/MemoryLogSink.js'); memorySink = new MemoryLogSink({ appCapacity: 10000, securityCapacity: 5000, perfCapacity: 2000, telemetryCapacity: 1000, }); } if (!metricsSink) { const { MemoryMetricsSink } = await import('./metrics/sinks/MemoryMetricsSink.js'); metricsSink = new MemoryMetricsSink(240); try { const metricsManager = container?.resolve('MetricsManager'); metricsManager?.registerSink(metricsSink); } catch { // MetricsManager may be unavailable in the degraded startup path. } } // Set up ingest routes so --web mode has a session registry (#1805). // Without this, the session indicator is always empty in standalone mode. const { createIngestRoutes } = await import('./web/console/IngestRoutes.js'); const ingestResult = createIngestRoutes({ logBroadcast: (_entry) => { }, metricsOnSnapshot: (snapshot) => { metricsSink?.onSnapshot(snapshot); }, }); ingestResult.registerConsoleSession(); // Initialize console token store so Auth tab routes mount (#1825). // Mirrors UnifiedConsole.ts:startAsLeader() — without this, // /api/console/totp and /api/console/token return 404. const { ConsoleTokenStore } = await import('./web/console/consoleToken.js'); const { pickRandomTokenName } = await import('./web/console/SessionNames.js'); const tokenStore = new ConsoleTokenStore(env.DOLLHOUSE_CONSOLE_TOKEN_FILE); try { await tokenStore.ensureInitialized(pickRandomTokenName()); } catch (err) { console.error('[DollhouseMCP] Failed to initialize console token store — Auth tab will be non-functional', err); } // Resolve port: CLI flag → config file → env var → default (#1840) const resolvedPort = cliPort || await resolvePortFromConfig(); const activationStore = container?.resolve('ActivationStore'); const { startWebServer } = await import('./web/server.js'); await startWebServer({ portfolioDir, port: resolvedPort, openBrowser: !noBrowser, mcpAqlHandler, memorySink, metricsSink, additionalRouters: [ingestResult.router], tokenStore, sessionId: activationStore?.getSessionId(), runtimeSessionId: activationStore?.getRuntimeSessionId(), }); // Listen for quit commands on stdin (standalone --web mode only). // In MCP stdio mode, stdin is consumed by the JSON-RPC transport. if (process.stdin.isTTY) { process.stdin.setEncoding('utf-8'); process.stdin.resume(); let quitDebounce = null; process.stdin.on('data', (data) => { const cmd = data.trim().toLowerCase(); if (cmd === 'q' || cmd === 'quit' || cmd === 'exit') { // Debounce rapid inputs (e.g., accidental double-tap) if (quitDebounce) return; quitDebounce = setTimeout(() => { quitDebounce = null; }, 200); console.error('\n Shutting down DollhouseMCP...\n'); process.exit(0); } }); } })().catch(err => { console.error("[DollhouseMCP] Web UI failed to start:", err); process.exit(1); }); } else { if (isDebugStartupLogging) { console.error("DEBUG: Server startup condition met. Calling startServerWithRetry."); } startServerWithRetry().catch(() => { // Note: Using console.error here is intentional as it's the final error before exit console.error("[DollhouseMCP] Server startup failed"); process.exit(1); }); } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUVBLGtFQUFrRTtBQUNsRSw4REFBOEQ7QUFDOUQsT0FBTyxFQUFFLEdBQUcsRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBRXRDLE9BQU8sS0FBSyxJQUFJLE1BQU0sTUFBTSxDQUFDO0FBQzdCLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxTQUFTLENBQUM7QUFDdkMsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLDJDQUEyQyxDQUFDO0FBQ25FLE9BQU8sRUFBRSxvQkFBb0IsRUFBRSxNQUFNLDJDQUEyQyxDQUFDO0FBQ2pGLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUN2RCxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFDM0MsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFDdkQsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBRTlELE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQWN6RCxPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFDMUQsT0FBTyxFQUFFLHFCQUFxQixFQUFFLE1BQU0scUNBQXFDLENBQUM7QUFDNUUsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBQ2hFLE9BQU8sS0FBSyxFQUFFLE1BQU0sSUFBSSxDQUFDO0FBR3pCLGlEQUFpRDtBQUNqRCxPQUFPLENBQUMsRUFBRSxDQUFDLG1CQUFtQixFQUFFLENBQUMsS0FBSyxFQUFFLEVBQUU7SUFDeEMsTUFBTSxDQUFDLEtBQUssQ0FBQyw4QkFBOEIsRUFBRTtRQUMzQyxLQUFLLEVBQUUsS0FBSyxZQUFZLEtBQUssQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENB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