Skip to main content
Glama

send-message

Send messages to Zulip channels or direct users using stream names or email addresses. Specify topic for channel messages and format content with Zulip Markdown.

Instructions

πŸ’¬ SEND MESSAGE: Send a message to a Zulip stream (channel) or direct message to users. IMPORTANT: For streams use exact names from 'get-subscribed-streams'. For DMs use actual email addresses from 'search-users' tool (NOT display names). Always include 'topic' for stream messages.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
typeYes'stream' for channel messages, 'direct' for private messages
toYesFor streams: channel name (e.g., 'general'). For direct: comma-separated user emails (e.g., 'user@example.com' or 'user1@example.com,user2@example.com')
contentYesMessage content using Zulip Markdown syntax. Support mentions (@**Name**), code blocks, links, etc.
topicNoTopic name for stream messages (required for streams, max length varies by server)

Implementation Reference

  • MCP handler function for 'send-message' tool: validates inputs (requires topic for streams, validates emails for direct), constructs params, calls ZulipClient.sendMessage, returns success/error in MCP format.
    server.tool(
      "send-message",
      "πŸ’¬ SEND MESSAGE: Send a message to a Zulip stream (channel) or direct message to users. IMPORTANT: For streams use exact names from 'get-subscribed-streams'. For DMs use actual email addresses from 'search-users' tool (NOT display names). Always include 'topic' for stream messages.",
      SendMessageSchema.shape,
      async ({ type, to, content, topic }) => {
        try {
          if (type === 'stream' && !topic) {
            return createErrorResponse('Topic is required for stream messages. Think of it as a subject line for your message.');
          }
    
          if (type === 'direct') {
            const validation = validateEmailList(to);
            if (!validation.isValid) {
              return createErrorResponse(`Invalid email format for direct message recipients: ${to}. Use 'search-users' tool to find correct email addresses. Don't use display names.`);
            }
          }
    
          const messageParams = { type, to, content, ...(topic && { topic }) };
          const result = await zulipClient.sendMessage(messageParams);
          return createSuccessResponse(`Message sent successfully! Message ID: ${result.id}`);
        } catch (error) {
          return createErrorResponse(`Error sending message: ${error instanceof Error ? error.message : 'Unknown error'}`);
        }
      }
    );
  • Zod schema defining the input parameters for the send-message tool: type (stream/direct), to (stream name or emails), content, optional topic.
    export const SendMessageSchema = z.object({
      type: z.enum(["stream", "direct"]).describe("'stream' for channel messages, 'direct' for private messages"),
      to: z.string().describe("For streams: channel name (e.g., 'general'). For direct: comma-separated user emails (e.g., 'user@example.com' or 'user1@example.com,user2@example.com')"),
      content: z.string().describe("Message content using Zulip Markdown syntax. Support mentions (@**Name**), code blocks, links, etc."),
      topic: z.string().optional().describe("Topic name for stream messages (required for streams, max length varies by server)")
    });
  • src/server.ts:404-428 (registration)
    Registers the 'send-message' tool with the MCP server using server.tool(), providing name, description, schema, and handler function.
    server.tool(
      "send-message",
      "πŸ’¬ SEND MESSAGE: Send a message to a Zulip stream (channel) or direct message to users. IMPORTANT: For streams use exact names from 'get-subscribed-streams'. For DMs use actual email addresses from 'search-users' tool (NOT display names). Always include 'topic' for stream messages.",
      SendMessageSchema.shape,
      async ({ type, to, content, topic }) => {
        try {
          if (type === 'stream' && !topic) {
            return createErrorResponse('Topic is required for stream messages. Think of it as a subject line for your message.');
          }
    
          if (type === 'direct') {
            const validation = validateEmailList(to);
            if (!validation.isValid) {
              return createErrorResponse(`Invalid email format for direct message recipients: ${to}. Use 'search-users' tool to find correct email addresses. Don't use display names.`);
            }
          }
    
          const messageParams = { type, to, content, ...(topic && { topic }) };
          const result = await zulipClient.sendMessage(messageParams);
          return createSuccessResponse(`Message sent successfully! Message ID: ${result.id}`);
        } catch (error) {
          return createErrorResponse(`Error sending message: ${error instanceof Error ? error.message : 'Unknown error'}`);
        }
      }
    );
  • ZulipClient.sendMessage helper: constructs API payload for /messages endpoint, handles direct (array of emails) vs stream (name + topic), tries JSON then form-encoded request to Zulip API.
    async sendMessage(params: {
      type: 'stream' | 'direct';
      to: string;
      content: string;
      topic?: string;
    }): Promise<{ id: number }> {
      if (process.env.NODE_ENV === 'development') {
        debugLog('πŸ” Debug - sendMessage called with:', JSON.stringify(params, null, 2));
      }
      
      // Use the type directly - newer API supports "direct" 
      const payload: any = {
        type: params.type,
        content: params.content
      };
    
      // Handle recipients based on message type
      if (params.type === 'direct') {
        // For direct messages, handle both single and multiple recipients
        const recipients = params.to.includes(',') 
          ? params.to.split(',').map(email => email.trim())
          : [params.to.trim()];
        
        // Try both formats to see which works
        payload.to = recipients;  // Array format first
        debugLog('πŸ” Debug - Direct message recipients:', recipients);
      } else {
        // For stream messages, 'to' is the stream name
        payload.to = params.to;
        if (params.topic) {
          payload.topic = params.topic;
        }
      }
    
      debugLog('πŸ” Debug - Final payload:', JSON.stringify(payload, null, 2));
    
      try {
        // Try JSON first (modern API)
        const response = await this.client.post('/messages', payload);
        debugLog('βœ… Debug - Message sent successfully:', response.data);
        return response.data;
      } catch (jsonError) {
        debugLog('⚠️ Debug - JSON request failed, trying form-encoded...');
        if (jsonError instanceof Error) {
          debugLog('Error:', (jsonError as any).response?.data || jsonError.message);
        }
        
        // Fallback to form-encoded with different recipient format
        const formPayload = { ...payload };
        if (params.type === 'direct') {
          // Try JSON string format for recipients
          const recipients = params.to.includes(',') 
            ? params.to.split(',').map(email => email.trim())
            : [params.to.trim()];
          formPayload.to = JSON.stringify(recipients);
        }
        
        debugLog('πŸ” Debug - Form payload:', JSON.stringify(formPayload, null, 2));
        
        const response = await this.client.post('/messages', formPayload, {
          headers: {
            'Content-Type': 'application/x-www-form-urlencoded'
          },
          transformRequest: [(data) => {
            const params = new URLSearchParams();
            for (const key in data) {
              if (data[key] !== undefined) {
                params.append(key, String(data[key]));
              }
            }
            const formString = params.toString();
            debugLog('πŸ” Debug - Form-encoded string:', formString);
            return formString;
          }]
        });
        
        debugLog('βœ… Debug - Form-encoded message sent successfully:', response.data);
        return response.data;
      }
    }

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/avisekrath/zulip-mcp-server'

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