@fukayatti0/discord-mcp-server
Version:
Discord integration for Model Context Protocol
1,677 lines (1,613 loc) • 85.3 kB
JavaScript
#!/usr/bin/env node
import {
Client,
GatewayIntentBits,
ChannelType,
EmbedBuilder,
AttachmentBuilder,
PermissionFlagsBits,
} from "discord.js";
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";
// Discord bot setup
const DISCORD_TOKEN = process.env.DISCORD_TOKEN;
if (!DISCORD_TOKEN) {
throw new Error("DISCORD_TOKEN environment variable is required");
}
// Initialize Discord client with necessary intents
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent,
GatewayIntentBits.GuildMembers,
GatewayIntentBits.GuildMessageReactions,
GatewayIntentBits.GuildVoiceStates,
GatewayIntentBits.GuildEmojisAndStickers,
GatewayIntentBits.GuildWebhooks,
GatewayIntentBits.GuildInvites,
GatewayIntentBits.GuildScheduledEvents,
GatewayIntentBits.GuildModeration,
],
});
// Store Discord client reference
let discordClient = null;
client.once("ready", () => {
discordClient = client;
console.log(`Logged in as ${client.user.tag}`);
});
// Helper function to ensure Discord client is ready
function requireDiscordClient() {
if (!discordClient) {
throw new Error("Discord client not ready");
}
return discordClient;
}
// Helper function to find a guild by name or ID
async function findGuild(guildIdentifier) {
const client = requireDiscordClient();
if (!guildIdentifier) {
// If no guild specified and bot is only in one guild, use that
if (client.guilds.cache.size === 1) {
return client.guilds.cache.first();
}
// List available guilds
const guildList = Array.from(client.guilds.cache.values())
.map((g) => `"${g.name}"`)
.join(", ");
throw new Error(
`I'm in multiple Discord servers! Please tell me which one you want to use. Available servers: ${guildList}`
);
}
// Try to fetch by ID first
try {
const guild = await client.guilds.fetch(guildIdentifier);
if (guild) return guild;
} catch {
// If ID fetch fails, search by name
const guilds = client.guilds.cache.filter(
(g) => g.name.toLowerCase() === guildIdentifier.toLowerCase()
);
if (guilds.size === 0) {
const availableGuilds = Array.from(client.guilds.cache.values())
.map((g) => `"${g.name}"`)
.join(", ");
throw new Error(
`I can't find a server called "${guildIdentifier}". Here are the servers I'm in: ${availableGuilds}`
);
}
if (guilds.size > 1) {
const guildList = guilds.map((g) => `${g.name} (ID: ${g.id})`).join(", ");
throw new Error(
`Multiple servers found with name "${guildIdentifier}": ${guildList}. Please specify the server ID.`
);
}
return guilds.first();
}
throw new Error(`Server "${guildIdentifier}" not found`);
}
// Helper function to find a channel by name or ID within a specific guild (includes threads)
async function findChannel(channelIdentifier, guildIdentifier) {
const client = requireDiscordClient();
// If guildIdentifier is provided, find within that guild
if (guildIdentifier) {
const guild = await findGuild(guildIdentifier);
// First try to fetch by ID
try {
const channel = await client.channels.fetch(channelIdentifier);
if (
channel &&
channel.guild &&
channel.guild.id === guild.id &&
channel.isTextBased()
) {
return channel;
}
} catch {
// If fetching by ID fails, search by name in the specified guild
const channels = guild.channels.cache.filter(
(channel) =>
channel.isTextBased() &&
(channel.name.toLowerCase() === channelIdentifier.toLowerCase() ||
channel.name.toLowerCase() ===
channelIdentifier.toLowerCase().replace("#", ""))
);
if (channels.size === 0) {
const availableChannels = guild.channels.cache
.filter((c) => c.isTextBased())
.map((c) => `"#${c.name}"`)
.join(", ");
throw new Error(
`I can't find a channel called "${channelIdentifier}" in ${guild.name}. Here are the channels I can see: ${availableChannels}`
);
}
if (channels.size > 1) {
const channelList = channels
.map((c) => `#${c.name} (${c.id})`)
.join(", ");
throw new Error(
`Multiple channels found with name "${channelIdentifier}" in server "${guild.name}": ${channelList}. Please specify the channel ID.`
);
}
return channels.first();
}
throw new Error(
`Channel "${channelIdentifier}" is not a text channel or not found in server "${guild.name}"`
);
}
// If no guild specified, try to fetch by ID directly
try {
const channel = await client.channels.fetch(channelIdentifier);
if (channel && channel.isTextBased()) {
return channel;
}
throw new Error(`Channel "${channelIdentifier}" is not a text channel`);
} catch {
throw new Error(
`Channel "${channelIdentifier}" not found. When using channel names, please also specify the server.`
);
}
}
// Helper function to find a category by name or ID within a specific guild
async function findCategory(categoryIdentifier, guildIdentifier) {
const client = requireDiscordClient();
const guild = await findGuild(guildIdentifier);
// First try to fetch by ID
try {
const category = await client.channels.fetch(categoryIdentifier);
if (
category &&
category.guild &&
category.guild.id === guild.id &&
category.type === ChannelType.GuildCategory
) {
return category;
}
} catch {
// If fetching by ID fails, search by name in the specified guild
const categories = guild.channels.cache.filter(
(channel) =>
channel.type === ChannelType.GuildCategory &&
channel.name.toLowerCase() === categoryIdentifier.toLowerCase()
);
if (categories.size === 0) {
const availableCategories = guild.channels.cache
.filter((c) => c.type === ChannelType.GuildCategory)
.map((c) => `"${c.name}"`)
.join(", ");
throw new Error(
`Category "${categoryIdentifier}" not found in server "${guild.name}". Available categories: ${availableCategories}`
);
}
if (categories.size > 1) {
const categoryList = categories
.map((c) => `${c.name} (${c.id})`)
.join(", ");
throw new Error(
`Multiple categories found with name "${categoryIdentifier}" in server "${guild.name}": ${categoryList}. Please specify the category ID.`
);
}
return categories.first();
}
throw new Error(
`Category "${categoryIdentifier}" not found in server "${guild.name}"`
);
}
// Validation schemas
const SendMessageSchema = z.object({
server: z
.string()
.optional()
.describe("Server name or ID (optional if bot is only in one server)"),
channel: z
.string()
.describe('Channel name (e.g., "general") or ID (including threads)'),
content: z.string().describe("Message content"),
});
const ReadMessagesSchema = z.object({
server: z
.string()
.optional()
.describe("Server name or ID (optional if bot is only in one server)"),
channel: z
.string()
.describe('Channel name (e.g., "general") or ID (including threads)'),
limit: z.number().min(1).default(50),
before: z
.string()
.optional()
.describe("Message ID to fetch messages before (for pagination)"),
after: z
.string()
.optional()
.describe("Message ID to fetch messages after (for pagination)"),
around: z
.string()
.optional()
.describe("Message ID to fetch messages around (for pagination)"),
});
const ReadMessagesBulkSchema = z.object({
server: z
.string()
.optional()
.describe("Server name or ID (optional if bot is only in one server)"),
channel: z
.string()
.describe('Channel name (e.g., "general") or ID (including threads)'),
total_limit: z
.number()
.min(1)
.default(200)
.describe(
"Total number of messages to fetch (will be fetched in batches of 100). Set to -1 for unlimited"
),
unlimited: z
.boolean()
.optional()
.describe(
"Set to true to fetch all available messages (ignores total_limit)"
),
});
const ReactionSchema = z.object({
server: z
.string()
.optional()
.describe("Server name or ID (optional if bot is only in one server)"),
channel: z
.string()
.describe('Channel name (e.g., "general") or ID (including threads)'),
message_id: z.string().describe("Message ID"),
emoji: z.string().describe("Emoji to react with"),
});
const MultipleReactionSchema = z.object({
server: z
.string()
.optional()
.describe("Server name or ID (optional if bot is only in one server)"),
channel: z
.string()
.describe('Channel name (e.g., "general") or ID (including threads)'),
message_id: z.string().describe("Message ID"),
emojis: z.array(z.string()).describe("Array of emojis to react with"),
});
const ModerateMessageSchema = z.object({
server: z
.string()
.optional()
.describe("Server name or ID (optional if bot is only in one server)"),
channel: z
.string()
.describe('Channel name (e.g., "general") or ID (including threads)'),
message_id: z.string().describe("Message ID"),
reason: z.string().describe("Reason for moderation"),
timeout_minutes: z
.number()
.min(0)
.max(40320)
.optional()
.describe("Optional timeout duration in minutes"),
});
const CategoryChannelsSchema = z.object({
server: z
.string()
.optional()
.describe("Server name or ID (optional if bot is only in one server)"),
category: z.string().describe("Category name or ID"),
limit: z.number().min(1).default(10).optional(),
});
// New schemas for additional functionality
const CreateVoiceChannelSchema = z.object({
server: z
.string()
.optional()
.describe("Server name or ID (optional if bot is only in one server)"),
name: z.string().describe("Voice channel name"),
category_id: z
.string()
.optional()
.describe("Optional category ID to place channel in"),
user_limit: z
.number()
.min(0)
.max(99)
.optional()
.describe("User limit (0 for unlimited)"),
bitrate: z
.number()
.min(8000)
.max(384000)
.optional()
.describe("Bitrate in bps"),
});
const CreateThreadSchema = z.object({
server: z
.string()
.optional()
.describe("Server name or ID (optional if bot is only in one server)"),
channel: z.string().describe("Parent channel name or ID"),
name: z.string().describe("Thread name"),
message_id: z
.string()
.optional()
.describe("Message ID to create thread from (for message threads)"),
auto_archive_duration: z
.number()
.optional()
.describe("Auto archive duration in minutes (60, 1440, 4320, 10080)"),
private: z
.boolean()
.optional()
.describe("Create private thread (requires permissions)"),
});
const CreateRoleSchema = z.object({
server: z
.string()
.optional()
.describe("Server name or ID (optional if bot is only in one server)"),
name: z.string().describe("Role name"),
color: z
.string()
.optional()
.describe("Role color (hex color code like #FF0000)"),
hoist: z
.boolean()
.optional()
.describe("Display role separately in member list"),
mentionable: z
.boolean()
.optional()
.describe("Allow role to be mentioned by everyone"),
permissions: z
.array(z.string())
.optional()
.describe("Array of permission names"),
});
const SendEmbedSchema = z.object({
server: z
.string()
.optional()
.describe("Server name or ID (optional if bot is only in one server)"),
channel: z.string().describe("Channel name or ID"),
title: z.string().optional().describe("Embed title"),
description: z.string().optional().describe("Embed description"),
color: z.string().optional().describe("Embed color (hex color code)"),
thumbnail: z.string().optional().describe("Thumbnail image URL"),
image: z.string().optional().describe("Main image URL"),
author_name: z.string().optional().describe("Author name"),
author_icon: z.string().optional().describe("Author icon URL"),
footer_text: z.string().optional().describe("Footer text"),
footer_icon: z.string().optional().describe("Footer icon URL"),
fields: z
.array(
z.object({
name: z.string(),
value: z.string(),
inline: z.boolean().optional(),
})
)
.optional()
.describe("Embed fields"),
});
const EditMessageSchema = z.object({
server: z
.string()
.optional()
.describe("Server name or ID (optional if bot is only in one server)"),
channel: z.string().describe("Channel name or ID"),
message_id: z.string().describe("Message ID to edit"),
content: z.string().optional().describe("New message content"),
embed: z
.object({
title: z.string().optional(),
description: z.string().optional(),
color: z.string().optional(),
})
.optional()
.describe("Embed data to add/update"),
});
const DeleteMessageSchema = z.object({
server: z
.string()
.optional()
.describe("Server name or ID (optional if bot is only in one server)"),
channel: z.string().describe("Channel name or ID"),
message_id: z.string().describe("Message ID to delete"),
reason: z.string().optional().describe("Reason for deletion"),
});
const CreateInviteSchema = z.object({
server: z
.string()
.optional()
.describe("Server name or ID (optional if bot is only in one server)"),
channel: z
.string()
.optional()
.describe("Channel for invite (defaults to system channel)"),
max_age: z
.number()
.optional()
.describe("Invite expiration in seconds (0 for never)"),
max_uses: z.number().optional().describe("Maximum uses (0 for unlimited)"),
temporary: z.boolean().optional().describe("Grant temporary membership"),
unique: z.boolean().optional().describe("Create unique invite"),
});
const ManageEmojiSchema = z.object({
server: z
.string()
.optional()
.describe("Server name or ID (optional if bot is only in one server)"),
name: z.string().describe("Emoji name"),
image_url: z.string().optional().describe("Image URL for creating emoji"),
emoji_id: z.string().optional().describe("Emoji ID for deleting emoji"),
});
const CreateWebhookSchema = z.object({
server: z
.string()
.optional()
.describe("Server name or ID (optional if bot is only in one server)"),
channel: z.string().describe("Channel name or ID"),
name: z.string().describe("Webhook name"),
avatar_url: z.string().optional().describe("Webhook avatar URL"),
});
const KickMemberSchema = z.object({
server: z
.string()
.optional()
.describe("Server name or ID (optional if bot is only in one server)"),
user_id: z.string().describe("User ID to kick"),
reason: z.string().optional().describe("Reason for kick"),
});
const BanMemberSchema = z.object({
server: z
.string()
.optional()
.describe("Server name or ID (optional if bot is only in one server)"),
user_id: z.string().describe("User ID to ban"),
reason: z.string().optional().describe("Reason for ban"),
delete_days: z
.number()
.min(0)
.max(7)
.optional()
.describe("Days of messages to delete (0-7)"),
});
const SendFileSchema = z.object({
server: z
.string()
.optional()
.describe("Server name or ID (optional if bot is only in one server)"),
channel: z.string().describe("Channel name or ID"),
file_url: z.string().describe("File URL to send"),
filename: z.string().optional().describe("Custom filename"),
content: z.string().optional().describe("Message content to send with file"),
});
// Initialize MCP server
const server = new Server(
{
name: "discord-server",
version: "0.1.0",
},
{
capabilities: {
tools: {},
},
}
);
// List available Discord tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
// Server Information Tools
{
name: "get_server_info",
description:
"Show me information about this Discord server including member count, creation date, and settings",
inputSchema: {
type: "object",
properties: {
server: {
type: "string",
description:
"Which Discord server? (leave empty if I'm only in one server)",
},
},
},
},
{
name: "list_members",
description:
"Show me all the members in this Discord server with their roles and join dates",
inputSchema: {
type: "object",
properties: {
server: {
type: "string",
description:
"Server name or ID (optional if bot is only in one server)",
},
limit: {
type: "number",
description: "Maximum number of members to fetch",
minimum: 1,
maximum: 1000,
},
},
},
},
// Category Tools
{
name: "list_category_channels",
description:
"Show me all the channels in a specific category, like 'General' or 'Gaming'",
inputSchema: {
type: "object",
properties: {
server: {
type: "string",
description:
"Server name or ID (optional if bot is only in one server)",
},
category: {
type: "string",
description: "Category name or ID",
},
},
required: ["category"],
},
},
{
name: "read_category_channels",
description:
"Read recent messages from all channels in a category like 'Support' or 'Development'",
inputSchema: {
type: "object",
properties: {
server: {
type: "string",
description:
"Server name or ID (optional if bot is only in one server)",
},
category: {
type: "string",
description: "Category name or ID",
},
limit: {
type: "number",
description: "Number of messages to fetch per channel",
minimum: 1,
default: 10,
},
},
required: ["category"],
},
},
// Role Management Tools
{
name: "add_role",
description: "Give a role like 'Moderator' or 'VIP' to a specific user",
inputSchema: {
type: "object",
properties: {
server: {
type: "string",
description:
"Server name or ID (optional if bot is only in one server)",
},
user_id: {
type: "string",
description: "User to add role to",
},
role_id: {
type: "string",
description: "Role ID to add",
},
},
required: ["user_id", "role_id"],
},
},
{
name: "remove_role",
description:
"Take away a role like 'Member' or 'Helper' from a specific user",
inputSchema: {
type: "object",
properties: {
server: {
type: "string",
description:
"Server name or ID (optional if bot is only in one server)",
},
user_id: {
type: "string",
description: "User to remove role from",
},
role_id: {
type: "string",
description: "Role ID to remove",
},
},
required: ["user_id", "role_id"],
},
},
// Channel Management Tools
{
name: "create_text_channel",
description:
"Create a new text channel with a custom name and optional topic",
inputSchema: {
type: "object",
properties: {
server: {
type: "string",
description:
"Server name or ID (optional if bot is only in one server)",
},
name: {
type: "string",
description: "Channel name",
},
category_id: {
type: "string",
description: "Optional category ID to place channel in",
},
topic: {
type: "string",
description: "Optional channel topic",
},
},
required: ["name"],
},
},
{
name: "delete_channel",
description:
"Delete a channel permanently (be careful - this cannot be undone)",
inputSchema: {
type: "object",
properties: {
server: {
type: "string",
description:
"Server name or ID (optional if bot is only in one server)",
},
channel: {
type: "string",
description:
"Channel name (e.g., 'general') or ID (including threads)",
},
reason: {
type: "string",
description: "Reason for deletion",
},
},
required: ["channel"],
},
},
// Message Reaction Tools
{
name: "add_reaction",
description:
"Add an emoji reaction like 👍, ❤️, or 😂 to a specific message",
inputSchema: {
type: "object",
properties: {
server: {
type: "string",
description:
"Server name or ID (optional if bot is only in one server)",
},
channel: {
type: "string",
description:
"Channel name (e.g., 'general') or ID (including threads)",
},
message_id: {
type: "string",
description: "Message to react to",
},
emoji: {
type: "string",
description: "Emoji to react with (Unicode or custom emoji ID)",
},
},
required: ["channel", "message_id", "emoji"],
},
},
{
name: "add_multiple_reactions",
description:
"Add several emoji reactions at once to a message, like 👍❤️😂",
inputSchema: {
type: "object",
properties: {
server: {
type: "string",
description:
"Server name or ID (optional if bot is only in one server)",
},
channel: {
type: "string",
description:
"Channel name (e.g., 'general') or ID (including threads)",
},
message_id: {
type: "string",
description: "Message to react to",
},
emojis: {
type: "array",
items: {
type: "string",
description: "Emoji to react with (Unicode or custom emoji ID)",
},
description: "List of emojis to add as reactions",
},
},
required: ["channel", "message_id", "emojis"],
},
},
{
name: "remove_reaction",
description:
"Remove my emoji reaction from a message (like undoing a 👍)",
inputSchema: {
type: "object",
properties: {
server: {
type: "string",
description:
"Server name or ID (optional if bot is only in one server)",
},
channel: {
type: "string",
description:
"Channel name (e.g., 'general') or ID (including threads)",
},
message_id: {
type: "string",
description: "Message to remove reaction from",
},
emoji: {
type: "string",
description: "Emoji to remove (Unicode or custom emoji ID)",
},
},
required: ["channel", "message_id", "emoji"],
},
},
{
name: "send_message",
description:
"Send a text message to any channel like #general, #announcements, or a thread",
inputSchema: {
type: "object",
properties: {
server: {
type: "string",
description:
"Server name or ID (optional if bot is only in one server)",
},
channel: {
type: "string",
description:
"Which channel? (like 'general', 'announcements', or a thread name)",
},
content: {
type: "string",
description: "What message do you want to send?",
},
},
required: ["channel", "content"],
},
},
{
name: "read_messages",
description:
"Read recent messages from any channel or thread - specify how many you want to see",
inputSchema: {
type: "object",
properties: {
server: {
type: "string",
description:
"Server name or ID (optional if bot is only in one server)",
},
channel: {
type: "string",
description:
"Channel name (e.g., 'general') or ID (including threads)",
},
limit: {
type: "number",
description:
"How many messages do you want to read? (e.g., 10, 50, 100)",
minimum: 1,
},
before: {
type: "string",
description:
"Message ID to fetch messages before (for pagination)",
},
after: {
type: "string",
description:
"Message ID to fetch messages after (for pagination)",
},
around: {
type: "string",
description:
"Message ID to fetch messages around (for pagination)",
},
},
required: ["channel"],
},
},
{
name: "read_messages_bulk",
description:
"Read ALL messages from a channel or thread - can fetch hundreds or thousands of messages automatically",
inputSchema: {
type: "object",
properties: {
server: {
type: "string",
description:
"Server name or ID (optional if bot is only in one server)",
},
channel: {
type: "string",
description:
"Channel name (e.g., 'general') or ID (including threads)",
},
total_limit: {
type: "number",
description:
"How many messages in total? (e.g., 500, 1000, or -1 for ALL messages)",
minimum: 1,
default: 200,
},
unlimited: {
type: "boolean",
description:
"Want ALL messages ever sent in this channel? Set this to true",
},
},
required: ["channel"],
},
},
{
name: "get_user_info",
description:
"Look up detailed information about any Discord user by their ID",
inputSchema: {
type: "object",
properties: {
user_id: {
type: "string",
description: "Discord user ID",
},
},
required: ["user_id"],
},
},
{
name: "moderate_message",
description:
"Delete inappropriate messages and optionally put the user in timeout",
inputSchema: {
type: "object",
properties: {
server: {
type: "string",
description:
"Server name or ID (optional if bot is only in one server)",
},
channel: {
type: "string",
description:
"Channel name (e.g., 'general') or ID (including threads)",
},
message_id: {
type: "string",
description: "ID of message to moderate",
},
reason: {
type: "string",
description: "Reason for moderation",
},
timeout_minutes: {
type: "number",
description: "Optional timeout duration in minutes",
minimum: 0,
maximum: 40320, // Max 4 weeks
},
},
required: ["channel", "message_id", "reason"],
},
},
// Voice Channel Management
{
name: "create_voice_channel",
description:
"Create a new voice channel with custom settings like user limit and bitrate",
inputSchema: {
type: "object",
properties: {
server: {
type: "string",
description:
"Server name or ID (optional if bot is only in one server)",
},
name: {
type: "string",
description: "Voice channel name",
},
category_id: {
type: "string",
description: "Optional category ID to place channel in",
},
user_limit: {
type: "number",
description: "User limit (0 for unlimited)",
minimum: 0,
maximum: 99,
},
bitrate: {
type: "number",
description: "Bitrate in bps",
minimum: 8000,
maximum: 384000,
},
},
required: ["name"],
},
},
// Thread Management
{
name: "create_thread",
description:
"Create a new thread in a channel, either standalone or from a message",
inputSchema: {
type: "object",
properties: {
server: {
type: "string",
description:
"Server name or ID (optional if bot is only in one server)",
},
channel: {
type: "string",
description: "Parent channel name or ID",
},
name: {
type: "string",
description: "Thread name",
},
message_id: {
type: "string",
description:
"Message ID to create thread from (for message threads)",
},
auto_archive_duration: {
type: "number",
description:
"Auto archive duration in minutes (60, 1440, 4320, 10080)",
},
private: {
type: "boolean",
description: "Create private thread (requires permissions)",
},
},
required: ["channel", "name"],
},
},
// Enhanced Role Management
{
name: "create_role",
description:
"Create a new role with custom permissions, color, and settings",
inputSchema: {
type: "object",
properties: {
server: {
type: "string",
description:
"Server name or ID (optional if bot is only in one server)",
},
name: {
type: "string",
description: "Role name",
},
color: {
type: "string",
description: "Role color (hex color code like #FF0000)",
},
hoist: {
type: "boolean",
description: "Display role separately in member list",
},
mentionable: {
type: "boolean",
description: "Allow role to be mentioned by everyone",
},
permissions: {
type: "array",
items: {
type: "string",
},
description: "Array of permission names",
},
},
required: ["name"],
},
},
{
name: "delete_role",
description: "Delete a role from the server",
inputSchema: {
type: "object",
properties: {
server: {
type: "string",
description:
"Server name or ID (optional if bot is only in one server)",
},
role_id: {
type: "string",
description: "Role ID to delete",
},
reason: {
type: "string",
description: "Reason for deletion",
},
},
required: ["role_id"],
},
},
{
name: "list_roles",
description: "List all roles in the server with their permissions",
inputSchema: {
type: "object",
properties: {
server: {
type: "string",
description:
"Server name or ID (optional if bot is only in one server)",
},
},
},
},
// Enhanced Message Features
{
name: "send_embed",
description:
"Send a rich embed message with title, description, fields, images, and more",
inputSchema: {
type: "object",
properties: {
server: {
type: "string",
description:
"Server name or ID (optional if bot is only in one server)",
},
channel: {
type: "string",
description: "Channel name or ID",
},
title: {
type: "string",
description: "Embed title",
},
description: {
type: "string",
description: "Embed description",
},
color: {
type: "string",
description: "Embed color (hex color code)",
},
thumbnail: {
type: "string",
description: "Thumbnail image URL",
},
image: {
type: "string",
description: "Main image URL",
},
author_name: {
type: "string",
description: "Author name",
},
author_icon: {
type: "string",
description: "Author icon URL",
},
footer_text: {
type: "string",
description: "Footer text",
},
footer_icon: {
type: "string",
description: "Footer icon URL",
},
fields: {
type: "array",
items: {
type: "object",
properties: {
name: { type: "string" },
value: { type: "string" },
inline: { type: "boolean" },
},
required: ["name", "value"],
},
description: "Embed fields",
},
},
required: ["channel"],
},
},
{
name: "edit_message",
description: "Edit an existing message content or embed",
inputSchema: {
type: "object",
properties: {
server: {
type: "string",
description:
"Server name or ID (optional if bot is only in one server)",
},
channel: {
type: "string",
description: "Channel name or ID",
},
message_id: {
type: "string",
description: "Message ID to edit",
},
content: {
type: "string",
description: "New message content",
},
embed: {
type: "object",
properties: {
title: { type: "string" },
description: { type: "string" },
color: { type: "string" },
},
description: "Embed data to add/update",
},
},
required: ["channel", "message_id"],
},
},
{
name: "delete_message",
description: "Delete a specific message from a channel",
inputSchema: {
type: "object",
properties: {
server: {
type: "string",
description:
"Server name or ID (optional if bot is only in one server)",
},
channel: {
type: "string",
description: "Channel name or ID",
},
message_id: {
type: "string",
description: "Message ID to delete",
},
reason: {
type: "string",
description: "Reason for deletion",
},
},
required: ["channel", "message_id"],
},
},
{
name: "send_file",
description: "Send a file attachment to a channel",
inputSchema: {
type: "object",
properties: {
server: {
type: "string",
description:
"Server name or ID (optional if bot is only in one server)",
},
channel: {
type: "string",
description: "Channel name or ID",
},
file_url: {
type: "string",
description: "File URL to send",
},
filename: {
type: "string",
description: "Custom filename",
},
content: {
type: "string",
description: "Message content to send with file",
},
},
required: ["channel", "file_url"],
},
},
// Invite Management
{
name: "create_invite",
description:
"Create an invite link for the server with custom settings",
inputSchema: {
type: "object",
properties: {
server: {
type: "string",
description:
"Server name or ID (optional if bot is only in one server)",
},
channel: {
type: "string",
description: "Channel for invite (defaults to system channel)",
},
max_age: {
type: "number",
description: "Invite expiration in seconds (0 for never)",
},
max_uses: {
type: "number",
description: "Maximum uses (0 for unlimited)",
},
temporary: {
type: "boolean",
description: "Grant temporary membership",
},
unique: {
type: "boolean",
description: "Create unique invite",
},
},
},
},
{
name: "list_invites",
description: "List all active invites for the server",
inputSchema: {
type: "object",
properties: {
server: {
type: "string",
description:
"Server name or ID (optional if bot is only in one server)",
},
},
},
},
// Emoji Management
{
name: "create_emoji",
description: "Create a custom emoji for the server",
inputSchema: {
type: "object",
properties: {
server: {
type: "string",
description:
"Server name or ID (optional if bot is only in one server)",
},
name: {
type: "string",
description: "Emoji name",
},
image_url: {
type: "string",
description: "Image URL for creating emoji",
},
},
required: ["name", "image_url"],
},
},
{
name: "delete_emoji",
description: "Delete a custom emoji from the server",
inputSchema: {
type: "object",
properties: {
server: {
type: "string",
description:
"Server name or ID (optional if bot is only in one server)",
},
emoji_id: {
type: "string",
description: "Emoji ID to delete",
},
reason: {
type: "string",
description: "Reason for deletion",
},
},
required: ["emoji_id"],
},
},
{
name: "list_emojis",
description: "List all custom emojis in the server",
inputSchema: {
type: "object",
properties: {
server: {
type: "string",
description:
"Server name or ID (optional if bot is only in one server)",
},
},
},
},
// Webhook Management
{
name: "create_webhook",
description: "Create a webhook for a channel",
inputSchema: {
type: "object",
properties: {
server: {
type: "string",
description:
"Server name or ID (optional if bot is only in one server)",
},
channel: {
type: "string",
description: "Channel name or ID",
},
name: {
type: "string",
description: "Webhook name",
},
avatar_url: {
type: "string",
description: "Webhook avatar URL",
},
},
required: ["channel", "name"],
},
},
{
name: "list_webhooks",
description: "List all webhooks in the server",
inputSchema: {
type: "object",
properties: {
server: {
type: "string",
description:
"Server name or ID (optional if bot is only in one server)",
},
},
},
},
// Advanced Moderation
{
name: "kick_member",
description: "Kick a member from the server",
inputSchema: {
type: "object",
properties: {
server: {
type: "string",
description:
"Server name or ID (optional if bot is only in one server)",
},
user_id: {
type: "string",
description: "User ID to kick",
},
reason: {
type: "string",
description: "Reason for kick",
},
},
required: ["user_id"],
},
},
{
name: "ban_member",
description: "Ban a member from the server",
inputSchema: {
type: "object",
properties: {
server: {
type: "string",
description:
"Server name or ID (optional if bot is only in one server)",
},
user_id: {
type: "string",
description: "User ID to ban",
},
reason: {
type: "string",
description: "Reason for ban",
},
delete_days: {
type: "number",
description: "Days of messages to delete (0-7)",
minimum: 0,
maximum: 7,
},
},
required: ["user_id"],
},
},
{
name: "unban_member",
description: "Unban a member from the server",
inputSchema: {
type: "object",
properties: {
server: {
type: "string",
description:
"Server name or ID (optional if bot is only in one server)",
},
user_id: {
type: "string",
description: "User ID to unban",
},
reason: {
type: "string",
description: "Reason for unban",
},
},
required: ["user_id"],
},
},
{
name: "timeout_member",
description: "Put a member in timeout for a specified duration",
inputSchema: {
type: "object",
properties: {
server: {
type: "string",
description:
"Server name or ID (optional if bot is only in one server)",
},
user_id: {
type: "string",
description: "User ID to timeout",
},
duration_minutes: {
type: "number",
description: "Timeout duration in minutes",
minimum: 1,
maximum: 40320,
},
reason: {
type: "string",
description: "Reason for timeout",
},
},
required: ["user_id", "duration_minutes"],
},
},
// Channel Information
{
name: "list_channels",
description: "List all channels in the server organized by category",
inputSchema: {
type: "object",
properties: {
server: {
type: "string",
description:
"Server name or ID (optional if bot is only in one server)",
},
include_threads: {
type: "boolean",
description: "Include active threads in the list",
},
},
},
},
{
name: "get_channel_info",
description: "Get detailed information about a specific channel",
inputSchema: {
type: "object",
properties: {
server: {
type: "string",
description:
"Server name or ID (optional if bot is only in one server)",
},
channel: {
type: "string",
description: "Channel name or ID",
},
},
required: ["channel"],
},
},
],
};
});
// Handle Discord tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
const client = requireDiscordClient();
try {
switch (name) {
case "send_message": {
const { server, channel, content } = SendMessageSchema.parse(args);
const channelObj = await findChannel(channel, server);
const message = await channelObj.send(content);
return {
content: [
{
type: "text",
text: `✅ Your message has been sent to #${channelObj.name} in ${channelObj.guild.name}!`,
},
],
};
}
case "read_messages": {
const { server, channel, limit, before, after, around } =
ReadMessagesSchema.parse(args);
const channelObj = await findChannel(channel, server);
// Build fetch options with pagination parameters
const fetchOptions = { limit };
if (before) fetchOptions.before = before;
if (after) fetchOptions.after = after;
if (around) fetchOptions.around = around;
const messages = await channelObj.messages.fetch(fetchOptions);
const messageList = Array.from(messages.values()).map((message) => {
const reactionData = message.reactions.cache.map((reaction) => ({
emoji:
reaction.emoji.name ||
reaction.emoji.id ||
reaction.emoji.toString(),
count: reaction.count,
}));
return {
id: message.id,
author: message.author.tag,
content: message.content,
timestamp: message.createdAt.toISOString(),
reactions: reactionData,
};
});
const formatReaction = (r) => `${r.emoji}(${r.count})`;
// Get the oldest and newest message IDs for pagination info
const oldestMessage =
messageList.length > 0 ? messageList[messageList.length - 1] : null;
const newestMessage = messageList.length > 0 ? messageList[0] : null;
let paginationInfo = "";
if (messageList.length > 0) {
paginationInfo =
`\n\nPagination Info:\n` +
`- To get older messages, use: before=${oldestMessage.id}\n` +
`- To get newer messages, use: after=${newestMessage.id}`;
}
return {
content: [
{
type: "text",
text:
`Retrieved ${messageList.length} messages from #${channelObj.name} in ${channelObj.guild.name}:\n\n` +
messageList
.map(