#!/usr/bin/env node import { Client, GatewayIntentBits } from "discord.js"; import { config as dotenvConfig } from 'dotenv'; import { DiscordMCPServer } from './server.js'; import { StdioTransport, StreamableHttpTransport } from './transport.js'; import { info, error } from './logger.js'; // Load environment variables from .env file if exists dotenvConfig(); // Configuration with priority for command line arguments const config = { DISCORD_TOKEN: (() => { try { // First try to get from command line arguments const configIndex = process.argv.indexOf('--config'); if (configIndex !== -1 && configIndex + 1 < process.argv.length) { const configArg = process.argv[configIndex + 1]; // Handle both string and object formats if (typeof configArg === 'string') { try { const parsedConfig = JSON.parse(configArg); return parsedConfig.DISCORD_TOKEN; } catch (err) { // If not valid JSON, try using the string directly return configArg; } } } // Then try environment variable return process.env.DISCORD_TOKEN; } catch (err) { error('Error parsing config: ' + String(err)); return null; } })(), TRANSPORT: (() => { // Check for transport type argument const transportIndex = process.argv.indexOf('--transport'); if (transportIndex !== -1 && transportIndex + 1 < process.argv.length) { return process.argv[transportIndex + 1]; } // Default to stdio return 'stdio'; })(), HTTP_PORT: (() => { // Check for port argument const portIndex = process.argv.indexOf('--port'); if (portIndex !== -1 && portIndex + 1 < process.argv.length) { return parseInt(process.argv[portIndex + 1]); } // Default port for MCP return 8080; })() }; // Create Discord client const client = new Client({ intents: [ GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent ] }); // Save token to client for login handler if (config.DISCORD_TOKEN) { client.token = config.DISCORD_TOKEN; } // Auto-login on startup if token is available const autoLogin = async () => { const token = config.DISCORD_TOKEN; if (token) { try { await client.login(token); info('Successfully logged in to Discord'); } catch (err: any) { if (typeof err.message === 'string' && err.message.includes('Privileged intent provided is not enabled or whitelisted')) { error('Login failed: One or more privileged intents are not enabled in the Discord Developer Portal. Please enable the required intents.'); } else { error('Auto-login failed: ' + String(err)); } } } else { info("No Discord token found in config, skipping auto-login"); } }; // Initialize transport based on configuration const initializeTransport = () => { switch (config.TRANSPORT.toLowerCase()) { case 'http': info(`Initializing HTTP transport on 0.0.0.0:${config.HTTP_PORT}`); return new StreamableHttpTransport(config.HTTP_PORT); case 'stdio': info('Initializing stdio transport'); return new StdioTransport(); default: error(`Unknown transport type: ${config.TRANSPORT}. Falling back to stdio.`); return new StdioTransport(); } }; // Start auto-login process await autoLogin(); // Create and start MCP server with selected transport const transport = initializeTransport(); const mcpServer = new DiscordMCPServer(client, transport); try { await mcpServer.start(); info('MCP server started successfully'); // Keep the Node.js process running if (config.TRANSPORT.toLowerCase() === 'http') { // Send a heartbeat every 30 seconds to keep the process alive setInterval(() => { info('MCP server is running'); }, 30000); // Handle termination signals process.on('SIGINT', async () => { info('Received SIGINT. Shutting down server...'); await mcpServer.stop(); process.exit(0); }); process.on('SIGTERM', async () => { info('Received SIGTERM. Shutting down server...'); await mcpServer.stop(); process.exit(0); }); info('Server running in keep-alive mode. Press Ctrl+C to stop.'); } } catch (err) { error('Failed to start MCP server: ' + String(err)); process.exit(1); }