Skip to main content
Glama
p-l-ta

mail-mcp

by p-l-ta

reply_to_email

Destructive

Reply to a specific email by providing its message ID and your response. Optionally reply to all recipients.

Instructions

Reply to an existing message identified by RFC message-id.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
message_idYesRFC message-id from search/list results
bodyYes
reply_allNo

Implementation Reference

  • Handler function that executes the reply logic. It calls findMessageAndAct with the AppleScript ACTION to reply (optionally reply-all), set the body content, send the reply, and return the result.
      async ({ message_id, body, reply_all }) => {
        const result = await findMessageAndAct({
          messageId: message_id,
          action: ACTION,
          extraArgs: {
            theBody: body,
            replyAllStr: reply_all ? "true" : "false",
          },
        });
        if (result === MESSAGE_NOT_FOUND) {
          return {
            content: [{ type: "text", text: `No message found with id ${message_id}` }],
            isError: true,
          };
        }
        return { content: [{ type: "text", text: result }] };
      },
    );
  • Zod schema defining the three input parameters: message_id (string), body (string), and reply_all (boolean, defaults to false).
    const schema = {
      message_id: z.string().describe("RFC message-id from search/list results"),
      body: z.string(),
      reply_all: z.boolean().default(false),
    };
  • Registration of the 'reply_to_email' tool on the MCP server with its description, schema, metadata, and handler callback.
    export function register(server: McpServer): void {
      server.tool(
        "reply_to_email",
        "Reply to an existing message identified by RFC message-id.",
        schema,
        { title: "Reply to Email", readOnlyHint: false, destructiveHint: true },
        async ({ message_id, body, reply_all }) => {
          const result = await findMessageAndAct({
            messageId: message_id,
            action: ACTION,
            extraArgs: {
              theBody: body,
              replyAllStr: reply_all ? "true" : "false",
            },
          });
          if (result === MESSAGE_NOT_FOUND) {
            return {
              content: [{ type: "text", text: `No message found with id ${message_id}` }],
              isError: true,
            };
          }
          return { content: [{ type: "text", text: result }] };
        },
      );
    }
  • src/server.ts:10-38 (registration)
    Server-level registration: imports the register function from reply.ts and invokes it to register the reply_to_email tool on the MCP server.
    import { register as registerReply } from "./tools/reply.js";
    import { register as registerFlags } from "./tools/flags.js";
    import { register as registerMove } from "./tools/move.js";
    import { register as registerTrash } from "./tools/trash.js";
    import { register as registerCreateMailbox } from "./tools/create_mailbox.js";
    import { register as registerBulkMarkRead } from "./tools/bulk_mark_read.js";
    import { register as registerGetUnsubscribeLink } from "./tools/get_unsubscribe_link.js";
    import { register as registerListSenders } from "./tools/list_senders.js";
    import { register as registerEmptyMailbox } from "./tools/empty_mailbox.js";
    
    const server = new McpServer({
      name: "mail-app-mcp",
      version: "1.0.0",
    });
    
    registerSearch(server);
    registerRead(server);
    registerAccounts(server);
    registerListRecent(server);
    registerSend(server);
    registerReply(server);
    registerFlags(server);
    registerMove(server);
    registerTrash(server);
    registerCreateMailbox(server);
    registerBulkMarkRead(server);
    registerGetUnsubscribeLink(server);
    registerListSenders(server);
    registerEmptyMailbox(server);
  • Helper utility that finds a message by RFC ID (via locateMessage) and executes the provided AppleScript action against it, with fallback to brute-force scanning all mailboxes.
    export async function findMessageAndAct(opts: FindAndActOptions): Promise<string> {
      const bareId = opts.messageId.replace(/^<|>$/g, "");
      const extraArgs = opts.extraArgs ?? {};
    
      const location = await locateMessage(opts.messageId);
    
      if (location) {
        const targetedScript = `
          tell application "Mail"
            set acctMatches to (every account whose user name is theUser)
            if (count of acctMatches) = 0 then return "${NOT_FOUND_MARKER}"
            set acct to item 1 of acctMatches
            try
              set mb to mailbox theMbox of acct
            on error
              return "${NOT_FOUND_MARKER}"
            end try
            set candidates to (messages of mb whose message id is theMsgId)
            if (count of candidates) = 0 then return "${NOT_FOUND_MARKER}"
            set foundMsg to item 1 of candidates
            ${opts.action}
          end tell
        `;
        const baseArgs: Record<string, string> = {
          theMsgId: bareId,
          theUser: location.userName,
          theMbox: location.mailboxPath,
        };
        const targetedRunArgs: { script: string; args: Record<string, string>; timeoutMs?: number } = {
          script: targetedScript,
          args: { ...baseArgs, ...extraArgs },
        };
        if (opts.timeoutMs !== undefined) targetedRunArgs.timeoutMs = opts.timeoutMs;
        const result = await runAppleScript(targetedRunArgs);
        if (result !== NOT_FOUND_MARKER) return result;
        // Fall through to brute-force on miss.
      }
    
      const fallbackScript = `
        tell application "Mail"
          set foundMsg to missing value
          repeat with acct in accounts
            repeat with mb in mailboxes of acct
              set candidates to (messages of mb whose message id is theMsgId)
              if (count of candidates) > 0 then
                set foundMsg to item 1 of candidates
                exit repeat
              end if
            end repeat
            if foundMsg is not missing value then exit repeat
          end repeat
          if foundMsg is missing value then return "${NOT_FOUND_MARKER}"
          ${opts.action}
        end tell
      `;
      const fallbackRunArgs: { script: string; args: Record<string, string>; timeoutMs?: number } = {
        script: fallbackScript,
        args: { theMsgId: bareId, ...extraArgs },
      };
      if (opts.timeoutMs !== undefined) fallbackRunArgs.timeoutMs = opts.timeoutMs;
      return runAppleScript(fallbackRunArgs);
    }
Behavior3/5

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

Annotations (destructiveHint: true) indicate write operation, but description adds no further behavioral details (e.g., threading, quoting, side effects). Minimal added value.

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

Conciseness5/5

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

Single sentence, concise and to the point. No unnecessary words.

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

Completeness3/5

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

For a simple tool with no output schema, description is functional but missing usage guidance and potential side effects. Adequate but leaves gaps.

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

Parameters2/5

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

Schema coverage is 33% (only message_id described). Description does not compensate for body and reply_all—no additional meaning beyond bare names.

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?

Specific verb 'Reply' and resource 'existing message' clearly define the action. Distinguishes from siblings like send_email or trash_email.

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?

Implies use on existing messages but lacks explicit guidance on when to use versus alternatives like send_email. No exclusions or conditions stated.

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/p-l-ta/mail-mcp'

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