import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import express from "express";
import { DiscordClientManager } from "../discord/client.js";
import { registerMessagingTools } from "../tools/messaging.js";
import { registerChannelTools } from "../tools/channels.js";
import { registerMemberTools } from "../tools/members.js";
import { registerRoleTools } from "../tools/roles.js";
import { registerServerTools } from "../tools/server.js";
import { registerModerationTools } from "../tools/moderation.js";
import { registerEmojiTools } from "../tools/emojis.js";
import { registerStickerTools } from "../tools/stickers.js";
import { registerScheduledEventTools } from "../tools/scheduled-events.js";
import { registerAutoModerationTools } from "../tools/automod.js";
import { registerApplicationCommandTools } from "../tools/application-commands.js";
import { registerGuildResources } from "../resources/guilds.js";
import { registerModerationPrompts } from "../prompts/moderation.js";
import { registerServerSetupPrompts } from "../prompts/server-setup.js";
import { registerEventPrompts } from "../prompts/events.js";
import { registerPermissionPrompts } from "../prompts/permissions.js";
import { Logger } from "../utils/logger.js";
import { loadConfig } from "./config.js";
async function main() {
const config = loadConfig();
const logger = new Logger(config.logLevel, config.logFormat);
logger.info("Starting Discord MCP Server", {
version: config.serverVersion,
transportMode: config.transportMode,
});
// Initialize Discord client ONCE at startup
const discordManager = new DiscordClientManager(config.discordToken, logger, {
maxReconnectAttempts: config.reconnectMaxRetries,
reconnectBackoffMs: config.reconnectBackoffMs,
});
try {
await discordManager.connect();
logger.info(
"Discord client connected successfully",
discordManager.getStats(),
);
} catch (error: any) {
logger.error("Failed to initialize Discord client", {
error: error.message,
});
process.exit(1);
}
// Create MCP server
const mcpServer = new McpServer({
name: config.serverName,
version: config.serverVersion,
});
// Register tools
logger.info("Registering tools...");
registerMessagingTools(mcpServer, discordManager, logger);
registerChannelTools(mcpServer, discordManager, logger);
registerMemberTools(mcpServer, discordManager, logger);
registerRoleTools(mcpServer, discordManager, logger);
registerServerTools(mcpServer, discordManager, logger);
registerModerationTools(mcpServer, discordManager, logger);
registerEmojiTools(mcpServer, discordManager, logger);
registerStickerTools(mcpServer, discordManager, logger);
registerScheduledEventTools(mcpServer, discordManager, logger);
registerAutoModerationTools(mcpServer, discordManager, logger);
registerApplicationCommandTools(mcpServer, discordManager, logger);
// Register resources
logger.info("Registering resources...");
registerGuildResources(mcpServer, discordManager, logger);
// Register prompts
logger.info("Registering prompts...");
registerModerationPrompts(mcpServer);
registerServerSetupPrompts(mcpServer);
registerEventPrompts(mcpServer);
registerPermissionPrompts(mcpServer);
// Setup transport based on configuration
if (config.transportMode === "stdio") {
logger.info("Starting with stdio transport");
const transport = new StdioServerTransport();
await mcpServer.connect(transport);
logger.info("MCP Server ready (stdio)");
} else {
// HTTP transport with Express
logger.info("Starting with HTTP transport", { port: config.httpPort });
const app = express();
app.use(express.json());
// Health check endpoint
app.get("/health", (_req, res) => {
const stats = discordManager.getStats();
res.json({
status: stats.connected ? "healthy" : "unhealthy",
server: {
name: config.serverName,
version: config.serverVersion,
},
discord: stats,
});
});
// MCP endpoint
app.post("/mcp", async (_req, res) => {
try {
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
enableJsonResponse: true,
});
res.on("close", () => {
transport.close();
});
await mcpServer.connect(transport);
await transport.handleRequest(_req, res, _req.body);
} catch (error: any) {
logger.error("Error handling MCP request", { error: error.message });
if (!res.headersSent) {
res.status(500).json({
jsonrpc: "2.0",
error: {
code: -32603,
message: "Internal server error",
},
id: null,
});
}
}
});
app
.listen(config.httpPort, () => {
logger.info(
`MCP Server running on http://localhost:${config.httpPort}/mcp`,
);
})
.on("error", (error: any) => {
logger.error("Server error", { error: error.message });
process.exit(1);
});
}
// Graceful shutdown
const shutdown = async () => {
logger.info("Shutting down gracefully...");
await discordManager.disconnect();
logger.info("Shutdown complete");
process.exit(0);
};
process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);
// Log unhandled errors
process.on("unhandledRejection", (reason, promise) => {
logger.error("Unhandled Rejection", { reason, promise });
});
process.on("uncaughtException", (error) => {
logger.error("Uncaught Exception", {
error: error.message,
stack: error.stack,
});
process.exit(1);
});
}
main().catch((error) => {
console.error("Fatal error:", error);
process.exit(1);
});