Skip to main content
Glama
Sealjay

mcp-hey

hey_screen

DestructiveIdempotent

Approve or reject email senders by address to control where their messages land. Approving routes future emails to imbox, feed, or paper trail. Rejecting blocks future emails without spam flagging.

Instructions

Approve or reject a sender by email address. Approve: routes the sender's current and future emails into the chosen destination (defaults to imbox). Reject (a.k.a. screen out): blocks the sender from sending you further emails — works for both pending screener entries AND already-approved senders (falls back to the contact-page 'Screened Out' affordance via /contacts/{id}/clearance). Reject does NOT flag emails as spam; existing emails are left untouched. Reversible from the Hey UI by visiting the contact page; not yet exposed via MCP. Returns {success, error?}. Use hey_list_screener to see pending senders, or hey_screen_by_id for clearance IDs.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
sender_emailYesThe sender's email address
actionYesapprove: allow this sender's emails through (routed to `destination`). reject: block future emails from this sender. Does not flag as spam, does not move existing emails. Reversible via the Hey UI's contact page.
destinationNoWhere future emails from this sender land when approved: imbox (default, important mail), feed (newsletters/updates), paper_trail (receipts/automated). Ignored when action is reject.

Implementation Reference

  • MCP tool call handler for 'hey_screen'. It validates sender_email, action (approve/reject), and optional destination, then delegates to either screenIn() or screenOut() from src/tools/organise.ts.
    case "hey_screen": {
      const senderEmail = validateEmail(args?.sender_email)
      const action = args?.action as string
      const destination = args?.destination as
        | "imbox"
        | "feed"
        | "paper_trail"
        | undefined
      if (!senderEmail) {
        return {
          content: [
            {
              type: "text",
              text: "Error: sender_email is required and must be a valid email",
            },
          ],
          isError: true,
        }
      }
      if (!action || !["approve", "reject"].includes(action)) {
        return {
          content: [
            {
              type: "text",
              text: "Error: action is required (approve or reject)",
            },
          ],
          isError: true,
        }
      }
      if (
        destination &&
        !["imbox", "feed", "paper_trail"].includes(destination)
      ) {
        return {
          content: [
            {
              type: "text",
              text: "Error: destination must be one of imbox, feed, paper_trail",
            },
          ],
          isError: true,
        }
      }
      result =
        action === "approve"
          ? await screenIn(senderEmail, destination)
          : await screenOut(senderEmail)
      break
  • Tool schema / registration for 'hey_screen'. Defines the tool name, annotations, description, and input schema (sender_email, action, destination).
      name: "hey_screen",
      annotations: {
        readOnlyHint: false,
        destructiveHint: true,
        idempotentHint: true,
        openWorldHint: true,
      },
      description:
        "Approve or reject a sender by email address. Approve: routes the sender's current and future emails into the chosen destination (defaults to imbox). Reject (a.k.a. screen out): blocks the sender from sending you further emails — works for both pending screener entries AND already-approved senders (falls back to the contact-page 'Screened Out' affordance via /contacts/{id}/clearance). Reject does NOT flag emails as spam; existing emails are left untouched. Reversible from the Hey UI by visiting the contact page; not yet exposed via MCP. Returns {success, error?}. Use hey_list_screener to see pending senders, or hey_screen_by_id for clearance IDs.",
      inputSchema: {
        type: "object" as const,
        properties: {
          sender_email: {
            type: "string",
            description: "The sender's email address",
          },
          action: {
            type: "string",
            enum: ["approve", "reject"],
            description:
              "approve: allow this sender's emails through (routed to `destination`). reject: block future emails from this sender. Does not flag as spam, does not move existing emails. Reversible via the Hey UI's contact page.",
          },
          destination: {
            type: "string",
            enum: ["imbox", "feed", "paper_trail"],
            description:
              "Where future emails from this sender land when approved: imbox (default, important mail), feed (newsletters/updates), paper_trail (receipts/automated). Ignored when action is reject.",
          },
        },
        required: ["sender_email", "action"],
      },
    },
  • The screenIn() helper function — resolves a sender email to a clearance ID by parsing the screener page, then delegates to screenInById() to approve the sender.
    export async function screenIn(
      senderEmail: string,
      destination?: MoveDestination,
    ): Promise<OrganiseResult> {
      if (!senderEmail) {
        return { success: false, error: "Sender email is required" }
      }
    
      try {
        const clearanceId = await findClearanceIdByEmail(senderEmail)
        if (!clearanceId) {
          return {
            success: false,
            error: `Sender ${senderEmail} not found in screener. Use hey_list_screener to see pending senders.`,
          }
        }
    
        return screenInById(clearanceId, destination)
      } catch (err) {
        return { success: false, error: toUserError(err) }
      }
    }
  • The screenOut() helper function — tries to reject via the screener (clearance page) first; if the sender is already approved, falls back to the contact-page 'Screened Out' affordance via /contacts/{id}/clearance.
    export async function screenOut(senderEmail: string): Promise<OrganiseResult> {
      if (!senderEmail) {
        return { success: false, error: "Sender email is required" }
      }
    
      try {
        const clearanceId = await findClearanceIdByEmail(senderEmail)
        if (clearanceId) {
          return screenOutById(clearanceId)
        }
    
        // Sender isn't pending in the screener; they've already been approved.
        // Fall back to the contact-page "Screened Out" affordance, which blocks
        // future emails without flagging existing ones as spam.
        const contactId = await findContactIdByEmail(senderEmail)
        if (contactId) {
          return screenOutByContactId(contactId)
        }
    
        return {
          success: false,
          error: `Sender ${senderEmail} not found in screener or contacts. They may not exist in your Hey account.`,
        }
      } catch (err) {
        return { success: false, error: toUserError(err) }
      }
    }
  • Helper that fetches the /clearances screener page and finds a clearance ID by matching on the sender email address.
    async function findClearanceIdByEmail(
      senderEmail: string,
    ): Promise<string | null> {
      const html = await heyClient.fetchHtml("/clearances")
      const root = parseHtml(html)
    
      const forms = root.querySelectorAll("form[action*='/clearances/']")
      for (const form of forms) {
        const formHtml = form.toString().toLowerCase()
        if (formHtml.includes(senderEmail.toLowerCase())) {
          const action = form.getAttribute("action")
          const match = action?.match(/\/clearances\/(\d+)/)
          if (match) {
            return match[1]
          }
        }
      }
    
      const articles = root.querySelectorAll(
        "article, section, [data-clearance-id]",
      )
      for (const article of articles) {
        const articleText = article.text.toLowerCase()
        if (articleText.includes(senderEmail.toLowerCase())) {
          const form = article.querySelector("form[action*='/clearances/']")
          const action = form?.getAttribute("action")
          const match = action?.match(/\/clearances\/(\d+)/)
          if (match) {
            return match[1]
          }
          const clearanceId = article.getAttribute("data-clearance-id")
          if (clearanceId) {
            return clearanceId
          }
        }
      }
    
      return null
    }
Behavior4/5

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

Adds value beyond annotations: explains that reject does not flag spam, leaves existing emails untouched, is reversible via the UI, and returns {success, error?}. No contradictions with annotations.

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?

Description is comprehensive yet well-structured. Starts with a summary, then details for each action and notes on alternatives. Every sentence adds value, though slightly long.

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

Completeness5/5

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

Covers return value, reversibility, edge cases, and related tools. For a tool with no output schema, it sufficiently informs the agent about behavior and limitations.

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%, but description adds meaning: explains destination options with examples, states default when not provided, and clarifies destination is ignored for reject. This enriches the schema.

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?

Clearly states the tool approves or rejects senders by email address. Distinguishes from related tools like hey_list_screener and hey_screen_by_id by referencing them explicitly.

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?

Provides clear guidance on when to use approve vs reject, including that reject works on both pending and already-approved senders. Mentions alternatives for related tasks. Lacks explicit 'when not to use', but overall strong.

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