From 9e335bdf789037b4a8afcb695dc19082a43c6e69 Mon Sep 17 00:00:00 2001 From: vycdev2 Date: Mon, 10 Aug 2026 02:30:14 +0000 Subject: [PATCH] fix: validate reaction message IDs --- CHANGELOG.md | 1 + src/mcp/server.ts | 6 ++++- tests/mcpMessageValidation.test.mjs | 38 ++++++++++++++++++++++++++++- 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d927b6..7ec7b9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ ### Fixed +- Reject non-numeric Discord message IDs in MCP reaction requests before contacting Discord. - Prevent mixed reaction replies from narrating the bot's internal choice to react while preserving natural reaction-plus-text responses. - Run Discord-initiated Claude login in a pseudo-terminal so the CLI accepts submitted OAuth codes. - Isolate saved history and summaries by Discord channel ID in dedicated storage namespaces so same-named channels do not share automatic context. diff --git a/src/mcp/server.ts b/src/mcp/server.ts index 2b4d341..203df75 100644 --- a/src/mcp/server.ts +++ b/src/mcp/server.ts @@ -25,7 +25,10 @@ const ReactToMessageSchema = z.object({ .optional() .describe("Server name or ID (optional if bot is only in one server)"), channel: z.string().describe('Channel name (e.g., "general") or ID'), - messageId: z.string().describe("The Discord message ID to react to"), + messageId: z + .string() + .regex(/^\d+$/, "Invalid Discord message ID") + .describe("The Discord message ID to react to"), emoji: z.string().describe('Emoji to react with — unicode emoji (e.g. "👍") or custom guild emoji name (e.g. "pepeclap")'), }); @@ -128,6 +131,7 @@ export function createMcpServer(): Server { }, messageId: { type: "string", + pattern: "^\\d+$", description: "The Discord message ID to react to", }, emoji: { diff --git a/tests/mcpMessageValidation.test.mjs b/tests/mcpMessageValidation.test.mjs index f07fe32..9c06d58 100644 --- a/tests/mcpMessageValidation.test.mjs +++ b/tests/mcpMessageValidation.test.mjs @@ -71,4 +71,40 @@ test("MCP send-message enforces Discord's content length limits", async (t) => { ), /Invalid arguments: message: String must contain at most 2000 character/, ); -}); \ No newline at end of file +}); + +test("MCP react-to-message validates message IDs before Discord lookup", async (t) => { + const { createMcpServer } = await import("../build/mcp/server.js"); + const server = createMcpServer(); + t.after(() => server.close().catch(() => {})); + const listToolsHandler = server._requestHandlers.get("tools/list"); + const callToolHandler = server._requestHandlers.get("tools/call"); + assert.ok(listToolsHandler); + assert.ok(callToolHandler); + + const { tools } = await listToolsHandler( + { method: "tools/list", params: {} }, + {}, + ); + const reactTool = tools.find(({ name }) => name === "react-to-message"); + assert.equal(reactTool.inputSchema.properties.messageId.pattern, "^\\d+$"); + + await assert.rejects( + () => + callToolHandler( + { + method: "tools/call", + params: { + name: "react-to-message", + arguments: { + channel: "general", + messageId: "not-a-discord-message-id", + emoji: "👍", + }, + }, + }, + {}, + ), + /Invalid arguments: messageId: Invalid Discord message ID/, + ); +});