@lmquang/mcp-discord-webhook
Version:
MCP server to send messages and embeds to Discord webhooks via stdio.
136 lines (134 loc) • 6.37 kB
JavaScript
// src/tool-handlers.ts
import axios from 'axios';
import OpenAI from 'openai';
import { zodResponseFormat } from "openai/helpers/zod";
import { OpenAICompatibleEmbedSchema } from './schema.js';
export async function handleDiscordSendMessage(params, extra) {
const { webhookUrl, content, username, avatarUrl } = params;
try {
const payload = { content };
if (username)
payload.username = username;
if (avatarUrl)
payload.avatar_url = avatarUrl;
await axios.post(webhookUrl, payload, { headers: { "Content-Type": "application/json" } });
return { content: [{ type: "text", text: "Message sent successfully." }] };
}
catch (error) {
const errorMessage = axios.isAxiosError(error) && error.response ?
`Discord API Error (${error.response.status}): ${JSON.stringify(error.response.data?.message || error.response.data)}` :
(error.message || "Unknown error sending message.");
console.error("Error in discord-send-message tool:", errorMessage); // Log error details
return { content: [{ type: "text", text: `Error: ${errorMessage}` }], isError: true };
}
}
// Helper function to format content using OpenAI
async function formatContentWithOpenAI(content, customPrompt) {
// Check for OpenAI API key
const apiKey = process.env.OPENAI_API_KEY;
if (!apiKey) {
throw new Error("OPENAI_API_KEY environment variable is required for autoFormat");
}
const openai = new OpenAI({ apiKey });
// Default system prompt for formatting Discord embeds
const defaultSystemPrompt = `
You are a Discord embed formatter. Convert the given content into a well-structured Discord embed.
You need to understand the content and create a title, description, and relevant fields based on the content following these instructions:
- Generate a title based on the content.
- Generate a description based on the content, make it short and concise within 15-20 words
- Make sure to use the content to create "fields" in the embed.
- Use appropriate formatting, emoji for the fields name, and structure to make the embed visually appealing.
Return a JSON object for a Discord embed with the following structure:
{
"title": "Title of the embed",
"description": "Description of content. Make it short and concise within 15-20 words",
"color": 3447003, // A blue color in decimal, you need to understand the content and make it appropriate
"fields": [
{
"name": "Header of section of field1",
"value": "Content of section of field2",
"inline": true/false
}
], // You need to use the content to create fields, make sure to use the content to create fields
"footer": {
"text": "Optional footer text"
}
}
`;
try {
// Use the beta.chat.completions.parse API with zodResponseFormat
const completion = await openai.beta.chat.completions.parse({
model: "gpt-4.1-mini-2025-04-14",
messages: [
{ role: "system", content: customPrompt || defaultSystemPrompt },
{ role: "user", content }
],
response_format: zodResponseFormat(OpenAICompatibleEmbedSchema, "discordEmbed"),
temperature: 0.0,
});
// Get the parsed result directly
const formattedEmbed = completion.choices[0].message.parsed;
if (!formattedEmbed) {
throw new Error("Failed to generate formatted content");
}
console.log("formattedEmbed", formattedEmbed);
return formattedEmbed;
}
catch (fallbackError) {
console.error("Error formatting content with OpenAI:", fallbackError.message);
// Return a basic embed if all attempts fail
return {
title: "Content Summary",
description: content.substring(0, 2000),
color: 3447003,
};
}
}
export async function handleDiscordSendEmbed(params, extra) {
const { webhookUrl, embeds, title, description, content, username, avatarUrl, autoFormat, autoFormatPrompt } = params;
console.log("params", params);
try {
let finalEmbeds = embeds;
// If autoFormat is enabled and we have content, use OpenAI to format it
if (autoFormat && content) {
try {
const formattedEmbed = await formatContentWithOpenAI(content, autoFormatPrompt);
finalEmbeds = [formattedEmbed];
}
catch (formatError) {
console.error("Error formatting content with OpenAI:", formatError.message);
// Fall back to original embeds if formatting fails
if (embeds.length === 0) {
// If no embeds were provided, create a basic one from the content
finalEmbeds = [{
title: title !== "" ? title : "Content Summary",
description: content.substring(0, 2000),
color: 3447003,
}];
}
}
}
const payload = { embeds: finalEmbeds };
if (username)
payload.username = username;
if (avatarUrl)
payload.avatar_url = avatarUrl;
if (content && !autoFormat)
payload.embeds[0].content = content;
if (title)
payload.embeds[0].title = title;
if (description)
payload.embeds[0].description = description;
console.log("payload", payload);
await axios.post(webhookUrl, payload, { headers: { "Content-Type": "application/json" } });
return { content: [{ type: "text", text: "Embed message sent successfully." }] };
}
catch (error) {
const errorMessage = axios.isAxiosError(error) && error.response ?
`Discord API Error (${error.response.status}): ${JSON.stringify(error.response.data?.message || error.response.data)}` :
(error.message || "Unknown error sending embed.");
console.error("Error in discord-send-embed tool:", errorMessage); // Log error details
return { content: [{ type: "text", text: `Error: ${errorMessage}` }], isError: true };
}
}
//# sourceMappingURL=tool-handlers.js.map