Skip to main content
Glama
owen-nash

Fastmail MCP Server

by owen-nash

advanced_search

Filter and find emails by subject, sender, date, attachment, or read status. Use advanced criteria to locate specific messages in your Fastmail account.

Instructions

Advanced email search with multiple criteria

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
queryNoText to search for in subject/body
fromNoFilter by sender email
toNoFilter by recipient email
subjectNoFilter by subject
hasAttachmentNoFilter emails with attachments
isUnreadNoFilter unread emails
isPinnedNoFilter pinned emails
mailboxIdNoSearch within specific mailbox
afterNoEmails after this date (ISO 8601)
beforeNoEmails before this date (ISO 8601)
limitNoMaximum results (default: 50)
ascendingNoSort oldest first instead of newest first (default: false)

Implementation Reference

  • src/index.ts:754-811 (registration)
    Tool registration in ListToolsRequestSchema: defines 'advanced_search' tool name, description, and input schema with all parameters (query, from, to, subject, hasAttachment, isUnread, isPinned, mailboxId, after, before, limit, ascending).
    {
      name: 'advanced_search',
      description: 'Advanced email search with multiple criteria',
      inputSchema: {
        type: 'object',
        properties: {
          query: {
            type: 'string',
            description: 'Text to search for in subject/body',
          },
          from: {
            type: 'string',
            description: 'Filter by sender email',
          },
          to: {
            type: 'string',
            description: 'Filter by recipient email',
          },
          subject: {
            type: 'string',
            description: 'Filter by subject',
          },
          hasAttachment: {
            type: 'boolean',
            description: 'Filter emails with attachments',
          },
          isUnread: {
            type: 'boolean',
            description: 'Filter unread emails',
          },
          isPinned: {
            type: 'boolean',
            description: 'Filter pinned emails',
          },
          mailboxId: {
            type: 'string',
            description: 'Search within specific mailbox',
          },
          after: {
            type: 'string',
            description: 'Emails after this date (ISO 8601)',
          },
          before: {
            type: 'string',
            description: 'Emails before this date (ISO 8601)',
          },
          limit: {
            type: ['number', 'string'],
            description: 'Maximum results (default: 50)',
            default: 50,
          },
          ascending: {
            type: 'boolean',
            description: 'Sort oldest first instead of newest first (default: false)',
          },
        },
      },
    },
  • CallToolRequestSchema handler for 'advanced_search': extracts arguments, calls client.advancedSearch() with the filter object, and returns JSON results.
    case 'advanced_search': {
      const { query, from, to, subject, hasAttachment, isUnread, isPinned, mailboxId, after, before, limit, ascending } = args as any;
      const client = initializeClient();
      const validLimit = Math.min(Math.max(Number(limit) || 50, 1), 100);
      const emails = await client.advancedSearch({
        query, from, to, subject, hasAttachment, isUnread, isPinned, mailboxId, after, before, limit: validLimit, ascending
      });
      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify(emails, null, 2),
          },
        ],
      };
    }
  • Core implementation of JmapClient.advancedSearch(): builds JMAP filter conditions (text, from, to, subject, hasAttachment, isUnread/$seen, isPinned/$flagged, inMailbox, after, before), handles hasKeyword/notKeyword conflicts with AND operator, executes Email/query + Email/get JMAP calls, and returns the results.
    async advancedSearch(filters: {
      query?: string;
      from?: string;
      to?: string;
      subject?: string;
      hasAttachment?: boolean;
      isUnread?: boolean;
      isPinned?: boolean;
      mailboxId?: string;
      after?: string;
      before?: string;
      limit?: number;
      ascending?: boolean;
    }): Promise<any[]> {
      const session = await this.getSession();
      
      // Build JMAP filter object
      const filter: any = {};
      
      if (filters.query) filter.text = filters.query;
      if (filters.from) filter.from = filters.from;
      if (filters.to) filter.to = filters.to;
      if (filters.subject) filter.subject = filters.subject;
      if (filters.hasAttachment !== undefined) filter.hasAttachment = filters.hasAttachment;
      if (filters.isUnread === true) filter.notKeyword = '$seen';
      else if (filters.isUnread === false) filter.hasKeyword = '$seen';
      if (filters.isPinned === true) filter.hasKeyword = '$flagged';
      if (filters.isPinned === false) filter.notKeyword = '$flagged';
      if (filters.mailboxId) filter.inMailbox = filters.mailboxId;
      if (filters.after) filter.after = filters.after;
      if (filters.before) filter.before = filters.before;
    
      // When both isUnread and isPinned are set, hasKeyword/notKeyword may conflict.
      // JMAP FilterCondition only supports one hasKeyword, so wrap in an AND operator.
      let finalFilter: any = filter;
      if (filters.isUnread !== undefined && filters.isPinned !== undefined) {
        delete filter.hasKeyword;
        delete filter.notKeyword;
        const conditions: any[] = [filter];
        conditions.push(filters.isUnread ? { notKeyword: '$seen' } : { hasKeyword: '$seen' });
        conditions.push(filters.isPinned ? { hasKeyword: '$flagged' } : { notKeyword: '$flagged' });
        finalFilter = { operator: 'AND', conditions };
      }
    
      const request: JmapRequest = {
        using: ['urn:ietf:params:jmap:core', 'urn:ietf:params:jmap:mail'],
        methodCalls: [
          ['Email/query', {
            accountId: session.accountId,
            filter: finalFilter,
            sort: [{ property: 'receivedAt', isAscending: filters.ascending ?? false }],
            limit: Math.min(filters.limit || 50, 100)
          }, 'query'],
          ['Email/get', {
            accountId: session.accountId,
            '#ids': { resultOf: 'query', name: 'Email/query', path: '/ids' },
            properties: ['id', 'subject', 'from', 'to', 'cc', 'replyTo', 'receivedAt', 'preview', 'hasAttachment', 'keywords', 'threadId']
          }, 'emails']
        ]
      };
    
      const response = await this.makeRequest(request);
      return this.getListResult(response, 1);
    }
  • Input schema for 'advanced_search' tool: defines all filter properties and their types (string, boolean, number/string for limit, boolean for ascending).
    inputSchema: {
      type: 'object',
      properties: {
        query: {
          type: 'string',
          description: 'Text to search for in subject/body',
        },
        from: {
          type: 'string',
          description: 'Filter by sender email',
        },
        to: {
          type: 'string',
          description: 'Filter by recipient email',
        },
        subject: {
          type: 'string',
          description: 'Filter by subject',
        },
        hasAttachment: {
          type: 'boolean',
          description: 'Filter emails with attachments',
        },
        isUnread: {
          type: 'boolean',
          description: 'Filter unread emails',
        },
        isPinned: {
          type: 'boolean',
          description: 'Filter pinned emails',
        },
        mailboxId: {
          type: 'string',
          description: 'Search within specific mailbox',
        },
        after: {
          type: 'string',
          description: 'Emails after this date (ISO 8601)',
        },
        before: {
          type: 'string',
          description: 'Emails before this date (ISO 8601)',
        },
        limit: {
          type: ['number', 'string'],
          description: 'Maximum results (default: 50)',
          default: 50,
        },
        ascending: {
          type: 'boolean',
          description: 'Sort oldest first instead of newest first (default: false)',
        },
      },
    },
  • Type signature of advancedSearch: defines the filters parameter interface with all optional fields (query, from, to, subject, hasAttachment, isUnread, isPinned, mailboxId, after, before, limit, ascending).
    async advancedSearch(filters: {
      query?: string;
      from?: string;
      to?: string;
      subject?: string;
      hasAttachment?: boolean;
      isUnread?: boolean;
      isPinned?: boolean;
      mailboxId?: string;
      after?: string;
      before?: string;
      limit?: number;
      ascending?: boolean;
    }): Promise<any[]> {
Behavior2/5

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

With no annotations provided, the description carries full burden for behavioral disclosure. It only mentions 'advanced search' and 'multiple criteria', but does not explain traits like pagination (limit param exists), result ordering, performance implications, or whether it combines criteria as AND/OR. Inadequate transparency for a complex search tool.

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

Conciseness3/5

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

The description is very concise (5 words), but at the cost of omitting critical information. It is front-loaded with 'Advanced email search', but could benefit from a brief note on its relationship to similar tools.

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 has 12 parameters, no output schema, and many sibling tools, the description is insufficiently complete. It lacks information on return type, default behavior, and how it differs from simpler search tools. The agent would lack key context to use it effectively.

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 baseline is 3. The description adds no additional meaning beyond what the schema already provides. It does not clarify parameter interactions or provide examples.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose3/5

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

The description states 'Advanced email search with multiple criteria', which identifies the tool as a search function for emails. However, it does not distinguish it from the sibling tool 'search_emails', leaving ambiguity about when to use which. The purpose is somewhat clear but lacks specificity.

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

Usage Guidelines2/5

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

No explicit guidance on when to use this tool versus alternatives like 'search_emails' or 'list_emails'. The description provides no usage context, prerequisites, or exclusions, leaving the agent to infer appropriateness.

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/owen-nash/fastmail-mcp'

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