Skip to main content
Glama

get_recent_messages

Retrieve recent Microsoft Teams messages with filtering by time, users, mentions, attachments, importance, and keywords. Includes options to search channels or chats and specify teams.

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

Implementation Reference

  • Registration of the 'get_recent_messages' tool in the registerSearchTools function using server.tool() with schema and handler.
    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.", { 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"), }, 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 get_recent_messages tool, including time range, limits, and various filters.
    { 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"), },
  • Core handler logic: Attempts Microsoft Graph Search API with KQL for advanced filters, falls back to direct chat message queries if poor quality or unsupported filters. Supports time-based, user, attachment, and keyword filtering.
    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}`, }, ], }; } }

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