feat: add logger utility and improve Discord login error message for token and privileged intents

This commit is contained in:
Barry Yip 2025-05-11 15:12:28 +08:00
parent 9c2d990658
commit e10d5166bc
4 changed files with 60 additions and 37 deletions

View File

@ -2,6 +2,7 @@ import { Client, GatewayIntentBits } from "discord.js";
import { config as dotenvConfig } from 'dotenv'; import { config as dotenvConfig } from 'dotenv';
import { DiscordMCPServer } from './server.js'; import { DiscordMCPServer } from './server.js';
import { StdioTransport, StreamableHttpTransport } from './transport.js'; import { StdioTransport, StreamableHttpTransport } from './transport.js';
import { info, error } from './logger.js';
// Load environment variables from .env file if exists // Load environment variables from .env file if exists
dotenvConfig(); dotenvConfig();
@ -19,7 +20,7 @@ const config = {
try { try {
const parsedConfig = JSON.parse(configArg); const parsedConfig = JSON.parse(configArg);
return parsedConfig.DISCORD_TOKEN; return parsedConfig.DISCORD_TOKEN;
} catch (e) { } catch (err) {
// If not valid JSON, try using the string directly // If not valid JSON, try using the string directly
return configArg; return configArg;
} }
@ -27,8 +28,8 @@ const config = {
} }
// Then try environment variable // Then try environment variable
return process.env.DISCORD_TOKEN; return process.env.DISCORD_TOKEN;
} catch (error) { } catch (err) {
console.error('Error parsing config:', error); error('Error parsing config: ' + String(err));
return null; return null;
} }
})(), })(),
@ -53,7 +54,7 @@ const config = {
}; };
if (!config.DISCORD_TOKEN) { 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); process.exit(1);
} }
@ -77,12 +78,16 @@ const autoLogin = async () => {
if (token) { if (token) {
try { try {
await client.login(token); await client.login(token);
console.log('Successfully logged in to Discord'); info('Successfully logged in to Discord');
} catch (error) { } catch (err: any) {
console.error("Auto-login failed:", error); 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 { } 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 = () => { const initializeTransport = () => {
switch (config.TRANSPORT.toLowerCase()) { switch (config.TRANSPORT.toLowerCase()) {
case 'http': 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); return new StreamableHttpTransport(config.HTTP_PORT);
case 'stdio': case 'stdio':
console.log('Initializing stdio transport'); info('Initializing stdio transport');
return new StdioTransport(); return new StdioTransport();
default: 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(); return new StdioTransport();
} }
}; };
@ -110,8 +115,8 @@ const mcpServer = new DiscordMCPServer(client, transport);
try { try {
await mcpServer.start(); await mcpServer.start();
console.log('MCP server started successfully'); info('MCP server started successfully');
} catch (error) { } catch (err) {
console.error('Failed to start MCP server:', error); error('Failed to start MCP server: ' + String(err));
process.exit(1); process.exit(1);
} }

19
src/logger.ts Normal file
View File

@ -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');
}

View File

@ -15,7 +15,7 @@ export const loginHandler: ToolHandler = async (args, { client }) => {
// loginHandler doesn't directly handle token, it needs to be set before invocation // loginHandler doesn't directly handle token, it needs to be set before invocation
if (!client.token) { if (!client.token) {
return { 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 isError: true
}; };
} }

View File

@ -26,6 +26,7 @@ import {
deleteWebhookHandler deleteWebhookHandler
} from './tools/tools.js'; } from './tools/tools.js';
import { Client } from "discord.js"; import { Client } from "discord.js";
import { info, error } from './logger.js';
export interface MCPTransport { export interface MCPTransport {
start(server: Server): Promise<void>; start(server: Server): Promise<void>;
@ -64,9 +65,9 @@ export class StreamableHttpTransport implements MCPTransport {
private setupEndpoints() { private setupEndpoints() {
// Handler for POST requests // Handler for POST requests
this.app.post('/mcp', (req: Request, res: Response) => { 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 => { 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 // Handle all tool requests in a generic way
if (!req.body.method) { 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 } // Handle the case where tool handlers return { content, isError }
if (result && typeof result === 'object' && 'content' in result) { if (result && typeof result === 'object' && 'content' in result) {
@ -257,34 +258,32 @@ export class StreamableHttpTransport implements MCPTransport {
result: result result: result
}); });
} catch (error) { } catch (err) {
console.error('Error processing tool request:', error); error('Error processing tool request: ' + String(err));
// Handle validation errors // 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({ return res.status(400).json({
jsonrpc: '2.0', jsonrpc: '2.0',
error: { error: {
code: -32602, 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, id: req.body?.id || null,
}); });
} }
// Handle all other errors // Handle all other errors
return res.status(500).json({ return res.status(500).json({
jsonrpc: '2.0', jsonrpc: '2.0',
error: { error: {
code: -32603, code: -32603,
message: error instanceof Error ? error.message : 'Unknown error', message: err instanceof Error ? err.message : 'Unknown error',
}, },
id: req.body?.id || null, id: req.body?.id || null,
}); });
} }
} catch (error) { } catch (err) {
console.error('Error handling MCP request:', error); error('Error handling MCP request: ' + String(err));
if (!res.headersSent) { if (!res.headersSent) {
res.status(500).json({ res.status(500).json({
jsonrpc: '2.0', jsonrpc: '2.0',
@ -300,7 +299,7 @@ export class StreamableHttpTransport implements MCPTransport {
async start(server: Server): Promise<void> { async start(server: Server): Promise<void> {
this.server = server; 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 // Try to get client from the DiscordMCPServer instance
// First, check if the server is passed from DiscordMCPServer // First, check if the server is passed from DiscordMCPServer
@ -311,25 +310,25 @@ export class StreamableHttpTransport implements MCPTransport {
if (anyServer._context?.client) { if (anyServer._context?.client) {
client = 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 // Also check if the server object has client directly
else if (anyServer.client instanceof Client) { else if (anyServer.client instanceof Client) {
client = anyServer.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 // Look in parent object if available
else if (anyServer._parent?.client instanceof Client) { else if (anyServer._parent?.client instanceof Client) {
client = anyServer._parent.client; client = anyServer._parent.client;
console.log('Found client in server._parent'); info('Found client in server._parent');
} }
if (client) { if (client) {
this.toolContext = createToolContext(client); this.toolContext = createToolContext(client);
console.log('Tool context initialized with Discord client'); info('Tool context initialized with Discord client');
} else { } else {
// Create a dummy client for testing - allows list_tools to work // 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); this.toolContext = createToolContext({} as Client);
} }
} }
@ -341,11 +340,11 @@ export class StreamableHttpTransport implements MCPTransport {
// Connect the transport // Connect the transport
await this.server.connect(this.transport); await this.server.connect(this.transport);
console.log('Transport connected'); info('Transport connected');
return new Promise((resolve) => { return new Promise((resolve) => {
this.httpServer = this.app.listen(this.port, () => { 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(); resolve();
}); });
}); });
@ -365,7 +364,7 @@ export class StreamableHttpTransport implements MCPTransport {
if (this.httpServer) { if (this.httpServer) {
return new Promise((resolve) => { return new Promise((resolve) => {
this.httpServer.close(() => { this.httpServer.close(() => {
console.log('HTTP server closed'); info('HTTP server closed');
this.httpServer = null; this.httpServer = null;
resolve(); resolve();
}); });