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:
parent
f2fc159fd6
commit
470842e396
|
|
@ -131,4 +131,21 @@ export const DeleteWebhookSchema = z.object({
|
||||||
reason: z.string().optional()
|
reason: z.string().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ListServersSchema = 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()
|
||||||
|
});
|
||||||
|
|
@ -30,7 +30,8 @@ import {
|
||||||
createCategoryHandler,
|
createCategoryHandler,
|
||||||
editCategoryHandler,
|
editCategoryHandler,
|
||||||
deleteCategoryHandler,
|
deleteCategoryHandler,
|
||||||
listServersHandler
|
listServersHandler,
|
||||||
|
searchMessagesHandler
|
||||||
} from './tools/tools.js';
|
} from './tools/tools.js';
|
||||||
import { MCPTransport } from './transport.js';
|
import { MCPTransport } from './transport.js';
|
||||||
import { info, error } from './logger.js';
|
import { info, error } from './logger.js';
|
||||||
|
|
@ -184,6 +185,11 @@ export class DiscordMCPServer {
|
||||||
toolResponse = await listServersHandler(args, this.toolContext);
|
toolResponse = await listServersHandler(args, this.toolContext);
|
||||||
return toolResponse;
|
return toolResponse;
|
||||||
|
|
||||||
|
case "discord_search_messages":
|
||||||
|
this.logClientState("before discord_search_messages handler");
|
||||||
|
toolResponse = await searchMessagesHandler(args, this.toolContext);
|
||||||
|
return toolResponse;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unknown tool: ${name}`);
|
throw new Error(`Unknown tool: ${name}`);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -301,5 +301,28 @@ export const toolList = [
|
||||||
properties: {},
|
properties: {},
|
||||||
required: []
|
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"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -20,6 +20,9 @@ import {
|
||||||
deleteCategoryHandler,
|
deleteCategoryHandler,
|
||||||
listServersHandler
|
listServersHandler
|
||||||
} from './channel.js';
|
} from './channel.js';
|
||||||
|
import {
|
||||||
|
searchMessagesHandler
|
||||||
|
} from "./server.js";
|
||||||
import {
|
import {
|
||||||
addReactionHandler,
|
addReactionHandler,
|
||||||
addMultipleReactionsHandler,
|
addMultipleReactionsHandler,
|
||||||
|
|
@ -57,7 +60,8 @@ export {
|
||||||
createCategoryHandler,
|
createCategoryHandler,
|
||||||
editCategoryHandler,
|
editCategoryHandler,
|
||||||
deleteCategoryHandler,
|
deleteCategoryHandler,
|
||||||
listServersHandler
|
listServersHandler,
|
||||||
|
searchMessagesHandler
|
||||||
};
|
};
|
||||||
|
|
||||||
// Export common types
|
// Export common types
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,8 @@ import {
|
||||||
editCategoryHandler,
|
editCategoryHandler,
|
||||||
createCategoryHandler,
|
createCategoryHandler,
|
||||||
deleteCategoryHandler,
|
deleteCategoryHandler,
|
||||||
listServersHandler
|
listServersHandler,
|
||||||
|
searchMessagesHandler
|
||||||
} from './tools/tools.js';
|
} from './tools/tools.js';
|
||||||
import { Client, GatewayIntentBits } from "discord.js";
|
import { Client, GatewayIntentBits } from "discord.js";
|
||||||
import { info, error } from './logger.js';
|
import { info, error } from './logger.js';
|
||||||
|
|
@ -215,6 +216,7 @@ export class StreamableHttpTransport implements MCPTransport {
|
||||||
case 'discord_edit_category':
|
case 'discord_edit_category':
|
||||||
case 'discord_delete_category':
|
case 'discord_delete_category':
|
||||||
case 'discord_list_servers':
|
case 'discord_list_servers':
|
||||||
|
case 'discord_search_messages':
|
||||||
// Check if client is logged in
|
// Check if client is logged in
|
||||||
if (!this.toolContext!.client.isReady()) {
|
if (!this.toolContext!.client.isReady()) {
|
||||||
error(`Client not ready for method ${method}, client state: ${JSON.stringify({
|
error(`Client not ready for method ${method}, client state: ${JSON.stringify({
|
||||||
|
|
@ -338,8 +340,9 @@ export class StreamableHttpTransport implements MCPTransport {
|
||||||
break;
|
break;
|
||||||
case 'discord_list_servers':
|
case 'discord_list_servers':
|
||||||
result = await listServersHandler(params, this.toolContext!);
|
result = await listServersHandler(params, this.toolContext!);
|
||||||
break;
|
case 'discord_search_messages':
|
||||||
|
result = await searchMessagesHandler(params, this.toolContext!);
|
||||||
|
break;break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
@ -507,6 +510,9 @@ export class StreamableHttpTransport implements MCPTransport {
|
||||||
case 'discord_list_servers':
|
case 'discord_list_servers':
|
||||||
result = await listServersHandler(toolArgs, this.toolContext!);
|
result = await listServersHandler(toolArgs, this.toolContext!);
|
||||||
break;
|
break;
|
||||||
|
case 'discord_search_messages':
|
||||||
|
result = await searchMessagesHandler(toolArgs, this.toolContext!);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue