diff --git a/src/index.ts b/src/index.ts index 30a0a49..32ff4df 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ 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(); @@ -19,7 +20,7 @@ const config = { try { const parsedConfig = JSON.parse(configArg); return parsedConfig.DISCORD_TOKEN; - } catch (e) { + } catch (err) { // If not valid JSON, try using the string directly return configArg; } @@ -27,8 +28,8 @@ const config = { } // Then try environment variable return process.env.DISCORD_TOKEN; - } catch (error) { - console.error('Error parsing config:', error); + } catch (err) { + error('Error parsing config: ' + String(err)); return null; } })(), @@ -53,7 +54,7 @@ const config = { }; if (!config.DISCORD_TOKEN) { - console.error('Discord token not found. Please provide it via --config argument or environment variable.'); + error('Discord token not found. Please provide it via --config argument or environment variable.'); process.exit(1); } @@ -77,12 +78,16 @@ const autoLogin = async () => { if (token) { try { await client.login(token); - console.log('Successfully logged in to Discord'); - } catch (error) { - console.error("Auto-login failed:", error); + 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 { - console.log("No Discord token found in config, skipping auto-login"); + info("No Discord token found in config, skipping auto-login"); } }; @@ -90,13 +95,13 @@ const autoLogin = async () => { const initializeTransport = () => { switch (config.TRANSPORT.toLowerCase()) { case 'http': - console.log(`Initializing HTTP transport on port ${config.HTTP_PORT}`); + info(`Initializing HTTP transport on port ${config.HTTP_PORT}`); return new StreamableHttpTransport(config.HTTP_PORT); case 'stdio': - console.log('Initializing stdio transport'); + info('Initializing stdio transport'); return new StdioTransport(); default: - console.error(`Unknown transport type: ${config.TRANSPORT}. Falling back to stdio.`); + error(`Unknown transport type: ${config.TRANSPORT}. Falling back to stdio.`); return new StdioTransport(); } }; @@ -110,8 +115,8 @@ const mcpServer = new DiscordMCPServer(client, transport); try { await mcpServer.start(); - console.log('MCP server started successfully'); -} catch (error) { - console.error('Failed to start MCP server:', error); + info('MCP server started successfully'); +} catch (err) { + error('Failed to start MCP server: ' + String(err)); process.exit(1); } \ No newline at end of file diff --git a/src/logger.ts b/src/logger.ts new file mode 100644 index 0000000..70a8941 --- /dev/null +++ b/src/logger.ts @@ -0,0 +1,19 @@ +export function log(message: string, level: 'info' | 'error' = 'info') { + const logMessage = { + jsonrpc: '2.0', + method: 'log', + params: { + level, + message + } + }; + process.stdout.write(JSON.stringify(logMessage) + '\n'); +} + +export function info(message: string) { + log(message, 'info'); +} + +export function error(message: string) { + log(message, 'error'); +} \ No newline at end of file diff --git a/src/tools/login.ts b/src/tools/login.ts index 213f19b..73b3de3 100644 --- a/src/tools/login.ts +++ b/src/tools/login.ts @@ -15,7 +15,7 @@ export const loginHandler: ToolHandler = async (args, { client }) => { // loginHandler doesn't directly handle token, it needs to be set before invocation if (!client.token) { return { - content: [{ type: "text", text: "Discord token not configured. Cannot log in." }], + content: [{ type: "text", text: "Discord token not configured. Cannot log in. Please check the following:\n1. Make sure the token is correctly set in your config or environment variables.\n\n2. Ensure all required privileged intents (Message Content, Server Members, Presence) are enabled in the Discord Developer Portal for your bot application." }], isError: true }; } diff --git a/src/transport.ts b/src/transport.ts index a2f462d..80ed783 100644 --- a/src/transport.ts +++ b/src/transport.ts @@ -26,6 +26,7 @@ import { deleteWebhookHandler } from './tools/tools.js'; import { Client } from "discord.js"; +import { info, error } from './logger.js'; export interface MCPTransport { start(server: Server): Promise; @@ -64,9 +65,9 @@ export class StreamableHttpTransport implements MCPTransport { private setupEndpoints() { // Handler for POST requests this.app.post('/mcp', (req: Request, res: Response) => { - console.log('Received MCP request:', req.body); + info('Received MCP request: ' + JSON.stringify(req.body)); this.handleMcpRequest(req, res).catch(error => { - console.error('Unhandled error in MCP request:', error); + error('Unhandled error in MCP request: ' + String(error)); }); }); @@ -98,7 +99,7 @@ export class StreamableHttpTransport implements MCPTransport { }); } - console.log('Request body:', JSON.stringify(req.body)); + info('Request body: ' + JSON.stringify(req.body)); // Handle all tool requests in a generic way if (!req.body.method) { @@ -224,7 +225,7 @@ export class StreamableHttpTransport implements MCPTransport { }); } - console.log(`Request for ${method} handled successfully`); + info(`Request for ${method} handled successfully`); // Handle the case where tool handlers return { content, isError } if (result && typeof result === 'object' && 'content' in result) { @@ -257,34 +258,32 @@ export class StreamableHttpTransport implements MCPTransport { result: result }); - } catch (error) { - console.error('Error processing tool request:', error); - + } catch (err) { + error('Error processing tool request: ' + String(err)); // Handle validation errors - if (error && typeof error === 'object' && 'name' in error && error.name === 'ZodError') { + if (err && typeof err === 'object' && 'name' in err && err.name === 'ZodError') { return res.status(400).json({ jsonrpc: '2.0', error: { code: -32602, - message: `Invalid parameters: ${error && typeof error === 'object' && 'message' in error ? String(error.message) : 'Unknown validation error'}`, + message: `Invalid parameters: ${err && typeof err === 'object' && 'message' in err ? String((err as any).message) : 'Unknown validation error'}`, }, id: req.body?.id || null, }); } - // Handle all other errors return res.status(500).json({ jsonrpc: '2.0', error: { code: -32603, - message: error instanceof Error ? error.message : 'Unknown error', + message: err instanceof Error ? err.message : 'Unknown error', }, id: req.body?.id || null, }); } - } catch (error) { - console.error('Error handling MCP request:', error); + } catch (err) { + error('Error handling MCP request: ' + String(err)); if (!res.headersSent) { res.status(500).json({ jsonrpc: '2.0', @@ -300,7 +299,7 @@ export class StreamableHttpTransport implements MCPTransport { async start(server: Server): Promise { this.server = server; - console.log('Starting HTTP transport with server:', !!this.server); + info('Starting HTTP transport with server: ' + String(!!this.server)); // Try to get client from the DiscordMCPServer instance // First, check if the server is passed from DiscordMCPServer @@ -311,25 +310,25 @@ export class StreamableHttpTransport implements MCPTransport { if (anyServer._context?.client) { client = anyServer._context.client; - console.log('Found client in server._context'); + info('Found client in server._context'); } // Also check if the server object has client directly else if (anyServer.client instanceof Client) { client = anyServer.client; - console.log('Found client directly on server object'); + info('Found client directly on server object'); } // Look in parent object if available else if (anyServer._parent?.client instanceof Client) { client = anyServer._parent.client; - console.log('Found client in server._parent'); + info('Found client in server._parent'); } if (client) { this.toolContext = createToolContext(client); - console.log('Tool context initialized with Discord client'); + info('Tool context initialized with Discord client'); } else { // Create a dummy client for testing - allows list_tools to work - console.log('Unable to get Discord client. Creating tool context without client.'); + info('Unable to get Discord client. Creating tool context without client.'); this.toolContext = createToolContext({} as Client); } } @@ -341,11 +340,11 @@ export class StreamableHttpTransport implements MCPTransport { // Connect the transport await this.server.connect(this.transport); - console.log('Transport connected'); + info('Transport connected'); return new Promise((resolve) => { this.httpServer = this.app.listen(this.port, () => { - console.log(`MCP Server listening on port ${this.port}`); + info(`MCP Server listening on port ${this.port}`); resolve(); }); }); @@ -365,7 +364,7 @@ export class StreamableHttpTransport implements MCPTransport { if (this.httpServer) { return new Promise((resolve) => { this.httpServer.close(() => { - console.log('HTTP server closed'); + info('HTTP server closed'); this.httpServer = null; resolve(); });