Skip to main content
Glama

reply_to_channel_message

Reply directly to a specific message in a Microsoft Teams channel with text, markdown formatting, mentions, and importance levels.

Instructions

Reply to a specific message in a channel. Supports text and markdown formatting, mentions, and importance levels.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
teamIdYesTeam ID
channelIdYesChannel ID
messageIdYesMessage ID to reply to
messageYesReply content
importanceNoMessage importance
formatNoMessage format (text or markdown)
mentionsNoArray of @mentions to include in the reply
imageUrlNoURL of an image to attach to the reply
imageDataNoBase64 encoded image data to attach
imageContentTypeNoMIME type of the image (e.g., 'image/jpeg', 'image/png')
imageFileNameNoName for the attached image file

Implementation Reference

  • The handler function that executes the reply_to_channel_message tool. It processes the input message (markdown to HTML conversion, mentions, image attachments), constructs the payload, and posts the reply to the Microsoft Graph API endpoint `/teams/{teamId}/channels/{channelId}/messages/{messageId}/replies`. Handles errors and returns formatted content.
    async ({
      teamId,
      channelId,
      messageId,
      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/${messageId}/replies`)
          .post(messagePayload)) as ChatMessage;
    
        // Build success message
        const successText = `āœ… Reply sent successfully. Reply 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 reply: ${error.message}`,
            },
          ],
          isError: true,
        };
      }
    }
  • Zod schema defining the input parameters and validation for the reply_to_channel_message tool, including required teamId, channelId, messageId, message, and optional parameters for formatting, mentions, and attachments.
    {
      teamId: z.string().describe("Team ID"),
      channelId: z.string().describe("Channel ID"),
      messageId: z.string().describe("Message ID to reply to"),
      message: z.string().describe("Reply 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 reply"),
      imageUrl: z.string().optional().describe("URL of an image to attach to the reply"),
      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"),
    },
  • The registration of the reply_to_channel_message tool using McpServer.tool() within the registerTeamsTools function, specifying name, description, input schema, and handler.
    server.tool(
      "reply_to_channel_message",
      "Reply to a specific message in a channel. Supports text and markdown formatting, mentions, and importance levels.",
      {
        teamId: z.string().describe("Team ID"),
        channelId: z.string().describe("Channel ID"),
        messageId: z.string().describe("Message ID to reply to"),
        message: z.string().describe("Reply 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 reply"),
        imageUrl: z.string().optional().describe("URL of an image to attach to the reply"),
        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,
        messageId,
        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/${messageId}/replies`)
            .post(messagePayload)) as ChatMessage;
    
          // Build success message
          const successText = `āœ… Reply sent successfully. Reply 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 reply: ${error.message}`,
              },
            ],
            isError: true,
          };
        }
      }
    );
Behavior2/5

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

No annotations are provided, so the description carries full burden. It mentions behavioral traits like supporting text/markdown formatting, mentions, and importance levels, but lacks critical details such as permission requirements, rate limits, whether replies are editable/deletable, or how replies appear in the channel. For a mutation tool with 11 parameters, this is insufficient.

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 lists key features. It avoids redundancy, but could be slightly more structured by separating purpose from capabilities for clarity.

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 tool's complexity (11 parameters, mutation operation) and lack of annotations or output schema, the description is incomplete. It does not cover error conditions, response format, or important behavioral aspects like side effects or authentication needs, leaving significant gaps for agent usage.

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 11 parameters. The description adds minimal value beyond the schema by hinting at features like formatting and mentions, but does not provide additional syntax, examples, or constraints. 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.

Purpose5/5

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

The description clearly states the specific action ('Reply to a specific message in a channel') and resource ('message'), distinguishing it from siblings like 'send_channel_message' (which creates new messages) and 'get_channel_message_replies' (which retrieves replies). It specifies the reply targets a particular message via messageId.

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

Usage Guidelines3/5

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

The description implies usage context by mentioning 'reply to a specific message' and listing supported features, but it does not explicitly state when to use this tool versus alternatives like 'send_channel_message' (for new messages) or 'create_chat' (for private chats). No exclusions or prerequisites are provided.

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