feat: add forum tags, update forum post, and edit message tools
Add three new Discord tools: - discord_get_forum_tags: retrieve available tags (id, name, emoji) for a forum channel - discord_update_forum_post: update a forum post's title, tags, archived/locked status - discord_edit_message: edit a bot-authored message with ownership validation Tags can be specified by name or ID in update_forum_post for convenience.
This commit is contained in:
parent
6558290613
commit
6b9dde03e9
|
|
@ -101,6 +101,24 @@ export const DeleteForumPostSchema = z.object({
|
|||
reason: z.string().optional()
|
||||
});
|
||||
|
||||
export const GetForumTagsSchema = z.object({
|
||||
forumChannelId: z.string()
|
||||
});
|
||||
|
||||
export const UpdateForumPostSchema = z.object({
|
||||
threadId: z.string(),
|
||||
name: z.string().optional(),
|
||||
tags: z.array(z.string()).optional(),
|
||||
archived: z.boolean().optional(),
|
||||
locked: z.boolean().optional()
|
||||
});
|
||||
|
||||
export const EditMessageSchema = z.object({
|
||||
channelId: z.string(),
|
||||
messageId: z.string(),
|
||||
content: z.string()
|
||||
});
|
||||
|
||||
export const DeleteMessageSchema = z.object({
|
||||
channelId: z.string(),
|
||||
messageId: z.string(),
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ import {
|
|||
listForumThreadsHandler,
|
||||
replyToForumHandler,
|
||||
deleteForumPostHandler,
|
||||
getForumTagsHandler,
|
||||
updateForumPostHandler,
|
||||
editMessageHandler,
|
||||
createTextChannelHandler,
|
||||
deleteChannelHandler,
|
||||
readMessagesHandler,
|
||||
|
|
@ -126,6 +129,21 @@ export class DiscordMCPServer {
|
|||
toolResponse = await deleteForumPostHandler(args, this.toolContext);
|
||||
return toolResponse;
|
||||
|
||||
case "discord_get_forum_tags":
|
||||
this.logClientState("before discord_get_forum_tags handler");
|
||||
toolResponse = await getForumTagsHandler(args, this.toolContext);
|
||||
return toolResponse;
|
||||
|
||||
case "discord_update_forum_post":
|
||||
this.logClientState("before discord_update_forum_post handler");
|
||||
toolResponse = await updateForumPostHandler(args, this.toolContext);
|
||||
return toolResponse;
|
||||
|
||||
case "discord_edit_message":
|
||||
this.logClientState("before discord_edit_message handler");
|
||||
toolResponse = await editMessageHandler(args, this.toolContext);
|
||||
return toolResponse;
|
||||
|
||||
case "discord_create_text_channel":
|
||||
this.logClientState("before discord_create_text_channel handler");
|
||||
toolResponse = await createTextChannelHandler(args, this.toolContext);
|
||||
|
|
|
|||
|
|
@ -235,6 +235,45 @@ export const toolList = [
|
|||
required: ["threadId"]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "discord_get_forum_tags",
|
||||
description: "Gets all available tags for a Discord forum channel, including tag IDs, names, and emoji",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
forumChannelId: { type: "string", description: "The ID of the forum channel to get tags from" }
|
||||
},
|
||||
required: ["forumChannelId"]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "discord_update_forum_post",
|
||||
description: "Updates a forum post's title, applied tags, archived status, or locked status. Tags can be specified by name or ID.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
threadId: { type: "string", description: "The ID of the forum post/thread to update" },
|
||||
name: { type: "string", description: "New title for the forum post" },
|
||||
tags: { type: "array", items: { type: "string" }, description: "Tags to apply (by name or ID). Replaces all existing tags." },
|
||||
archived: { type: "boolean", description: "Whether to archive or unarchive the post" },
|
||||
locked: { type: "boolean", description: "Whether to lock or unlock the post" }
|
||||
},
|
||||
required: ["threadId"]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "discord_edit_message",
|
||||
description: "Edits a message previously sent by the bot. Only messages authored by the bot can be edited.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
channelId: { type: "string", description: "The ID of the channel containing the message" },
|
||||
messageId: { type: "string", description: "The ID of the message to edit" },
|
||||
content: { type: "string", description: "The new content for the message" }
|
||||
},
|
||||
required: ["channelId", "messageId", "content"]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "discord_delete_message",
|
||||
description: "Deletes a specific message from a Discord text channel",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { ChannelType, ForumChannel } from 'discord.js';
|
||||
import { GetForumChannelsSchema, CreateForumPostSchema, GetForumPostSchema, ListForumThreadsSchema, ReplyToForumSchema, DeleteForumPostSchema } from '../schemas.js';
|
||||
import { GetForumChannelsSchema, CreateForumPostSchema, GetForumPostSchema, ListForumThreadsSchema, ReplyToForumSchema, DeleteForumPostSchema, GetForumTagsSchema, UpdateForumPostSchema } from '../schemas.js';
|
||||
import { ToolHandler } from './types.js';
|
||||
import { handleDiscordError } from "../errorHandler.js";
|
||||
|
||||
|
|
@ -310,3 +310,104 @@ export const deleteForumPostHandler: ToolHandler = async (args, { client }) => {
|
|||
return handleDiscordError(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const getForumTagsHandler: ToolHandler = async (args, { client }) => {
|
||||
const { forumChannelId } = GetForumTagsSchema.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;
|
||||
const tags = forumChannel.availableTags.map(tag => ({
|
||||
id: tag.id,
|
||||
name: tag.name,
|
||||
moderated: tag.moderated,
|
||||
emoji: tag.emoji ? (tag.emoji.name || tag.emoji.id) : null
|
||||
}));
|
||||
|
||||
return {
|
||||
content: [{ type: "text", text: JSON.stringify(tags, null, 2) }]
|
||||
};
|
||||
} catch (error) {
|
||||
return handleDiscordError(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const updateForumPostHandler: ToolHandler = async (args, { client }) => {
|
||||
const { threadId, name, tags, archived, locked } = UpdateForumPostSchema.parse(args);
|
||||
|
||||
try {
|
||||
if (!client.isReady()) {
|
||||
return {
|
||||
content: [{ type: "text", text: "Discord client not logged in." }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
|
||||
const thread = await client.channels.fetch(threadId);
|
||||
if (!thread || !thread.isThread()) {
|
||||
return {
|
||||
content: [{ type: "text", text: `Cannot find thread with ID: ${threadId}` }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
|
||||
const editOptions: any = {};
|
||||
if (name !== undefined) editOptions.name = name;
|
||||
if (archived !== undefined) editOptions.archived = archived;
|
||||
if (locked !== undefined) editOptions.locked = locked;
|
||||
|
||||
// Resolve tag names to IDs if tags are provided
|
||||
if (tags !== undefined) {
|
||||
const parent = thread.parent;
|
||||
if (parent && parent.type === ChannelType.GuildForum) {
|
||||
const forumChannel = parent as ForumChannel;
|
||||
const availableTags = forumChannel.availableTags;
|
||||
const tagIds = tags.map(tagInput => {
|
||||
// Accept either tag name or tag ID
|
||||
const byName = availableTags.find(t => t.name === tagInput);
|
||||
if (byName) return byName.id;
|
||||
const byId = availableTags.find(t => t.id === tagInput);
|
||||
if (byId) return byId.id;
|
||||
return null;
|
||||
}).filter((id): id is string => id !== null);
|
||||
editOptions.appliedTags = tagIds;
|
||||
} else {
|
||||
return {
|
||||
content: [{ type: "text", text: `Thread's parent channel is not a forum channel. Tags can only be applied to forum posts.` }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const updated = await thread.edit(editOptions);
|
||||
|
||||
const changes: string[] = [];
|
||||
if (name !== undefined) changes.push(`name → "${name}"`);
|
||||
if (tags !== undefined) changes.push(`tags → [${tags.join(', ')}]`);
|
||||
if (archived !== undefined) changes.push(`archived → ${archived}`);
|
||||
if (locked !== undefined) changes.push(`locked → ${locked}`);
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `Successfully updated forum post ${threadId}: ${changes.join(', ')}`
|
||||
}]
|
||||
};
|
||||
} catch (error) {
|
||||
return handleDiscordError(error);
|
||||
}
|
||||
};
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { SendMessageSchema } from '../schemas.js';
|
||||
import { SendMessageSchema, EditMessageSchema } from '../schemas.js';
|
||||
import { ToolHandler } from './types.js';
|
||||
import { handleDiscordError } from "../errorHandler.js";
|
||||
|
||||
|
|
@ -69,3 +69,54 @@ export const sendMessageHandler: ToolHandler = async (args, { client }) => {
|
|||
return handleDiscordError(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const editMessageHandler: ToolHandler = async (args, { client }) => {
|
||||
const { channelId, messageId, content } = EditMessageSchema.parse(args);
|
||||
|
||||
try {
|
||||
if (!client.isReady()) {
|
||||
return {
|
||||
content: [{ type: "text", text: "Discord client not logged in." }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
|
||||
const channel = await client.channels.fetch(channelId);
|
||||
if (!channel || !channel.isTextBased()) {
|
||||
return {
|
||||
content: [{ type: "text", text: `Cannot find text channel ID: ${channelId}` }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
|
||||
if (!('messages' in channel)) {
|
||||
return {
|
||||
content: [{ type: "text", text: `This channel type does not support message editing` }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
|
||||
const message = await channel.messages.fetch(messageId);
|
||||
if (!message) {
|
||||
return {
|
||||
content: [{ type: "text", text: `Cannot find message with ID: ${messageId}` }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
|
||||
if (message.author.id !== client.user?.id) {
|
||||
return {
|
||||
content: [{ type: "text", text: `Cannot edit message: only messages sent by the bot can be edited` }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
|
||||
await message.edit(content);
|
||||
|
||||
return {
|
||||
content: [{ type: "text", text: `Successfully edited message ${messageId} in channel ${channelId}` }]
|
||||
};
|
||||
} catch (error) {
|
||||
return handleDiscordError(error);
|
||||
}
|
||||
};
|
||||
|
|
@ -2,14 +2,16 @@ import { Client } from "discord.js";
|
|||
import { z } from "zod";
|
||||
import { ToolResponse, ToolContext, ToolHandler } from "./types.js";
|
||||
import { loginHandler } from './login.js';
|
||||
import { sendMessageHandler } from './send-message.js';
|
||||
import { sendMessageHandler, editMessageHandler } from './send-message.js';
|
||||
import {
|
||||
getForumChannelsHandler,
|
||||
createForumPostHandler,
|
||||
getForumPostHandler,
|
||||
listForumThreadsHandler,
|
||||
replyToForumHandler,
|
||||
deleteForumPostHandler
|
||||
deleteForumPostHandler,
|
||||
getForumTagsHandler,
|
||||
updateForumPostHandler
|
||||
} from './forum.js';
|
||||
import {
|
||||
createTextChannelHandler,
|
||||
|
|
@ -47,6 +49,9 @@ export {
|
|||
listForumThreadsHandler,
|
||||
replyToForumHandler,
|
||||
deleteForumPostHandler,
|
||||
getForumTagsHandler,
|
||||
updateForumPostHandler,
|
||||
editMessageHandler,
|
||||
createTextChannelHandler,
|
||||
deleteChannelHandler,
|
||||
readMessagesHandler,
|
||||
|
|
|
|||
Loading…
Reference in New Issue