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 { 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);
}

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
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
};
}

View File

@ -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<void>;
@ -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<void> {
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();
});