UNPKG

imo-publications-mcp-server

Version:

MCP server for IMO (International Maritime Organization) publications - Node.js TypeScript version

303 lines (263 loc) 10.8 kB
#!/usr/bin/env node import path from 'path'; import { fileURLToPath } from 'url'; import { spawn } from 'child_process'; import fs from 'fs'; import readline from 'readline'; import os from 'os'; // Debug info - write to a debug file const debugFile = path.join(os.tmpdir(), 'imo-publications-debug.log'); const writeDebug = (message) => { try { fs.appendFileSync(debugFile, `${new Date().toISOString()} - ${message}\n`); } catch (err) { // Silently fail if we can't write to the debug file } }; // Start debugging writeDebug('CLI script started'); writeDebug(`Node version: ${process.version}`); writeDebug(`Platform: ${process.platform}`); writeDebug(`CLI Arguments: ${process.argv.join(' ')}`); writeDebug(`Is stdin a TTY: ${process.stdin.isTTY}`); writeDebug(`Is stdout a TTY: ${process.stdout.isTTY}`); writeDebug(`Process PID: ${process.pid}`); writeDebug(`Executable path: ${process.execPath}`); writeDebug(`Current directory: ${process.cwd()}`); // Print debug file location to stderr (not stdout) console.error(`Debug log: ${debugFile}`); const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const packageRoot = path.resolve(__dirname, '..'); writeDebug(`__filename: ${__filename}`); writeDebug(`__dirname: ${__dirname}`); writeDebug(`packageRoot: ${packageRoot}`); // Check if bin/cli.js is executable try { const stats = fs.statSync(__filename); const isExecutable = !!(stats.mode & fs.constants.S_IXUSR); writeDebug(`Is CLI executable: ${isExecutable}`); // Make it executable if it's not if (!isExecutable) { fs.chmodSync(__filename, '755'); writeDebug('Made CLI executable'); } } catch (err) { writeDebug(`Error checking/setting executable: ${err.message}`); } // Check for stored configuration let storedConfig = {}; const configDir = path.join(process.env.HOME || process.env.USERPROFILE, '.imo-publications-mcp'); const configFile = path.join(configDir, 'config.json'); try { if (fs.existsSync(configFile)) { storedConfig = JSON.parse(fs.readFileSync(configFile, 'utf8')); writeDebug('Found stored configuration'); } } catch (err) { console.warn('Warning: Could not read stored configuration.'); writeDebug(`Error reading config: ${err.message}`); } // Parse command line arguments const args = process.argv.slice(2); let mongoUri = storedConfig.mongoUri || ''; let dbName = storedConfig.dbName || 'imo_publications'; let typesenseHost = storedConfig.typesenseHost || ''; let typesensePort = storedConfig.typesensePort || ''; let typesenseProtocol = storedConfig.typesenseProtocol || ''; let typesenseApiKey = storedConfig.typesenseApiKey || ''; let debug = false; let nonInteractive = false; // Detect if we're running under an MCP context (Claude/ChatGPT/etc.) const isMcpContext = !process.stdin.isTTY || process.env.npm_execpath?.includes('npx') || process.env.CLAUDE_API_KEY || args.includes('--non-interactive') || args.includes('-n'); writeDebug(`Detected MCP context: ${isMcpContext}`); if (isMcpContext) { nonInteractive = true; writeDebug('Setting non-interactive mode due to MCP context detection'); } // Process command line arguments for (let i = 0; i < args.length; i++) { const arg = args[i]; if ((arg === '--mongo-uri' || arg === '-m') && i + 1 < args.length) { mongoUri = args[++i]; writeDebug('Found mongo uri in arguments'); } else if ((arg === '--db-name' || arg === '-d') && i + 1 < args.length) { dbName = args[++i]; writeDebug('Found db name in arguments'); } else if ((arg === '--typesense-host' || arg === '-th') && i + 1 < args.length) { typesenseHost = args[++i]; writeDebug('Found typesense host in arguments'); } else if ((arg === '--typesense-port' || arg === '-tp') && i + 1 < args.length) { typesensePort = args[++i]; writeDebug('Found typesense port in arguments'); } else if ((arg === '--typesense-protocol' || arg === '-tpr') && i + 1 < args.length) { typesenseProtocol = args[++i]; writeDebug('Found typesense protocol in arguments'); } else if ((arg === '--typesense-api-key' || arg === '-tk') && i + 1 < args.length) { typesenseApiKey = args[++i]; writeDebug('Found typesense api key in arguments'); } else if (arg === '--debug') { debug = true; writeDebug('Debug mode enabled'); } else if (arg === '--non-interactive' || arg === '-n') { nonInteractive = true; writeDebug('Non-interactive mode enabled via flag'); } else if (arg === '--help' || arg === '-h') { console.log(` IMO Publications MCP Server - International Maritime Organization Publications API Usage: npx imo-publications-mcp-server [options] Options: --mongo-uri, -m <uri> Your MongoDB URI (required if not stored) --db-name, -d <name> Database name (default: imo_publications) --typesense-host, -th <host> Typesense host (required if not stored) --typesense-port, -tp <port> Typesense port (required if not stored) --typesense-protocol, -tpr <protocol> Typesense protocol (http/https) (required if not stored) --typesense-api-key, -tk <key> Typesense API key (required if not stored) --debug Enable debug output --non-interactive, -n Run in non-interactive mode (no prompt) --help, -h Show this help message Example: npx imo-publications-mcp-server --mongo-uri YOUR_MONGO_URI --db-name imo_publications --typesense-host localhost --typesense-port 8108 --typesense-protocol http --typesense-api-key YOUR_API_KEY Environment Variables: MONGODB_URI MongoDB connection string MONGODB_DB_NAME Database name TYPESENSE_HOST Typesense server hostname TYPESENSE_PORT Typesense server port TYPESENSE_PROTOCOL Protocol to use (http/https) TYPESENSE_API_KEY Typesense API key COHERE_API_KEY Cohere API key for reranking OPENAI_API_KEY OpenAI API key for LLM features PERPLEXITY_API_KEY Perplexity API key LLAMA_API_KEY LlamaParse API key `); process.exit(0); } } // Check for required configuration const requiredConfigs = [ { value: mongoUri, name: 'MongoDB URI', arg: '--mongo-uri' }, { value: typesenseHost, name: 'Typesense host', arg: '--typesense-host' }, { value: typesensePort, name: 'Typesense port', arg: '--typesense-port' }, { value: typesenseProtocol, name: 'Typesense protocol', arg: '--typesense-protocol' }, { value: typesenseApiKey, name: 'Typesense API key', arg: '--typesense-api-key' } ]; const missingConfigs = requiredConfigs.filter(config => !config.value); if (missingConfigs.length > 0) { if (nonInteractive) { console.error('Error: Missing required configuration in non-interactive mode:'); missingConfigs.forEach(config => { console.error(` ${config.name}: Use ${config.arg} argument`); }); writeDebug('Exiting: missing configuration in non-interactive mode'); process.exit(1); } // Interactive mode - prompt for missing configuration const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); let currentConfigIndex = 0; const promptForConfig = () => { if (currentConfigIndex >= missingConfigs.length) { rl.close(); // Ask if user wants to save the configuration const saveRl = readline.createInterface({ input: process.stdin, output: process.stdout }); saveRl.question('Would you like to save this configuration for future use? (y/n): ', (saveAnswer) => { saveRl.close(); if (saveAnswer.toLowerCase() === 'y' || saveAnswer.toLowerCase() === 'yes') { try { if (!fs.existsSync(configDir)) { fs.mkdirSync(configDir, { recursive: true }); } const configToSave = { mongoUri, dbName, typesenseHost, typesensePort, typesenseProtocol, typesenseApiKey }; fs.writeFileSync(configFile, JSON.stringify(configToSave, null, 2)); console.log(`Configuration saved to ${configFile}`); writeDebug('Configuration saved'); } catch (err) { console.warn('Warning: Could not save configuration.'); writeDebug(`Error saving config: ${err.message}`); } } startServer(); }); } else { const config = missingConfigs[currentConfigIndex]; rl.question(`Please enter ${config.name}: `, (answer) => { switch (config.arg) { case '--mongo-uri': mongoUri = answer; break; case '--typesense-host': typesenseHost = answer; break; case '--typesense-port': typesensePort = answer; break; case '--typesense-protocol': typesenseProtocol = answer; break; case '--typesense-api-key': typesenseApiKey = answer; break; } currentConfigIndex++; promptForConfig(); }); } }; console.log('Missing required configuration. Please provide the following:'); promptForConfig(); } else { startServer(); } function startServer() { writeDebug('Starting server with configuration'); // Set environment variables for the server process.env.MONGODB_URI = mongoUri; process.env.MONGODB_DB_NAME = dbName; process.env.TYPESENSE_HOST = typesenseHost; process.env.TYPESENSE_PORT = typesensePort; process.env.TYPESENSE_PROTOCOL = typesenseProtocol; process.env.TYPESENSE_API_KEY = typesenseApiKey; writeDebug(`Environment variables set`); // Try to start the server const serverPath = path.join(packageRoot, 'dist', 'index.js'); writeDebug(`Trying to start server at: ${serverPath}`); if (fs.existsSync(serverPath)) { startServerWithPath(serverPath); } else { console.error('Error: Server not found. Please run "npm run build" first.'); writeDebug('Server file not found'); process.exit(1); } } function startServerWithPath(serverPath) { writeDebug(`Starting server process: node ${serverPath}`); const child = spawn('node', [serverPath], { stdio: 'inherit', env: process.env }); child.on('error', (error) => { console.error('Error starting IMO Publications MCP server:', error); writeDebug(`Server error: ${error.message}`); process.exit(1); }); child.on('exit', (code) => { writeDebug(`Server exited with code: ${code}`); process.exit(code); }); }