feat(schemas): enhance schemas with descriptions

- Makes tools and their arguments self-documenting, removing the need for a separate toolList
This commit is contained in:
Sooty 2025-10-21 21:02:35 +01:00
parent 02a5c47a27
commit 2df48da13b
2 changed files with 134 additions and 83 deletions

View File

@ -65,7 +65,10 @@ function createMcpServer(client: Client) {
// Register each tool on the MCP server // Register each tool on the MCP server
for (const t of toolMap) { for (const t of toolMap) {
try { 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<ToolResponse> // Handlers follow the signature: (args, context) => Promise<ToolResponse>
return await t.handler(args, toolContext); return await t.handler(args, toolContext);
}); });

View File

@ -1,153 +1,201 @@
import { z } from "zod"; import { z } from "zod";
export const DiscordLoginSchema = z.object({ 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({ export const SendMessageSchema = z.object({
channelId: z.string(), channelId: z.string({ description: "The ID of the channel to send the message to." }),
message: z.string(), message: z.string({ description: "The content of the message to send." }),
replyToMessageId: z.string().optional() 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({ 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({ export const CreateForumPostSchema = z.object({
forumChannelId: z.string(), forumChannelId: z.string({ description: "The ID of the forum channel where the thread will be created." }),
title: z.string(), title: z.string({ description: "The title of the forum post (thread)." }),
content: z.string(), content: z.string({ description: "The body content of the forum post." }),
tags: z.array(z.string()).optional() 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({ 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({ export const ReplyToForumSchema = z.object({
threadId: z.string(), threadId: z.string({ description: "The ID of the forum thread to reply to." }),
message: z.string() 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({ export const CreateTextChannelSchema = z.object({
guildId: z.string(), guildId: z.string({ description: "The ID of the server (guild) where the text channel will be created." }),
channelName: z.string(), channelName: z.string({ description: "The name for the new text channel." }),
topic: z.string().optional(), topic: z.string({ description: "The (optional) topic/description for the channel." }).optional(),
reason: z.string().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 // Category schemas
export const CreateCategorySchema = z.object({ export const CreateCategorySchema = z.object({
guildId: z.string(), guildId: z.string({ description: "The ID of the server (guild) where the category will be created." }),
name: z.string(), name: z.string({ description: "The name of the category to create." }),
position: z.number().optional(), position: z.number({ description: "Optional sorting position index for the category." }).optional(),
reason: z.string().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({ export const EditCategorySchema = z.object({
categoryId: z.string(), categoryId: z.string({ description: "The ID of the category to edit." }),
name: z.string().optional(), name: z.string({ description: "New name for the category (optional)." }).optional(),
position: z.number().optional(), position: z.number({ description: "New position index for the category (optional)." }).optional(),
reason: z.string().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({ export const DeleteCategorySchema = z.object({
categoryId: z.string(), categoryId: z.string({ description: "The ID of the category to delete." }),
reason: z.string().optional() 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({ export const DeleteChannelSchema = z.object({
channelId: z.string(), channelId: z.string({ description: "The ID of the channel to delete." }),
reason: z.string().optional() 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({ export const ReadMessagesSchema = z.object({
channelId: z.string(), channelId: z.string({ description: "The ID of the channel to read messages from." }),
limit: z.number().min(1).max(100).optional().default(50) 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({ 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({ export const AddReactionSchema = z.object({
channelId: z.string(), channelId: z.string({ description: "The ID of the channel containing the message to react to." }),
messageId: z.string(), messageId: z.string({ description: "The ID of the message to add a reaction to." }),
emoji: z.string() 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({ export const AddMultipleReactionsSchema = z.object({
channelId: z.string(), channelId: z.string({ description: "The ID of the channel containing the message to react to." }),
messageId: z.string(), messageId: z.string({ description: "The ID of the message to add reactions to." }),
emojis: z.array(z.string()) 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({ export const RemoveReactionSchema = z.object({
channelId: z.string(), channelId: z.string({ description: "The ID of the channel containing the message to modify reactions on." }),
messageId: z.string(), messageId: z.string({ description: "The ID of the message to remove the reaction from." }),
emoji: z.string(), emoji: z.string({ description: "The emoji reaction to remove." }),
userId: z.string().optional() 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({ export const DeleteForumPostSchema = z.object({
threadId: z.string(), threadId: z.string({ description: "The ID of the forum thread to delete." }),
reason: z.string().optional() 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({ export const DeleteMessageSchema = z.object({
channelId: z.string(), channelId: z.string({ description: "The ID of the channel containing the message to delete." }),
messageId: z.string(), messageId: z.string({ description: "The ID of the message to delete." }),
reason: z.string().optional() 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({ export const CreateWebhookSchema = z.object({
channelId: z.string(), channelId: z.string({ description: "The ID of the channel to create the webhook in." }),
name: z.string(), name: z.string({ description: "The name to assign to the webhook." }),
avatar: z.string().optional(), avatar: z.string({ description: "Optional avatar URL or data for the webhook." }).optional(),
reason: z.string().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({ export const SendWebhookMessageSchema = z.object({
webhookId: z.string(), webhookId: z.string({ description: "The ID of the webhook to send the message with." }),
webhookToken: z.string(), webhookToken: z.string({ description: "The token for the webhook (used for authentication)." }),
content: z.string(), content: z.string({ description: "The message content to send via the webhook." }),
username: z.string().optional(), username: z.string({ description: "Optional username to display for the webhook message." }).optional(),
avatarURL: z.string().optional(), avatarURL: z.string({ description: "Optional avatar URL to display for the webhook message." }).optional(),
threadId: z.string().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({ export const EditWebhookSchema = z.object({
webhookId: z.string(), webhookId: z.string({ description: "The ID of the webhook to edit." }),
webhookToken: z.string().optional(), webhookToken: z.string({ description: "Optional token for the webhook if required to authorize edits." }).optional(),
name: z.string().optional(), name: z.string({ description: "Optional new name for the webhook." }).optional(),
avatar: z.string().optional(), avatar: z.string({ description: "Optional new avatar URL or data for the webhook." }).optional(),
channelId: z.string().optional(), channelId: z.string({ description: "Optional channel ID to move the webhook to." }).optional(),
reason: z.string().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({ export const DeleteWebhookSchema = z.object({
webhookId: z.string(), webhookId: z.string({ description: "The ID of the webhook to delete." }),
webhookToken: z.string().optional(), webhookToken: z.string({ description: "Optional token for the webhook if required for deletion." }).optional(),
reason: z.string().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({ export const SearchMessagesSchema = z.object({
guildId: z.string().min(1, "guildId is required"), guildId: z.string({ description: "The ID of the server (guild) to search in." }).min(1, "guildId is required"),
// Optional filters // Optional filters
content: z.string().optional(), content: z.string({ description: "Search for messages that contain this text." }).optional(),
authorId: z.string().optional(), authorId: z.string({ description: "Filter messages to those authored by this user ID." }).optional(),
mentions: z.string().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']).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().optional(), maxId: z.string({ description: "Only include messages with IDs less than or equal to this (pagination)." }).optional(),
minId: z.string().optional(), minId: z.string({ description: "Only include messages with IDs greater than or equal to this (pagination)." }).optional(),
channelId: z.string().optional(), channelId: z.string({ description: "If provided, restrict search to a specific channel ID." }).optional(),
pinned: z.boolean().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']).optional(), authorType: z.enum(['user', 'bot', 'webhook'], { description: "Filter by the type of author (user, bot, or webhook)." }).optional(),
sortBy: z.enum(['timestamp','relevance']).optional(), sortBy: z.enum(['timestamp', 'relevance'], { description: "Field to sort search results by." }).optional(),
sortOrder: z.enum(['desc','asc']).optional(), sortOrder: z.enum(['desc', 'asc'], { description: "Sort direction for results." }).optional(),
limit: z.number().min(1).max(100).default(25).optional(), limit: z.number({ description: "Number of results to return (1-100)." }).min(1).max(100).default(25).optional(),
offset: z.number().min(0).default(0).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."
}); });