feat: add forum channel creation, channel editing, and forum tag management
Add three new tools: - discord_create_forum_channel: create forum channels with optional parent category - discord_edit_channel: edit channel name, topic, parent category, and position - discord_set_forum_tags: set available tags on a forum channel (replaces all)
This commit is contained in:
parent
a2890ae1b8
commit
ed42331176
|
|
@ -56,6 +56,27 @@ export const CreateTextChannelSchema = z.object({
|
|||
reason: z.string().optional()
|
||||
});
|
||||
|
||||
export const CreateForumChannelSchema = z.object({
|
||||
guildId: z.string({ description: "The ID of the server (guild) to create the forum channel in." }),
|
||||
name: z.string({ description: "The name of the forum channel to create." }),
|
||||
topic: z.string({ description: "The forum channel guidelines/description." }).optional(),
|
||||
categoryId: z.string({ description: "The ID of the parent category to create the channel under." }).optional(),
|
||||
reason: z.string({ description: "Optional reason for audit logs." }).optional()
|
||||
}, {
|
||||
description: "Create a new forum channel in a specified server (guild), optionally under a category."
|
||||
});
|
||||
|
||||
export const EditChannelSchema = z.object({
|
||||
channelId: z.string({ description: "The ID of the channel to edit." }),
|
||||
name: z.string({ description: "New name for the channel." }).optional(),
|
||||
topic: z.string({ description: "New topic for the channel." }).optional(),
|
||||
parentId: z.string({ description: "The ID of a category to move the channel under." }).optional(),
|
||||
position: z.number({ description: "New position of the channel in the list." }).optional(),
|
||||
reason: z.string({ description: "Optional reason for audit logs." }).optional()
|
||||
}, {
|
||||
description: "Edit a Discord channel's name, topic, parent category, or position."
|
||||
});
|
||||
|
||||
// Category schemas
|
||||
export const CreateCategorySchema = z.object({
|
||||
guildId: z.string({ description: "The ID of the server (guild) where the category will be created." }),
|
||||
|
|
@ -146,6 +167,17 @@ export const UpdateForumPostSchema = z.object({
|
|||
locked: z.boolean().optional()
|
||||
});
|
||||
|
||||
export const SetForumTagsSchema = z.object({
|
||||
forumChannelId: z.string({ description: "The ID of the forum channel to set tags on." }),
|
||||
tags: z.array(z.object({
|
||||
name: z.string({ description: "Tag name." }),
|
||||
emoji: z.string({ description: "Unicode emoji for the tag (e.g. '🔬')." }).optional(),
|
||||
moderated: z.boolean({ description: "Whether only moderators can apply this tag (default: false)." }).optional()
|
||||
}))
|
||||
}, {
|
||||
description: "Sets the available tags for a Discord forum channel. Replaces all existing tags."
|
||||
});
|
||||
|
||||
export const EditMessageSchema = z.object({
|
||||
channelId: z.string(),
|
||||
messageId: z.string(),
|
||||
|
|
|
|||
|
|
@ -17,9 +17,12 @@ import {
|
|||
replyToForumHandler,
|
||||
deleteForumPostHandler,
|
||||
getForumTagsHandler,
|
||||
setForumTagsHandler,
|
||||
updateForumPostHandler,
|
||||
editMessageHandler,
|
||||
createTextChannelHandler,
|
||||
createForumChannelHandler,
|
||||
editChannelHandler,
|
||||
deleteChannelHandler,
|
||||
readMessagesHandler,
|
||||
getServerInfoHandler,
|
||||
|
|
@ -145,6 +148,11 @@ export class DiscordMCPServer {
|
|||
toolResponse = await getForumTagsHandler(args, this.toolContext);
|
||||
return toolResponse;
|
||||
|
||||
case "discord_set_forum_tags":
|
||||
this.logClientState("before discord_set_forum_tags handler");
|
||||
toolResponse = await setForumTagsHandler(args, this.toolContext);
|
||||
return toolResponse;
|
||||
|
||||
case "discord_update_forum_post":
|
||||
this.logClientState("before discord_update_forum_post handler");
|
||||
toolResponse = await updateForumPostHandler(args, this.toolContext);
|
||||
|
|
@ -160,6 +168,16 @@ export class DiscordMCPServer {
|
|||
toolResponse = await createTextChannelHandler(args, this.toolContext);
|
||||
return toolResponse;
|
||||
|
||||
case "discord_create_forum_channel":
|
||||
this.logClientState("before discord_create_forum_channel handler");
|
||||
toolResponse = await createForumChannelHandler(args, this.toolContext);
|
||||
return toolResponse;
|
||||
|
||||
case "discord_edit_channel":
|
||||
this.logClientState("before discord_edit_channel handler");
|
||||
toolResponse = await editChannelHandler(args, this.toolContext);
|
||||
return toolResponse;
|
||||
|
||||
case "discord_delete_channel":
|
||||
this.logClientState("before discord_delete_channel handler");
|
||||
toolResponse = await deleteChannelHandler(args, this.toolContext);
|
||||
|
|
|
|||
|
|
@ -141,6 +141,36 @@ export const toolList = [
|
|||
required: ["guildId", "channelName"]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "discord_create_forum_channel",
|
||||
description: "Creates a new forum channel in a Discord server, optionally under a category",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
guildId: { type: "string" },
|
||||
name: { type: "string" },
|
||||
topic: { type: "string", description: "The forum channel guidelines/description" },
|
||||
categoryId: { type: "string", description: "The ID of the parent category to create the channel under" }
|
||||
},
|
||||
required: ["guildId", "name"]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "discord_edit_channel",
|
||||
description: "Edits a Discord channel's name, topic, parent category, or position",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
channelId: { type: "string", description: "The ID of the channel to edit" },
|
||||
name: { type: "string", description: "New name for the channel" },
|
||||
topic: { type: "string", description: "New topic for the channel" },
|
||||
parentId: { type: "string", description: "The ID of a category to move the channel under" },
|
||||
position: { type: "number", description: "New position of the channel in the list" },
|
||||
reason: { type: "string", description: "Reason for editing (shown in audit log)" }
|
||||
},
|
||||
required: ["channelId"]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "discord_delete_channel",
|
||||
description: "Deletes a Discord channel with an optional reason",
|
||||
|
|
@ -247,6 +277,30 @@ export const toolList = [
|
|||
required: ["forumChannelId"]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "discord_set_forum_tags",
|
||||
description: "Sets the available tags for a Discord forum channel. Replaces all existing tags.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
forumChannelId: { type: "string", description: "The ID of the forum channel to set tags on" },
|
||||
tags: {
|
||||
type: "array",
|
||||
description: "Array of tag objects to set on the forum channel",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: { type: "string", description: "Tag name" },
|
||||
emoji: { type: "string", description: "Unicode emoji for the tag (e.g. '🔬')" },
|
||||
moderated: { type: "boolean", description: "Whether only moderators can apply this tag (default: false)" }
|
||||
},
|
||||
required: ["name"]
|
||||
}
|
||||
}
|
||||
},
|
||||
required: ["forumChannelId", "tags"]
|
||||
}
|
||||
},
|
||||
{
|
||||
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.",
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import { ChannelType, PermissionsBitField } from "discord.js";
|
|||
import { ToolContext, ToolResponse } from "./types.js";
|
||||
import {
|
||||
CreateTextChannelSchema,
|
||||
CreateForumChannelSchema,
|
||||
EditChannelSchema,
|
||||
DeleteChannelSchema,
|
||||
ReadMessagesSchema,
|
||||
CreateCategorySchema,
|
||||
|
|
@ -151,6 +153,96 @@ export async function createTextChannelHandler(
|
|||
}
|
||||
}
|
||||
|
||||
// Forum channel creation handler
|
||||
export async function createForumChannelHandler(
|
||||
args: unknown,
|
||||
context: ToolContext
|
||||
): Promise<ToolResponse> {
|
||||
const { guildId, name, topic, categoryId, reason } = CreateForumChannelSchema.parse(args);
|
||||
try {
|
||||
if (!context.client.isReady()) {
|
||||
return {
|
||||
content: [{ type: "text", text: "Discord client not logged in." }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
|
||||
const guild = await context.client.guilds.fetch(guildId);
|
||||
if (!guild) {
|
||||
return {
|
||||
content: [{ type: "text", text: `Cannot find guild with ID: ${guildId}` }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
|
||||
const channelOptions: any = {
|
||||
name,
|
||||
type: ChannelType.GuildForum
|
||||
};
|
||||
if (topic) channelOptions.topic = topic;
|
||||
if (categoryId) channelOptions.parent = categoryId;
|
||||
if (reason) channelOptions.reason = reason;
|
||||
const channel = await guild.channels.create(channelOptions);
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `Successfully created forum channel "${name}" with ID: ${channel.id}`
|
||||
}]
|
||||
};
|
||||
} catch (error) {
|
||||
return handleDiscordError(error);
|
||||
}
|
||||
}
|
||||
|
||||
// Edit channel handler
|
||||
export async function editChannelHandler(
|
||||
args: unknown,
|
||||
context: ToolContext
|
||||
): Promise<ToolResponse> {
|
||||
const { channelId, name, topic, parentId, position, reason } = EditChannelSchema.parse(args);
|
||||
try {
|
||||
if (!context.client.isReady()) {
|
||||
return {
|
||||
content: [{ type: "text", text: "Discord client not logged in." }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
|
||||
const channel = await context.client.channels.fetch(channelId);
|
||||
if (!channel) {
|
||||
return {
|
||||
content: [{ type: "text", text: `Cannot find channel with ID: ${channelId}` }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
|
||||
if (!('edit' in channel)) {
|
||||
return {
|
||||
content: [{ type: "text", text: `This channel type does not support editing` }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
|
||||
const update: any = {};
|
||||
if (name) update.name = name;
|
||||
if (topic !== undefined) update.topic = topic;
|
||||
if (parentId) update.parent = parentId;
|
||||
if (typeof position === "number") update.position = position;
|
||||
if (reason) update.reason = reason;
|
||||
await channel.edit(update);
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `Successfully edited channel with ID: ${channelId}`
|
||||
}]
|
||||
};
|
||||
} catch (error) {
|
||||
return handleDiscordError(error);
|
||||
}
|
||||
}
|
||||
|
||||
// Channel deletion handler
|
||||
export async function deleteChannelHandler(
|
||||
args: unknown,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { ChannelType, ForumChannel } from 'discord.js';
|
||||
import { GetForumChannelsSchema, CreateForumPostSchema, GetForumPostSchema, ListForumThreadsSchema, ReplyToForumSchema, DeleteForumPostSchema, GetForumTagsSchema, UpdateForumPostSchema } from '../schemas.js';
|
||||
import { GetForumChannelsSchema, CreateForumPostSchema, GetForumPostSchema, ListForumThreadsSchema, ReplyToForumSchema, DeleteForumPostSchema, GetForumTagsSchema, SetForumTagsSchema, UpdateForumPostSchema } from '../schemas.js';
|
||||
import { ToolHandler } from './types.js';
|
||||
import { handleDiscordError } from "../errorHandler.js";
|
||||
|
||||
|
|
@ -346,6 +346,45 @@ export const getForumTagsHandler: ToolHandler = async (args, { client }) => {
|
|||
}
|
||||
};
|
||||
|
||||
export const setForumTagsHandler: ToolHandler = async (args, { client }) => {
|
||||
const { forumChannelId, tags } = SetForumTagsSchema.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 newTags = tags.map(tag => ({
|
||||
name: tag.name,
|
||||
moderated: tag.moderated ?? false,
|
||||
emoji: tag.emoji ? { name: tag.emoji } : null
|
||||
}));
|
||||
|
||||
await forumChannel.setAvailableTags(newTags as any);
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `Successfully set ${tags.length} tag(s) on forum channel ${forumChannelId}: ${tags.map(t => t.name).join(', ')}`
|
||||
}]
|
||||
};
|
||||
} catch (error) {
|
||||
return handleDiscordError(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const updateForumPostHandler: ToolHandler = async (args, { client }) => {
|
||||
const { threadId, name, tags, archived, locked } = UpdateForumPostSchema.parse(args);
|
||||
|
||||
|
|
|
|||
|
|
@ -11,10 +11,13 @@ import {
|
|||
replyToForumHandler,
|
||||
deleteForumPostHandler,
|
||||
getForumTagsHandler,
|
||||
setForumTagsHandler,
|
||||
updateForumPostHandler
|
||||
} from './forum.js';
|
||||
import {
|
||||
createTextChannelHandler,
|
||||
createForumChannelHandler,
|
||||
editChannelHandler,
|
||||
deleteChannelHandler,
|
||||
readMessagesHandler,
|
||||
createCategoryHandler,
|
||||
|
|
@ -63,9 +66,12 @@ export {
|
|||
replyToForumHandler,
|
||||
deleteForumPostHandler,
|
||||
getForumTagsHandler,
|
||||
setForumTagsHandler,
|
||||
updateForumPostHandler,
|
||||
editMessageHandler,
|
||||
createTextChannelHandler,
|
||||
createForumChannelHandler,
|
||||
editChannelHandler,
|
||||
deleteChannelHandler,
|
||||
readMessagesHandler,
|
||||
getServerInfoHandler,
|
||||
|
|
|
|||
Loading…
Reference in New Issue