Skip to main content
Glama
thread-tools.ts14.8 kB
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { z } from 'zod'; import { TextChannel, NewsChannel, ForumChannel, ChannelType, ThreadAutoArchiveDuration } from 'discord.js'; import { getDiscordClient } from '../utils/discord-client.js'; import { withErrorHandling } from '../utils/error-handler.js'; export function registerThreadTools(server: McpServer): void { // List threads in a channel server.tool( 'list_threads', 'List all threads in a channel', { guildId: z.string().describe('The ID of the server (guild)'), channelId: z.string().describe('The ID of the parent channel'), archived: z.boolean().optional().describe('Include archived threads (default false)'), }, async ({ guildId, channelId, archived = false }) => { 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 || (channel.type !== ChannelType.GuildText && channel.type !== ChannelType.GuildAnnouncement && channel.type !== ChannelType.GuildForum)) { throw new Error('Channel does not support threads'); } const threadChannel = channel as TextChannel | NewsChannel | ForumChannel; const threads = archived ? await threadChannel.threads.fetchArchived() : await threadChannel.threads.fetchActive(); return threads.threads.map((thread) => ({ id: thread.id, name: thread.name, type: ChannelType[thread.type], parentId: thread.parentId, ownerId: thread.ownerId, archived: thread.archived, locked: thread.locked, autoArchiveDuration: thread.autoArchiveDuration, messageCount: thread.messageCount, memberCount: thread.memberCount, createdAt: thread.createdAt?.toISOString(), archiveTimestamp: thread.archiveTimestamp ? new Date(thread.archiveTimestamp).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) }] }; } ); // Create thread server.tool( 'create_thread', 'Create a new thread', { guildId: z.string().describe('The ID of the server (guild)'), channelId: z.string().describe('The ID of the parent channel'), name: z.string().describe('Name of the thread'), messageId: z.string().optional().describe('Message ID to start thread from'), autoArchiveDuration: z.enum(['60', '1440', '4320', '10080']).optional().describe('Auto archive after minutes'), type: z.enum(['public', 'private']).optional().describe('Thread type (default public)'), reason: z.string().optional().describe('Reason for creating'), }, async ({ guildId, channelId, name, messageId, autoArchiveDuration, type = 'public', 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 || channel.type !== ChannelType.GuildText) { throw new Error('Channel does not support creating threads'); } const textChannel = channel as TextChannel; const archiveDurationMap: Record<string, ThreadAutoArchiveDuration> = { '60': ThreadAutoArchiveDuration.OneHour, '1440': ThreadAutoArchiveDuration.OneDay, '4320': ThreadAutoArchiveDuration.ThreeDays, '10080': ThreadAutoArchiveDuration.OneWeek, }; let thread; if (messageId) { const message = await textChannel.messages.fetch(messageId); thread = await message.startThread({ name, autoArchiveDuration: autoArchiveDuration ? archiveDurationMap[autoArchiveDuration] : undefined, reason, }); } else { thread = await textChannel.threads.create({ name, autoArchiveDuration: autoArchiveDuration ? archiveDurationMap[autoArchiveDuration] : undefined, type: type === 'private' ? ChannelType.PrivateThread : ChannelType.PublicThread, reason, }); } return { id: thread.id, name: thread.name, type: ChannelType[thread.type], parentId: thread.parentId, message: 'Thread 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) }] }; } ); // Create forum post server.tool( 'create_forum_post', 'Create a new post in a forum channel', { guildId: z.string().describe('The ID of the server (guild)'), channelId: z.string().describe('The ID of the forum channel'), name: z.string().describe('Title of the post'), content: z.string().describe('Content of the initial message'), appliedTags: z.array(z.string()).optional().describe('Tag IDs to apply'), autoArchiveDuration: z.enum(['60', '1440', '4320', '10080']).optional().describe('Auto archive minutes'), reason: z.string().optional().describe('Reason for creating'), }, async ({ guildId, channelId, name, content, appliedTags, autoArchiveDuration, 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 || channel.type !== ChannelType.GuildForum) { throw new Error('Channel is not a forum channel'); } const forumChannel = channel as ForumChannel; const archiveDurationMap: Record<string, ThreadAutoArchiveDuration> = { '60': ThreadAutoArchiveDuration.OneHour, '1440': ThreadAutoArchiveDuration.OneDay, '4320': ThreadAutoArchiveDuration.ThreeDays, '10080': ThreadAutoArchiveDuration.OneWeek, }; const thread = await forumChannel.threads.create({ name, message: { content }, appliedTags, autoArchiveDuration: autoArchiveDuration ? archiveDurationMap[autoArchiveDuration] : undefined, reason, }); return { id: thread.id, name: thread.name, parentId: thread.parentId, appliedTags: thread.appliedTags, message: 'Forum post 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 thread server.tool( 'modify_thread', 'Modify a thread', { guildId: z.string().describe('The ID of the server (guild)'), threadId: z.string().describe('The ID of the thread'), name: z.string().optional().describe('New name'), archived: z.boolean().optional().describe('Archive/unarchive the thread'), locked: z.boolean().optional().describe('Lock/unlock the thread'), autoArchiveDuration: z.enum(['60', '1440', '4320', '10080']).optional().describe('Auto archive minutes'), rateLimitPerUser: z.number().optional().describe('Slowmode in seconds (0-21600)'), appliedTags: z.array(z.string()).optional().describe('Applied tags (forum posts only)'), reason: z.string().optional().describe('Reason for modifying'), }, async ({ guildId, threadId, name, archived, locked, autoArchiveDuration, rateLimitPerUser, appliedTags, reason }) => { const result = await withErrorHandling(async () => { const client = await getDiscordClient(); const guild = await client.guilds.fetch(guildId); const thread = await guild.channels.fetch(threadId); if (!thread || !thread.isThread()) { throw new Error('Thread not found'); } const archiveDurationMap: Record<string, ThreadAutoArchiveDuration> = { '60': ThreadAutoArchiveDuration.OneHour, '1440': ThreadAutoArchiveDuration.OneDay, '4320': ThreadAutoArchiveDuration.ThreeDays, '10080': ThreadAutoArchiveDuration.OneWeek, }; const editData: Record<string, unknown> = {}; if (name !== undefined) editData.name = name; if (archived !== undefined) editData.archived = archived; if (locked !== undefined) editData.locked = locked; if (autoArchiveDuration) editData.autoArchiveDuration = archiveDurationMap[autoArchiveDuration]; if (rateLimitPerUser !== undefined) editData.rateLimitPerUser = rateLimitPerUser; if (appliedTags) editData.appliedTags = appliedTags; if (reason) editData.reason = reason; const updated = await thread.edit(editData); return { id: updated.id, name: updated.name, archived: updated.archived, locked: updated.locked, message: 'Thread 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 thread server.tool( 'delete_thread', 'Delete a thread', { guildId: z.string().describe('The ID of the server (guild)'), threadId: z.string().describe('The ID of the thread to delete'), reason: z.string().optional().describe('Reason for deleting'), }, async ({ guildId, threadId, reason }) => { const result = await withErrorHandling(async () => { const client = await getDiscordClient(); const guild = await client.guilds.fetch(guildId); const thread = await guild.channels.fetch(threadId); if (!thread || !thread.isThread()) { throw new Error('Thread not found'); } const threadName = thread.name; await thread.delete(reason); return { threadId, threadName, message: 'Thread 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) }] }; } ); // Join thread server.tool( 'join_thread', 'Make the bot join a thread', { guildId: z.string().describe('The ID of the server (guild)'), threadId: z.string().describe('The ID of the thread'), }, async ({ guildId, threadId }) => { const result = await withErrorHandling(async () => { const client = await getDiscordClient(); const guild = await client.guilds.fetch(guildId); const thread = await guild.channels.fetch(threadId); if (!thread || !thread.isThread()) { throw new Error('Thread not found'); } await thread.join(); return { threadId, threadName: thread.name, message: 'Joined thread successfully' }; }); if (!result.success) { return { content: [{ type: 'text', text: result.error }], isError: true }; } return { content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }] }; } ); // Leave thread server.tool( 'leave_thread', 'Make the bot leave a thread', { guildId: z.string().describe('The ID of the server (guild)'), threadId: z.string().describe('The ID of the thread'), }, async ({ guildId, threadId }) => { const result = await withErrorHandling(async () => { const client = await getDiscordClient(); const guild = await client.guilds.fetch(guildId); const thread = await guild.channels.fetch(threadId); if (!thread || !thread.isThread()) { throw new Error('Thread not found'); } await thread.leave(); return { threadId, threadName: thread.name, message: 'Left thread successfully' }; }); if (!result.success) { return { content: [{ type: 'text', text: result.error }], isError: true }; } return { content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }] }; } ); // Add thread member server.tool( 'add_thread_member', 'Add a member to a thread', { guildId: z.string().describe('The ID of the server (guild)'), threadId: z.string().describe('The ID of the thread'), userId: z.string().describe('The ID of the user to add'), }, async ({ guildId, threadId, userId }) => { const result = await withErrorHandling(async () => { const client = await getDiscordClient(); const guild = await client.guilds.fetch(guildId); const thread = await guild.channels.fetch(threadId); if (!thread || !thread.isThread()) { throw new Error('Thread not found'); } await thread.members.add(userId); return { threadId, userId, message: 'Member added to thread' }; }); if (!result.success) { return { content: [{ type: 'text', text: result.error }], isError: true }; } return { content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }] }; } ); // Remove thread member server.tool( 'remove_thread_member', 'Remove a member from a thread', { guildId: z.string().describe('The ID of the server (guild)'), threadId: z.string().describe('The ID of the thread'), userId: z.string().describe('The ID of the user to remove'), }, async ({ guildId, threadId, userId }) => { const result = await withErrorHandling(async () => { const client = await getDiscordClient(); const guild = await client.guilds.fetch(guildId); const thread = await guild.channels.fetch(threadId); if (!thread || !thread.isThread()) { throw new Error('Thread not found'); } await thread.members.remove(userId); return { threadId, userId, message: 'Member removed from thread' }; }); if (!result.success) { return { content: [{ type: 'text', text: result.error }], isError: true }; } return { content: [{ type: 'text', text: JSON.stringify(result.data, null, 2) }] }; } ); }

Latest Blog Posts

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/scarecr0w12/discord-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server