Skip to main content
Glama

Discord Agent MCP

by aj-geddes
channels.ts•41.1 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, ChannelNotFoundError, GuildNotFoundError, } from "../errors/discord.js"; import { ChannelType, PermissionFlagsBits } from "discord.js"; export function registerChannelTools( server: McpServer, discordManager: DiscordClientManager, logger: Logger, ) { // Create Text Channel Tool server.registerTool( "create_text_channel", { title: "Create Text Channel", description: "Create a new text channel in a Discord server", inputSchema: { guildId: z.string().describe("Guild ID"), name: z.string().min(1).max(100).describe("Channel name"), topic: z .string() .max(1024) .optional() .describe("Channel topic/description"), parent: z.string().optional().describe("Parent category ID"), nsfw: z.boolean().default(false).describe("Mark channel as NSFW"), }, outputSchema: { success: z.boolean(), channel: z .object({ id: z.string(), name: z.string(), type: z.number(), position: z.number(), parentId: z.string().nullable(), }) .optional(), error: z.string().optional(), }, }, async ({ guildId, name, topic, parent, nsfw }) => { try { const client = discordManager.getClient(); const guild = await client.guilds.fetch(guildId).catch(() => null); if (!guild) { throw new GuildNotFoundError(guildId); } // Check permissions const botMember = await guild.members.fetchMe(); if (!botMember.permissions.has(PermissionFlagsBits.ManageChannels)) { throw new PermissionDeniedError("ManageChannels", guildId); } const channel = await guild.channels.create({ name, type: ChannelType.GuildText, topic, parent: parent || undefined, nsfw, }); const output = { success: true, channel: { id: channel.id, name: channel.name, type: channel.type, position: channel.position, parentId: channel.parentId, }, }; logger.info("Channel created", { guildId, channelId: channel.id, name, }); return { content: [ { type: "text" as const, text: `Channel <#${channel.id}> created successfully`, }, ], structuredContent: output, }; } catch (error: any) { logger.error("Failed to create channel", { error: error.message, guildId, name, }); const output = { success: false, error: error.message, }; return { content: [ { type: "text" as const, text: `Failed to create channel: ${error.message}`, }, ], structuredContent: output, isError: true, }; } }, ); // Delete Channel Tool server.registerTool( "delete_channel", { title: "Delete Channel", description: "Permanently delete a Discord channel", inputSchema: { channelId: z.string().describe("Channel ID to delete"), reason: z .string() .optional() .describe("Reason for deletion (audit log)"), }, outputSchema: { success: z.boolean(), deletedChannelId: z.string().optional(), error: z.string().optional(), }, }, async ({ channelId, reason }) => { try { const client = discordManager.getClient(); const channel = await client.channels .fetch(channelId) .catch(() => null); if (!channel) { throw new ChannelNotFoundError(channelId); } // Check permissions if ("guild" in channel && channel.guild) { const botMember = await channel.guild.members.fetchMe(); if (!botMember.permissions.has(PermissionFlagsBits.ManageChannels)) { throw new PermissionDeniedError("ManageChannels", channel.guild.id); } } await channel.delete(reason); const output = { success: true, deletedChannelId: channelId, }; logger.info("Channel deleted", { channelId, reason }); return { content: [ { type: "text" as const, text: `Channel ${channelId} deleted successfully`, }, ], structuredContent: output, }; } catch (error: any) { logger.error("Failed to delete channel", { error: error.message, channelId, }); const output = { success: false, error: error.message, }; return { content: [ { type: "text" as const, text: `Failed to delete channel: ${error.message}`, }, ], structuredContent: output, isError: true, }; } }, ); // Get Server Info Tool server.registerTool( "get_server_info", { title: "Get Server Information", description: "Retrieve detailed information about a Discord server", inputSchema: { guildId: z.string().describe("Guild ID"), }, outputSchema: { success: z.boolean(), guild: z .object({ id: z.string(), name: z.string(), ownerId: z.string(), memberCount: z.number(), channelCount: z.number(), roleCount: z.number(), description: z.string().nullable(), createdTimestamp: z.number(), }) .optional(), error: z.string().optional(), }, }, async ({ guildId }) => { try { const client = discordManager.getClient(); const guild = await client.guilds.fetch(guildId).catch(() => null); if (!guild) { throw new GuildNotFoundError(guildId); } const output = { success: true, guild: { id: guild.id, name: guild.name, ownerId: guild.ownerId, memberCount: guild.memberCount, channelCount: guild.channels.cache.size, roleCount: guild.roles.cache.size, description: guild.description, createdTimestamp: guild.createdTimestamp, }, }; logger.info("Server info retrieved", { guildId }); return { content: [ { type: "text" as const, text: `Server: ${guild.name}\nMembers: ${guild.memberCount}\nChannels: ${guild.channels.cache.size}`, }, ], structuredContent: output, }; } catch (error: any) { logger.error("Failed to get server info", { error: error.message, guildId, }); const output = { success: false, error: error.message, }; return { content: [ { type: "text" as const, text: `Failed to get server info: ${error.message}`, }, ], structuredContent: output, isError: true, }; } }, ); // Create Voice Channel Tool server.registerTool( "create_voice_channel", { title: "Create Voice Channel", description: "Create a new voice channel in the server", inputSchema: { guildId: z.string().describe("Guild ID"), name: z.string().min(1).max(100).describe("Channel name"), parent: z.string().optional().describe("Parent category ID"), userLimit: z .number() .int() .min(0) .max(99) .optional() .describe("User limit (0 = unlimited)"), bitrate: z .number() .int() .min(8000) .max(384000) .optional() .describe("Audio bitrate in bps (8000-384000)"), }, outputSchema: { success: z.boolean(), channel: z .object({ id: z.string(), name: z.string(), type: z.number(), userLimit: z.number(), bitrate: z.number(), }) .optional(), error: z.string().optional(), }, }, async ({ guildId, name, parent, userLimit, bitrate }) => { try { const client = discordManager.getClient(); const guild = await client.guilds.fetch(guildId).catch(() => null); if (!guild) { throw new GuildNotFoundError(guildId); } const botMember = await guild.members.fetchMe(); if (!botMember.permissions.has(PermissionFlagsBits.ManageChannels)) { throw new PermissionDeniedError("ManageChannels", guildId); } const channel = await guild.channels.create({ name, type: ChannelType.GuildVoice, parent: parent || undefined, userLimit: userLimit, bitrate: bitrate, }); const output = { success: true, channel: { id: channel.id, name: channel.name, type: channel.type, userLimit: (channel as any).userLimit || 0, bitrate: (channel as any).bitrate || 64000, }, }; logger.info("Voice channel created", { guildId, channelId: channel.id, }); return { content: [ { type: "text" as const, text: `Voice channel "${name}" created successfully (ID: ${channel.id})`, }, ], structuredContent: output, }; } catch (error: any) { logger.error("Failed to create voice channel", { error: error.message, guildId, }); const output = { success: false, error: error.message, }; return { content: [ { type: "text" as const, text: `Failed to create voice channel: ${error.message}`, }, ], structuredContent: output, isError: true, }; } }, ); // Create Category Tool server.registerTool( "create_category", { title: "Create Channel Category", description: "Create a new category to organize channels", inputSchema: { guildId: z.string().describe("Guild ID"), name: z.string().min(1).max(100).describe("Category name"), }, outputSchema: { success: z.boolean(), category: z .object({ id: z.string(), name: z.string(), type: z.number(), position: z.number(), }) .optional(), error: z.string().optional(), }, }, async ({ guildId, name }) => { try { const client = discordManager.getClient(); const guild = await client.guilds.fetch(guildId).catch(() => null); if (!guild) { throw new GuildNotFoundError(guildId); } const botMember = await guild.members.fetchMe(); if (!botMember.permissions.has(PermissionFlagsBits.ManageChannels)) { throw new PermissionDeniedError("ManageChannels", guildId); } const category = await guild.channels.create({ name, type: ChannelType.GuildCategory, }); const output = { success: true, category: { id: category.id, name: category.name, type: category.type, position: category.position, }, }; logger.info("Category created", { guildId, categoryId: category.id }); return { content: [ { type: "text" as const, text: `Category "${name}" created successfully (ID: ${category.id})`, }, ], structuredContent: output, }; } catch (error: any) { logger.error("Failed to create category", { error: error.message, guildId, }); const output = { success: false, error: error.message, }; return { content: [ { type: "text" as const, text: `Failed to create category: ${error.message}`, }, ], structuredContent: output, isError: true, }; } }, ); // Create Forum Channel Tool server.registerTool( "create_forum_channel", { title: "Create Forum Channel", description: "Create a new forum channel for threaded discussions", inputSchema: { guildId: z.string().describe("Guild ID"), name: z.string().min(1).max(100).describe("Forum name"), topic: z.string().max(1024).optional().describe("Forum description"), parent: z.string().optional().describe("Parent category ID"), tags: z .array(z.string()) .max(20) .optional() .describe("Forum tags (max 20)"), }, outputSchema: { success: z.boolean(), forum: z .object({ id: z.string(), name: z.string(), type: z.number(), }) .optional(), error: z.string().optional(), }, }, async ({ guildId, name, topic, parent, tags }) => { try { const client = discordManager.getClient(); const guild = await client.guilds.fetch(guildId).catch(() => null); if (!guild) { throw new GuildNotFoundError(guildId); } const botMember = await guild.members.fetchMe(); if (!botMember.permissions.has(PermissionFlagsBits.ManageChannels)) { throw new PermissionDeniedError("ManageChannels", guildId); } const availableTags = tags ? tags.map((tag) => ({ name: tag })) : undefined; const forum = await guild.channels.create({ name, type: ChannelType.GuildForum, topic: topic, parent: parent || undefined, availableTags, }); const output = { success: true, forum: { id: forum.id, name: forum.name, type: forum.type, }, }; logger.info("Forum created", { guildId, forumId: forum.id }); return { content: [ { type: "text" as const, text: `Forum "${name}" created successfully (ID: ${forum.id})`, }, ], structuredContent: output, }; } catch (error: any) { logger.error("Failed to create forum", { error: error.message, guildId, }); const output = { success: false, error: error.message, }; return { content: [ { type: "text" as const, text: `Failed to create forum: ${error.message}`, }, ], structuredContent: output, isError: true, }; } }, ); // Modify Channel Tool server.registerTool( "modify_channel", { title: "Modify Channel Settings", description: "Update channel name, topic, slowmode, or other settings", inputSchema: { channelId: z.string().describe("Channel ID to modify"), name: z .string() .min(1) .max(100) .optional() .describe("New channel name"), topic: z.string().max(1024).optional().describe("New channel topic"), nsfw: z.boolean().optional().describe("Mark channel as NSFW"), slowmode: z .number() .int() .min(0) .max(21600) .optional() .describe("Slowmode delay in seconds (0-21600)"), reason: z .string() .optional() .describe("Reason for modification (audit log)"), }, outputSchema: { success: z.boolean(), channel: z .object({ id: z.string(), name: z.string(), }) .optional(), error: z.string().optional(), }, }, async ({ channelId, name, topic, nsfw, slowmode, reason }) => { try { const client = discordManager.getClient(); const channel = await client.channels .fetch(channelId) .catch(() => null); if (!channel) { throw new ChannelNotFoundError(channelId); } if (!("guild" in channel) || !channel.guild) { throw new Error("This tool only works with server channels"); } const botMember = await channel.guild.members.fetchMe(); if (!botMember.permissions.has(PermissionFlagsBits.ManageChannels)) { throw new PermissionDeniedError("ManageChannels", channel.guild.id); } const updateOptions: any = {}; if (name !== undefined) updateOptions.name = name; if (topic !== undefined) updateOptions.topic = topic; if (nsfw !== undefined) updateOptions.nsfw = nsfw; if (slowmode !== undefined) updateOptions.rateLimitPerUser = slowmode; if (reason !== undefined) updateOptions.reason = reason; const updatedChannel = await (channel as any).edit(updateOptions); const output = { success: true, channel: { id: updatedChannel.id, name: updatedChannel.name, }, }; logger.info("Channel modified", { channelId, reason }); return { content: [ { type: "text" as const, text: `Channel "${updatedChannel.name}" modified successfully`, }, ], structuredContent: output, }; } catch (error: any) { logger.error("Failed to modify channel", { error: error.message, channelId, }); const output = { success: false, error: error.message, }; return { content: [ { type: "text" as const, text: `Failed to modify channel: ${error.message}`, }, ], structuredContent: output, isError: true, }; } }, ); // Create Thread Tool server.registerTool( "create_thread", { title: "Create Discussion Thread", description: "Create a new thread in a channel or from a message", inputSchema: { channelId: z.string().describe("Channel ID to create thread in"), name: z.string().min(1).max(100).describe("Thread name"), message: z .string() .optional() .describe("Initial message content for thread"), autoArchiveDuration: z .enum(["60", "1440", "4320", "10080"]) .optional() .describe( "Auto-archive after inactivity: 60=1hr, 1440=24hr, 4320=3d, 10080=7d", ), }, outputSchema: { success: z.boolean(), thread: z .object({ id: z.string(), name: z.string(), parentId: z.string(), }) .optional(), error: z.string().optional(), }, }, async ({ channelId, name, message, autoArchiveDuration }) => { try { const client = discordManager.getClient(); const channel = await client.channels .fetch(channelId) .catch(() => null); if (!channel || !channel.isTextBased()) { throw new ChannelNotFoundError(channelId); } if ("guild" in channel && channel.guild) { const permissions = channel.permissionsFor(client.user!); if (!permissions?.has(PermissionFlagsBits.CreatePublicThreads)) { throw new PermissionDeniedError("CreatePublicThreads", channelId); } } // Create thread if (!("threads" in channel)) { throw new Error("This channel does not support threads"); } const thread = await (channel as any).threads.create({ name, autoArchiveDuration: autoArchiveDuration ? parseInt(autoArchiveDuration) : 60, reason: "Thread created via MCP", }); // Send initial message if provided if (message) { await thread.send(message); } const output = { success: true, thread: { id: thread.id, name: thread.name, parentId: thread.parentId || channelId, }, }; logger.info("Thread created", { channelId, threadId: thread.id }); return { content: [ { type: "text" as const, text: `Thread "${name}" created successfully (ID: ${thread.id})`, }, ], structuredContent: output, }; } catch (error: any) { logger.error("Failed to create thread", { error: error.message, channelId, }); const output = { success: false, error: error.message, }; return { content: [ { type: "text" as const, text: `Failed to create thread: ${error.message}`, }, ], structuredContent: output, isError: true, }; } }, ); // Archive Thread Tool server.registerTool( "archive_thread", { title: "Archive Thread", description: "Archive (lock) a thread", inputSchema: { threadId: z.string().describe("Thread ID to archive"), locked: z .boolean() .optional() .default(true) .describe("Lock thread (prevents new messages)"), }, outputSchema: { success: z.boolean(), threadId: z.string().optional(), error: z.string().optional(), }, }, async ({ threadId, locked = true }) => { try { const client = discordManager.getClient(); const thread = await client.channels.fetch(threadId).catch(() => null); if (!thread || !thread.isThread()) { throw new Error(`Thread ${threadId} not found or is not a thread`); } if (thread.guild) { const botMember = await thread.guild.members.fetchMe(); if (!botMember.permissions.has(PermissionFlagsBits.ManageThreads)) { throw new PermissionDeniedError("ManageThreads", thread.guild.id); } } await thread.setArchived(true); if (locked) { await thread.setLocked(true); } const output = { success: true, threadId, }; logger.info("Thread archived", { threadId, locked }); return { content: [ { type: "text" as const, text: `Thread archived successfully${locked ? " and locked" : ""}`, }, ], structuredContent: output, }; } catch (error: any) { logger.error("Failed to archive thread", { error: error.message, threadId, }); const output = { success: false, error: error.message, }; return { content: [ { type: "text" as const, text: `Failed to archive thread: ${error.message}`, }, ], structuredContent: output, isError: true, }; } }, ); // List Channels Tool server.registerTool( "list_channels", { title: "List Server Channels", description: "Get all channels in the server organized by type", inputSchema: { guildId: z.string().describe("Guild ID"), type: z .enum(["text", "voice", "category", "forum", "all"]) .optional() .default("all") .describe("Filter by channel type"), }, outputSchema: { success: z.boolean(), channels: z .array( z.object({ id: z.string(), name: z.string(), type: z.string(), position: z.number(), parentId: z.string().nullable(), }), ) .optional(), totalCount: z.number().optional(), error: z.string().optional(), }, }, async ({ guildId, type = "all" }) => { try { const client = discordManager.getClient(); const guild = await client.guilds.fetch(guildId).catch(() => null); if (!guild) { throw new GuildNotFoundError(guildId); } const channels = await guild.channels.fetch(); let filteredChannels = Array.from(channels.values()).filter( (c): c is NonNullable<typeof c> => c !== null, ); // Filter by type if (type !== "all") { const typeMap: Record<string, ChannelType> = { text: ChannelType.GuildText, voice: ChannelType.GuildVoice, category: ChannelType.GuildCategory, forum: ChannelType.GuildForum, }; filteredChannels = filteredChannels.filter( (c) => c.type === typeMap[type], ); } const channelList = filteredChannels.map((channel) => ({ id: channel.id, name: channel.name, type: ChannelType[channel.type], position: channel.position, parentId: channel.parentId, })); // Sort by position channelList.sort((a, b) => a.position - b.position); const output = { success: true, channels: channelList, totalCount: channelList.length, }; logger.info("Channels listed", { guildId, type, count: channelList.length, }); return { content: [ { type: "text" as const, text: `Found ${channelList.length} channel(s) in server`, }, ], structuredContent: output, }; } catch (error: any) { logger.error("Failed to list channels", { error: error.message, guildId, }); const output = { success: false, error: error.message, }; return { content: [ { type: "text" as const, text: `Failed to list channels: ${error.message}`, }, ], structuredContent: output, isError: true, }; } }, ); // Create Stage Channel Tool server.registerTool( "create_stage_channel", { title: "Create Stage Channel", description: "Create a stage voice channel for presentations and events", inputSchema: { guildId: z.string().describe("Guild ID"), name: z.string().min(1).max(100).describe("Stage channel name"), topic: z.string().max(1024).optional().describe("Stage topic"), parent: z.string().optional().describe("Parent category ID"), }, outputSchema: { success: z.boolean(), channel: z .object({ id: z.string(), name: z.string(), type: z.number(), }) .optional(), error: z.string().optional(), }, }, async ({ guildId, name, topic, parent }) => { try { const client = discordManager.getClient(); const guild = await client.guilds.fetch(guildId).catch(() => null); if (!guild) { throw new GuildNotFoundError(guildId); } const botMember = await guild.members.fetchMe(); if (!botMember.permissions.has(PermissionFlagsBits.ManageChannels)) { throw new PermissionDeniedError("ManageChannels", guildId); } const channel = await guild.channels.create({ name, type: ChannelType.GuildStageVoice, topic: topic, parent: parent || undefined, }); const output = { success: true, channel: { id: channel.id, name: channel.name, type: channel.type, }, }; logger.info("Stage channel created", { guildId, channelId: channel.id, }); return { content: [ { type: "text" as const, text: `Stage channel "${name}" created successfully (ID: ${channel.id})`, }, ], structuredContent: output, }; } catch (error: any) { logger.error("Failed to create stage channel", { error: error.message, guildId, }); const output = { success: false, error: error.message, }; return { content: [ { type: "text" as const, text: `Failed to create stage channel: ${error.message}`, }, ], structuredContent: output, isError: true, }; } }, ); // Set Channel Permissions Tool server.registerTool( "set_channel_permissions", { title: "Set Channel Permissions", description: "Override permissions for a role or member on a specific channel", inputSchema: { channelId: z.string().describe("Channel ID"), targetId: z .string() .describe("Role ID or User ID to set permissions for"), targetType: z .enum(["role", "member"]) .describe("Whether target is a role or member"), allow: z .array(z.string()) .optional() .describe("Array of permission names to allow"), deny: z .array(z.string()) .optional() .describe("Array of permission names to deny"), }, outputSchema: { success: z.boolean(), channelId: z.string().optional(), targetId: z.string().optional(), error: z.string().optional(), }, }, async ({ channelId, targetId, targetType, allow, deny }) => { try { const client = discordManager.getClient(); const channel = await client.channels .fetch(channelId) .catch(() => null); if (!channel) { throw new ChannelNotFoundError(channelId); } // Check if channel is in a guild if (!("guild" in channel) || !channel.guild) { throw new Error("Channel is not in a guild"); } const botMember = await channel.guild.members.fetchMe(); if (!botMember.permissions.has(PermissionFlagsBits.ManageChannels)) { throw new PermissionDeniedError("ManageChannels", channel.guild.id); } // Build permission overwrites const permissionOverwrites: any = {}; if (allow && allow.length > 0) { permissionOverwrites.allow = allow; } if (deny && deny.length > 0) { permissionOverwrites.deny = deny; } // Apply permission overwrite await (channel as any).permissionOverwrites.create( targetId, permissionOverwrites, ); const output = { success: true, channelId: channel.id, targetId, }; logger.info("Channel permissions set", { channelId, targetId, targetType, }); return { content: [ { type: "text" as const, text: `Permissions set for ${targetType} ${targetId} on channel ${channelId}`, }, ], structuredContent: output, }; } catch (error: any) { logger.error("Failed to set channel permissions", { error: error.message, channelId, targetId, }); const output = { success: false, error: error.message, }; return { content: [ { type: "text" as const, text: `Failed to set channel permissions: ${error.message}`, }, ], structuredContent: output, isError: true, }; } }, ); // Find Threads Tool server.registerTool( "find_threads", { title: "Find Threads in Forum", description: "Search for threads in a forum channel by name or list all threads", inputSchema: { forumId: z.string().describe("Forum channel ID"), name: z.string().optional().describe("Search for threads with this name (partial match)"), archived: z.boolean().optional().describe("Include archived threads"), limit: z.number().int().min(1).max(100).optional().default(50).describe("Max number of threads to return"), }, outputSchema: { success: z.boolean(), threads: z.array(z.object({ id: z.string(), name: z.string(), ownerId: z.string(), messageCount: z.number(), memberCount: z.number(), createdAt: z.string(), archived: z.boolean(), locked: z.boolean(), lastMessageAt: z.string().nullable(), })).optional(), totalCount: z.number().optional(), error: z.string().optional(), }, }, async ({ forumId, name, archived = false, limit = 50 }) => { try { const client = discordManager.getClient(); const forum = await client.channels.fetch(forumId).catch(() => null); if (!forum || forum.type !== ChannelType.GuildForum) { throw new Error(`Channel ${forumId} is not a forum channel`); } if (forum.guild) { const botMember = await forum.guild.members.fetchMe(); if (!botMember.permissions.has(PermissionFlagsBits.ViewChannel)) { throw new PermissionDeniedError("ViewChannel", forum.guild.id); } } // Fetch threads const threadsData = await forum.threads.fetchActive(); let threads = Array.from(threadsData.threads.values()); // Optionally fetch archived threads if (archived) { const archivedThreads = await forum.threads.fetchArchived(); threads = threads.concat(Array.from(archivedThreads.threads.values())); } // Filter by name if provided if (name) { const searchLower = name.toLowerCase(); threads = threads.filter(t => t.name.toLowerCase().includes(searchLower)); } // Limit results threads = threads.slice(0, limit); const threadList = threads.map(thread => ({ id: thread.id, name: thread.name, ownerId: thread.ownerId || "", messageCount: thread.messageCount || 0, memberCount: thread.memberCount || 0, createdAt: thread.createdAt?.toISOString() || new Date().toISOString(), archived: thread.archived || false, locked: thread.locked || false, lastMessageAt: thread.lastMessage?.createdAt?.toISOString() || null, })); const output = { success: true, threads: threadList, totalCount: threadList.length, }; logger.info("Threads found", { forumId, count: threadList.length, name }); return { content: [ { type: "text" as const, text: `Found ${threadList.length} thread(s) in forum${name ? ` matching "${name}"` : ""}`, }, ], structuredContent: output, }; } catch (error: any) { logger.error("Failed to find threads", { error: error.message, forumId, }); const output = { success: false, error: error.message, }; return { content: [ { type: "text" as const, text: `Failed to find threads: ${error.message}`, }, ], structuredContent: output, isError: true, }; } }, ); // Get Channel Details Tool server.registerTool( "get_channel_details", { title: "Get Channel Details", description: "Get detailed information about a channel including type, permissions, and capabilities", inputSchema: { channelId: z.string().describe("Channel ID"), }, outputSchema: { success: z.boolean(), channel: z.object({ id: z.string(), name: z.string(), type: z.number(), typeName: z.string(), topic: z.string().nullable(), nsfw: z.boolean().optional(), parentId: z.string().nullable(), parentName: z.string().nullable(), position: z.number(), rateLimitPerUser: z.number().optional(), supportsThreads: z.boolean(), supportsMessages: z.boolean(), isTextBased: z.boolean(), isForum: z.boolean(), isVoice: z.boolean(), }).optional(), error: z.string().optional(), }, }, async ({ channelId }) => { try { const client = discordManager.getClient(); const channel = await client.channels.fetch(channelId).catch(() => null); if (!channel) { throw new ChannelNotFoundError(channelId); } // Check if this is a guild channel (not a DM) if (!("guild" in channel) || !channel.guild) { throw new Error("This tool only works with server channels, not DMs"); } // Determine channel capabilities const supportsThreads = channel.type === ChannelType.GuildText || channel.type === ChannelType.GuildAnnouncement || channel.type === ChannelType.GuildForum; const supportsMessages = channel.isTextBased(); const isForum = channel.type === ChannelType.GuildForum; const isVoice = channel.type === ChannelType.GuildVoice || channel.type === ChannelType.GuildStageVoice; // Get parent channel name if exists let parentName: string | null = null; if ("parentId" in channel && channel.parentId) { const parent = await channel.guild.channels.fetch(channel.parentId).catch(() => null); parentName = parent?.name || null; } // At this point we know it's a guild channel, so we can safely cast const guildChannel = channel as any; const channelDetails = { id: guildChannel.id, name: guildChannel.name || guildChannel.id, type: guildChannel.type, typeName: ChannelType[guildChannel.type], topic: guildChannel.topic || null, nsfw: guildChannel.nsfw ?? false, parentId: guildChannel.parentId || null, parentName, position: guildChannel.position ?? 0, rateLimitPerUser: guildChannel.rateLimitPerUser, supportsThreads, supportsMessages, isTextBased: guildChannel.isTextBased(), isForum, isVoice, }; const output = { success: true, channel: channelDetails, }; logger.info("Channel details retrieved", { channelId }); const channelName = guildChannel.name || guildChannel.id; return { content: [ { type: "text" as const, text: `Channel: ${channelName} (${ChannelType[channel.type]})`, }, ], structuredContent: output, }; } catch (error: any) { logger.error("Failed to get channel details", { error: error.message, channelId, }); const output = { success: false, error: error.message, }; return { content: [ { type: "text" as const, text: `Failed to get channel details: ${error.message}`, }, ], structuredContent: output, isError: true, }; } }, ); }

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