Skip to main content
Glama

Discord Agent MCP

by aj-geddes
automod.ts22.2 kB
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { DiscordClientManager } from "../discord/client.js"; import { Logger } from "../utils/logger.js"; import { z } from "zod"; import { PermissionDeniedError, InvalidInputError, } from "../errors/discord.js"; import { validateGuildAccess } from "../utils/guild-validation.js"; import { PermissionFlagsBits, AutoModerationRuleTriggerType, AutoModerationRuleEventType, AutoModerationActionType, AutoModerationRuleKeywordPresetType, } from "discord.js"; export function registerAutoModerationTools( server: McpServer, discordManager: DiscordClientManager, logger: Logger, ) { // List Auto-Moderation Rules Tool server.registerTool( "list_automod_rules", { title: "List Auto-Moderation Rules", description: "Get all auto-moderation rules for a guild", inputSchema: { guildId: z.string().describe("Guild ID"), }, outputSchema: { success: z.boolean(), rules: z .array( z.object({ id: z.string(), name: z.string(), enabled: z.boolean(), creatorId: z.string(), triggerType: z.string(), eventType: z.string(), exemptRoles: z.array(z.string()), exemptChannels: z.array(z.string()), }), ) .optional(), count: z.number().optional(), error: z.string().optional(), }, }, async ({ guildId }) => { try { const client = discordManager.getClient(); const guild = await validateGuildAccess(client, guildId); // Check permissions const botMember = await guild.members.fetch(client.user!.id); if (!botMember.permissions.has(PermissionFlagsBits.ManageGuild)) { throw new PermissionDeniedError("ManageGuild", guildId); } // Fetch auto-mod rules const rules = await guild.autoModerationRules.fetch(); const ruleList = rules.map((rule) => ({ id: rule.id, name: rule.name, enabled: rule.enabled, creatorId: rule.creatorId, triggerType: AutoModerationRuleTriggerType[rule.triggerType], eventType: AutoModerationRuleEventType[rule.eventType], exemptRoles: rule.exemptRoles.map((r) => r.id), exemptChannels: rule.exemptChannels.map((c) => c.id), })); const output = { success: true, rules: ruleList, count: ruleList.length, }; logger.info("Listed auto-mod rules", { guildId, count: ruleList.length }); return { content: [ { type: "text" as const, text: `Found ${ruleList.length} auto-moderation rules in ${guild.name}`, }, ], structuredContent: output, }; } catch (error: any) { logger.error("Failed to list auto-mod rules", { error: error.message, guildId, }); return { content: [ { type: "text" as const, text: `Error: ${error.message}`, }, ], structuredContent: { success: false, error: error.message, }, }; } }, ); // Get Auto-Moderation Rule Details Tool server.registerTool( "get_automod_rule", { title: "Get Auto-Moderation Rule Details", description: "Get detailed information about a specific auto-mod rule", inputSchema: { guildId: z.string().describe("Guild ID"), ruleId: z.string().describe("Auto-moderation rule ID"), }, outputSchema: { success: z.boolean(), rule: z .object({ id: z.string(), name: z.string(), enabled: z.boolean(), creatorId: z.string(), triggerType: z.string(), eventType: z.string(), actions: z.array(z.any()), triggerMetadata: z.any().optional(), exemptRoles: z.array(z.string()), exemptChannels: z.array(z.string()), }) .optional(), error: z.string().optional(), }, }, async ({ guildId, ruleId }) => { try { const client = discordManager.getClient(); const guild = await validateGuildAccess(client, guildId); // Check permissions const botMember = await guild.members.fetch(client.user!.id); if (!botMember.permissions.has(PermissionFlagsBits.ManageGuild)) { throw new PermissionDeniedError("ManageGuild", guildId); } // Fetch rule const rule = await guild.autoModerationRules.fetch(ruleId); if (!rule) { throw new InvalidInputError("ruleId", "Rule not found"); } const ruleDetails = { id: rule.id, name: rule.name, enabled: rule.enabled, creatorId: rule.creatorId, triggerType: AutoModerationRuleTriggerType[rule.triggerType], eventType: AutoModerationRuleEventType[rule.eventType], actions: rule.actions.map((action) => ({ type: AutoModerationActionType[action.type], metadata: action.metadata, })), triggerMetadata: rule.triggerMetadata, exemptRoles: rule.exemptRoles.map((r) => r.id), exemptChannels: rule.exemptChannels.map((c) => c.id), }; const output = { success: true, rule: ruleDetails, }; logger.info("Fetched auto-mod rule details", { guildId, ruleId }); return { content: [ { type: "text" as const, text: `Auto-mod rule "${rule.name}" details retrieved`, }, ], structuredContent: output, }; } catch (error: any) { logger.error("Failed to get auto-mod rule", { error: error.message, guildId, ruleId, }); return { content: [ { type: "text" as const, text: `Error: ${error.message}`, }, ], structuredContent: { success: false, error: error.message, }, }; } }, ); // Create Auto-Moderation Rule Tool server.registerTool( "create_automod_rule", { title: "Create Auto-Moderation Rule", description: "Create a new auto-moderation rule for keyword filtering, spam detection, or content moderation", inputSchema: { guildId: z.string().describe("Guild ID"), name: z.string().min(1).max(100).describe("Rule name"), enabled: z .boolean() .optional() .describe("Enable rule immediately (default: true)"), triggerType: z .enum([ "KEYWORD", "SPAM", "KEYWORD_PRESET", "MENTION_SPAM", "MEMBER_PROFILE", ]) .describe("Trigger type for the rule"), eventType: z .enum(["MESSAGE_SEND", "MEMBER_UPDATE"]) .optional() .describe("Event type (default: MESSAGE_SEND)"), keywords: z .array(z.string()) .optional() .describe("Keywords to filter (for KEYWORD trigger)"), regexPatterns: z .array(z.string()) .optional() .describe("Regex patterns (for KEYWORD trigger)"), allowList: z .array(z.string()) .optional() .describe("Keywords to allow (exceptions)"), presets: z .array(z.enum(["PROFANITY", "SEXUAL_CONTENT", "SLURS"])) .optional() .describe("Preset keyword lists (for KEYWORD_PRESET trigger)"), mentionLimit: z .number() .min(1) .max(50) .optional() .describe("Max mentions per message (for MENTION_SPAM trigger)"), actions: z .array( z.object({ type: z.enum(["BLOCK_MESSAGE", "SEND_ALERT_MESSAGE", "TIMEOUT"]), channelId: z.string().optional(), durationSeconds: z.number().optional(), customMessage: z.string().optional(), }), ) .describe("Actions to take when rule triggers"), exemptRoles: z .array(z.string()) .optional() .describe("Role IDs exempt from this rule"), exemptChannels: z .array(z.string()) .optional() .describe("Channel IDs exempt from this rule"), reason: z.string().optional().describe("Audit log reason"), }, outputSchema: { success: z.boolean(), rule: z .object({ id: z.string(), name: z.string(), enabled: z.boolean(), triggerType: z.string(), }) .optional(), error: z.string().optional(), }, }, async ({ guildId, name, enabled = true, triggerType, eventType = "MESSAGE_SEND", keywords, regexPatterns, allowList, presets, mentionLimit, actions, exemptRoles, exemptChannels, reason, }) => { try { const client = discordManager.getClient(); const guild = await validateGuildAccess(client, guildId); // Check permissions const botMember = await guild.members.fetch(client.user!.id); if (!botMember.permissions.has(PermissionFlagsBits.ManageGuild)) { throw new PermissionDeniedError("ManageGuild", guildId); } // Map trigger type const triggerTypeMap: Record<string, AutoModerationRuleTriggerType> = { KEYWORD: AutoModerationRuleTriggerType.Keyword, SPAM: AutoModerationRuleTriggerType.Spam, KEYWORD_PRESET: AutoModerationRuleTriggerType.KeywordPreset, MENTION_SPAM: AutoModerationRuleTriggerType.MentionSpam, MEMBER_PROFILE: AutoModerationRuleTriggerType.MemberProfile, }; const eventTypeMap: Record<string, AutoModerationRuleEventType> = { MESSAGE_SEND: AutoModerationRuleEventType.MessageSend, MEMBER_UPDATE: AutoModerationRuleEventType.MemberUpdate, }; const presetMap: Record< string, AutoModerationRuleKeywordPresetType > = { PROFANITY: AutoModerationRuleKeywordPresetType.Profanity, SEXUAL_CONTENT: AutoModerationRuleKeywordPresetType.SexualContent, SLURS: AutoModerationRuleKeywordPresetType.Slurs, }; const actionTypeMap: Record<string, AutoModerationActionType> = { BLOCK_MESSAGE: AutoModerationActionType.BlockMessage, SEND_ALERT_MESSAGE: AutoModerationActionType.SendAlertMessage, TIMEOUT: AutoModerationActionType.Timeout, }; // Build trigger metadata based on trigger type const triggerMetadata: any = {}; if (triggerType === "KEYWORD") { if (keywords) triggerMetadata.keywordFilter = keywords; if (regexPatterns) triggerMetadata.regexPatterns = regexPatterns; if (allowList) triggerMetadata.allowList = allowList; } else if (triggerType === "KEYWORD_PRESET") { if (presets) { triggerMetadata.presets = presets.map((p) => presetMap[p]); } if (allowList) triggerMetadata.allowList = allowList; } else if (triggerType === "MENTION_SPAM") { if (mentionLimit) triggerMetadata.mentionTotalLimit = mentionLimit; } // Build actions const mappedActions = actions.map((action) => { const baseAction: any = { type: actionTypeMap[action.type], }; if (action.type === "SEND_ALERT_MESSAGE" && action.channelId) { baseAction.metadata = { channelId: action.channelId }; if (action.customMessage) { baseAction.metadata.customMessage = action.customMessage; } } else if (action.type === "TIMEOUT" && action.durationSeconds) { baseAction.metadata = { durationSeconds: action.durationSeconds }; } return baseAction; }); // Create the rule const rule = await guild.autoModerationRules.create({ name, enabled, triggerType: triggerTypeMap[triggerType], eventType: eventTypeMap[eventType], triggerMetadata: Object.keys(triggerMetadata).length > 0 ? triggerMetadata : undefined, actions: mappedActions, exemptRoles: exemptRoles || [], exemptChannels: exemptChannels || [], reason, }); const output = { success: true, rule: { id: rule.id, name: rule.name, enabled: rule.enabled, triggerType: AutoModerationRuleTriggerType[rule.triggerType], }, }; logger.info("Created auto-mod rule", { guildId, ruleId: rule.id, name, }); return { content: [ { type: "text" as const, text: `Created auto-mod rule "${rule.name}" in ${guild.name}`, }, ], structuredContent: output, }; } catch (error: any) { logger.error("Failed to create auto-mod rule", { error: error.message, guildId, name, }); return { content: [ { type: "text" as const, text: `Error: ${error.message}`, }, ], structuredContent: { success: false, error: error.message, }, }; } }, ); // Modify Auto-Moderation Rule Tool server.registerTool( "modify_automod_rule", { title: "Modify Auto-Moderation Rule", description: "Update an existing auto-moderation rule's settings", inputSchema: { guildId: z.string().describe("Guild ID"), ruleId: z.string().describe("Auto-moderation rule ID"), name: z.string().min(1).max(100).optional().describe("New rule name"), enabled: z.boolean().optional().describe("Enable/disable rule"), keywords: z .array(z.string()) .optional() .describe("Updated keywords (replaces existing)"), regexPatterns: z .array(z.string()) .optional() .describe("Updated regex patterns (replaces existing)"), allowList: z .array(z.string()) .optional() .describe("Updated allow list (replaces existing)"), mentionLimit: z .number() .min(1) .max(50) .optional() .describe("Updated mention limit"), actions: z .array( z.object({ type: z.enum(["BLOCK_MESSAGE", "SEND_ALERT_MESSAGE", "TIMEOUT"]), channelId: z.string().optional(), durationSeconds: z.number().optional(), customMessage: z.string().optional(), }), ) .optional() .describe("Updated actions (replaces existing)"), exemptRoles: z .array(z.string()) .optional() .describe("Updated exempt roles (replaces existing)"), exemptChannels: z .array(z.string()) .optional() .describe("Updated exempt channels (replaces existing)"), reason: z.string().optional().describe("Audit log reason"), }, outputSchema: { success: z.boolean(), rule: z .object({ id: z.string(), name: z.string(), enabled: z.boolean(), }) .optional(), error: z.string().optional(), }, }, async ({ guildId, ruleId, name, enabled, keywords, regexPatterns, allowList, mentionLimit, actions, exemptRoles, exemptChannels, reason, }) => { try { const client = discordManager.getClient(); const guild = await validateGuildAccess(client, guildId); // Check permissions const botMember = await guild.members.fetch(client.user!.id); if (!botMember.permissions.has(PermissionFlagsBits.ManageGuild)) { throw new PermissionDeniedError("ManageGuild", guildId); } // Fetch existing rule const rule = await guild.autoModerationRules.fetch(ruleId); if (!rule) { throw new InvalidInputError("ruleId", "Rule not found"); } const actionTypeMap: Record<string, AutoModerationActionType> = { BLOCK_MESSAGE: AutoModerationActionType.BlockMessage, SEND_ALERT_MESSAGE: AutoModerationActionType.SendAlertMessage, TIMEOUT: AutoModerationActionType.Timeout, }; // Build updates const updates: any = {}; if (name !== undefined) updates.name = name; if (enabled !== undefined) updates.enabled = enabled; // Update trigger metadata if provided if ( keywords !== undefined || regexPatterns !== undefined || allowList !== undefined || mentionLimit !== undefined ) { updates.triggerMetadata = { ...rule.triggerMetadata }; if (keywords !== undefined) updates.triggerMetadata.keywordFilter = keywords; if (regexPatterns !== undefined) updates.triggerMetadata.regexPatterns = regexPatterns; if (allowList !== undefined) updates.triggerMetadata.allowList = allowList; if (mentionLimit !== undefined) updates.triggerMetadata.mentionTotalLimit = mentionLimit; } // Update actions if provided if (actions !== undefined) { updates.actions = actions.map((action) => { const baseAction: any = { type: actionTypeMap[action.type], }; if (action.type === "SEND_ALERT_MESSAGE" && action.channelId) { baseAction.metadata = { channelId: action.channelId }; if (action.customMessage) { baseAction.metadata.customMessage = action.customMessage; } } else if (action.type === "TIMEOUT" && action.durationSeconds) { baseAction.metadata = { durationSeconds: action.durationSeconds }; } return baseAction; }); } if (exemptRoles !== undefined) updates.exemptRoles = exemptRoles; if (exemptChannels !== undefined) updates.exemptChannels = exemptChannels; if (reason !== undefined) updates.reason = reason; // Update the rule const updated = await rule.edit(updates); const output = { success: true, rule: { id: updated.id, name: updated.name, enabled: updated.enabled, }, }; logger.info("Modified auto-mod rule", { guildId, ruleId }); return { content: [ { type: "text" as const, text: `Updated auto-mod rule "${updated.name}" in ${guild.name}`, }, ], structuredContent: output, }; } catch (error: any) { logger.error("Failed to modify auto-mod rule", { error: error.message, guildId, ruleId, }); return { content: [ { type: "text" as const, text: `Error: ${error.message}`, }, ], structuredContent: { success: false, error: error.message, }, }; } }, ); // Delete Auto-Moderation Rule Tool server.registerTool( "delete_automod_rule", { title: "Delete Auto-Moderation Rule", description: "Delete an auto-moderation rule from the guild", inputSchema: { guildId: z.string().describe("Guild ID"), ruleId: z.string().describe("Auto-moderation rule ID to delete"), reason: z.string().optional().describe("Audit log reason"), }, outputSchema: { success: z.boolean(), ruleId: z.string().optional(), error: z.string().optional(), }, }, async ({ guildId, ruleId, reason }) => { try { const client = discordManager.getClient(); const guild = await validateGuildAccess(client, guildId); // Check permissions const botMember = await guild.members.fetch(client.user!.id); if (!botMember.permissions.has(PermissionFlagsBits.ManageGuild)) { throw new PermissionDeniedError("ManageGuild", guildId); } // Fetch and delete rule const rule = await guild.autoModerationRules.fetch(ruleId); if (!rule) { throw new InvalidInputError("ruleId", "Rule not found"); } const ruleName = rule.name; await rule.delete(reason); const output = { success: true, ruleId: ruleId, }; logger.info("Deleted auto-mod rule", { guildId, ruleId }); return { content: [ { type: "text" as const, text: `Deleted auto-mod rule "${ruleName}" from ${guild.name}`, }, ], structuredContent: output, }; } catch (error: any) { logger.error("Failed to delete auto-mod rule", { error: error.message, guildId, ruleId, }); return { content: [ { type: "text" as const, text: `Error: ${error.message}`, }, ], structuredContent: { success: false, error: error.message, }, }; } }, ); }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/aj-geddes/discord-agent-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server