Skip to main content
Glama

send_notification

Send notifications to Discord or Slack via webhooks, including messages, embeds, and attachments, with automatic service detection, retry logic, and secure input validation.

Instructions

Send a notification to Discord and/or Slack webhooks

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
avatar_urlNo
embed_jsonNo
messageNo
serviceNo
ttsNo
usernameNo

Implementation Reference

  • Full implementation of the send_notification tool handler, including inline schema validation, service detection, payload construction, API calls to Discord/Slack, error handling, and result aggregation.
    server.tool(
      "send_notification",
      "Send a notification to Discord and/or Slack webhooks",
      {
        message: z.string().optional(),
        service: z.enum(["discord", "slack", "both"]).optional(),
        embed_json: z.union([z.string(), z.object({}).passthrough(), z.array(z.unknown())]).optional(),
        username: z.string().optional(),
        avatar_url: z.string().url().optional(),
        tts: z.boolean().optional(),
      },
      async (args) => {
        // Validate that either message or embed_json is provided
        if (!args.message && args.embed_json === undefined) {
          return {
            content: [
              {
                type: "text",
                text: JSON.stringify({ 
                  error: "You must provide either message or embed_json parameter" 
                }, null, 2)
              }
            ]
          };
        }
    
        try {
          const targetServices = detectService(args.service);
          const config = getConfig();
          const results: ServiceResult[] = [];
    
          for (const service of targetServices) {
            try {
              let result;
              
              if (service === "discord") {
                const payload = buildDiscordPayload({
                  message: args.message,
                  embedJson: args.embed_json,
                  username: args.username,
                  avatarUrl: args.avatar_url,
                  tts: args.tts
                });
                result = await sendDiscord(config.discordWebhookUrl!, payload);
              } else {
                const payload = buildSlackPayload({
                  message: args.message,
                  embedJson: args.embed_json,
                  username: args.username,
                  avatarUrl: args.avatar_url
                });
                result = await sendSlack(config.slackWebhookUrl!, payload);
              }
    
              results.push({
                service,
                success: result.success,
                httpCode: result.httpCode,
                timestamp: result.timestamp
              });
            } catch (error) {
              results.push({
                service,
                success: false,
                error: error instanceof Error ? error.message : "Unknown error"
              });
            }
          }
    
          const overall = results.some(r => r.success);
          const response: SendResult = {
            success: overall,
            overall,
            results
          };
    
          return {
            content: [
              {
                type: "text",
                text: JSON.stringify(response, null, 2)
              }
            ]
          };
        } catch (error) {
          const errorMsg = error instanceof Error ? error.message : "Unknown error";
          return {
            content: [
              {
                type: "text",
                text: JSON.stringify({ error: errorMsg }, null, 2)
              }
            ]
          };
        }
      }
    );
  • Zod input schema for the send_notification tool parameters.
    export const SendNotificationSchema = z.object({
      message: z.string().optional(),
      service: ServiceSchema.optional(),
      embed_json: z.union([z.string(), z.object({}).passthrough(), z.array(z.unknown())]).optional(),
      username: z.string().optional(),
      avatar_url: z.string().url().optional(),
      tts: z.boolean().optional(),
    });
  • Helper function to send notifications to Discord webhook with retry logic.
    export async function sendDiscord(
      webhookUrl: string, 
      payload: object, 
      maxRetries = 2
    ): Promise<HttpResult> {
      logger.debug("Sending Discord notification");
      
      const result = await postJsonWithRetries(webhookUrl, payload, "discord", maxRetries);
      
      logger.serviceResult("discord", result.success, result.httpCode);
      
      if (!result.success && result.httpCode > 0) {
        logger.apiError("discord", result.httpCode, maxRetries + 1);
      }
      
      return result;
    }
  • Helper function to send notifications to Slack webhook with retry logic.
    export async function sendSlack(
      webhookUrl: string, 
      payload: object, 
      maxRetries = 2
    ): Promise<HttpResult> {
      logger.debug("Sending Slack notification");
      
      const result = await postJsonWithRetries(webhookUrl, payload, "slack", maxRetries);
      
      logger.serviceResult("slack", result.success, result.httpCode);
      
      if (!result.success && result.httpCode > 0) {
        logger.apiError("slack", result.httpCode, maxRetries + 1);
      }
      
      return result;
    }
  • Core HTTP POST helper with retries, rate limiting handling, and error management used by both sendDiscord and sendSlack.
    async function postJsonWithRetries(
      url: string, 
      payload: object, 
      service: "discord" | "slack",
      maxRetries = 2
    ): Promise<HttpResult> {
      let attempt = 0;
      
      while (true) {
        attempt++;
        
        try {
          const response = await fetch(url, {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
            body: JSON.stringify(payload),
          });
    
          // Success cases
          if (response.status === 200 || response.status === 204) {
            return {
              success: true,
              httpCode: response.status,
              timestamp: new Date().toISOString(),
            };
          }
    
          // Rate limiting (429) - retry with delay
          if (response.status === 429 && attempt <= maxRetries) {
            const retryAfterHeader = response.headers.get("retry-after");
            const retryAfter = retryAfterHeader ? Number(retryAfterHeader) : 1;
            const delayMs = retryAfter * 1000;
            
            logger.retryAttempt(service, attempt, maxRetries, delayMs);
            await sleep(delayMs);
            continue;
          }
    
          // Other error - read body safely for context
          const errorBody = await safeReadBody(response);
          
          return {
            success: false,
            httpCode: response.status,
            timestamp: new Date().toISOString(),
          };
          
        } catch (error) {
          if (attempt <= maxRetries) {
            logger.retryAttempt(service, attempt, maxRetries, 1000);
            await sleep(1000);
            continue;
          }
          
          return {
            success: false,
            httpCode: 0, // Network error
            timestamp: new Date().toISOString(),
          };
        }
      }
    }
Install Server

Other Tools

Related Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/thesammykins/notifyme_mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server