import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import { ChannelType, PermissionFlagsBits, GuildChannel, TextChannel, VoiceChannel } from 'discord.js';
import { getDiscordClient } from '../utils/discord-client.js';
import { withErrorHandling } from '../utils/error-handler.js';
const channelTypeMap: Record<string, ChannelType> = {
text: ChannelType.GuildText,
voice: ChannelType.GuildVoice,
category: ChannelType.GuildCategory,
announcement: ChannelType.GuildAnnouncement,
forum: ChannelType.GuildForum,
stage: ChannelType.GuildStageVoice,
};
export function registerChannelTools(server: McpServer): void {
// List all channels in a server
server.tool(
'list_channels',
'List all channels in a Discord 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 channels = await guild.channels.fetch();
return channels.map((ch) => ({
id: ch?.id,
name: ch?.name,
type: ch?.type,
typeName: ChannelType[ch?.type ?? 0],
parentId: (ch as GuildChannel)?.parentId,
position: (ch as GuildChannel)?.position,
})).filter((ch) => ch.id);
});
if (!result.success) {
return { content: [{ type: 'text', text: result.error }], isError: true };
}
return { content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }] };
}
);
// Get channel information
server.tool(
'get_channel_info',
'Get detailed information about a specific channel',
{
guildId: z.string().describe('The ID of the server (guild)'),
channelId: z.string().describe('The ID of the channel'),
},
async ({ guildId, channelId }) => {
const result = await withErrorHandling(async () => {
const client = await getDiscordClient();
const guild = await client.guilds.fetch(guildId);
const channel = await guild.channels.fetch(channelId);
if (!channel) throw new Error('Channel not found');
// Cast to GuildChannel for common properties
const guildChannel = channel as GuildChannel;
const baseInfo: Record<string, unknown> = {
id: channel.id,
name: channel.name,
type: channel.type,
typeName: ChannelType[channel.type],
parentId: guildChannel.parentId,
createdAt: channel.createdAt?.toISOString(),
};
// Add position and permission overwrites for non-thread channels
if ('position' in guildChannel) {
baseInfo.position = guildChannel.position;
}
if ('permissionOverwrites' in guildChannel && guildChannel.permissionOverwrites) {
baseInfo.permissionOverwrites = guildChannel.permissionOverwrites.cache.map((po) => ({
id: po.id,
type: po.type,
allow: po.allow.toArray(),
deny: po.deny.toArray(),
}));
}
// Add text channel specific info
if (channel.type === ChannelType.GuildText || channel.type === ChannelType.GuildAnnouncement) {
const textChannel = channel as TextChannel;
return {
...baseInfo,
topic: textChannel.topic,
nsfw: textChannel.nsfw,
rateLimitPerUser: textChannel.rateLimitPerUser,
};
}
// Add voice channel specific info
if (channel.type === ChannelType.GuildVoice || channel.type === ChannelType.GuildStageVoice) {
const voiceChannel = channel as VoiceChannel;
return {
...baseInfo,
bitrate: voiceChannel.bitrate,
userLimit: voiceChannel.userLimit,
rtcRegion: voiceChannel.rtcRegion,
};
}
return baseInfo;
});
if (!result.success) {
return { content: [{ type: 'text', text: result.error }], isError: true };
}
return { content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }] };
}
);
// Create a new channel
server.tool(
'create_channel',
'Create a new channel in a Discord server',
{
guildId: z.string().describe('The ID of the server (guild)'),
name: z.string().describe('Name of the new channel'),
type: z.enum(['text', 'voice', 'category', 'announcement', 'forum', 'stage']).describe('Type of channel'),
parentId: z.string().optional().describe('ID of the parent category'),
topic: z.string().optional().describe('Channel topic (text channels only)'),
nsfw: z.boolean().optional().describe('Whether the channel is NSFW'),
bitrate: z.number().optional().describe('Bitrate for voice channels'),
userLimit: z.number().optional().describe('User limit for voice channels'),
rateLimitPerUser: z.number().optional().describe('Slowmode in seconds'),
position: z.number().optional().describe('Position of the channel'),
reason: z.string().optional().describe('Reason for creating the channel'),
},
async ({ guildId, name, type, parentId, topic, nsfw, bitrate, userLimit, rateLimitPerUser, position, reason }) => {
const result = await withErrorHandling(async () => {
const client = await getDiscordClient();
const guild = await client.guilds.fetch(guildId);
const channelOptions: Record<string, unknown> = {
name,
type: channelTypeMap[type],
parent: parentId,
topic,
nsfw,
bitrate,
userLimit,
rateLimitPerUser,
position,
reason,
};
// Remove undefined values
Object.keys(channelOptions).forEach((key) => {
if (channelOptions[key] === undefined) delete channelOptions[key];
});
const newChannel = await guild.channels.create(channelOptions as any);
return { id: newChannel.id, name: newChannel.name, type: ChannelType[newChannel.type], message: 'Channel created' };
});
if (!result.success) {
return { content: [{ type: 'text', text: result.error }], isError: true };
}
return { content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }] };
}
);
// Delete a channel
server.tool(
'delete_channel',
'Delete a channel from a Discord server',
{
guildId: z.string().describe('The ID of the server (guild)'),
channelId: z.string().describe('The ID of the channel to delete'),
reason: z.string().optional().describe('Reason for deleting the channel'),
},
async ({ guildId, channelId, reason }) => {
const result = await withErrorHandling(async () => {
const client = await getDiscordClient();
const guild = await client.guilds.fetch(guildId);
const channel = await guild.channels.fetch(channelId);
if (!channel) throw new Error('Channel not found');
const channelName = channel.name;
await channel.delete(reason);
return { channelId, channelName, message: 'Channel 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) }] };
}
);
// Modify channel settings
server.tool(
'modify_channel',
'Modify channel settings (name, topic, permissions, position, etc.)',
{
guildId: z.string().describe('The ID of the server (guild)'),
channelId: z.string().describe('The ID of the channel to modify'),
name: z.string().optional().describe('New channel name'),
topic: z.string().optional().describe('New channel topic'),
nsfw: z.boolean().optional().describe('Whether the channel is NSFW'),
parentId: z.string().optional().describe('New parent category ID'),
position: z.number().optional().describe('New channel position'),
rateLimitPerUser: z.number().optional().describe('New slowmode in seconds'),
bitrate: z.number().optional().describe('New bitrate (voice channels)'),
userLimit: z.number().optional().describe('New user limit (voice channels)'),
reason: z.string().optional().describe('Reason for the modification'),
},
async ({ guildId, channelId, name, topic, nsfw, parentId, position, rateLimitPerUser, bitrate, userLimit, reason }) => {
const result = await withErrorHandling(async () => {
const client = await getDiscordClient();
const guild = await client.guilds.fetch(guildId);
const channel = await guild.channels.fetch(channelId);
if (!channel) throw new Error('Channel not found');
const updateData: Record<string, unknown> = {};
if (name !== undefined) updateData.name = name;
if (topic !== undefined) updateData.topic = topic;
if (nsfw !== undefined) updateData.nsfw = nsfw;
if (parentId !== undefined) updateData.parent = parentId;
if (position !== undefined) updateData.position = position;
if (rateLimitPerUser !== undefined) updateData.rateLimitPerUser = rateLimitPerUser;
if (bitrate !== undefined) updateData.bitrate = bitrate;
if (userLimit !== undefined) updateData.userLimit = userLimit;
if (reason !== undefined) updateData.reason = reason;
const updatedChannel = await channel.edit(updateData);
return {
id: updatedChannel.id,
name: updatedChannel.name,
type: ChannelType[updatedChannel.type],
message: 'Channel 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) }] };
}
);
}