Skip to main content
Glama
Sealjay

mcp-hey

hey_download_attachment

Download an email attachment by providing the email ID and attachment ID. Decodes base64 content and saves it to a specified path or default directory.

Instructions

Download a single attachment from an email and save it to disk. Decodes the base64-encoded MIME part and writes it to the supplied path (or ~/Downloads/hey-attachments/<email_id>/ by default). Use the attachment_id from hey_read_email's attachments array.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
email_idYesThe email ID containing the attachment
attachment_idYesThe attachment ID from hey_read_email's attachments array (e.g. 'part-1')
save_pathNoOptional absolute path or directory to save into. Defaults to ~/Downloads/hey-attachments/<email_id>/<filename>. Trailing '/' is treated as a directory.

Implementation Reference

  • The main handler function `downloadAttachment` that decodes a base64-encoded MIME part and writes it to disk. It fetches the raw RFC822 message, finds the attachment by ID, derives the filename, resolves the save path, creates directories, and writes the file.
    export async function downloadAttachment(args: {
      emailId: string
      attachmentId: string
      savePath?: string
    }): Promise<{
      local_path: string
      filename: string
      size: number
      mime: string
    }> {
      const { emailId, attachmentId } = args
      const { attachmentParts } = await fetchAndWalkRawMessage(emailId)
      const found = attachmentParts.find((a) => a.id === attachmentId)
      if (!found) {
        const ids = attachmentParts.map((a) => a.id).join(", ") || "(none)"
        throw new Error(
          `Attachment ${attachmentId} not found on email ${emailId}. Available ids: ${ids}`,
        )
      }
    
      const filename = deriveFilename(found.part, found.index)
      const mime = pureMime(found.part.headers["content-type"])
      const targetPath = resolveSavePath(args.savePath, emailId, filename)
    
      await mkdir(dirname(targetPath), { recursive: true })
      await writeFile(targetPath, found.part.decoded)
    
      return {
        local_path: targetPath,
        filename,
        size: found.part.decoded.byteLength,
        mime,
      }
    }
  • src/index.ts:521-545 (registration)
    Tool registration in the MCP tools array: defines the 'hey_download_attachment' tool with its name, description, and inputSchema (email_id, attachment_id, save_path).
    {
      name: "hey_download_attachment",
      description:
        "Download a single attachment from an email and save it to disk. Decodes the base64-encoded MIME part and writes it to the supplied path (or ~/Downloads/hey-attachments/<email_id>/<filename> by default). Use the attachment_id from hey_read_email's `attachments` array.",
      inputSchema: {
        type: "object" as const,
        properties: {
          email_id: {
            type: "string",
            description: "The email ID containing the attachment",
          },
          attachment_id: {
            type: "string",
            description:
              "The attachment ID from hey_read_email's attachments array (e.g. 'part-1')",
          },
          save_path: {
            type: "string",
            description:
              "Optional absolute path or directory to save into. Defaults to ~/Downloads/hey-attachments/<email_id>/<filename>. Trailing '/' is treated as a directory.",
          },
        },
        required: ["email_id", "attachment_id"],
      },
    },
  • src/index.ts:1267-1310 (registration)
    The CallToolRequestSchema switch case for 'hey_download_attachment' which validates parameters (emailId, attachmentId, savePath) using validateId/validateAttachmentId/validateSavePath, and calls the downloadAttachment handler.
    case "hey_download_attachment": {
      const emailId = validateId(args?.email_id)
      const attachmentId = validateAttachmentId(args?.attachment_id)
      const savePath = validateSavePath(args?.save_path)
      if (!emailId) {
        return {
          content: [
            {
              type: "text",
              text: "Error: email_id is required and must be valid",
            },
          ],
          isError: true,
        }
      }
      if (!attachmentId) {
        return {
          content: [
            {
              type: "text",
              text: "Error: attachment_id is required (e.g. 'part-1' from hey_read_email)",
            },
          ],
          isError: true,
        }
      }
      if (savePath === null) {
        return {
          content: [
            {
              type: "text",
              text: "Error: save_path must be a non-empty string under 1024 chars",
            },
          ],
          isError: true,
        }
      }
      result = await downloadAttachment({
        emailId,
        attachmentId,
        savePath,
      })
      break
    }
  • Validation function `validateAttachmentId` that validates the attachment ID format (must match 'part-N' pattern) used by the tool.
    function validateAttachmentId(value: unknown): string | null {
      if (typeof value !== "string") return null
      const trimmed = value.trim()
      if (trimmed.length === 0 || trimmed.length > 64) return null
      if (!/^part-\d+$/.test(trimmed)) return null
      return trimmed
    }
  • Validation function `validateSavePath` that validates the optional save_path parameter for the tool.
    function validateSavePath(value: unknown): string | null | undefined {
      if (value === undefined) return undefined
      if (typeof value !== "string") return null
      const trimmed = value.trim()
      if (trimmed.length === 0 || trimmed.length > 1024) return null
      return trimmed
    }
Behavior4/5

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

No annotations provided, so description carries full burden. It discloses that it decodes base64 MIME parts and writes to disk. Mentions default path and optional directory behavior. Could be more explicit about overwrite behavior or error handling, but overall transparent for a download operation.

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?

Three sentences, each adding distinct information. No fluff, well-structured, and efficiently communicates the tool's purpose and usage.

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

Completeness4/5

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

No output schema exists, but the description explains the effect (file saved to disk) and default behavior. Could mention return value (success/failure) or constraints like file size limits, but core completeness is adequate.

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

Parameters4/5

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

Schema coverage is 100% with descriptions for all 3 params. The description adds value by specifying that attachment_id comes from hey_read_email's attachments array and explaining save_path behavior (trailing slash as directory).

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 'Download a single attachment from an email and save it to disk.' It specifies the verb (download), resource (attachment), and action (save). It distinguishes from siblings by referencing attachment_id from hey_read_email, which is unique among sibling tools.

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

Usage Guidelines4/5

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

The description tells when to use: requires email_id and attachment_id from hey_read_email. It mentions the default save path and optional save_path. Does not explicitly state when not to use or alternatives, but no other download tool exists, so context is sufficient.

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/Sealjay/mcp-hey'

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