UNPKG

@todo-for-ai/mcp

Version:

Model Context Protocol server for Todo for AI task management system with Streamable HTTP transport. Provides AI assistants with access to task management, project information, and feedback submission capabilities through modern HTTP-based communication.

277 lines (267 loc) 12.6 kB
import { config } from 'dotenv'; import { readFileSync } from 'fs'; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; // Load environment variables from the package directory, not the current working directory const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const envPath = join(__dirname, '..', '.env'); // Try to load .env file from package directory, but don't fail if it doesn't exist try { config({ path: envPath }); console.log(`[CONFIG] Loaded .env file from: ${envPath}`); } catch (error) { console.log(`[CONFIG] No .env file found at: ${envPath}, using system environment variables only`); } // Get package.json for version info const packageJsonPath = join(__dirname, '..', 'package.json'); const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')); function showHelp() { console.log(` ${packageJson.name} v${packageJson.version} ${packageJson.description} Usage: todo-for-ai-mcp [options] npx @todo-for-ai/mcp [options] Options: --api-base-url <url> API base URL (default: https://todo4ai.org/todo-for-ai/api/v1) --base-url <url> Alias for --api-base-url --api-token <token> API authentication token (required) --token <token> Alias for --api-token --api-timeout <ms> API request timeout in milliseconds (default: 10000) --timeout <ms> Alias for --api-timeout --log-level <level> Log level: debug, info, warn, error (default: info) --http-port <port> HTTP server port (default: 3000) --http-host <host> HTTP server host (default: 127.0.0.1) --session-timeout <ms> Session timeout in milliseconds (default: 300000) --dns-protection Enable DNS rebinding protection (default: true) --allowed-origins <origins> Comma-separated list of allowed origins --max-connections <num> Maximum concurrent connections (default: 100) --help Show this help message --version Show version information Environment Variables: TODO_API_BASE_URL API base URL TODO_API_TOKEN API authentication token TODO_API_TIMEOUT API request timeout in milliseconds LOG_LEVEL Log level TODO_HTTP_PORT HTTP server port TODO_HTTP_HOST HTTP server host TODO_SESSION_TIMEOUT Session timeout in milliseconds TODO_DNS_PROTECTION Enable DNS rebinding protection (true/false) TODO_ALLOWED_ORIGINS Comma-separated list of allowed origins TODO_MAX_CONNECTIONS Maximum concurrent connections Configuration Priority: Command line arguments > Environment variables > Defaults Examples: # Using command line arguments todo-for-ai-mcp --api-token your-token --log-level debug # Using HTTP transport with custom port todo-for-ai-mcp --api-token your-token --http-port 8080 # Using environment variables TODO_API_TOKEN=your-token LOG_LEVEL=debug todo-for-ai-mcp # HTTP transport with environment variables TODO_API_TOKEN=your-token TODO_HTTP_PORT=3000 todo-for-ai-mcp # Mixed configuration (CLI args override env vars) TODO_API_TOKEN=your-token todo-for-ai-mcp --http-port 8080 For more information, visit: https://github.com/todo-for-ai/todo-for-ai `); } function showVersion() { console.log(`${packageJson.name} v${packageJson.version}`); } function parseArgs() { const args = {}; const argv = process.argv.slice(2); // Check for help or version first if (argv.includes('--help') || argv.includes('-h')) { showHelp(); process.exit(0); } if (argv.includes('--version') || argv.includes('-v')) { showVersion(); process.exit(0); } for (let i = 0; i < argv.length; i++) { const arg = argv[i]; if (arg && arg.startsWith('--')) { // Handle --key=value format if (arg.includes('=')) { const [key, ...valueParts] = arg.substring(2).split('='); const value = valueParts.join('='); // Handle values that contain '=' if (key) { args[key] = value; } } else { // Handle --key value format const key = arg.substring(2); const value = argv[i + 1]; if (value && !value.startsWith('--')) { args[key] = value; i++; // Skip the value in next iteration } else { args[key] = 'true'; // Boolean flag } } } } return args; } /** * Get configuration from command line arguments, environment variables, or defaults * Priority: CLI args > Environment variables > Defaults */ export function getConfig() { console.log('[CONFIG] Starting configuration initialization...'); // Parse command line arguments const args = parseArgs(); console.log('[CONFIG] Command line arguments:', Object.keys(args).length > 0 ? args : 'none'); // Log all relevant environment variables const envVars = { TODO_API_BASE_URL: process.env.TODO_API_BASE_URL, TODO_API_TIMEOUT: process.env.TODO_API_TIMEOUT, LOG_LEVEL: process.env.LOG_LEVEL, TODO_API_TOKEN: process.env.TODO_API_TOKEN, TODO_HTTP_PORT: process.env.TODO_HTTP_PORT, TODO_HTTP_HOST: process.env.TODO_HTTP_HOST, TODO_SESSION_TIMEOUT: process.env.TODO_SESSION_TIMEOUT, TODO_DNS_PROTECTION: process.env.TODO_DNS_PROTECTION, TODO_ALLOWED_ORIGINS: process.env.TODO_ALLOWED_ORIGINS, TODO_MAX_CONNECTIONS: process.env.TODO_MAX_CONNECTIONS, NODE_ENV: process.env.NODE_ENV, PWD: process.env.PWD }; console.log('[CONFIG] Environment variables detected:', { TODO_API_BASE_URL: envVars.TODO_API_BASE_URL || 'undefined', TODO_API_TIMEOUT: envVars.TODO_API_TIMEOUT || 'undefined', LOG_LEVEL: envVars.LOG_LEVEL || 'undefined', TODO_API_TOKEN: envVars.TODO_API_TOKEN ? `${envVars.TODO_API_TOKEN.substring(0, 8)}...` : 'NOT_SET', TODO_HTTP_PORT: envVars.TODO_HTTP_PORT || 'undefined', TODO_HTTP_HOST: envVars.TODO_HTTP_HOST || 'undefined', TODO_SESSION_TIMEOUT: envVars.TODO_SESSION_TIMEOUT || 'undefined', TODO_DNS_PROTECTION: envVars.TODO_DNS_PROTECTION || 'undefined', TODO_ALLOWED_ORIGINS: envVars.TODO_ALLOWED_ORIGINS || 'undefined', TODO_MAX_CONNECTIONS: envVars.TODO_MAX_CONNECTIONS || 'undefined', NODE_ENV: envVars.NODE_ENV || 'undefined', PWD: envVars.PWD || 'undefined' }); // Priority: CLI args > Environment variables > Defaults let apiBaseUrl = args['api-base-url'] || args['base-url'] || process.env.TODO_API_BASE_URL || 'https://todo4ai.org/todo-for-ai/api/v1'; // Normalize baseURL - ensure it doesn't end with a slash for consistent axios behavior if (apiBaseUrl.endsWith('/')) { apiBaseUrl = apiBaseUrl.slice(0, -1); } // Parse HTTP configuration const httpPort = parseInt(args['http-port'] || process.env.TODO_HTTP_PORT || '3000', 10); const httpHost = args['http-host'] || process.env.TODO_HTTP_HOST || '127.0.0.1'; const sessionTimeout = parseInt(args['session-timeout'] || process.env.TODO_SESSION_TIMEOUT || '300000', 10); const enableDnsRebindingProtection = args['dns-protection'] !== undefined ? args['dns-protection'] !== 'false' && args['dns-protection'] !== '0' : process.env.TODO_DNS_PROTECTION !== undefined ? process.env.TODO_DNS_PROTECTION !== 'false' && process.env.TODO_DNS_PROTECTION !== '0' : false; // Parse allowed origins const allowedOriginsStr = args['allowed-origins'] || process.env.TODO_ALLOWED_ORIGINS; const allowedOrigins = allowedOriginsStr ? allowedOriginsStr.split(',').map(o => o.trim()) : ['http://localhost:*', 'https://localhost:*']; const maxConnections = parseInt(args['max-connections'] || process.env.TODO_MAX_CONNECTIONS || '100', 10); const config = { apiBaseUrl, apiTimeout: parseInt(args['api-timeout'] || args['timeout'] || process.env.TODO_API_TIMEOUT || '10000', 10), logLevel: (args['log-level'] || process.env.LOG_LEVEL || 'info'), transport: (args['transport'] || process.env.TODO_TRANSPORT || 'auto'), httpPort, httpHost, sessionTimeout, enableDnsRebindingProtection, allowedOrigins, maxConnections, }; console.log('[CONFIG] Base configuration created:', { apiBaseUrl: config.apiBaseUrl, apiTimeout: config.apiTimeout, logLevel: config.logLevel, httpPort: config.httpPort, httpHost: config.httpHost, sessionTimeout: config.sessionTimeout, enableDnsRebindingProtection: config.enableDnsRebindingProtection, allowedOrigins: config.allowedOrigins, maxConnections: config.maxConnections, isDefaultBaseUrl: !args['api-base-url'] && !args['base-url'] && !process.env.TODO_API_BASE_URL, isDefaultTimeout: !args['api-timeout'] && !args['timeout'] && !process.env.TODO_API_TIMEOUT, isDefaultLogLevel: !args['log-level'] && !process.env.LOG_LEVEL, isDefaultHttpPort: !args['http-port'] && !process.env.TODO_HTTP_PORT, configSource: { baseUrl: args['api-base-url'] || args['base-url'] ? 'args' : (process.env.TODO_API_BASE_URL ? 'env' : 'default'), timeout: args['api-timeout'] || args['timeout'] ? 'args' : (process.env.TODO_API_TIMEOUT ? 'env' : 'default'), logLevel: args['log-level'] ? 'args' : (process.env.LOG_LEVEL ? 'env' : 'default'), httpPort: args['http-port'] ? 'args' : (process.env.TODO_HTTP_PORT ? 'env' : 'default') } }); // Handle API token - Priority: CLI args > Environment variables const apiToken = args['api-token'] || args['token'] || process.env.TODO_API_TOKEN; if (apiToken) { config.apiToken = apiToken; console.log('[CONFIG] API token found and added to config:', { tokenLength: config.apiToken.length, tokenPrefix: config.apiToken.substring(0, 8) + '...', tokenSuffix: '...' + config.apiToken.substring(config.apiToken.length - 4), tokenSource: args['api-token'] || args['token'] ? 'args' : 'env' }); } else { console.log('[CONFIG] No API token found in command line arguments or environment variables'); } console.log('[CONFIG] Final configuration summary:', { apiBaseUrl: config.apiBaseUrl, apiTimeout: config.apiTimeout, logLevel: config.logLevel, httpPort: config.httpPort, httpHost: config.httpHost, sessionTimeout: config.sessionTimeout, enableDnsRebindingProtection: config.enableDnsRebindingProtection, allowedOriginsCount: config.allowedOrigins.length, maxConnections: config.maxConnections, hasApiToken: !!config.apiToken, apiTokenPrefix: config.apiToken ? `${config.apiToken.substring(0, 8)}...` : 'NOT_SET', configurationComplete: true }); return config; } /** * Validate configuration */ export function validateConfig(config) { if (!config.apiBaseUrl) { throw new Error('TODO_API_BASE_URL is required'); } if (!config.apiBaseUrl.startsWith('http')) { throw new Error('TODO_API_BASE_URL must be a valid HTTP URL'); } if (!config.apiToken) { throw new Error('API token is required. Please provide --api-token argument or set TODO_API_TOKEN environment variable'); } if (config.apiTimeout < 1000) { throw new Error('TODO_API_TIMEOUT must be at least 1000ms'); } const validLogLevels = ['debug', 'info', 'warn', 'error']; if (!validLogLevels.includes(config.logLevel)) { throw new Error(`LOG_LEVEL must be one of: ${validLogLevels.join(', ')}`); } // Validate HTTP configuration if (config.httpPort < 1 || config.httpPort > 65535) { throw new Error('HTTP port must be between 1 and 65535'); } if (config.sessionTimeout < 10000) { throw new Error('Session timeout must be at least 10000ms (10 seconds)'); } if (config.maxConnections < 1) { throw new Error('Max connections must be at least 1'); } if (!config.httpHost.match(/^[a-zA-Z0-9.-]+$/)) { throw new Error('HTTP host must be a valid hostname or IP address'); } } export const CONFIG = getConfig(); validateConfig(CONFIG); //# sourceMappingURL=config.js.map