send_channel_message
Send formatted messages to Microsoft Teams channels, including text, markdown, mentions, and importance levels, enabling efficient team communication and collaboration.
Instructions
Send a message to a specific channel in a Microsoft Team. Supports text and markdown formatting, mentions, and importance levels.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| channelId | Yes | Channel ID | |
| format | No | Message format (text or markdown) | |
| imageContentType | No | MIME type of the image (e.g., 'image/jpeg', 'image/png') | |
| imageData | No | Base64 encoded image data to attach | |
| imageFileName | No | Name for the attached image file | |
| imageUrl | No | URL of an image to attach to the message | |
| importance | No | Message importance | |
| mentions | No | Array of @mentions to include in the message | |
| message | Yes | Message content | |
| teamId | Yes | Team ID |
Implementation Reference
- src/tools/teams.ts:242-419 (handler)The complete handler function for the send_channel_message tool. It processes markdown to HTML, handles user mentions by fetching display names and inserting proper HTML mention tags, manages image attachments by uploading them as hosted content, constructs the message payload, and sends the message to the Teams channel using the Microsoft Graph API. Returns formatted success or error response.async ({ teamId, channelId, message, importance = "normal", format = "text", mentions, imageUrl, imageData, imageContentType, imageFileName, }) => { try { const client = await graphService.getClient(); // Process message content based on format let content: string; let contentType: "text" | "html"; if (format === "markdown") { content = await markdownToHtml(message); contentType = "html"; } else { content = message; contentType = "text"; } // Process @mentions if provided const mentionMappings: Array<{ mention: string; userId: string; displayName: string }> = []; if (mentions && mentions.length > 0) { // Convert provided mentions to mappings with display names for (const mention of mentions) { try { // Get user info to get display name const userResponse = await client .api(`/users/${mention.userId}`) .select("displayName") .get(); mentionMappings.push({ mention: mention.mention, userId: mention.userId, displayName: userResponse.displayName || mention.mention, }); } catch (_error) { console.warn( `Could not resolve user ${mention.userId}, using mention text as display name` ); mentionMappings.push({ mention: mention.mention, userId: mention.userId, displayName: mention.mention, }); } } } // Process mentions in HTML content let finalMentions: Array<{ id: number; mentionText: string; mentioned: { user: { id: string } }; }> = []; if (mentionMappings.length > 0) { const result = processMentionsInHtml(content, mentionMappings); content = result.content; finalMentions = result.mentions; // Ensure we're using HTML content type when mentions are present contentType = "html"; } // Handle image attachment const attachments: ImageAttachment[] = []; if (imageUrl || imageData) { let imageInfo: { data: string; contentType: string } | null = null; if (imageUrl) { imageInfo = await imageUrlToBase64(imageUrl); if (!imageInfo) { return { content: [ { type: "text" as const, text: `❌ Failed to download image from URL: ${imageUrl}`, }, ], isError: true, }; } } else if (imageData && imageContentType) { if (!isValidImageType(imageContentType)) { return { content: [ { type: "text" as const, text: `❌ Unsupported image type: ${imageContentType}`, }, ], isError: true, }; } imageInfo = { data: imageData, contentType: imageContentType }; } if (imageInfo) { const uploadResult = await uploadImageAsHostedContent( graphService, teamId, channelId, imageInfo.data, imageInfo.contentType, imageFileName ); if (uploadResult) { attachments.push(uploadResult.attachment); } else { return { content: [ { type: "text" as const, text: "❌ Failed to upload image attachment", }, ], isError: true, }; } } } // Build message payload const messagePayload: any = { body: { content, contentType, }, importance, }; if (finalMentions.length > 0) { messagePayload.mentions = finalMentions; } if (attachments.length > 0) { messagePayload.attachments = attachments; } const result = (await client .api(`/teams/${teamId}/channels/${channelId}/messages`) .post(messagePayload)) as ChatMessage; // Build success message const successText = `✅ Message sent successfully. Message ID: ${result.id}${ finalMentions.length > 0 ? `\n📱 Mentions: ${finalMentions.map((m) => m.mentionText).join(", ")}` : "" }${attachments.length > 0 ? `\n🖼️ Image attached: ${attachments[0].name}` : ""}`; return { content: [ { type: "text" as const, text: successText, }, ], }; } catch (error: any) { return { content: [ { type: "text" as const, text: `❌ Failed to send message: ${error.message}`, }, ], isError: true, }; } }
- src/tools/teams.ts:217-241 (schema)Zod schema validating the input parameters for the send_channel_message tool, including required teamId, channelId, message; optional importance, format, mentions array (with mention text and userId), and image attachment options.{ teamId: z.string().describe("Team ID"), channelId: z.string().describe("Channel ID"), message: z.string().describe("Message content"), importance: z.enum(["normal", "high", "urgent"]).optional().describe("Message importance"), format: z.enum(["text", "markdown"]).optional().describe("Message format (text or markdown)"), mentions: z .array( z.object({ mention: z .string() .describe("The @mention text (e.g., 'john.doe' or 'john.doe@company.com')"), userId: z.string().describe("Azure AD User ID of the mentioned user"), }) ) .optional() .describe("Array of @mentions to include in the message"), imageUrl: z.string().optional().describe("URL of an image to attach to the message"), imageData: z.string().optional().describe("Base64 encoded image data to attach"), imageContentType: z .string() .optional() .describe("MIME type of the image (e.g., 'image/jpeg', 'image/png')"), imageFileName: z.string().optional().describe("Name for the attached image file"), },
- src/tools/teams.ts:215-420 (registration)Local registration of the send_channel_message tool within the registerTeamsTools function using server.tool(name, description, schema, handler)."send_channel_message", "Send a message to a specific channel in a Microsoft Team. Supports text and markdown formatting, mentions, and importance levels.", { teamId: z.string().describe("Team ID"), channelId: z.string().describe("Channel ID"), message: z.string().describe("Message content"), importance: z.enum(["normal", "high", "urgent"]).optional().describe("Message importance"), format: z.enum(["text", "markdown"]).optional().describe("Message format (text or markdown)"), mentions: z .array( z.object({ mention: z .string() .describe("The @mention text (e.g., 'john.doe' or 'john.doe@company.com')"), userId: z.string().describe("Azure AD User ID of the mentioned user"), }) ) .optional() .describe("Array of @mentions to include in the message"), imageUrl: z.string().optional().describe("URL of an image to attach to the message"), imageData: z.string().optional().describe("Base64 encoded image data to attach"), imageContentType: z .string() .optional() .describe("MIME type of the image (e.g., 'image/jpeg', 'image/png')"), imageFileName: z.string().optional().describe("Name for the attached image file"), }, async ({ teamId, channelId, message, importance = "normal", format = "text", mentions, imageUrl, imageData, imageContentType, imageFileName, }) => { try { const client = await graphService.getClient(); // Process message content based on format let content: string; let contentType: "text" | "html"; if (format === "markdown") { content = await markdownToHtml(message); contentType = "html"; } else { content = message; contentType = "text"; } // Process @mentions if provided const mentionMappings: Array<{ mention: string; userId: string; displayName: string }> = []; if (mentions && mentions.length > 0) { // Convert provided mentions to mappings with display names for (const mention of mentions) { try { // Get user info to get display name const userResponse = await client .api(`/users/${mention.userId}`) .select("displayName") .get(); mentionMappings.push({ mention: mention.mention, userId: mention.userId, displayName: userResponse.displayName || mention.mention, }); } catch (_error) { console.warn( `Could not resolve user ${mention.userId}, using mention text as display name` ); mentionMappings.push({ mention: mention.mention, userId: mention.userId, displayName: mention.mention, }); } } } // Process mentions in HTML content let finalMentions: Array<{ id: number; mentionText: string; mentioned: { user: { id: string } }; }> = []; if (mentionMappings.length > 0) { const result = processMentionsInHtml(content, mentionMappings); content = result.content; finalMentions = result.mentions; // Ensure we're using HTML content type when mentions are present contentType = "html"; } // Handle image attachment const attachments: ImageAttachment[] = []; if (imageUrl || imageData) { let imageInfo: { data: string; contentType: string } | null = null; if (imageUrl) { imageInfo = await imageUrlToBase64(imageUrl); if (!imageInfo) { return { content: [ { type: "text" as const, text: `❌ Failed to download image from URL: ${imageUrl}`, }, ], isError: true, }; } } else if (imageData && imageContentType) { if (!isValidImageType(imageContentType)) { return { content: [ { type: "text" as const, text: `❌ Unsupported image type: ${imageContentType}`, }, ], isError: true, }; } imageInfo = { data: imageData, contentType: imageContentType }; } if (imageInfo) { const uploadResult = await uploadImageAsHostedContent( graphService, teamId, channelId, imageInfo.data, imageInfo.contentType, imageFileName ); if (uploadResult) { attachments.push(uploadResult.attachment); } else { return { content: [ { type: "text" as const, text: "❌ Failed to upload image attachment", }, ], isError: true, }; } } } // Build message payload const messagePayload: any = { body: { content, contentType, }, importance, }; if (finalMentions.length > 0) { messagePayload.mentions = finalMentions; } if (attachments.length > 0) { messagePayload.attachments = attachments; } const result = (await client .api(`/teams/${teamId}/channels/${channelId}/messages`) .post(messagePayload)) as ChatMessage; // Build success message const successText = `✅ Message sent successfully. Message ID: ${result.id}${ finalMentions.length > 0 ? `\n📱 Mentions: ${finalMentions.map((m) => m.mentionText).join(", ")}` : "" }${attachments.length > 0 ? `\n🖼️ Image attached: ${attachments[0].name}` : ""}`; return { content: [ { type: "text" as const, text: successText, }, ], }; } catch (error: any) { return { content: [ { type: "text" as const, text: `❌ Failed to send message: ${error.message}`, }, ], isError: true, }; } } );
- src/index.ts:134-134 (registration)Top-level registration call in the main MCP server setup that invokes registerTeamsTools to add all Teams tools, including send_channel_message, to the server.registerTeamsTools(server, graphService);