Skip to main content
Glama

Beep Boop MCP

config.tsβ€’19.4 kB
/** * Configuration management for the beep/boop coordination system */ import os from 'os'; import path from 'path'; export interface BeepBoopConfig { // Core settings defaultMaxAgeHours: number; autoCleanupEnabled: boolean; maxAgentIdLength: number; filePermissions: string; // Logging and debugging logLevel: 'error' | 'warn' | 'info' | 'debug'; timezone: string; // Security and access control allowedDirectories: string[]; blockedDirectories: string[]; requireTeamPrefix: boolean; teamPrefixes: string[]; // Backup and recovery backupEnabled: boolean; backupDir: string; // Monitoring and metrics enableMetrics: boolean; enableNotifications: boolean; notificationWebhook?: string; // Legacy - backward compatibility // Webhook notifications notificationService: 'discord' | 'slack' | 'both'; discordWebhookUrl?: string; slackWebhookUrl?: string; notificationRetryAttempts: number; notificationTimeoutMs: number; // Audit and compliance auditLogEnabled: boolean; auditLogPath: string; // Work management maxWorkDurationHours: number; warnThresholdHours: number; escalationEnabled: boolean; escalationAfterHours: number; // Environment-specific devMode: boolean; ciMode: boolean; watchMode: boolean; forceCleanupOnStart: boolean; failOnStale: boolean; maxConcurrentOperations: number; // Git integration manageGitIgnore: boolean; // Ingress listener feature (capture & local HTTP) ingressEnabled: boolean; ingressProvider: 'slack' | 'discord' | 'none'; ingressHttpEnabled: boolean; ingressHttpPort: number; ingressHttpAuthToken?: string; ingressInboxDir: string; // Central HTTP listener delegation (synchronous request/response) listenerEnabled: boolean; listenerBaseUrl?: string; listenerAuthToken?: string; listenerTimeoutBaseMs: number; listenerTimeoutPerCharMs: number; // adaptive wait for larger responses listenerTimeoutMaxMs: number; // hard cap maxConcurrentListenerRequests: number; // Slack (Socket Mode) slackAppToken?: string; // xapp-... (Socket Mode) slackBotToken?: string; // xoxb-... // Discord discordBotToken?: string; discordDefaultChannelId?: string; // Channel for proactive agent messaging // Conversation flow settings conversationTimeoutMinutes: number; // How long to wait for user responses conversationPollIntervalMs: number; // How often to check for user responses // Discord API reliability settings discordApiRetryAttempts: number; // Number of retry attempts for Discord API calls discordApiRetryBaseDelayMs: number; // Base delay between retries (exponential backoff) discordApiTimeoutMs: number; // Timeout for individual Discord API calls // Inbox cleanup settings inboxCleanupEnabled: boolean; // Enable automatic cleanup of old inbox messages inboxProcessedRetentionDays: number; // How long to keep processed messages (default: 7 days) inboxUnprocessedRetentionDays: number; // How long to keep unprocessed messages (default: 30 days) inboxMaxFilesPerDir: number; // Maximum files per directory before forcing cleanup (0 = no limit) inboxCleanupOnStartup: boolean; // Run cleanup when MCP server starts inboxCleanupIntervalHours: number; // How often to run automatic cleanup (0 = disabled) } /** * Load configuration from environment variables */ export function loadConfig(): BeepBoopConfig { const config: BeepBoopConfig = { // Core settings defaultMaxAgeHours: parseFloat(process.env.BEEP_BOOP_DEFAULT_MAX_AGE_HOURS || '24'), autoCleanupEnabled: process.env.BEEP_BOOP_AUTO_CLEANUP_ENABLED === 'true', maxAgentIdLength: parseInt(process.env.BEEP_BOOP_MAX_AGENT_ID_LENGTH || '100', 10), filePermissions: process.env.BEEP_BOOP_FILE_PERMISSIONS || '0644', // Logging and debugging logLevel: (process.env.BEEP_BOOP_LOG_LEVEL || 'info') as BeepBoopConfig['logLevel'], timezone: process.env.BEEP_BOOP_TIMEZONE || 'UTC', // Security and access control allowedDirectories: parseDirectories(process.env.BEEP_BOOP_ALLOWED_DIRECTORIES), blockedDirectories: parseDirectories(process.env.BEEP_BOOP_BLOCKED_DIRECTORIES || '/tmp,/var,/etc'), requireTeamPrefix: process.env.BEEP_BOOP_REQUIRE_TEAM_PREFIX === 'true', teamPrefixes: parseList(process.env.BEEP_BOOP_TEAM_PREFIXES), // Backup and recovery backupEnabled: process.env.BEEP_BOOP_BACKUP_ENABLED === 'true', backupDir: process.env.BEEP_BOOP_BACKUP_DIR || './.beep-boop-backups', // Monitoring and metrics enableMetrics: process.env.BEEP_BOOP_ENABLE_METRICS === 'true', enableNotifications: process.env.BEEP_BOOP_ENABLE_NOTIFICATIONS === 'true', notificationWebhook: process.env.BEEP_BOOP_NOTIFICATION_WEBHOOK, // Webhook notifications notificationService: (process.env.BEEP_BOOP_NOTIFICATION_SERVICE || 'both') as BeepBoopConfig['notificationService'], discordWebhookUrl: process.env.BEEP_BOOP_DISCORD_WEBHOOK_URL, slackWebhookUrl: process.env.BEEP_BOOP_SLACK_WEBHOOK_URL, notificationRetryAttempts: parseInt(process.env.BEEP_BOOP_NOTIFICATION_RETRY_ATTEMPTS || '3', 10), notificationTimeoutMs: parseInt(process.env.BEEP_BOOP_NOTIFICATION_TIMEOUT_MS || '5000', 10), // Audit and compliance auditLogEnabled: process.env.BEEP_BOOP_AUDIT_LOG_ENABLED === 'true', auditLogPath: process.env.BEEP_BOOP_AUDIT_LOG_PATH || './logs/coordination-audit.log', // Work management maxWorkDurationHours: parseFloat(process.env.BEEP_BOOP_MAX_WORK_DURATION_HOURS || '48'), warnThresholdHours: parseFloat(process.env.BEEP_BOOP_WARN_THRESHOLD_HOURS || '8'), escalationEnabled: process.env.BEEP_BOOP_ESCALATION_ENABLED === 'true', escalationAfterHours: parseFloat(process.env.BEEP_BOOP_ESCALATION_AFTER_HOURS || '24'), // Environment-specific devMode: process.env.BEEP_BOOP_DEV_MODE === 'true' || process.env.NODE_ENV === 'development', ciMode: process.env.BEEP_BOOP_CI_MODE === 'true' || process.env.CI === 'true', watchMode: process.env.BEEP_BOOP_WATCH_MODE === 'true', forceCleanupOnStart: process.env.BEEP_BOOP_FORCE_CLEANUP_ON_START === 'true', failOnStale: process.env.BEEP_BOOP_FAIL_ON_STALE === 'true', maxConcurrentOperations: parseInt(process.env.BEEP_BOOP_MAX_CONCURRENT_OPERATIONS || '5', 10), // Git integration manageGitIgnore: process.env.BEEP_BOOP_MANAGE_GITIGNORE !== 'false', // Default to true // Ingress listener feature ingressEnabled: process.env.BEEP_BOOP_INGRESS_ENABLED === 'true', ingressProvider: (process.env.BEEP_BOOP_INGRESS_PROVIDER || 'none') as BeepBoopConfig['ingressProvider'], ingressHttpEnabled: process.env.BEEP_BOOP_INGRESS_HTTP_ENABLED !== 'false', ingressHttpPort: parseInt(process.env.BEEP_BOOP_INGRESS_HTTP_PORT || '7077', 10), ingressHttpAuthToken: process.env.BEEP_BOOP_INGRESS_HTTP_AUTH_TOKEN, ingressInboxDir: process.env.BEEP_BOOP_INGRESS_INBOX_DIR || path.join(os.homedir(), '.beep-boop-inbox'), // Central HTTP listener delegation (synchronous request/response) listenerEnabled: process.env.BEEP_BOOP_LISTENER_ENABLED === 'true', listenerBaseUrl: process.env.BEEP_BOOP_LISTENER_BASE_URL || `http://localhost:${process.env.BEEP_BOOP_INGRESS_HTTP_PORT || '7077'}`, listenerAuthToken: process.env.BEEP_BOOP_LISTENER_AUTH_TOKEN, listenerTimeoutBaseMs: parseInt(process.env.BEEP_BOOP_LISTENER_TIMEOUT_BASE_MS || '10000', 10), listenerTimeoutPerCharMs: parseInt(process.env.BEEP_BOOP_LISTENER_TIMEOUT_PER_CHAR_MS || '5', 10), listenerTimeoutMaxMs: parseInt(process.env.BEEP_BOOP_LISTENER_TIMEOUT_MAX_MS || '60000', 10), maxConcurrentListenerRequests: parseInt(process.env.BEEP_BOOP_MAX_CONCURRENT_LISTENER_REQUESTS || '25', 10), // Slack slackAppToken: process.env.BEEP_BOOP_SLACK_APP_TOKEN, slackBotToken: process.env.BEEP_BOOP_SLACK_BOT_TOKEN, // Discord discordBotToken: process.env.BEEP_BOOP_DISCORD_BOT_TOKEN, discordDefaultChannelId: process.env.BEEP_BOOP_DISCORD_DEFAULT_CHANNEL_ID, // Conversation flow settings conversationTimeoutMinutes: parseInt(process.env.BEEP_BOOP_CONVERSATION_TIMEOUT_MINUTES || '5', 10), conversationPollIntervalMs: parseInt(process.env.BEEP_BOOP_CONVERSATION_POLL_INTERVAL_MS || '2000', 10), // Discord API reliability settings discordApiRetryAttempts: parseInt(process.env.BEEP_BOOP_DISCORD_API_RETRY_ATTEMPTS || '3', 10), discordApiRetryBaseDelayMs: parseInt(process.env.BEEP_BOOP_DISCORD_API_RETRY_BASE_DELAY_MS || '1000', 10), discordApiTimeoutMs: parseInt(process.env.BEEP_BOOP_DISCORD_API_TIMEOUT_MS || '30000', 10), // Inbox cleanup settings inboxCleanupEnabled: process.env.BEEP_BOOP_INBOX_CLEANUP_ENABLED !== 'false', // Default to true inboxProcessedRetentionDays: parseInt(process.env.BEEP_BOOP_INBOX_PROCESSED_RETENTION_DAYS || '7', 10), inboxUnprocessedRetentionDays: parseInt(process.env.BEEP_BOOP_INBOX_UNPROCESSED_RETENTION_DAYS || '30', 10), inboxMaxFilesPerDir: parseInt(process.env.BEEP_BOOP_INBOX_MAX_FILES_PER_DIR || '0', 10), // 0 = no limit inboxCleanupOnStartup: process.env.BEEP_BOOP_INBOX_CLEANUP_ON_STARTUP !== 'false', // Default to true inboxCleanupIntervalHours: parseInt(process.env.BEEP_BOOP_INBOX_CLEANUP_INTERVAL_HOURS || '24', 10) // Run daily }; // Handle backward compatibility for legacy webhook config handleLegacyWebhookConfig(config); // If listener is targeting local ingress and no explicit listener token provided, reuse ingress token try { const defaultLocalBase = `http://localhost:${config.ingressHttpPort}`; if (config.listenerEnabled && !config.listenerAuthToken && config.listenerBaseUrl && config.listenerBaseUrl.startsWith(defaultLocalBase) && config.ingressHttpAuthToken) { config.listenerAuthToken = config.ingressHttpAuthToken; if (config.logLevel === 'debug') { console.error('πŸ” Using ingress HTTP auth token for listener requests'); } } } catch {} // Validate configuration validateConfig(config); return config; } /** * Parse directory list from environment variable */ function parseDirectories(envVar?: string): string[] { if (!envVar) return []; return envVar.split(',').map(dir => dir.trim()).filter(dir => dir.length > 0); } /** * Parse comma-separated list from environment variable */ function parseList(envVar?: string): string[] { if (!envVar) return []; return envVar.split(',').map(item => item.trim()).filter(item => item.length > 0); } /** * Validate configuration values */ function validateConfig(config: BeepBoopConfig): void { // Validate age thresholds if (config.defaultMaxAgeHours < 0) { throw new Error('BEEP_BOOP_DEFAULT_MAX_AGE_HOURS must be >= 0'); } if (config.maxWorkDurationHours < config.defaultMaxAgeHours) { throw new Error('BEEP_BOOP_MAX_WORK_DURATION_HOURS must be >= BEEP_BOOP_DEFAULT_MAX_AGE_HOURS'); } // Validate agent ID length if (config.maxAgentIdLength < 1 || config.maxAgentIdLength > 500) { throw new Error('BEEP_BOOP_MAX_AGENT_ID_LENGTH must be between 1 and 500'); } // Validate log level const validLogLevels = ['error', 'warn', 'info', 'debug']; if (!validLogLevels.includes(config.logLevel)) { throw new Error(`BEEP_BOOP_LOG_LEVEL must be one of: ${validLogLevels.join(', ')}`); } // Validate file permissions if (!/^0[0-7]{3}$/.test(config.filePermissions)) { throw new Error('BEEP_BOOP_FILE_PERMISSIONS must be in octal format (e.g., 0644)'); } // Validate concurrent operations if (config.maxConcurrentOperations < 1 || config.maxConcurrentOperations > 100) { throw new Error('BEEP_BOOP_MAX_CONCURRENT_OPERATIONS must be between 1 and 100'); } // Validate ingress provider selection const validProviders = ['slack', 'discord', 'none']; if (!validProviders.includes(config.ingressProvider)) { throw new Error('BEEP_BOOP_INGRESS_PROVIDER must be one of slack, discord, none'); } if (config.ingressEnabled && config.ingressProvider === 'none') { throw new Error('Ingress enabled but no provider selected. Set BEEP_BOOP_INGRESS_PROVIDER=slack or discord'); } // Validate listener delegation if (config.listenerEnabled) { if (!config.listenerBaseUrl || !/^https?:\/\//.test(config.listenerBaseUrl)) { throw new Error('BEEP_BOOP_LISTENER_BASE_URL must be set to a valid http(s) URL when BEEP_BOOP_LISTENER_ENABLED=true'); } if (config.maxConcurrentListenerRequests < 1 || config.maxConcurrentListenerRequests > 500) { throw new Error('BEEP_BOOP_MAX_CONCURRENT_LISTENER_REQUESTS must be between 1 and 500'); } if (config.listenerTimeoutBaseMs < 1000 || config.listenerTimeoutMaxMs < config.listenerTimeoutBaseMs) { throw new Error('Listener timeout values are invalid. Ensure base >= 1000 and max >= base.'); } } } /** * Check if a directory path is allowed based on configuration */ export function isDirectoryAllowed(path: string, config: BeepBoopConfig): boolean { // Check blocked directories first for (const blocked of config.blockedDirectories) { if (path.startsWith(blocked)) { return false; } } // If allowedDirectories is empty, allow all (except blocked) if (config.allowedDirectories.length === 0) { return true; } // Check if path starts with any allowed directory return config.allowedDirectories.some(allowed => path.startsWith(allowed)); } /** * Check if an agent ID follows team prefix requirements */ export function validateAgentIdPrefix(agentId: string, config: BeepBoopConfig): boolean { if (!config.requireTeamPrefix) { return true; } if (config.teamPrefixes.length === 0) { return true; } return config.teamPrefixes.some(prefix => agentId.startsWith(prefix)); } /** * Get environment-specific default values */ export function getEnvironmentDefaults(): Partial<BeepBoopConfig> { const env = process.env.NODE_ENV || 'development'; switch (env) { case 'development': return { defaultMaxAgeHours: 1, autoCleanupEnabled: true, logLevel: 'debug', backupEnabled: false, devMode: true }; case 'test': return { defaultMaxAgeHours: 0.1, // 6 minutes autoCleanupEnabled: true, logLevel: 'warn', backupEnabled: false, forceCleanupOnStart: true }; case 'production': return { defaultMaxAgeHours: 24, autoCleanupEnabled: false, logLevel: 'info', backupEnabled: true, auditLogEnabled: true }; default: return {}; } } /** * Handle backward compatibility for legacy webhook configuration */ function handleLegacyWebhookConfig(config: BeepBoopConfig): void { if (config.notificationWebhook && !config.discordWebhookUrl && !config.slackWebhookUrl) { // Legacy BEEP_BOOP_NOTIFICATION_WEBHOOK was set, try to determine service type if (config.notificationWebhook.includes('discord.com')) { config.discordWebhookUrl = config.notificationWebhook; config.notificationService = 'discord'; if (config.logLevel === 'debug') { console.error('⚠️ Using legacy notification webhook as Discord URL'); } } else if (config.notificationWebhook.includes('hooks.slack.com')) { config.slackWebhookUrl = config.notificationWebhook; config.notificationService = 'slack'; if (config.logLevel === 'debug') { console.error('⚠️ Using legacy notification webhook as Slack URL'); } } else { // Unknown service, default to Slack format config.slackWebhookUrl = config.notificationWebhook; config.notificationService = 'slack'; if (config.logLevel === 'debug') { console.error('⚠️ Using legacy notification webhook as Slack URL (default)'); } } } } /** * Check if webhook URLs are valid format */ export function validateWebhookUrls(config: BeepBoopConfig): string[] { const errors: string[] = []; if (config.discordWebhookUrl && !config.discordWebhookUrl.includes('discord.com/api/webhooks')) { errors.push('Discord webhook URL must be a valid Discord webhook URL'); } if (config.slackWebhookUrl && !config.slackWebhookUrl.includes('hooks.slack.com')) { errors.push('Slack webhook URL must be a valid Slack webhook URL'); } return errors; } /** * Print configuration summary (for debugging) */ export function printConfigSummary(config: BeepBoopConfig): void { if (config.logLevel === 'debug') { console.error('πŸ“‹ Beep/Boop Configuration:'); console.error(` β€’ Environment: ${process.env.NODE_ENV || 'development'}`); console.error(` β€’ Max age threshold: ${config.defaultMaxAgeHours} hours`); console.error(` β€’ Auto cleanup: ${config.autoCleanupEnabled ? 'enabled' : 'disabled'}`); console.error(` β€’ Log level: ${config.logLevel}`); console.error(` β€’ Allowed directories: ${config.allowedDirectories.length === 0 ? 'all (except blocked)' : config.allowedDirectories.join(', ')}`); console.error(` β€’ Blocked directories: ${config.blockedDirectories.join(', ')}`); console.error(` β€’ Team prefix required: ${config.requireTeamPrefix}`); console.error(` β€’ Backup enabled: ${config.backupEnabled}`); console.error(` β€’ Audit logging: ${config.auditLogEnabled}`); console.error(` β€’ Notifications: ${config.enableNotifications ? 'enabled' : 'disabled'}`); if (config.enableNotifications) { console.error(` β€’ Notification service: ${config.notificationService}`); console.error(` β€’ Discord webhook: ${config.discordWebhookUrl ? 'configured' : 'not configured'}`); console.error(` β€’ Slack webhook: ${config.slackWebhookUrl ? 'configured' : 'not configured'}`); console.error(` β€’ Retry attempts: ${config.notificationRetryAttempts}`); console.error(` β€’ Timeout: ${config.notificationTimeoutMs}ms`); } console.error(` β€’ Git integration: ${config.manageGitIgnore ? 'enabled' : 'disabled'}`); console.error(` β€’ Ingress: ${config.ingressEnabled ? 'enabled' : 'disabled'} (${config.ingressProvider})`); if (config.ingressEnabled) { console.error(` β€’ Ingress HTTP: ${config.ingressHttpEnabled ? `enabled on port ${config.ingressHttpPort}` : 'disabled'}`); console.error(` β€’ Inbox dir: ${config.ingressInboxDir}`); console.error(` β€’ Slack Socket Mode: ${config.slackAppToken && config.slackBotToken ? 'configured' : 'not configured'}`); console.error(` β€’ Discord Bot: ${config.discordBotToken ? 'configured' : 'not configured'}`); if (config.discordBotToken && config.discordDefaultChannelId) { console.error(` β€’ Discord Default Channel: ${config.discordDefaultChannelId}`); } } console.error(` β€’ Central Listener: ${config.listenerEnabled ? `enabled (${config.listenerBaseUrl})` : 'disabled'}`); if (config.listenerEnabled) { console.error(` β€’ Listener timeouts: base=${config.listenerTimeoutBaseMs}ms, perChar=${config.listenerTimeoutPerCharMs}ms, max=${config.listenerTimeoutMaxMs}ms`); console.error(` β€’ Max concurrent listener requests: ${config.maxConcurrentListenerRequests}`); } } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/thesammykins/beep_boop_mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server