import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import { AuditLogEvent } from 'discord.js';
import { getDiscordClient } from '../utils/discord-client.js';
import { withErrorHandling } from '../utils/error-handler.js';
export function registerAuditTools(server: McpServer): void {
// Get audit logs
server.tool(
'get_audit_logs',
'Get audit logs from a server',
{
guildId: z.string().describe('The ID of the server (guild)'),
limit: z.number().optional().describe('Number of entries to fetch (1-100, default 50)'),
userId: z.string().optional().describe('Filter by user who performed action'),
actionType: z.string().optional().describe('Filter by action type (e.g., MEMBER_BAN_ADD, CHANNEL_CREATE)'),
before: z.string().optional().describe('Get entries before this audit log entry ID'),
},
async ({ guildId, limit = 50, userId, actionType, before }) => {
const result = await withErrorHandling(async () => {
const client = await getDiscordClient();
const guild = await client.guilds.fetch(guildId);
const fetchOptions: { limit: number; user?: string; type?: AuditLogEvent; before?: string } = {
limit: Math.min(Math.max(1, limit), 100),
};
if (userId) fetchOptions.user = userId;
if (before) fetchOptions.before = before;
if (actionType) {
const eventType = AuditLogEvent[actionType as keyof typeof AuditLogEvent];
if (eventType !== undefined) fetchOptions.type = eventType;
}
const auditLogs = await guild.fetchAuditLogs(fetchOptions);
return auditLogs.entries.map((entry) => ({
id: entry.id,
action: AuditLogEvent[entry.action],
actionType: entry.actionType,
targetType: entry.targetType,
targetId: entry.targetId,
executorId: entry.executorId,
executor: entry.executor ? {
id: entry.executor.id,
username: entry.executor.username,
} : null,
reason: entry.reason,
createdAt: entry.createdAt.toISOString(),
changes: entry.changes.map((change) => ({
key: change.key,
old: change.old,
new: change.new,
})),
extra: entry.extra,
}));
});
if (!result.success) {
return { content: [{ type: 'text', text: result.error }], isError: true };
}
return { content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }] };
}
);
// List audit log action types
server.tool(
'list_audit_log_types',
'List all available audit log action types',
{},
async () => {
const actionTypes = Object.entries(AuditLogEvent)
.filter(([key]) => isNaN(Number(key)))
.map(([name, value]) => ({ name, value }));
const categorized = {
guild: actionTypes.filter(t => t.name.startsWith('Guild')),
channel: actionTypes.filter(t => t.name.startsWith('Channel') || t.name.startsWith('Thread')),
member: actionTypes.filter(t => t.name.startsWith('Member')),
role: actionTypes.filter(t => t.name.startsWith('Role')),
invite: actionTypes.filter(t => t.name.startsWith('Invite')),
webhook: actionTypes.filter(t => t.name.startsWith('Webhook')),
emoji: actionTypes.filter(t => t.name.startsWith('Emoji')),
message: actionTypes.filter(t => t.name.startsWith('Message')),
integration: actionTypes.filter(t => t.name.startsWith('Integration')),
stageInstance: actionTypes.filter(t => t.name.startsWith('StageInstance')),
sticker: actionTypes.filter(t => t.name.startsWith('Sticker')),
scheduledEvent: actionTypes.filter(t => t.name.startsWith('GuildScheduledEvent')),
autoMod: actionTypes.filter(t => t.name.startsWith('AutoMod')),
};
return {
content: [{ type: 'text', text: JSON.stringify({ actionTypes, categorized }, null, 2) }],
};
}
);
// List auto-moderation rules
server.tool(
'list_automod_rules',
'List all auto-moderation rules in a server',
{
guildId: z.string().describe('The ID of the server (guild)'),
},
async ({ guildId }) => {
const result = await withErrorHandling(async () => {
const client = await getDiscordClient();
const guild = await client.guilds.fetch(guildId);
const rules = await guild.autoModerationRules.fetch();
return rules.map((rule) => ({
id: rule.id,
name: rule.name,
enabled: rule.enabled,
eventType: rule.eventType,
triggerType: rule.triggerType,
triggerMetadata: rule.triggerMetadata,
actions: rule.actions.map((action) => ({
type: action.type,
metadata: action.metadata,
})),
exemptRoles: rule.exemptRoles.map((r) => ({ id: r.id, name: r.name })),
exemptChannels: rule.exemptChannels.map((c) => ({ id: c.id, name: c.name })),
creatorId: rule.creatorId,
}));
});
if (!result.success) {
return { content: [{ type: 'text', text: result.error }], isError: true };
}
return { content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }] };
}
);
// Get auto-moderation rule
server.tool(
'get_automod_rule',
'Get details of a specific auto-moderation rule',
{
guildId: z.string().describe('The ID of the server (guild)'),
ruleId: z.string().describe('The ID of the rule'),
},
async ({ guildId, ruleId }) => {
const result = await withErrorHandling(async () => {
const client = await getDiscordClient();
const guild = await client.guilds.fetch(guildId);
const rule = await guild.autoModerationRules.fetch(ruleId);
return {
id: rule.id,
name: rule.name,
enabled: rule.enabled,
eventType: rule.eventType,
triggerType: rule.triggerType,
triggerMetadata: rule.triggerMetadata,
actions: rule.actions,
exemptRoles: rule.exemptRoles.map((r) => ({ id: r.id, name: r.name })),
exemptChannels: rule.exemptChannels.map((c) => ({ id: c.id, name: c.name })),
creatorId: rule.creatorId,
};
});
if (!result.success) {
return { content: [{ type: 'text', text: result.error }], isError: true };
}
return { content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }] };
}
);
// Delete auto-moderation rule
server.tool(
'delete_automod_rule',
'Delete an auto-moderation rule',
{
guildId: z.string().describe('The ID of the server (guild)'),
ruleId: z.string().describe('The ID of the rule to delete'),
reason: z.string().optional().describe('Reason for deletion'),
},
async ({ guildId, ruleId, reason }) => {
const result = await withErrorHandling(async () => {
const client = await getDiscordClient();
const guild = await client.guilds.fetch(guildId);
const rule = await guild.autoModerationRules.fetch(ruleId);
const ruleName = rule.name;
await rule.delete(reason);
return { ruleId, ruleName, message: 'Auto-moderation rule deleted successfully' };
});
if (!result.success) {
return { content: [{ type: 'text', text: result.error }], isError: true };
}
return { content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }] };
}
);
// Toggle auto-moderation rule
server.tool(
'toggle_automod_rule',
'Enable or disable an auto-moderation rule',
{
guildId: z.string().describe('The ID of the server (guild)'),
ruleId: z.string().describe('The ID of the rule'),
enabled: z.boolean().describe('Enable or disable the rule'),
reason: z.string().optional().describe('Reason for change'),
},
async ({ guildId, ruleId, enabled, reason }) => {
const result = await withErrorHandling(async () => {
const client = await getDiscordClient();
const guild = await client.guilds.fetch(guildId);
const rule = await guild.autoModerationRules.fetch(ruleId);
await rule.edit({ enabled, reason });
return {
ruleId,
ruleName: rule.name,
enabled,
message: `Auto-moderation rule ${enabled ? 'enabled' : 'disabled'} successfully`,
};
});
if (!result.success) {
return { content: [{ type: 'text', text: result.error }], isError: true };
}
return { content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }] };
}
);
}