Skip to main content
Glama

Watchtower DAP Windows Debugging

by rlaksana
config.ts10.3 kB
import * as fs from 'fs'; import * as path from 'path'; /** * Configuration Manager * * Manages configuration settings with validation, environment variable support, * and hierarchical configuration (defaults < config file < environment variables). */ export class ConfigManager { private static instance: ConfigManager; private config: Record<string, any>; private configPath: string; private constructor() { this.configPath = path.join(process.cwd(), 'watchtower.config.json'); this.config = this.loadConfig(); } static getInstance(): ConfigManager { if (!ConfigManager.instance) { ConfigManager.instance = new ConfigManager(); } return ConfigManager.instance; } /** * Load configuration with hierarchical merging */ private loadConfig(): Record<string, any> { const defaults = this.getDefaultConfig(); let fileConfig: Record<string, any> = {}; let envConfig: Record<string, any> = {}; // Load from config file if it exists try { if (fs.existsSync(this.configPath)) { const fileContent = fs.readFileSync(this.configPath, 'utf-8'); fileConfig = JSON.parse(fileContent); } } catch (error) { console.warn(`Failed to load config file ${this.configPath}:`, (error as Error).message); } // Load from environment variables envConfig = this.getEnvConfig(); // Hierarchical merge: defaults < file < environment return this.mergeConfigs(defaults, fileConfig, envConfig); } /** * Get default configuration */ private getDefaultConfig(): Record<string, any> { return { server: { port: 0, // Use dynamic port host: '127.0.0.1', maxConnections: 10, idleTimeout: 300000, // 5 minutes }, mcp: { name: 'watchtower', description: 'Windows-native MCP ⇄ DAP bridge', version: '0.1.0-alpha', maxTools: 50, }, debugging: { defaultTimeout: 30000, // 30 seconds maxSessions: 5, sessionTimeout: 3600000, // 1 hour enableMetrics: true, enableTracing: true, }, transport: { type: 'stdio', // stdio or tcp tcpPort: 4711, // For attach scenarios tcpHost: '127.0.0.1', }, adapters: { autoDiscover: true, windows: { vsdbg: { name: 'Visual Studio Debugger', path: 'C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\Common7\\IDE\\vsdbg.exe', requiredPaths: ['C:\\Program Files\\Microsoft Visual Studio\\2022\\Common7\\IDE'], versionRequirements: { min: '17.0.0', recommended: '17.8.0', }, installHints: [ 'Download Visual Studio Installer: https://visualstudio.microsoft.com/downloads/', 'Install "Desktop development with C#" workload', 'Ensure vsdbg.exe is in your PATH or provide full path', ], }, netcoredbg: { name: '.NET Core Debugger', path: 'C:\\Program Files\\dotnet\\tools\\netcoredbg.exe', requiredPaths: ['C:\\Program Files\\dotnet'], versionRequirements: { min: '6.0.0', recommended: '7.0.0', }, installHints: [ 'Install .NET SDK: https://dotnet.microsoft.com/download', 'netcoredbg will be automatically installed with .NET tools', ], }, 'vscode-js-debug': { name: 'JavaScript Debugger', path: '%USERPROFILE%\\.vscode\\extensions\\ms-vscode.js-debug-1.xx.x\\out\\node\\debugAdapter.js', requiredPaths: ['%USERPROFILE%\\.vscode\\extensions\\ms-vscode.js-debug-*'], versionRequirements: { min: '1.x.x', recommended: '1.80.0', }, installHints: [ 'Install Visual Studio Code: https://code.visualstudio.com/', 'Install JavaScript Debugger extension: ms-vscode.js-debug', 'Restart VS Code after installation', ], }, debugpy: { name: 'Python Debugger', path: 'C:\\Users\\%USERNAME%\\AppData\\Local\\Programs\\Python\\Python39\\Scripts\\debugpy.exe', requiredPaths: [ 'C:\\Users\\%USERNAME%\\AppData\\Local\\Programs\\Python\\Python39\\Scripts', ], versionRequirements: { min: '1.6.0', recommended: '1.8.0', }, installHints: [ 'Install Python: https://python.org', 'Install debugpy: pip install debugpy', 'Ensure Python is in your PATH', ], }, dap: { name: 'Generic DAP Adapter', path: null, // Will be discovered dynamically requiredPaths: [], versionRequirements: {}, installHints: [ 'Place DAP adapter executable in PATH or specify full path', 'Ensure adapter supports DAP protocol', ], }, }, }, security: { redactSensitiveData: true, maxLogSize: 1024 * 1024, // 1MB logRotation: true, allowedOrigins: ['*'], requireAuth: false, }, metrics: { enabled: true, otlpEndpoint: 'http://localhost:4318', serviceName: 'watchtower', exportInterval: 60000, // 1 minute }, logging: { level: 'info', format: 'json', destination: 'stdout', file: { path: 'watchtower.log', maxSize: '10MB', maxFiles: 5, }, }, }; } /** * Extract configuration from environment variables */ private getEnvConfig(): Record<string, any> { const envConfig: Record<string, any> = {}; const envToConfigMap: Record<string, string> = { WATCHTOWER_SERVER_PORT: 'server.port', WATCHTOWER_SERVER_HOST: 'server.host', WATCHTOWER_DEBUGGING_DEFAULT_TIMEOUT: 'debugging.defaultTimeout', WATCHTOWER_DEBUGGING_MAX_SESSIONS: 'debugging.maxSessions', WATCHTOWER_TRANSPORT_TYPE: 'transport.type', WATCHTOWER_METRICS_ENABLED: 'metrics.enabled', WATCHTOWER_LOGGING_LEVEL: 'logging.level', }; for (const [envKey, configKey] of Object.entries(envToConfigMap)) { if (process.env[envKey]) { this.setNestedValue(envConfig, configKey, this.parseEnvValue(process.env[envKey])); } } return envConfig; } /** * Parse environment variable value with type conversion */ private parseEnvValue(value: string): any { // Boolean if (value === 'true') return true; if (value === 'false') return false; // Number if (/^-?\d+$/.test(value)) { return parseInt(value, 10); } if (/^-?\d+\.\d+$/.test(value)) { return parseFloat(value); } // String return value; } /** * Set nested value in object using dot notation */ private setNestedValue(obj: Record<string, any>, path: string, value: any): void { const keys = path.split('.'); let current = obj; for (let i = 0; i < keys.length - 1; i++) { const key = keys[i]; if (key && !(key in current)) { current[key] = {}; } current = current[key as keyof typeof current]; } current[keys[keys.length - 1] as keyof typeof current] = value; } /** * Deep merge objects */ private mergeConfigs(...configs: Record<string, any>[]): Record<string, any> { const result: Record<string, any> = {}; for (const config of configs) { this.deepMerge(result, config); } return result; } /** * Deep merge two objects */ private deepMerge(target: Record<string, any>, source: Record<string, any>): void { for (const key in source) { if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) { if (!target[key]) { target[key] = {}; } this.deepMerge(target[key], source[key]); } else { target[key] = source[key]; } } } /** * Get configuration value */ get<T>(key: string, defaultValue?: T): T { const value = this.getNestedValue(this.config, key); return value !== undefined ? value : (defaultValue as T); } /** * Get nested value using dot notation */ private getNestedValue(obj: Record<string, any>, path: string): any { const keys = path.split('.'); let current = obj; for (const key of keys) { if (current && typeof current === 'object' && key in current) { current = current[key]; } else { return undefined; } } return current; } /** * Set configuration value */ set(key: string, value: any): void { this.setNestedValue(this.config, key, value); this.saveConfig(); } /** * Get entire configuration */ getAll(): Record<string, any> { return { ...this.config }; } /** * Save configuration to file */ private saveConfig(): void { try { fs.writeFileSync(this.configPath, JSON.stringify(this.config, null, 2)); } catch (error) { console.warn(`Failed to save config file ${this.configPath}:`, (error as Error).message); } } /** * Reload configuration from files and environment */ reload(): void { this.config = this.loadConfig(); } /** * Validate configuration */ validate(): { valid: boolean; errors: string[] } { const errors: string[] = []; // Validate server configuration if (this.get('server.port', 0) < 0 || this.get('server.port', 0) > 65535) { errors.push('Invalid server port'); } // Validate debugging configuration if (this.get('debugging.maxSessions', 1) <= 0) { errors.push('maxSessions must be positive'); } // Validate transport configuration const transportType = this.get('transport.type', 'stdio'); if (transportType !== 'stdio' && transportType !== 'tcp') { errors.push('transport.type must be either "stdio" or "tcp"'); } return { valid: errors.length === 0, errors, }; } }

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/rlaksana/mcp-watchtower'

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