Skip to main content
Glama

get_recent_messages

Retrieve recent Microsoft Teams messages with filters for time, scope, users, attachments, importance, and keywords to find specific conversations.

Instructions

Get recent messages from across Teams with advanced filtering options. Can filter by time range, scope (channels vs chats), teams, channels, and users.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
hoursNoGet messages from the last N hours (max 168 = 1 week)
limitNoMaximum number of messages to return
mentionsUserNoFilter messages that mention this user ID
fromUserNoFilter messages from this user ID
hasAttachmentsNoFilter messages with attachments
importanceNoFilter by message importance
includeChannelsNoInclude channel messages
includeChatsNoInclude chat messages
teamIdsNoSpecific team IDs to search in
keywordsNoKeywords to search for in message content

Implementation Reference

  • The main execution logic for the 'get_recent_messages' tool. It attempts to use the Microsoft Graph Search API for advanced filtering (keywords, mentions, attachments, importance) and falls back to direct queries on user chats if the search results are poor quality or fail. Supports filtering by time (hours), limit, users, attachments, importance, channels/chats inclusion, teams, and keywords. Returns formatted JSON with message details.
    async ({
      hours,
      limit,
      mentionsUser,
      fromUser,
      hasAttachments,
      importance,
      includeChannels,
      includeChats,
      teamIds,
      keywords,
    }) => {
      try {
        const client = await graphService.getClient();
    
        let attemptedAdvancedSearch = false;
    
        // Try using the Search API first for rich filtering
        if (keywords || mentionsUser || hasAttachments !== undefined || importance) {
          attemptedAdvancedSearch = true;
          // Calculate the date threshold
          const since = new Date(Date.now() - hours * 60 * 60 * 1000).toISOString();
    
          // Build KQL query for Microsoft Search API
          const queryParts: string[] = [];
    
          // Add time filter - use a more permissive date format
          queryParts.push(`sent>=${since.split("T")[0]}`); // Use just the date part
    
          // Add user filters
          if (mentionsUser) {
            queryParts.push(`mentions:${mentionsUser}`);
          }
          if (fromUser) {
            queryParts.push(`from:${fromUser}`);
          }
    
          // Add content filters
          if (hasAttachments !== undefined) {
            queryParts.push(`hasAttachment:${hasAttachments}`);
          }
          if (importance) {
            queryParts.push(`importance:${importance}`);
          }
    
          // Add keyword search
          if (keywords) {
            queryParts.push(`"${keywords}"`);
          }
    
          // If no specific filters, search for all recent messages
          if (queryParts.length === 1) {
            // Only has the time filter
            queryParts.push("*"); // Match all messages
          }
    
          const searchQuery = queryParts.join(" AND ");
    
          const searchRequest: SearchRequest = {
            entityTypes: ["chatMessage"],
            query: {
              queryString: searchQuery,
            },
            from: 0,
            size: Math.min(limit, 100),
            enableTopResults: false, // For recent messages, prefer chronological order
          };
    
          try {
            const response = (await client
              .api("/search/query")
              .post({ requests: [searchRequest] })) as SearchResponse;
    
            if (response?.value?.length && response.value[0]?.hitsContainers?.length) {
              const hits = response.value[0].hitsContainers[0].hits;
              const recentMessages = hits
                .filter((hit) => {
                  // Apply scope filters
                  const isChannelMessage = hit.resource.channelIdentity?.channelId;
                  const isChatMessage = hit.resource.chatId && !isChannelMessage;
    
                  if (!includeChannels && isChannelMessage) return false;
                  if (!includeChats && isChatMessage) return false;
    
                  // Apply team filter if specified
                  if (teamIds?.length && isChannelMessage) {
                    return teamIds.includes(hit.resource.channelIdentity?.teamId || "");
                  }
    
                  return true;
                })
                .map((hit: SearchHit) => ({
                  id: hit.resource.id,
                  content: hit.resource.body?.content || "No content",
                  from: hit.resource.from?.user?.displayName || "Unknown",
                  fromUserId: hit.resource.from?.user?.id,
                  createdDateTime: hit.resource.createdDateTime,
                  chatId: hit.resource.chatId,
                  teamId: hit.resource.channelIdentity?.teamId,
                  channelId: hit.resource.channelIdentity?.channelId,
                  type: hit.resource.channelIdentity?.channelId ? "channel" : "chat",
                }))
                .slice(0, limit); // Apply final limit after filtering
    
              // Check if Search API returned poor quality results (No content/Unknown)
              const poorQualityResults = recentMessages.filter(
                (msg) => msg.content === "No content" || msg.from === "Unknown"
              ).length;
    
              const qualityThreshold = 0.5; // If more than 50% of results are poor quality, fall back
              if (
                recentMessages.length > 0 &&
                poorQualityResults / recentMessages.length > qualityThreshold
              ) {
                console.log(
                  "Search API returned poor quality results, falling back to direct queries"
                );
                // Fall through to direct chat queries
              } else {
                return {
                  content: [
                    {
                      type: "text",
                      text: JSON.stringify(
                        {
                          method: "search_api",
                          timeRange: `Last ${hours} hours`,
                          filters: {
                            mentionsUser,
                            fromUser,
                            hasAttachments,
                            importance,
                            keywords,
                          },
                          totalFound: recentMessages.length,
                          messages: recentMessages,
                        },
                        null,
                        2
                      ),
                    },
                  ],
                };
              }
            }
          } catch (searchError) {
            console.error("Search API failed, falling back to direct queries:", searchError);
          }
        }
    
        // Fallback: Get recent messages from user's chats directly
        // This method is more reliable but doesn't support advanced filtering
        const chatsResponse = await client.api("/me/chats?$expand=members").get();
        const chats = chatsResponse?.value || [];
    
        const allMessages: Array<{
          id: string;
          content: string;
          from: string;
          fromUserId?: string;
          createdDateTime: string;
          chatId: string;
          type: string;
        }> = [];
        const since = new Date(Date.now() - hours * 60 * 60 * 1000);
    
        // Get recent messages from each chat
        for (const chat of chats.slice(0, 10)) {
          // Limit to first 10 chats to avoid rate limits
          try {
            let queryString = `$top=${Math.min(limit, 50)}&$orderby=createdDateTime desc`;
    
            // Apply user filter if specified
            if (fromUser) {
              queryString += `&$filter=from/user/id eq '${fromUser}'`;
            }
    
            const messagesResponse = await client
              .api(`/me/chats/${chat.id}/messages?${queryString}`)
              .get();
    
            const messages = messagesResponse?.value || [];
    
            for (const message of messages) {
              // Filter by time
              if (message.createdDateTime) {
                const messageDate = new Date(message.createdDateTime);
                if (messageDate < since) continue;
              }
    
              // Apply scope filter for chats
              if (!includeChats) {
                continue; // Skip chat messages if includeChats is false
              }
    
              // Apply keyword filter (simple text search)
              if (
                keywords &&
                message.body?.content &&
                !message.body.content.toLowerCase().includes(keywords.toLowerCase())
              ) {
                continue;
              }
    
              allMessages.push({
                id: message.id || "",
                content: message.body?.content || "No content",
                from: message.from?.user?.displayName || "Unknown",
                fromUserId: message.from?.user?.id,
                createdDateTime: message.createdDateTime || "",
                chatId: message.chatId || "",
                type: "chat",
              });
    
              if (allMessages.length >= limit) break;
            }
    
            if (allMessages.length >= limit) break;
          } catch (chatError) {
            console.error(`Error getting messages from chat ${chat.id}:`, chatError);
          }
        }
    
        // Sort by creation date (newest first)
        allMessages.sort(
          (a, b) => new Date(b.createdDateTime).getTime() - new Date(a.createdDateTime).getTime()
        );
    
        return {
          content: [
            {
              type: "text",
              text: JSON.stringify(
                {
                  method: attemptedAdvancedSearch
                    ? "direct_chat_queries_fallback"
                    : "direct_chat_queries",
                  timeRange: `Last ${hours} hours`,
                  filters: {
                    mentionsUser,
                    fromUser,
                    hasAttachments,
                    importance,
                    keywords,
                  },
                  note: attemptedAdvancedSearch
                    ? "Search API returned poor quality results, using direct chat queries as fallback"
                    : "Using direct chat queries for better content reliability",
                  totalFound: allMessages.slice(0, limit).length,
                  messages: allMessages.slice(0, limit),
                },
                null,
                2
              ),
            },
          ],
        };
      } catch (error: unknown) {
        const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
        return {
          content: [
            {
              type: "text",
              text: `❌ Error getting recent messages: ${errorMessage}`,
            },
          ],
        };
      }
    }
  • Zod schema defining input parameters for the tool: hours (1-168, default 24), limit (1-100, default 50), mentionsUser, fromUser, hasAttachments, importance (low/normal/high/urgent), includeChannels (default true), includeChats (default true), teamIds array, keywords.
    {
      hours: z
        .number()
        .min(1)
        .max(168)
        .optional()
        .default(24)
        .describe("Get messages from the last N hours (max 168 = 1 week)"),
      limit: z
        .number()
        .min(1)
        .max(100)
        .optional()
        .default(50)
        .describe("Maximum number of messages to return"),
      mentionsUser: z.string().optional().describe("Filter messages that mention this user ID"),
      fromUser: z.string().optional().describe("Filter messages from this user ID"),
      hasAttachments: z.boolean().optional().describe("Filter messages with attachments"),
      importance: z
        .enum(["low", "normal", "high", "urgent"])
        .optional()
        .describe("Filter by message importance"),
      includeChannels: z.boolean().optional().default(true).describe("Include channel messages"),
      includeChats: z.boolean().optional().default(true).describe("Include chat messages"),
      teamIds: z.array(z.string()).optional().describe("Specific team IDs to search in"),
      keywords: z.string().optional().describe("Keywords to search for in message content"),
    },
  • Registration of the 'get_recent_messages' tool using server.tool(), including name, description, schema, and handler function.
    server.tool(
      "get_recent_messages",
      "Get recent messages from across Teams with advanced filtering options. Can filter by time range, scope (channels vs chats), teams, channels, and users.",
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It states the tool retrieves messages with filtering but lacks critical details: whether it requires authentication, any rate limits, pagination behavior, error conditions, or the format of returned data. For a read operation with 10 parameters, this is a significant gap.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence that front-loads the core purpose and summarizes filtering capabilities. It avoids redundancy and wastes no words, though it could be slightly more structured by separating purpose from filter examples.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the complexity (10 parameters, no annotations, no output schema), the description is incomplete. It doesn't address behavioral aspects like authentication needs, rate limits, or output format, which are crucial for a tool with extensive filtering options. The agent lacks sufficient context to use this tool effectively.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema fully documents all 10 parameters. The description adds minimal value by listing filter types ('time range, scope, teams, channels, and users') but doesn't provide additional semantics beyond what's in the schema descriptions. Baseline 3 is appropriate when schema does the heavy lifting.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'Get recent messages from across Teams with advanced filtering options.' It specifies the verb ('Get'), resource ('recent messages'), and scope ('across Teams'), though it doesn't explicitly differentiate from siblings like 'get_channel_messages' or 'search_messages' beyond mentioning 'advanced filtering options.'

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives. It mentions filtering options but doesn't compare to sibling tools like 'search_messages' or 'get_my_mentions,' leaving the agent to infer usage based on parameter names alone.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other 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/floriscornel/teams-mcp'

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