diff --git a/.gitignore b/.gitignore index e383887..dcfe864 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/src/schemas.ts b/src/schemas.ts index 0d2518f..a492989 100644 --- a/src/schemas.ts +++ b/src/schemas.ts @@ -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({ diff --git a/src/server.ts b/src/server.ts index 5b6b709..93974c9 100644 --- a/src/server.ts +++ b/src/server.ts @@ -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 diff --git a/src/toolList.ts b/src/toolList.ts index 732aa24..bf4b939 100644 --- a/src/toolList.ts +++ b/src/toolList.ts @@ -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", diff --git a/src/tools/channel.ts b/src/tools/channel.ts index 79db5b9..78d55df 100644 --- a/src/tools/channel.ts +++ b/src/tools/channel.ts @@ -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 { + 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 { + 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 { + 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 { - 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: [{ diff --git a/src/tools/tools.ts b/src/tools/tools.ts index 1ae1fe4..4540b62 100644 --- a/src/tools/tools.ts +++ b/src/tools/tools.ts @@ -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