search_messages
Find messages in Microsoft Teams by searching across channels and chats using KQL queries for sender, mentions, attachments, and other filters.
Instructions
Search for messages across all Microsoft Teams channels and chats using Microsoft Search API. Supports advanced KQL syntax for filtering by sender, mentions, attachments, and more.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | Search query. Supports KQL syntax like 'from:user mentions:userId hasAttachment:true' | |
| scope | No | Scope of search | all |
| limit | No | Number of results to return | |
| enableTopResults | No | Enable relevance-based ranking |
Implementation Reference
- src/tools/search.ts:35-117 (handler)The handler function that implements the core logic of the 'search_messages' tool. It constructs a SearchRequest for Microsoft Graph's /search/query endpoint, applies scope filters, processes search hits into results, and returns formatted JSON output or error messages.async ({ query, scope, limit, enableTopResults }) => { try { const client = await graphService.getClient(); // Build the search request const searchRequest: SearchRequest = { entityTypes: ["chatMessage"], query: { queryString: query, }, from: 0, size: limit, enableTopResults, }; // Add scope-specific filters to the query if needed let enhancedQuery = query; if (scope === "channels") { enhancedQuery = `${query} AND (channelIdentity/channelId:*)`; } else if (scope === "chats") { enhancedQuery = `${query} AND (chatId:* AND NOT channelIdentity/channelId:*)`; } searchRequest.query.queryString = enhancedQuery; const response = (await client .api("/search/query") .post({ requests: [searchRequest] })) as SearchResponse; if (!response?.value?.length || !response.value[0]?.hitsContainers?.length) { return { content: [ { type: "text", text: "No messages found matching your search criteria.", }, ], }; } const hits = response.value[0].hitsContainers[0].hits; const searchResults = hits.map((hit: SearchHit) => ({ id: hit.resource.id, summary: hit.summary, rank: hit.rank, content: hit.resource.body?.content || "No content", from: hit.resource.from?.user?.displayName || "Unknown", createdDateTime: hit.resource.createdDateTime, chatId: hit.resource.chatId, teamId: hit.resource.channelIdentity?.teamId, channelId: hit.resource.channelIdentity?.channelId, })); return { content: [ { type: "text", text: JSON.stringify( { query, scope, totalResults: response.value[0].hitsContainers[0].total, results: searchResults, moreResultsAvailable: response.value[0].hitsContainers[0].moreResultsAvailable, }, null, 2 ), }, ], }; } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"; return { content: [ { type: "text", text: `❌ Error searching messages: ${errorMessage}`, }, ], }; } }
- src/tools/search.ts:11-34 (schema)Zod schema defining the input parameters for the 'search_messages' tool: query (required string), scope (enum: all/channels/chats, default 'all'), limit (1-100, default 25), enableTopResults (boolean, default true).{ query: z .string() .describe( "Search query. Supports KQL syntax like 'from:user mentions:userId hasAttachment:true'" ), scope: z .enum(["all", "channels", "chats"]) .optional() .default("all") .describe("Scope of search"), limit: z .number() .min(1) .max(100) .optional() .default(25) .describe("Number of results to return"), enableTopResults: z .boolean() .optional() .default(true) .describe("Enable relevance-based ranking"), },
- src/tools/search.ts:8-118 (registration)The server.tool() call that registers the 'search_messages' tool with the MCP server, including name, description, input schema, and handler function.server.tool( "search_messages", "Search for messages across all Microsoft Teams channels and chats using Microsoft Search API. Supports advanced KQL syntax for filtering by sender, mentions, attachments, and more.", { query: z .string() .describe( "Search query. Supports KQL syntax like 'from:user mentions:userId hasAttachment:true'" ), scope: z .enum(["all", "channels", "chats"]) .optional() .default("all") .describe("Scope of search"), limit: z .number() .min(1) .max(100) .optional() .default(25) .describe("Number of results to return"), enableTopResults: z .boolean() .optional() .default(true) .describe("Enable relevance-based ranking"), }, async ({ query, scope, limit, enableTopResults }) => { try { const client = await graphService.getClient(); // Build the search request const searchRequest: SearchRequest = { entityTypes: ["chatMessage"], query: { queryString: query, }, from: 0, size: limit, enableTopResults, }; // Add scope-specific filters to the query if needed let enhancedQuery = query; if (scope === "channels") { enhancedQuery = `${query} AND (channelIdentity/channelId:*)`; } else if (scope === "chats") { enhancedQuery = `${query} AND (chatId:* AND NOT channelIdentity/channelId:*)`; } searchRequest.query.queryString = enhancedQuery; const response = (await client .api("/search/query") .post({ requests: [searchRequest] })) as SearchResponse; if (!response?.value?.length || !response.value[0]?.hitsContainers?.length) { return { content: [ { type: "text", text: "No messages found matching your search criteria.", }, ], }; } const hits = response.value[0].hitsContainers[0].hits; const searchResults = hits.map((hit: SearchHit) => ({ id: hit.resource.id, summary: hit.summary, rank: hit.rank, content: hit.resource.body?.content || "No content", from: hit.resource.from?.user?.displayName || "Unknown", createdDateTime: hit.resource.createdDateTime, chatId: hit.resource.chatId, teamId: hit.resource.channelIdentity?.teamId, channelId: hit.resource.channelIdentity?.channelId, })); return { content: [ { type: "text", text: JSON.stringify( { query, scope, totalResults: response.value[0].hitsContainers[0].total, results: searchResults, moreResultsAvailable: response.value[0].hitsContainers[0].moreResultsAvailable, }, null, 2 ), }, ], }; } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"; return { content: [ { type: "text", text: `❌ Error searching messages: ${errorMessage}`, }, ], }; } } );
- src/index.ts:136-136 (registration)Invocation of registerSearchTools in the main MCP server setup, which registers the 'search_messages' tool along with other search-related tools.registerSearchTools(server, graphService);