import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import { GuildScheduledEventEntityType, GuildScheduledEventPrivacyLevel, GuildScheduledEventStatus } from 'discord.js';
import { getDiscordClient } from '../utils/discord-client.js';
import { withErrorHandling } from '../utils/error-handler.js';
export function registerEventTools(server: McpServer): void {
// List scheduled events
server.tool(
'list_events',
'List all scheduled events in a server',
{
guildId: z.string().describe('The ID of the server (guild)'),
withUserCount: z.boolean().optional().describe('Include user count (default true)'),
},
async ({ guildId, withUserCount = true }) => {
const result = await withErrorHandling(async () => {
const client = await getDiscordClient();
const guild = await client.guilds.fetch(guildId);
const events = await guild.scheduledEvents.fetch({ withUserCount });
return events.map((event) => ({
id: event.id,
name: event.name,
description: event.description,
scheduledStartTime: event.scheduledStartAt?.toISOString(),
scheduledEndTime: event.scheduledEndAt?.toISOString(),
privacyLevel: GuildScheduledEventPrivacyLevel[event.privacyLevel],
status: GuildScheduledEventStatus[event.status],
entityType: GuildScheduledEventEntityType[event.entityType],
entityId: event.entityId,
channelId: event.channelId,
creatorId: event.creatorId,
userCount: event.userCount,
location: event.entityMetadata?.location,
image: event.coverImageURL(),
url: event.url,
}));
});
if (!result.success) {
return { content: [{ type: 'text', text: result.error }], isError: true };
}
return { content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }] };
}
);
// Get event info
server.tool(
'get_event_info',
'Get detailed information about a scheduled event',
{
guildId: z.string().describe('The ID of the server (guild)'),
eventId: z.string().describe('The ID of the event'),
},
async ({ guildId, eventId }) => {
const result = await withErrorHandling(async () => {
const client = await getDiscordClient();
const guild = await client.guilds.fetch(guildId);
const event = await guild.scheduledEvents.fetch(eventId);
return {
id: event.id,
name: event.name,
description: event.description,
scheduledStartTime: event.scheduledStartAt?.toISOString(),
scheduledEndTime: event.scheduledEndAt?.toISOString(),
privacyLevel: GuildScheduledEventPrivacyLevel[event.privacyLevel],
status: GuildScheduledEventStatus[event.status],
entityType: GuildScheduledEventEntityType[event.entityType],
entityId: event.entityId,
channelId: event.channelId,
creatorId: event.creatorId,
creator: event.creator ? { id: event.creator.id, username: event.creator.username } : null,
userCount: event.userCount,
location: event.entityMetadata?.location,
image: event.coverImageURL(),
url: event.url,
};
});
if (!result.success) {
return { content: [{ type: 'text', text: result.error }], isError: true };
}
return { content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }] };
}
);
// Create scheduled event
server.tool(
'create_event',
'Create a scheduled event in a server',
{
guildId: z.string().describe('The ID of the server (guild)'),
name: z.string().describe('Name of the event'),
description: z.string().optional().describe('Description of the event'),
scheduledStartTime: z.string().describe('Start time in ISO 8601 format'),
scheduledEndTime: z.string().optional().describe('End time in ISO 8601 format (required for external events)'),
entityType: z.enum(['STAGE_INSTANCE', 'VOICE', 'EXTERNAL']).describe('Type of event'),
channelId: z.string().optional().describe('Channel ID for voice/stage events'),
location: z.string().optional().describe('Location for external events'),
image: z.string().optional().describe('URL of the cover image'),
reason: z.string().optional().describe('Reason for creating'),
},
async ({ guildId, name, description, scheduledStartTime, scheduledEndTime, entityType, channelId, location, image, reason }) => {
const result = await withErrorHandling(async () => {
const client = await getDiscordClient();
const guild = await client.guilds.fetch(guildId);
const entityTypeMap: Record<string, GuildScheduledEventEntityType> = {
'STAGE_INSTANCE': GuildScheduledEventEntityType.StageInstance,
'VOICE': GuildScheduledEventEntityType.Voice,
'EXTERNAL': GuildScheduledEventEntityType.External,
};
const eventOptions: {
name: string;
scheduledStartTime: string;
privacyLevel: GuildScheduledEventPrivacyLevel;
entityType: GuildScheduledEventEntityType;
description?: string;
scheduledEndTime?: string;
channel?: string;
entityMetadata?: { location: string };
image?: string;
reason?: string;
} = {
name,
scheduledStartTime,
privacyLevel: GuildScheduledEventPrivacyLevel.GuildOnly,
entityType: entityTypeMap[entityType],
};
if (description) eventOptions.description = description;
if (scheduledEndTime) eventOptions.scheduledEndTime = scheduledEndTime;
if (channelId) eventOptions.channel = channelId;
if (location) eventOptions.entityMetadata = { location };
if (image) eventOptions.image = image;
if (reason) eventOptions.reason = reason;
const event = await guild.scheduledEvents.create(eventOptions);
return {
id: event.id,
name: event.name,
scheduledStartTime: event.scheduledStartAt?.toISOString(),
entityType: GuildScheduledEventEntityType[event.entityType],
url: event.url,
message: 'Event created successfully',
};
});
if (!result.success) {
return { content: [{ type: 'text', text: result.error }], isError: true };
}
return { content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }] };
}
);
// Modify event
server.tool(
'modify_event',
'Modify a scheduled event',
{
guildId: z.string().describe('The ID of the server (guild)'),
eventId: z.string().describe('The ID of the event'),
name: z.string().optional().describe('New name'),
description: z.string().optional().describe('New description'),
scheduledStartTime: z.string().optional().describe('New start time (ISO 8601)'),
scheduledEndTime: z.string().optional().describe('New end time (ISO 8601)'),
status: z.enum(['SCHEDULED', 'ACTIVE', 'COMPLETED', 'CANCELED']).optional().describe('New status'),
location: z.string().optional().describe('New location'),
reason: z.string().optional().describe('Reason for modifying'),
},
async ({ guildId, eventId, name, description, scheduledStartTime, scheduledEndTime, status, location, reason }) => {
const result = await withErrorHandling(async () => {
const client = await getDiscordClient();
const guild = await client.guilds.fetch(guildId);
const event = await guild.scheduledEvents.fetch(eventId);
const statusMap: Record<string, GuildScheduledEventStatus> = {
'SCHEDULED': GuildScheduledEventStatus.Scheduled,
'ACTIVE': GuildScheduledEventStatus.Active,
'COMPLETED': GuildScheduledEventStatus.Completed,
'CANCELED': GuildScheduledEventStatus.Canceled,
};
const editData: Record<string, unknown> = {};
if (name) editData.name = name;
if (description) editData.description = description;
if (scheduledStartTime) editData.scheduledStartTime = scheduledStartTime;
if (scheduledEndTime) editData.scheduledEndTime = scheduledEndTime;
if (status) editData.status = statusMap[status];
if (location) editData.entityMetadata = { location };
if (reason) editData.reason = reason;
const updated = await event.edit(editData);
return {
id: updated.id,
name: updated.name,
status: GuildScheduledEventStatus[updated.status],
message: 'Event updated successfully',
};
});
if (!result.success) {
return { content: [{ type: 'text', text: result.error }], isError: true };
}
return { content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }] };
}
);
// Delete event
server.tool(
'delete_event',
'Delete a scheduled event',
{
guildId: z.string().describe('The ID of the server (guild)'),
eventId: z.string().describe('The ID of the event to delete'),
},
async ({ guildId, eventId }) => {
const result = await withErrorHandling(async () => {
const client = await getDiscordClient();
const guild = await client.guilds.fetch(guildId);
const event = await guild.scheduledEvents.fetch(eventId);
const eventName = event.name;
await event.delete();
return { eventId, eventName, message: 'Event 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) }] };
}
);
// Get event subscribers
server.tool(
'get_event_subscribers',
'Get users subscribed to a scheduled event',
{
guildId: z.string().describe('The ID of the server (guild)'),
eventId: z.string().describe('The ID of the event'),
limit: z.number().optional().describe('Max users to fetch (default 100)'),
},
async ({ guildId, eventId, limit = 100 }) => {
const result = await withErrorHandling(async () => {
const client = await getDiscordClient();
const guild = await client.guilds.fetch(guildId);
const event = await guild.scheduledEvents.fetch(eventId);
const subscribers = await event.fetchSubscribers({ limit, withMember: true });
return subscribers.map((sub) => ({
id: sub.user.id,
username: sub.user.username,
member: sub.member ? {
nickname: sub.member.nickname,
joinedAt: sub.member.joinedAt?.toISOString(),
} : null,
}));
});
if (!result.success) {
return { content: [{ type: 'text', text: result.error }], isError: true };
}
return { content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }] };
}
);
}