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