From 2df48da13bbb5119fc8abb165e41a758bafa9293 Mon Sep 17 00:00:00 2001 From: Sooty <7614538+SootyOwl@users.noreply.github.com> Date: Tue, 21 Oct 2025 21:02:35 +0100 Subject: [PATCH] feat(schemas): enhance schemas with descriptions - Makes tools and their arguments self-documenting, removing the need for a separate toolList --- src/app.ts | 5 +- src/schemas.ts | 212 ++++++++++++++++++++++++++++++------------------- 2 files changed, 134 insertions(+), 83 deletions(-) diff --git a/src/app.ts b/src/app.ts index db57f44..8696d21 100644 --- a/src/app.ts +++ b/src/app.ts @@ -65,7 +65,10 @@ function createMcpServer(client: Client) { // Register each tool on the MCP server for (const t of toolMap) { try { - server.tool(t.name, t.schema ? t.schema.shape ?? t.schema : undefined, async (args: any) => { + server.tool( + t.name, + t.schema ? t.schema.description ?? '' : '', + t.schema ? t.schema.shape ?? t.schema : undefined, async (args: any) => { // Handlers follow the signature: (args, context) => Promise return await t.handler(args, toolContext); }); diff --git a/src/schemas.ts b/src/schemas.ts index fabc61d..a30793e 100644 --- a/src/schemas.ts +++ b/src/schemas.ts @@ -1,153 +1,201 @@ import { z } from "zod"; export const DiscordLoginSchema = z.object({ - token: z.string().optional() + token: z.string({ description: "The bot token to use for login." }).optional() +}, { + description: "Login to Discord using a bot token. If no token is provided, the bot will attempt to use the token from the environment variable DISCORD_TOKEN." }); export const SendMessageSchema = z.object({ - channelId: z.string(), - message: z.string(), - replyToMessageId: z.string().optional() + channelId: z.string({ description: "The ID of the channel to send the message to." }), + message: z.string({ description: "The content of the message to send." }), + replyToMessageId: z.string({ description: "The ID of the message to reply to, if any." }).optional() +}, { + description: "Send a message to a specified channel, optionally as a reply to another message." }); export const GetForumChannelsSchema = z.object({ - guildId: z.string() + guildId: z.string({ description: "The ID of the server (guild) to get forum channels from." }) +}, { + description: "Get all forum channels in a specified server (guild)." }); export const CreateForumPostSchema = z.object({ - forumChannelId: z.string(), - title: z.string(), - content: z.string(), - tags: z.array(z.string()).optional() + forumChannelId: z.string({ description: "The ID of the forum channel where the thread will be created." }), + title: z.string({ description: "The title of the forum post (thread)." }), + content: z.string({ description: "The body content of the forum post." }), + tags: z.array(z.string({ description: "A tag to attach to the forum post." })).optional() +}, { + description: "Create a new forum post (thread) in a specified forum channel." }); export const GetForumPostSchema = z.object({ - threadId: z.string() + threadId: z.string({ description: "The ID of the forum thread to retrieve." }) +}, { + description: "Get details of a specific forum post (thread) by its ID." }); export const ReplyToForumSchema = z.object({ - threadId: z.string(), - message: z.string() + threadId: z.string({ description: "The ID of the forum thread to reply to." }), + message: z.string({ description: "The content of the reply message." }) +}, { + description: "Reply to a specific forum post (thread) by its ID." }); export const CreateTextChannelSchema = z.object({ - guildId: z.string(), - channelName: z.string(), - topic: z.string().optional(), - reason: z.string().optional() + guildId: z.string({ description: "The ID of the server (guild) where the text channel will be created." }), + channelName: z.string({ description: "The name for the new text channel." }), + topic: z.string({ description: "The (optional) topic/description for the channel." }).optional(), + reason: z.string({ description: "Optional reason for audit logs when creating the channel." }).optional() +}, { + description: "Create a new text channel in a specified server (guild)." }); // Category schemas export const CreateCategorySchema = z.object({ - guildId: z.string(), - name: z.string(), - position: z.number().optional(), - reason: z.string().optional() + guildId: z.string({ description: "The ID of the server (guild) where the category will be created." }), + name: z.string({ description: "The name of the category to create." }), + position: z.number({ description: "Optional sorting position index for the category." }).optional(), + reason: z.string({ description: "Optional reason for audit logs when creating the category." }).optional() +}, { + description: "Create a new category in a specified server (guild)." }); export const EditCategorySchema = z.object({ - categoryId: z.string(), - name: z.string().optional(), - position: z.number().optional(), - reason: z.string().optional() + categoryId: z.string({ description: "The ID of the category to edit." }), + name: z.string({ description: "New name for the category (optional)." }).optional(), + position: z.number({ description: "New position index for the category (optional)." }).optional(), + reason: z.string({ description: "Optional reason for audit logs when editing the category." }).optional() +}, { + description: "Edit an existing category's properties." }); export const DeleteCategorySchema = z.object({ - categoryId: z.string(), - reason: z.string().optional() + categoryId: z.string({ description: "The ID of the category to delete." }), + reason: z.string({ description: "Optional reason for audit logs when deleting the category." }).optional() +}, { + description: "Delete a category by its ID." }); export const DeleteChannelSchema = z.object({ - channelId: z.string(), - reason: z.string().optional() + channelId: z.string({ description: "The ID of the channel to delete." }), + reason: z.string({ description: "Optional reason for audit logs when deleting the channel." }).optional() +}, { + description: "Delete a channel by its ID." }); export const ReadMessagesSchema = z.object({ - channelId: z.string(), - limit: z.number().min(1).max(100).optional().default(50) + channelId: z.string({ description: "The ID of the channel to read messages from." }), + limit: z.number({ description: "How many recent messages to fetch (1-100)." }).min(1).max(100).optional().default(50) +}, { + description: "Read recent messages from a specified channel." }); export const GetServerInfoSchema = z.object({ - guildId: z.string() + guildId: z.string({ description: "The ID of the server (guild) to get information for." }) +}, { + description: "Get information about a specific server (guild) by its ID." }); export const AddReactionSchema = z.object({ - channelId: z.string(), - messageId: z.string(), - emoji: z.string() + channelId: z.string({ description: "The ID of the channel containing the message to react to." }), + messageId: z.string({ description: "The ID of the message to add a reaction to." }), + emoji: z.string({ description: "The emoji to use for the reaction (unicode or custom)." }) +}, { + description: "Add a reaction to a specific message in a channel." }); export const AddMultipleReactionsSchema = z.object({ - channelId: z.string(), - messageId: z.string(), - emojis: z.array(z.string()) + channelId: z.string({ description: "The ID of the channel containing the message to react to." }), + messageId: z.string({ description: "The ID of the message to add reactions to." }), + emojis: z.array(z.string({ description: "An emoji to add (unicode or custom)." })) +}, { + description: "Add multiple reactions to a specific message in a channel." }); export const RemoveReactionSchema = z.object({ - channelId: z.string(), - messageId: z.string(), - emoji: z.string(), - userId: z.string().optional() + channelId: z.string({ description: "The ID of the channel containing the message to modify reactions on." }), + messageId: z.string({ description: "The ID of the message to remove the reaction from." }), + emoji: z.string({ description: "The emoji reaction to remove." }), + userId: z.string({ description: "Optional ID of the user whose reaction should be removed; if omitted, removes the current bot's reaction." }).optional() +}, { + description: "Remove a reaction from a specific message in a channel." }); export const DeleteForumPostSchema = z.object({ - threadId: z.string(), - reason: z.string().optional() + threadId: z.string({ description: "The ID of the forum thread to delete." }), + reason: z.string({ description: "Optional reason for audit logs when deleting the forum post." }).optional() +}, { + description: "Delete a forum post (thread) by its ID." }); export const DeleteMessageSchema = z.object({ - channelId: z.string(), - messageId: z.string(), - reason: z.string().optional() + channelId: z.string({ description: "The ID of the channel containing the message to delete." }), + messageId: z.string({ description: "The ID of the message to delete." }), + reason: z.string({ description: "Optional reason for audit logs when deleting the message." }).optional() +}, { + description: "Delete a message by its ID in a specified channel." }); export const CreateWebhookSchema = z.object({ - channelId: z.string(), - name: z.string(), - avatar: z.string().optional(), - reason: z.string().optional() + channelId: z.string({ description: "The ID of the channel to create the webhook in." }), + name: z.string({ description: "The name to assign to the webhook." }), + avatar: z.string({ description: "Optional avatar URL or data for the webhook." }).optional(), + reason: z.string({ description: "Optional reason for audit logs when creating the webhook." }).optional() +}, { + description: "Create a webhook in a specified channel." }); export const SendWebhookMessageSchema = z.object({ - webhookId: z.string(), - webhookToken: z.string(), - content: z.string(), - username: z.string().optional(), - avatarURL: z.string().optional(), - threadId: z.string().optional() + webhookId: z.string({ description: "The ID of the webhook to send the message with." }), + webhookToken: z.string({ description: "The token for the webhook (used for authentication)." }), + content: z.string({ description: "The message content to send via the webhook." }), + username: z.string({ description: "Optional username to display for the webhook message." }).optional(), + avatarURL: z.string({ description: "Optional avatar URL to display for the webhook message." }).optional(), + threadId: z.string({ description: "Optional ID of the thread to post the webhook message into." }).optional() +}, { + description: "Send a message using a webhook." }); export const EditWebhookSchema = z.object({ - webhookId: z.string(), - webhookToken: z.string().optional(), - name: z.string().optional(), - avatar: z.string().optional(), - channelId: z.string().optional(), - reason: z.string().optional() + webhookId: z.string({ description: "The ID of the webhook to edit." }), + webhookToken: z.string({ description: "Optional token for the webhook if required to authorize edits." }).optional(), + name: z.string({ description: "Optional new name for the webhook." }).optional(), + avatar: z.string({ description: "Optional new avatar URL or data for the webhook." }).optional(), + channelId: z.string({ description: "Optional channel ID to move the webhook to." }).optional(), + reason: z.string({ description: "Optional reason for audit logs when editing the webhook." }).optional() +}, { + description: "Edit a webhook's properties." }); export const DeleteWebhookSchema = z.object({ - webhookId: z.string(), - webhookToken: z.string().optional(), - reason: z.string().optional() + webhookId: z.string({ description: "The ID of the webhook to delete." }), + webhookToken: z.string({ description: "Optional token for the webhook if required for deletion." }).optional(), + reason: z.string({ description: "Optional reason for audit logs when deleting the webhook." }).optional() +}, { + description: "Delete a webhook by its ID and token." }); -export const ListServersSchema = z.object({}); +export const ListServersSchema = z.object({}, { + description: "List all servers (guilds) the bot is a member of." +}); export const SearchMessagesSchema = z.object({ - guildId: z.string().min(1, "guildId is required"), - // Optional filters - content: z.string().optional(), - authorId: z.string().optional(), - mentions: z.string().optional(), - has: z.enum(['link','embed','file','poll','image','video','sound','sticker','snapshot']).optional(), - maxId: z.string().optional(), - minId: z.string().optional(), - channelId: z.string().optional(), - pinned: z.boolean().optional(), - authorType: z.enum(['user','bot','webhook']).optional(), - sortBy: z.enum(['timestamp','relevance']).optional(), - sortOrder: z.enum(['desc','asc']).optional(), - limit: z.number().min(1).max(100).default(25).optional(), - offset: z.number().min(0).default(0).optional() -}); \ No newline at end of file + guildId: z.string({ description: "The ID of the server (guild) to search in." }).min(1, "guildId is required"), + // Optional filters + content: z.string({ description: "Search for messages that contain this text." }).optional(), + authorId: z.string({ description: "Filter messages to those authored by this user ID." }).optional(), + mentions: z.string({ description: "Filter messages that mention a specific user or role ID." }).optional(), + has: z.enum(['link', 'embed', 'file', 'poll', 'image', 'video', 'sound', 'sticker', 'snapshot'], { description: "Filter messages that contain a specific type of content." }).optional(), + maxId: z.string({ description: "Only include messages with IDs less than or equal to this (pagination)." }).optional(), + minId: z.string({ description: "Only include messages with IDs greater than or equal to this (pagination)." }).optional(), + channelId: z.string({ description: "If provided, restrict search to a specific channel ID." }).optional(), + pinned: z.boolean({ description: "If true, only include pinned messages; if false, only include unpinned; if omitted, include both." }).optional(), + authorType: z.enum(['user', 'bot', 'webhook'], { description: "Filter by the type of author (user, bot, or webhook)." }).optional(), + sortBy: z.enum(['timestamp', 'relevance'], { description: "Field to sort search results by." }).optional(), + sortOrder: z.enum(['desc', 'asc'], { description: "Sort direction for results." }).optional(), + limit: z.number({ description: "Number of results to return (1-100)." }).min(1).max(100).default(25).optional(), + offset: z.number({ description: "Number of results to skip (for pagination)." }).min(0).default(0).optional() +}, { + description: "Search messages in a server with various filters." +});