Merge pull request #26 from WarrenParks/feat/forum-channel-mgmt
feat: add forum channel creation, channel editing, and forum tag management
This commit is contained in:
commit
eb522e2c3c
|
|
@ -56,6 +56,27 @@ export const CreateTextChannelSchema = z.object({
|
||||||
reason: z.string().optional()
|
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
|
// Category schemas
|
||||||
export const CreateCategorySchema = z.object({
|
export const CreateCategorySchema = z.object({
|
||||||
guildId: z.string({ description: "The ID of the server (guild) where the category will be created." }),
|
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()
|
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({
|
export const EditMessageSchema = z.object({
|
||||||
channelId: z.string(),
|
channelId: z.string(),
|
||||||
messageId: z.string(),
|
messageId: z.string(),
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,12 @@ import {
|
||||||
replyToForumHandler,
|
replyToForumHandler,
|
||||||
deleteForumPostHandler,
|
deleteForumPostHandler,
|
||||||
getForumTagsHandler,
|
getForumTagsHandler,
|
||||||
|
setForumTagsHandler,
|
||||||
updateForumPostHandler,
|
updateForumPostHandler,
|
||||||
editMessageHandler,
|
editMessageHandler,
|
||||||
createTextChannelHandler,
|
createTextChannelHandler,
|
||||||
|
createForumChannelHandler,
|
||||||
|
editChannelHandler,
|
||||||
deleteChannelHandler,
|
deleteChannelHandler,
|
||||||
readMessagesHandler,
|
readMessagesHandler,
|
||||||
getServerInfoHandler,
|
getServerInfoHandler,
|
||||||
|
|
@ -145,6 +148,11 @@ export class DiscordMCPServer {
|
||||||
toolResponse = await getForumTagsHandler(args, this.toolContext);
|
toolResponse = await getForumTagsHandler(args, this.toolContext);
|
||||||
return toolResponse;
|
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":
|
case "discord_update_forum_post":
|
||||||
this.logClientState("before discord_update_forum_post handler");
|
this.logClientState("before discord_update_forum_post handler");
|
||||||
toolResponse = await updateForumPostHandler(args, this.toolContext);
|
toolResponse = await updateForumPostHandler(args, this.toolContext);
|
||||||
|
|
@ -160,6 +168,16 @@ export class DiscordMCPServer {
|
||||||
toolResponse = await createTextChannelHandler(args, this.toolContext);
|
toolResponse = await createTextChannelHandler(args, this.toolContext);
|
||||||
return toolResponse;
|
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":
|
case "discord_delete_channel":
|
||||||
this.logClientState("before discord_delete_channel handler");
|
this.logClientState("before discord_delete_channel handler");
|
||||||
toolResponse = await deleteChannelHandler(args, this.toolContext);
|
toolResponse = await deleteChannelHandler(args, this.toolContext);
|
||||||
|
|
|
||||||
|
|
@ -141,6 +141,36 @@ export const toolList = [
|
||||||
required: ["guildId", "channelName"]
|
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",
|
name: "discord_delete_channel",
|
||||||
description: "Deletes a Discord channel with an optional reason",
|
description: "Deletes a Discord channel with an optional reason",
|
||||||
|
|
@ -247,6 +277,30 @@ export const toolList = [
|
||||||
required: ["forumChannelId"]
|
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",
|
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.",
|
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 { ToolContext, ToolResponse } from "./types.js";
|
||||||
import {
|
import {
|
||||||
CreateTextChannelSchema,
|
CreateTextChannelSchema,
|
||||||
|
CreateForumChannelSchema,
|
||||||
|
EditChannelSchema,
|
||||||
DeleteChannelSchema,
|
DeleteChannelSchema,
|
||||||
ReadMessagesSchema,
|
ReadMessagesSchema,
|
||||||
CreateCategorySchema,
|
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
|
// Channel deletion handler
|
||||||
export async function deleteChannelHandler(
|
export async function deleteChannelHandler(
|
||||||
args: unknown,
|
args: unknown,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { ChannelType, ForumChannel } from 'discord.js';
|
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 { ToolHandler } from './types.js';
|
||||||
import { handleDiscordError } from "../errorHandler.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 }) => {
|
export const updateForumPostHandler: ToolHandler = async (args, { client }) => {
|
||||||
const { threadId, name, tags, archived, locked } = UpdateForumPostSchema.parse(args);
|
const { threadId, name, tags, archived, locked } = UpdateForumPostSchema.parse(args);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,13 @@ import {
|
||||||
replyToForumHandler,
|
replyToForumHandler,
|
||||||
deleteForumPostHandler,
|
deleteForumPostHandler,
|
||||||
getForumTagsHandler,
|
getForumTagsHandler,
|
||||||
|
setForumTagsHandler,
|
||||||
updateForumPostHandler
|
updateForumPostHandler
|
||||||
} from './forum.js';
|
} from './forum.js';
|
||||||
import {
|
import {
|
||||||
createTextChannelHandler,
|
createTextChannelHandler,
|
||||||
|
createForumChannelHandler,
|
||||||
|
editChannelHandler,
|
||||||
deleteChannelHandler,
|
deleteChannelHandler,
|
||||||
readMessagesHandler,
|
readMessagesHandler,
|
||||||
createCategoryHandler,
|
createCategoryHandler,
|
||||||
|
|
@ -63,9 +66,12 @@ export {
|
||||||
replyToForumHandler,
|
replyToForumHandler,
|
||||||
deleteForumPostHandler,
|
deleteForumPostHandler,
|
||||||
getForumTagsHandler,
|
getForumTagsHandler,
|
||||||
|
setForumTagsHandler,
|
||||||
updateForumPostHandler,
|
updateForumPostHandler,
|
||||||
editMessageHandler,
|
editMessageHandler,
|
||||||
createTextChannelHandler,
|
createTextChannelHandler,
|
||||||
|
createForumChannelHandler,
|
||||||
|
editChannelHandler,
|
||||||
deleteChannelHandler,
|
deleteChannelHandler,
|
||||||
readMessagesHandler,
|
readMessagesHandler,
|
||||||
getServerInfoHandler,
|
getServerInfoHandler,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue