From 8021295cbbffdeea3ddf4d22392c684c9dc9188f Mon Sep 17 00:00:00 2001 From: LiQiuDGG Date: Tue, 13 Jan 2026 09:56:37 -0500 Subject: [PATCH] feat: add discord_list_forum_threads tool Add new tool to list all threads (posts) in a Discord forum channel, including both active and archived threads. This addresses the limitation where discord_search_messages only returns 25 message results and doesn't provide a complete thread listing. - Fetch active threads via channel.threads.fetchActive() - Optionally fetch archived threads via channel.threads.fetchArchived() - Deduplicate and sort threads by creation date (newest first) - Return thread metadata including id, name, creator, dates, message count --- src/schemas.ts | 6 +++ src/server.ts | 6 +++ src/toolList.ts | 13 +++++++ src/tools/forum.ts | 95 +++++++++++++++++++++++++++++++++++++++++++++- src/tools/tools.ts | 10 +++-- 5 files changed, 125 insertions(+), 5 deletions(-) diff --git a/src/schemas.ts b/src/schemas.ts index fabc61d..08f6a52 100644 --- a/src/schemas.ts +++ b/src/schemas.ts @@ -25,6 +25,12 @@ export const GetForumPostSchema = z.object({ threadId: z.string() }); +export const ListForumThreadsSchema = z.object({ + forumChannelId: z.string(), + includeArchived: z.boolean().optional().default(true), + limit: z.number().min(1).max(100).optional().default(100) +}); + export const ReplyToForumSchema = z.object({ threadId: z.string(), message: z.string() diff --git a/src/server.ts b/src/server.ts index d086f86..535349b 100644 --- a/src/server.ts +++ b/src/server.ts @@ -13,6 +13,7 @@ import { getForumChannelsHandler, createForumPostHandler, getForumPostHandler, + listForumThreadsHandler, replyToForumHandler, deleteForumPostHandler, createTextChannelHandler, @@ -110,6 +111,11 @@ export class DiscordMCPServer { toolResponse = await getForumPostHandler(args, this.toolContext); return toolResponse; + case "discord_list_forum_threads": + this.logClientState("before discord_list_forum_threads handler"); + toolResponse = await listForumThreadsHandler(args, this.toolContext); + return toolResponse; + case "discord_reply_to_forum": this.logClientState("before discord_reply_to_forum handler"); toolResponse = await replyToForumHandler(args, this.toolContext); diff --git a/src/toolList.ts b/src/toolList.ts index 4ae460d..2fc7864 100644 --- a/src/toolList.ts +++ b/src/toolList.ts @@ -102,6 +102,19 @@ export const toolList = [ required: ["threadId"] } }, + { + name: "discord_list_forum_threads", + description: "Lists all threads (posts) in a Discord forum channel, including both active and archived threads", + inputSchema: { + type: "object", + properties: { + forumChannelId: { type: "string", description: "The ID of the forum channel to list threads from" }, + includeArchived: { type: "boolean", description: "Whether to include archived threads (default: true)", default: true }, + limit: { type: "number", description: "Maximum number of archived threads to fetch (default: 100, max: 100)", minimum: 1, maximum: 100, default: 100 } + }, + required: ["forumChannelId"] + } + }, { name: "discord_reply_to_forum", description: "Adds a reply to an existing forum post or thread", diff --git a/src/tools/forum.ts b/src/tools/forum.ts index 8af3a24..8fec4b0 100644 --- a/src/tools/forum.ts +++ b/src/tools/forum.ts @@ -1,5 +1,5 @@ import { ChannelType, ForumChannel } from 'discord.js'; -import { GetForumChannelsSchema, CreateForumPostSchema, GetForumPostSchema, ReplyToForumSchema, DeleteForumPostSchema } from '../schemas.js'; +import { GetForumChannelsSchema, CreateForumPostSchema, GetForumPostSchema, ListForumThreadsSchema, ReplyToForumSchema, DeleteForumPostSchema } from '../schemas.js'; import { ToolHandler } from './types.js'; import { handleDiscordError } from "../errorHandler.js"; @@ -145,6 +145,99 @@ export const getForumPostHandler: ToolHandler = async (args, { client }) => { } }; +export const listForumThreadsHandler: ToolHandler = async (args, { client }) => { + const { forumChannelId, includeArchived, limit } = ListForumThreadsSchema.parse(args); + + try { + if (!client.isReady()) { + return { + content: [{ type: "text", text: "Discord client not logged in." }], + isError: true + }; + } + + const channel = await client.channels.fetch(forumChannelId); + if (!channel || channel.type !== ChannelType.GuildForum) { + return { + content: [{ type: "text", text: `Channel ID ${forumChannelId} is not a forum channel.` }], + isError: true + }; + } + + const forumChannel = channel as ForumChannel; + + // Fetch active threads + const activeThreads = await forumChannel.threads.fetchActive(); + + // Fetch archived threads if requested + let archivedThreads: typeof activeThreads | null = null; + if (includeArchived) { + archivedThreads = await forumChannel.threads.fetchArchived({ limit: limit }); + } + + // Combine and format thread information + const threads: Array<{ + id: string; + name: string; + createdAt: Date | null; + archived: boolean; + locked: boolean; + messageCount: number | null; + ownerId: string | null; + }> = []; + + // Add active threads + activeThreads.threads.forEach(thread => { + threads.push({ + id: thread.id, + name: thread.name, + createdAt: thread.createdAt, + archived: thread.archived || false, + locked: thread.locked || false, + messageCount: thread.messageCount, + ownerId: thread.ownerId + }); + }); + + // Add archived threads if fetched + if (archivedThreads) { + archivedThreads.threads.forEach(thread => { + // Avoid duplicates + if (!threads.find(t => t.id === thread.id)) { + threads.push({ + id: thread.id, + name: thread.name, + createdAt: thread.createdAt, + archived: thread.archived || false, + locked: thread.locked || false, + messageCount: thread.messageCount, + ownerId: thread.ownerId + }); + } + }); + } + + // Sort by creation date (newest first) + threads.sort((a, b) => { + if (!a.createdAt || !b.createdAt) return 0; + return b.createdAt.getTime() - a.createdAt.getTime(); + }); + + return { + content: [{ + type: "text", + text: JSON.stringify({ + forumChannelId, + totalThreads: threads.length, + threads + }, null, 2) + }] + }; + } catch (error) { + return handleDiscordError(error); + } +}; + export const replyToForumHandler: ToolHandler = async (args, { client }) => { const { threadId, message } = ReplyToForumSchema.parse(args); diff --git a/src/tools/tools.ts b/src/tools/tools.ts index 78e74c0..a35a785 100644 --- a/src/tools/tools.ts +++ b/src/tools/tools.ts @@ -3,10 +3,11 @@ import { z } from "zod"; import { ToolResponse, ToolContext, ToolHandler } from "./types.js"; import { loginHandler } from './login.js'; import { sendMessageHandler } from './send-message.js'; -import { - getForumChannelsHandler, - createForumPostHandler, - getForumPostHandler, +import { + getForumChannelsHandler, + createForumPostHandler, + getForumPostHandler, + listForumThreadsHandler, replyToForumHandler, deleteForumPostHandler } from './forum.js'; @@ -43,6 +44,7 @@ export { getForumChannelsHandler, createForumPostHandler, getForumPostHandler, + listForumThreadsHandler, replyToForumHandler, deleteForumPostHandler, createTextChannelHandler,