feat(server): add search messages handler and schema

- Implement searchMessagesHandler to search for messages in a Discord server.
- Add SearchMessagesSchema for input validation.
- Update toolList to include discord_search_messages tool.
- Modify transport and tools to handle new search messages functionality.
This commit is contained in:
Sooty 2025-10-15 16:03:09 +01:00
parent f2fc159fd6
commit 470842e396
6 changed files with 115 additions and 6 deletions

View File

@ -132,3 +132,20 @@ export const DeleteWebhookSchema = z.object({
});
export const ListServersSchema = z.object({});
export const SearchMessagesSchema = z.object({
guildId: z.string().min(1, "guildId is required"),
// Optional filters
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()
});

View File

@ -30,7 +30,8 @@ import {
createCategoryHandler,
editCategoryHandler,
deleteCategoryHandler,
listServersHandler
listServersHandler,
searchMessagesHandler
} from './tools/tools.js';
import { MCPTransport } from './transport.js';
import { info, error } from './logger.js';
@ -184,6 +185,11 @@ export class DiscordMCPServer {
toolResponse = await listServersHandler(args, this.toolContext);
return toolResponse;
case "discord_search_messages":
this.logClientState("before discord_search_messages handler");
toolResponse = await searchMessagesHandler(args, this.toolContext);
return toolResponse;
default:
throw new Error(`Unknown tool: ${name}`);
}

View File

@ -301,5 +301,28 @@ export const toolList = [
properties: {},
required: []
}
},
{
name: "discord_search_messages",
description: "Searches for messages in a Discord server",
inputSchema: {
type: "object",
properties: {
guildId: { type: "string", description: "The ID of the Discord server (guild) to search within" },
authorId: { type: "string", description: "Filter messages by a specific user ID" },
mentions: { type: "string", description: "Filter messages that mention a specific user ID" },
has: { type: "string", description: "Filter messages that contain specific content types (e.g., link, embed, file, poll, image, video, sound, sticker, snapshot)" },
maxId: { type: "string", description: "Filter messages with IDs less than this value (messages before this ID)" },
minId: { type: "string", description: "Filter messages with IDs greater than this value (messages after this ID)" },
channelId: { type: "string", description: "Filter messages within a specific channel ID" },
pinned: { type: "boolean", description: "Filter messages based on whether they are pinned" },
authorType: { type: "string", description: "Filter messages by author type (user, bot, webhook)" },
sortBy: { type: "string", description: "Sort results by 'timestamp' or 'relevance'" },
sortOrder: { type: "string", description: "Sort order: 'desc' for descending or 'asc' for ascending" },
limit: { type: "number", description: "Maximum number of messages to return (default 25, max 100)" },
offset: { type: "number", description: "Number of messages to skip (for pagination)" }
},
required: ["guildId"]
}
}
];

53
src/tools/server.ts Normal file
View File

@ -0,0 +1,53 @@
import { handleDiscordError } from "../errorHandler.js";
import { SearchMessagesSchema } from "../schemas.js";
import { ToolContext, ToolResponse } from "./types.js";
// Search server messages handler
export async function searchMessagesHandler(
args: unknown,
context: ToolContext
): Promise<ToolResponse> {
const { guildId, authorId, mentions, has, maxId, minId, channelId, pinned, authorType, sortBy, sortOrder, limit, offset } = SearchMessagesSchema.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
};
}
// Note: Discord.js does not support guild message search natively.
// This requires direct API calls or using a library that supports it.
// Here we will construct the API request using context.client.rest
const params = new URLSearchParams();
if (authorId) params.append('author_id', authorId);
if (mentions) params.append('mentions', mentions);
if (has) params.append('has', has);
if (maxId) params.append('max_id', maxId);
if (minId) params.append('min_id', minId);
if (channelId) params.append('channel_id', channelId);
if (typeof pinned === 'boolean') params.append('pinned', String(pinned));
if (authorType) params.append('author_type', authorType);
if (sortBy) params.append('sort_by', sortBy);
if (sortOrder) params.append('sort_order', sortOrder);
params.append('limit', String(limit || 25));
params.append('offset', String(offset || 0));
const response = await context.client.rest.get(`/guilds/${guildId}/messages/search?${params.toString()}`);
return {
content: [{ type: "text", text: JSON.stringify(response, null, 2) }]
};
} catch (error) {
return handleDiscordError(error);
}
}

View File

@ -20,6 +20,9 @@ import {
deleteCategoryHandler,
listServersHandler
} from './channel.js';
import {
searchMessagesHandler
} from "./server.js";
import {
addReactionHandler,
addMultipleReactionsHandler,
@ -57,7 +60,8 @@ export {
createCategoryHandler,
editCategoryHandler,
deleteCategoryHandler,
listServersHandler
listServersHandler,
searchMessagesHandler
};
// Export common types

View File

@ -27,7 +27,8 @@ import {
editCategoryHandler,
createCategoryHandler,
deleteCategoryHandler,
listServersHandler
listServersHandler,
searchMessagesHandler
} from './tools/tools.js';
import { Client, GatewayIntentBits } from "discord.js";
import { info, error } from './logger.js';
@ -215,6 +216,7 @@ export class StreamableHttpTransport implements MCPTransport {
case 'discord_edit_category':
case 'discord_delete_category':
case 'discord_list_servers':
case 'discord_search_messages':
// Check if client is logged in
if (!this.toolContext!.client.isReady()) {
error(`Client not ready for method ${method}, client state: ${JSON.stringify({
@ -338,8 +340,9 @@ export class StreamableHttpTransport implements MCPTransport {
break;
case 'discord_list_servers':
result = await listServersHandler(params, this.toolContext!);
break;
case 'discord_search_messages':
result = await searchMessagesHandler(params, this.toolContext!);
break;break;
}
break;
@ -507,6 +510,9 @@ export class StreamableHttpTransport implements MCPTransport {
case 'discord_list_servers':
result = await listServersHandler(toolArgs, this.toolContext!);
break;
case 'discord_search_messages':
result = await searchMessagesHandler(toolArgs, this.toolContext!);
break;
default:
return res.status(400).json({