@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
JavaScript
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