Skip to main content
Glama
discourse

Discourse MCP

Official
by discourse

discourse_get_chat_messages

Retrieve chat channel messages with pagination, date filtering, or from last read position to access conversation history efficiently.

Instructions

Get messages from a chat channel with flexible pagination and date-based filtering. Supports: (1) paginating with direction='past'/'future' from a target_message_id, (2) querying messages around a specific target_date, (3) getting messages around a target_message_id, or (4) fetching from last read position.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
channel_idYesThe chat channel ID
page_sizeNoNumber of messages to return (default: 50, max: 500)
target_message_idNoMessage ID to query around or paginate from
directionNoPagination direction: 'past' for older messages (DESC), 'future' for newer messages (ASC)
target_dateNoISO 8601 date string (e.g., '2024-01-15' or '2024-01-15T10:30:00Z') to query messages around that date
fetch_from_last_readNoIf true, start from the user's last read message
include_target_message_idNoWhether to include the target message in results (default: true)

Implementation Reference

  • The main execution logic of the tool: an async handler that queries the Discourse chat API with flexible pagination parameters (target_message_id, direction, target_date, etc.), fetches messages, formats them into a detailed Markdown output including metadata, reactions, attachments, pagination hints, and handles errors.
      async ({
        channel_id,
        page_size = 50,
        target_message_id,
        direction,
        target_date,
        fetch_from_last_read,
        include_target_message_id
      }, _extra: any) => {
        try {
          const { base, client } = ctx.siteState.ensureSelectedSite();
    
          // Build query parameters
          const params = new URLSearchParams();
          params.append("page_size", String(page_size));
    
          if (target_message_id !== undefined) {
            params.append("target_message_id", String(target_message_id));
          }
    
          if (direction) {
            params.append("direction", direction);
          }
    
          if (target_date) {
            params.append("target_date", target_date);
          }
    
          if (fetch_from_last_read !== undefined) {
            params.append("fetch_from_last_read", String(fetch_from_last_read));
          }
    
          if (include_target_message_id !== undefined) {
            params.append("include_target_message_id", String(include_target_message_id));
          }
    
          const url = `/chat/api/channels/${channel_id}/messages?${params.toString()}`;
          const data = (await client.get(url)) as any;
    
          const messages: any[] = data?.messages || [];
          const meta = data?.meta || {};
    
          if (messages.length === 0) {
            return { content: [{ type: "text", text: "No messages found in this channel." }] };
          }
    
          const limit = Number.isFinite(ctx.maxReadLength) ? ctx.maxReadLength : 50000;
          const lines: string[] = [];
    
          // Header
          lines.push(`# Chat Messages (Channel ${channel_id})`);
          lines.push(`Showing ${messages.length} messages`);
          if (target_date) {
            lines.push(`Around date: ${target_date}`);
          }
          if (meta.target_message_id) {
            lines.push(`Target message ID: ${meta.target_message_id}`);
          }
          lines.push("");
    
          // Pagination info
          // Note: The API only sets flags for the direction you queried:
          // - direction='future': only can_load_more_future is meaningful
          // - direction='past': only can_load_more_past is meaningful
          // - no direction: defaults to latest messages, so can_load_more_past is meaningful
          const canLoadMorePast = meta.can_load_more_past ?? false;
          const canLoadMoreFuture = meta.can_load_more_future ?? false;
          const hints: string[] = [];
    
          // When querying in a specific direction, assume the opposite direction is available
          // unless we're at the absolute start/end of the channel
          if (direction === "future") {
            // Going forward in time (to newer messages)
            if (canLoadMoreFuture) {
              const newestId = messages[messages.length - 1]?.id;
              hints.push(`more messages available (use direction='future' with target_message_id=${newestId})`);
            } else {
              hints.push(`no more messages (reached end of channel)`);
            }
            // We came from somewhere, so there are likely older messages
            if (target_message_id) {
              hints.push(`to go back, use direction='past' with target_message_id=${messages[0]?.id}`);
            }
          } else if (direction === "past") {
            // Going backward in time (to older messages)
            if (canLoadMorePast) {
              const oldestId = messages[0]?.id;
              hints.push(`more messages available (use direction='past' with target_message_id=${oldestId})`);
            } else {
              hints.push(`no more messages (reached start of channel)`);
            }
            // We came from somewhere, so there are likely newer messages
            if (target_message_id) {
              hints.push(`to go forward, use direction='future' with target_message_id=${messages[messages.length - 1]?.id}`);
            }
          } else {
            // No direction specified = fetching latest messages
            if (canLoadMorePast) {
              const oldestId = messages[0]?.id;
              hints.push(`older messages available (use direction='past' with target_message_id=${oldestId})`);
            } else {
              hints.push(`no older messages (this is the entire channel history)`);
            }
            // At the latest, so no newer messages
            hints.push(`no newer messages (at end of channel)`);
          }
    
          if (hints.length > 0) {
            lines.push(`_Pagination: ${hints.join("; ")}_`);
            lines.push("");
          }
    
          // Messages
          for (const msg of messages) {
            const username = msg.user?.username || "unknown";
            const createdAt = msg.created_at || "unknown time";
            const messageText = (msg.message || msg.cooked || "").toString().slice(0, limit);
            const edited = msg.edited ? " (edited)" : "";
            const threadId = msg.thread_id ? ` [thread:${msg.thread_id}]` : "";
            const inReplyTo = msg.in_reply_to ? ` [reply to #${msg.in_reply_to.id}]` : "";
    
            lines.push(`## Message #${msg.id}${edited}`);
            lines.push(`**@${username}** at ${createdAt}${threadId}${inReplyTo}`);
            lines.push("");
            lines.push(messageText);
            lines.push("");
    
            // Reactions
            if (msg.reactions && msg.reactions.length > 0) {
              const reactionStr = msg.reactions
                .map((r: any) => `${r.emoji} ${r.count}`)
                .join(", ");
              lines.push(`_Reactions: ${reactionStr}_`);
              lines.push("");
            }
    
            // Uploads/attachments
            if (msg.uploads && msg.uploads.length > 0) {
              lines.push("_Attachments:_");
              for (const upload of msg.uploads) {
                lines.push(`- ${upload.original_filename || upload.url}`);
              }
              lines.push("");
            }
    
            // Mentioned users
            if (msg.mentioned_users && msg.mentioned_users.length > 0) {
              const mentions = msg.mentioned_users.map((u: any) => `@${u.username}`).join(", ");
              lines.push(`_Mentions: ${mentions}_`);
              lines.push("");
            }
    
            lines.push("---");
            lines.push("");
          }
    
          return { content: [{ type: "text", text: lines.join("\n") }] };
        } catch (e: any) {
          return {
            content: [{ type: "text", text: `Failed to get chat messages: ${e?.message || String(e)}` }],
            isError: true
          };
        }
      }
    );
  • Zod schema defining the tool's input parameters: channel_id (required), page_size, target_message_id, direction ('past'/'future'), target_date, fetch_from_last_read, include_target_message_id.
    const schema = z.object({
      channel_id: z.number().int().positive().describe("The chat channel ID"),
      page_size: z.number().int().min(1).max(500).optional().describe("Number of messages to return (default: 50, max: 500)"),
      target_message_id: z.number().int().positive().optional().describe("Message ID to query around or paginate from"),
      direction: z.enum(["past", "future"]).optional().describe("Pagination direction: 'past' for older messages (DESC), 'future' for newer messages (ASC)"),
      target_date: z.string().optional().describe("ISO 8601 date string (e.g., '2024-01-15' or '2024-01-15T10:30:00Z') to query messages around that date"),
      fetch_from_last_read: z.boolean().optional().describe("If true, start from the user's last read message"),
      include_target_message_id: z.boolean().optional().describe("Whether to include the target message in results (default: true)"),
    }).strict();
  • Local registration via server.registerTool('discourse_get_chat_messages', {title, description, inputSchema}, handler), exported as registerGetChatMessages.
      server.registerTool(
        "discourse_get_chat_messages",
        {
          title: "Get Chat Messages",
          description: "Get messages from a chat channel with flexible pagination and date-based filtering. Supports: (1) paginating with direction='past'/'future' from a target_message_id, (2) querying messages around a specific target_date, (3) getting messages around a target_message_id, or (4) fetching from last read position.",
          inputSchema: schema.shape,
        },
        async ({
          channel_id,
          page_size = 50,
          target_message_id,
          direction,
          target_date,
          fetch_from_last_read,
          include_target_message_id
        }, _extra: any) => {
          try {
            const { base, client } = ctx.siteState.ensureSelectedSite();
    
            // Build query parameters
            const params = new URLSearchParams();
            params.append("page_size", String(page_size));
    
            if (target_message_id !== undefined) {
              params.append("target_message_id", String(target_message_id));
            }
    
            if (direction) {
              params.append("direction", direction);
            }
    
            if (target_date) {
              params.append("target_date", target_date);
            }
    
            if (fetch_from_last_read !== undefined) {
              params.append("fetch_from_last_read", String(fetch_from_last_read));
            }
    
            if (include_target_message_id !== undefined) {
              params.append("include_target_message_id", String(include_target_message_id));
            }
    
            const url = `/chat/api/channels/${channel_id}/messages?${params.toString()}`;
            const data = (await client.get(url)) as any;
    
            const messages: any[] = data?.messages || [];
            const meta = data?.meta || {};
    
            if (messages.length === 0) {
              return { content: [{ type: "text", text: "No messages found in this channel." }] };
            }
    
            const limit = Number.isFinite(ctx.maxReadLength) ? ctx.maxReadLength : 50000;
            const lines: string[] = [];
    
            // Header
            lines.push(`# Chat Messages (Channel ${channel_id})`);
            lines.push(`Showing ${messages.length} messages`);
            if (target_date) {
              lines.push(`Around date: ${target_date}`);
            }
            if (meta.target_message_id) {
              lines.push(`Target message ID: ${meta.target_message_id}`);
            }
            lines.push("");
    
            // Pagination info
            // Note: The API only sets flags for the direction you queried:
            // - direction='future': only can_load_more_future is meaningful
            // - direction='past': only can_load_more_past is meaningful
            // - no direction: defaults to latest messages, so can_load_more_past is meaningful
            const canLoadMorePast = meta.can_load_more_past ?? false;
            const canLoadMoreFuture = meta.can_load_more_future ?? false;
            const hints: string[] = [];
    
            // When querying in a specific direction, assume the opposite direction is available
            // unless we're at the absolute start/end of the channel
            if (direction === "future") {
              // Going forward in time (to newer messages)
              if (canLoadMoreFuture) {
                const newestId = messages[messages.length - 1]?.id;
                hints.push(`more messages available (use direction='future' with target_message_id=${newestId})`);
              } else {
                hints.push(`no more messages (reached end of channel)`);
              }
              // We came from somewhere, so there are likely older messages
              if (target_message_id) {
                hints.push(`to go back, use direction='past' with target_message_id=${messages[0]?.id}`);
              }
            } else if (direction === "past") {
              // Going backward in time (to older messages)
              if (canLoadMorePast) {
                const oldestId = messages[0]?.id;
                hints.push(`more messages available (use direction='past' with target_message_id=${oldestId})`);
              } else {
                hints.push(`no more messages (reached start of channel)`);
              }
              // We came from somewhere, so there are likely newer messages
              if (target_message_id) {
                hints.push(`to go forward, use direction='future' with target_message_id=${messages[messages.length - 1]?.id}`);
              }
            } else {
              // No direction specified = fetching latest messages
              if (canLoadMorePast) {
                const oldestId = messages[0]?.id;
                hints.push(`older messages available (use direction='past' with target_message_id=${oldestId})`);
              } else {
                hints.push(`no older messages (this is the entire channel history)`);
              }
              // At the latest, so no newer messages
              hints.push(`no newer messages (at end of channel)`);
            }
    
            if (hints.length > 0) {
              lines.push(`_Pagination: ${hints.join("; ")}_`);
              lines.push("");
            }
    
            // Messages
            for (const msg of messages) {
              const username = msg.user?.username || "unknown";
              const createdAt = msg.created_at || "unknown time";
              const messageText = (msg.message || msg.cooked || "").toString().slice(0, limit);
              const edited = msg.edited ? " (edited)" : "";
              const threadId = msg.thread_id ? ` [thread:${msg.thread_id}]` : "";
              const inReplyTo = msg.in_reply_to ? ` [reply to #${msg.in_reply_to.id}]` : "";
    
              lines.push(`## Message #${msg.id}${edited}`);
              lines.push(`**@${username}** at ${createdAt}${threadId}${inReplyTo}`);
              lines.push("");
              lines.push(messageText);
              lines.push("");
    
              // Reactions
              if (msg.reactions && msg.reactions.length > 0) {
                const reactionStr = msg.reactions
                  .map((r: any) => `${r.emoji} ${r.count}`)
                  .join(", ");
                lines.push(`_Reactions: ${reactionStr}_`);
                lines.push("");
              }
    
              // Uploads/attachments
              if (msg.uploads && msg.uploads.length > 0) {
                lines.push("_Attachments:_");
                for (const upload of msg.uploads) {
                  lines.push(`- ${upload.original_filename || upload.url}`);
                }
                lines.push("");
              }
    
              // Mentioned users
              if (msg.mentioned_users && msg.mentioned_users.length > 0) {
                const mentions = msg.mentioned_users.map((u: any) => `@${u.username}`).join(", ");
                lines.push(`_Mentions: ${mentions}_`);
                lines.push("");
              }
    
              lines.push("---");
              lines.push("");
            }
    
            return { content: [{ type: "text", text: lines.join("\n") }] };
          } catch (e: any) {
            return {
              content: [{ type: "text", text: `Failed to get chat messages: ${e?.message || String(e)}` }],
              isError: true
            };
          }
        }
      );
    };
  • Top-level invocation of registerGetChatMessages(server, ctx, {allowWrites: false}) in the main tools registry function.
    registerGetChatMessages(server, ctx, { allowWrites: false });

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/discourse/discourse-mcp'

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