feat: add Discord category management (create/edit/delete)

- Added handlers, schemas, and MCP tool wiring for creating, editing, and deleting Discord categories (GuildCategory).
- Fixed all .create/.edit argument errors for both category and text channel operations.
- Registered new tools in toolList and MCP server.
This commit is contained in:
Shaskola 2025-05-21 01:44:07 -05:00
parent c827761ccb
commit 6f80c47b89
6 changed files with 195 additions and 10 deletions

7
.gitignore vendored
View File

@ -4,6 +4,13 @@ npm-debug.log
yarn-debug.log
yarn-error.log
# Logs
logs
*.log
# Internal AI memory/log
knowledge.md
# Environment Variables
.env
.env.local

View File

@ -32,7 +32,28 @@ export const ReplyToForumSchema = z.object({
export const CreateTextChannelSchema = z.object({
guildId: z.string(),
channelName: z.string(),
topic: z.string().optional()
topic: z.string().optional(),
reason: z.string().optional()
});
// Category schemas
export const CreateCategorySchema = z.object({
guildId: z.string(),
name: z.string(),
position: z.number().optional(),
reason: z.string().optional()
});
export const EditCategorySchema = z.object({
categoryId: z.string(),
name: z.string().optional(),
position: z.number().optional(),
reason: z.string().optional()
});
export const DeleteCategorySchema = z.object({
categoryId: z.string(),
reason: z.string().optional()
});
export const DeleteChannelSchema = z.object({

View File

@ -26,7 +26,10 @@ import {
createWebhookHandler,
sendWebhookMessageHandler,
editWebhookHandler,
deleteWebhookHandler
deleteWebhookHandler,
createCategoryHandler,
editCategoryHandler,
deleteCategoryHandler
} from './tools/tools.js';
import { MCPTransport } from './transport.js';
import { info, error } from './logger.js';
@ -71,6 +74,15 @@ export class DiscordMCPServer {
try {
let toolResponse;
switch (name) {
case "discord_create_category":
toolResponse = await createCategoryHandler(args, this.toolContext);
return toolResponse;
case "discord_edit_category":
toolResponse = await editCategoryHandler(args, this.toolContext);
return toolResponse;
case "discord_delete_category":
toolResponse = await deleteCategoryHandler(args, this.toolContext);
return toolResponse;
case "discord_login":
toolResponse = await loginHandler(args, this.toolContext);
// Check the client state after login

View File

@ -1,4 +1,44 @@
export const toolList = [
{
name: "discord_create_category",
description: "Creates a new category in a Discord server.",
inputSchema: {
type: "object",
properties: {
guildId: { type: "string" },
name: { type: "string" },
position: { type: "number" },
reason: { type: "string" }
},
required: ["guildId", "name"]
}
},
{
name: "discord_edit_category",
description: "Edits an existing Discord category (name and position).",
inputSchema: {
type: "object",
properties: {
categoryId: { type: "string" },
name: { type: "string" },
position: { type: "number" },
reason: { type: "string" }
},
required: ["categoryId"]
}
},
{
name: "discord_delete_category",
description: "Deletes a Discord category by ID.",
inputSchema: {
type: "object",
properties: {
categoryId: { type: "string" },
reason: { type: "string" }
},
required: ["categoryId"]
}
},
{
name: "discord_login",
description: "Logs in to Discord using the configured token",

View File

@ -5,16 +5,113 @@ import {
CreateTextChannelSchema,
DeleteChannelSchema,
ReadMessagesSchema,
GetServerInfoSchema
GetServerInfoSchema,
CreateCategorySchema,
EditCategorySchema,
DeleteCategorySchema
} from "../schemas.js";
import { handleDiscordError } from "../errorHandler.js";
// Category creation handler
export async function createCategoryHandler(
args: unknown,
context: ToolContext
): Promise<ToolResponse> {
const { guildId, name, position, reason } = CreateCategorySchema.parse(args);
try {
if (!context.client.isReady()) {
return {
content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }],
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 options: any = { name, type: ChannelType.GuildCategory };
if (typeof position === "number") options.position = position;
if (reason) options.reason = reason;
const category = await guild.channels.create(options);
return {
content: [{ type: "text", text: `Successfully created category "${name}" with ID: ${category.id}` }]
};
} catch (error) {
return handleDiscordError(error);
}
}
// Category edit handler
export async function editCategoryHandler(
args: unknown,
context: ToolContext
): Promise<ToolResponse> {
const { categoryId, name, position, reason } = EditCategorySchema.parse(args);
try {
if (!context.client.isReady()) {
return {
content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }],
isError: true
};
}
const category = await context.client.channels.fetch(categoryId);
if (!category || category.type !== ChannelType.GuildCategory) {
return {
content: [{ type: "text", text: `Cannot find category with ID: ${categoryId}` }],
isError: true
};
}
const update: any = {};
if (name) update.name = name;
if (typeof position === "number") update.position = position;
if (reason) update.reason = reason;
await category.edit(update);
return {
content: [{ type: "text", text: `Successfully edited category with ID: ${categoryId}` }]
};
} catch (error) {
return handleDiscordError(error);
}
}
// Category deletion handler
export async function deleteCategoryHandler(
args: unknown,
context: ToolContext
): Promise<ToolResponse> {
const { categoryId, reason } = DeleteCategorySchema.parse(args);
try {
if (!context.client.isReady()) {
return {
content: [{ type: "text", text: "Discord client not logged in. Please use discord_login tool first." }],
isError: true
};
}
const category = await context.client.channels.fetch(categoryId);
if (!category || category.type !== ChannelType.GuildCategory) {
return {
content: [{ type: "text", text: `Cannot find category with ID: ${categoryId}` }],
isError: true
};
}
await category.delete(reason || "Category deleted via API");
return {
content: [{ type: "text", text: `Successfully deleted category with ID: ${categoryId}` }]
};
} catch (error) {
return handleDiscordError(error);
}
}
// Text channel creation handler
export async function createTextChannelHandler(
args: unknown,
context: ToolContext
): Promise<ToolResponse> {
const { guildId, channelName, topic } = CreateTextChannelSchema.parse(args);
const { guildId, channelName, topic, reason } = CreateTextChannelSchema.parse(args);
try {
if (!context.client.isReady()) {
return {
@ -32,11 +129,13 @@ export async function createTextChannelHandler(
}
// Create the text channel
const channel = await guild.channels.create({
const channelOptions: any = {
name: channelName,
type: ChannelType.GuildText,
topic: topic
});
type: ChannelType.GuildText
};
if (topic) channelOptions.topic = topic;
if (reason) channelOptions.reason = reason;
const channel = await guild.channels.create(channelOptions);
return {
content: [{

View File

@ -14,7 +14,10 @@ import {
createTextChannelHandler,
deleteChannelHandler,
readMessagesHandler,
getServerInfoHandler
getServerInfoHandler,
createCategoryHandler,
editCategoryHandler,
deleteCategoryHandler
} from './channel.js';
import {
addReactionHandler,
@ -49,7 +52,10 @@ export {
createWebhookHandler,
sendWebhookMessageHandler,
editWebhookHandler,
deleteWebhookHandler
deleteWebhookHandler,
createCategoryHandler,
editCategoryHandler,
deleteCategoryHandler
};
// Export common types