add_labels
Add labels to an email without removing existing ones.
Instructions
Add labels (mailboxes) to an email without removing existing ones
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| emailId | Yes | ID of the email to add labels to | |
| mailboxIds | Yes | Array of mailbox IDs to add as labels |
Implementation Reference
- src/index.ts:677-695 (schema)Schema definition for the 'add_labels' tool - registered with input validation requiring emailId and mailboxIds array.
{ name: 'add_labels', description: 'Add labels (mailboxes) to an email without removing existing ones', inputSchema: { type: 'object', properties: { emailId: { type: 'string', description: 'ID of the email to add labels to', }, mailboxIds: { type: 'array', items: { type: 'string' }, description: 'Array of mailbox IDs to add as labels', }, }, required: ['emailId', 'mailboxIds'], }, }, - src/index.ts:1472-1490 (handler)Request handler for the 'add_labels' tool - validates inputs and delegates to JmapClient.addLabels.
case 'add_labels': { const { emailId, mailboxIds } = args as any; if (!emailId) { throw new McpError(ErrorCode.InvalidParams, 'emailId is required'); } if (!mailboxIds || !Array.isArray(mailboxIds) || mailboxIds.length === 0) { throw new McpError(ErrorCode.InvalidParams, 'mailboxIds array is required and must not be empty'); } const client = initializeClient(); await client.addLabels(emailId, mailboxIds); return { content: [ { type: 'text', text: `Labels added successfully to email`, }, ], }; } - src/index.ts:131-987 (registration)Tool registration within ListToolsRequestSchema where 'add_labels' is listed as an available tool.
server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'list_mailboxes', description: 'List all mailboxes in the Fastmail account', inputSchema: { type: 'object', properties: {}, }, }, { name: 'list_emails', description: 'List emails from a mailbox', inputSchema: { type: 'object', properties: { mailboxId: { type: 'string', description: 'ID of the mailbox to list emails from (optional, defaults to all)', }, limit: { type: ['number', 'string'], description: 'Maximum number of emails to return (default: 20)', default: 20, }, ascending: { type: 'boolean', description: 'Sort oldest first instead of newest first (default: false)', }, }, }, }, { name: 'get_email', description: 'Get a specific email by ID', inputSchema: { type: 'object', properties: { emailId: { type: 'string', description: 'ID of the email to retrieve', }, }, required: ['emailId'], }, }, { name: 'send_email', description: 'Send an email', inputSchema: { type: 'object', properties: { to: { oneOf: [ { type: 'array', items: { type: 'string' } }, { type: 'string' }, ], description: 'Recipient email addresses (array of strings, or a comma-separated string)', }, cc: { type: 'array', items: { type: 'string' }, description: 'CC email addresses (optional)', }, bcc: { type: 'array', items: { type: 'string' }, description: 'BCC email addresses (optional)', }, from: { type: 'string', description: 'Sender email address (optional, defaults to account primary email)', }, mailboxId: { type: 'string', description: 'Mailbox ID to save the email to (optional, defaults to Drafts folder)', }, subject: { type: 'string', description: 'Email subject', }, textBody: { type: 'string', description: 'Plain text body (optional)', }, htmlBody: { type: 'string', description: 'HTML body (optional)', }, inReplyTo: { type: 'array', items: { type: 'string' }, description: 'Message-ID(s) of the email being replied to (optional, for threading)', }, references: { type: 'array', items: { type: 'string' }, description: 'Full reference chain of Message-IDs (optional, for threading)', }, replyTo: { type: 'array', items: { type: 'string' }, description: 'Reply-To email addresses (replies go here instead of to the sender)', }, }, required: ['to', 'subject'], }, }, { name: 'reply_email', description: 'Reply to an existing email with proper threading headers (In-Reply-To, References). Automatically fetches the original email to build the reply chain. By default sends immediately; set send=false to save as a draft instead.', inputSchema: { type: 'object', properties: { originalEmailId: { type: 'string', description: 'ID of the email to reply to', }, to: { type: 'array', items: { type: 'string' }, description: 'Recipient email addresses (optional, defaults to the original sender)', }, cc: { type: 'array', items: { type: 'string' }, description: 'CC email addresses (optional)', }, bcc: { type: 'array', items: { type: 'string' }, description: 'BCC email addresses (optional)', }, from: { type: 'string', description: 'Sender email address (optional, defaults to account primary email)', }, textBody: { type: 'string', description: 'Plain text body (optional)', }, htmlBody: { type: 'string', description: 'HTML body (optional)', }, send: { type: ['boolean', 'string'], description: 'Whether to send the reply immediately (default: true). Set to false to save as draft instead.', }, replyTo: { type: 'array', items: { type: 'string' }, description: 'Reply-To email addresses (replies go here instead of to the sender)', }, }, required: ['originalEmailId'], }, }, { name: 'create_draft', description: 'Create an email draft without sending it. Supports threading headers for replies. IMPORTANT: each call creates a new draft — do not call twice for the same message.', inputSchema: { type: 'object', properties: { to: { type: 'array', items: { type: 'string' }, description: 'Recipient email addresses (optional)', }, cc: { type: 'array', items: { type: 'string' }, description: 'CC email addresses (optional)', }, bcc: { type: 'array', items: { type: 'string' }, description: 'BCC email addresses (optional)', }, from: { type: 'string', description: 'Sender email address (optional, defaults to account primary email)', }, mailboxId: { type: 'string', description: 'Mailbox ID to save the draft to (optional, defaults to Drafts folder)', }, subject: { type: 'string', description: 'Email subject (optional)', }, textBody: { type: 'string', description: 'Plain text body (optional)', }, htmlBody: { type: 'string', description: 'HTML body (optional)', }, inReplyTo: { type: 'array', items: { type: 'string' }, description: 'Message-IDs to reply to (optional, for threading)', }, references: { type: 'array', items: { type: 'string' }, description: 'Message-IDs for References header (optional, for threading)', }, replyTo: { type: 'array', items: { type: 'string' }, description: 'Reply-To email addresses (replies go here instead of to the sender)', }, }, }, }, { name: 'edit_draft', description: 'Edit an existing draft email. Since JMAP emails are immutable, this atomically destroys the old draft and creates a new one with the updated fields. Only fields you provide will be changed; others are preserved from the original draft.', inputSchema: { type: 'object', properties: { emailId: { type: 'string', description: 'The ID of the draft email to edit', }, to: { type: 'array', items: { type: 'string' }, description: 'Updated recipient email addresses (optional, keeps existing if omitted)', }, cc: { type: 'array', items: { type: 'string' }, description: 'Updated CC email addresses (optional)', }, bcc: { type: 'array', items: { type: 'string' }, description: 'Updated BCC email addresses (optional)', }, from: { type: 'string', description: 'Updated sender email address (optional)', }, subject: { type: 'string', description: 'Updated email subject (optional)', }, textBody: { type: 'string', description: 'Updated plain text body (optional)', }, htmlBody: { type: 'string', description: 'Updated HTML body (optional)', }, replyTo: { type: 'array', items: { type: 'string' }, description: 'Reply-To email addresses (replies go here instead of to the sender)', }, }, required: ['emailId'], }, }, { name: 'send_draft', description: 'Send an existing draft email. The draft must have recipients (to/cc/bcc) and a from address. After sending, the email is moved to the Sent folder and the draft keyword is removed.', inputSchema: { type: 'object', properties: { emailId: { type: 'string', description: 'The ID of the draft email to send', }, }, required: ['emailId'], }, }, { name: 'search_emails', description: 'Search emails by subject or content', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query string', }, limit: { type: ['number', 'string'], description: 'Maximum number of results (default: 20)', default: 20, }, ascending: { type: 'boolean', description: 'Sort oldest first instead of newest first (default: false)', }, }, required: ['query'], }, }, { name: 'list_contacts', description: 'List contacts from the address book', inputSchema: { type: 'object', properties: { limit: { type: 'number', description: 'Maximum number of contacts to return (default: 50)', default: 50, }, }, }, }, { name: 'get_contact', description: 'Get a specific contact by ID', inputSchema: { type: 'object', properties: { contactId: { type: 'string', description: 'ID of the contact to retrieve', }, }, required: ['contactId'], }, }, { name: 'search_contacts', description: 'Search contacts by name or email', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query string', }, limit: { type: 'number', description: 'Maximum number of results (default: 20)', default: 20, }, }, required: ['query'], }, }, { name: 'list_calendars', description: 'List all calendars', inputSchema: { type: 'object', properties: {}, }, }, { name: 'list_calendar_events', description: 'List events from a calendar', inputSchema: { type: 'object', properties: { calendarId: { type: 'string', description: 'ID of the calendar (optional, defaults to all calendars)', }, startDate: { type: 'string', description: 'Filter events starting from this date (ISO 8601, e.g. 2026-03-23T00:00:00Z)', }, endDate: { type: 'string', description: 'Filter events ending before this date (ISO 8601, e.g. 2026-03-30T00:00:00Z)', }, limit: { type: 'number', description: 'Maximum number of events to return (default: 50)', default: 50, }, }, }, }, { name: 'get_calendar_event', description: 'Get a specific calendar event by ID', inputSchema: { type: 'object', properties: { eventId: { type: 'string', description: 'ID of the event to retrieve', }, }, required: ['eventId'], }, }, { name: 'create_calendar_event', description: 'Create a new calendar event', inputSchema: { type: 'object', properties: { calendarId: { type: 'string', description: 'ID of the calendar to create the event in', }, title: { type: 'string', description: 'Event title', }, description: { type: 'string', description: 'Event description (optional)', }, start: { type: 'string', description: 'Start time in ISO 8601 format', }, end: { type: 'string', description: 'End time in ISO 8601 format', }, location: { type: 'string', description: 'Event location (optional)', }, participants: { type: 'array', items: { type: 'object', properties: { email: { type: 'string' }, name: { type: 'string' } } }, description: 'Event participants (optional)', }, }, required: ['calendarId', 'title', 'start', 'end'], }, }, { name: 'list_identities', description: 'List sending identities (email addresses that can be used for sending)', inputSchema: { type: 'object', properties: {}, }, }, { name: 'get_recent_emails', description: 'Get the most recent emails from inbox (like top-ten)', inputSchema: { type: 'object', properties: { limit: { type: ['number', 'string'], description: 'Number of recent emails to retrieve (default: 10, max: 50)', default: 10, }, mailboxName: { type: 'string', description: 'Mailbox to search (default: inbox)', default: 'inbox', }, ascending: { type: 'boolean', description: 'Sort oldest first instead of newest first (default: false)', }, }, }, }, { name: 'mark_email_read', description: 'Mark an email as read or unread', inputSchema: { type: 'object', properties: { emailId: { type: 'string', description: 'ID of the email to mark', }, read: { type: 'boolean', description: 'true to mark as read, false to mark as unread', default: true, }, }, required: ['emailId'], }, }, { name: 'pin_email', description: 'Pin or unpin an email', inputSchema: { type: 'object', properties: { emailId: { type: 'string', description: 'ID of the email to pin/unpin', }, pinned: { type: 'boolean', description: 'true to pin, false to unpin', default: true, }, }, required: ['emailId'], }, }, { name: 'delete_email', description: 'Delete an email (move to trash)', inputSchema: { type: 'object', properties: { emailId: { type: 'string', description: 'ID of the email to delete', }, }, required: ['emailId'], }, }, { name: 'move_email', description: 'Move an email to a different mailbox', inputSchema: { type: 'object', properties: { emailId: { type: 'string', description: 'ID of the email to move', }, targetMailboxId: { type: 'string', description: 'ID of the target mailbox', }, }, required: ['emailId', 'targetMailboxId'], }, }, { name: 'add_labels', description: 'Add labels (mailboxes) to an email without removing existing ones', inputSchema: { type: 'object', properties: { emailId: { type: 'string', description: 'ID of the email to add labels to', }, mailboxIds: { type: 'array', items: { type: 'string' }, description: 'Array of mailbox IDs to add as labels', }, }, required: ['emailId', 'mailboxIds'], }, }, { name: 'remove_labels', description: 'Remove specific labels (mailboxes) from an email', inputSchema: { type: 'object', properties: { emailId: { type: 'string', description: 'ID of the email to remove labels from', }, mailboxIds: { type: 'array', items: { type: 'string' }, description: 'Array of mailbox IDs to remove as labels', }, }, required: ['emailId', 'mailboxIds'], }, }, { name: 'get_email_attachments', description: 'Get list of attachments for an email', inputSchema: { type: 'object', properties: { emailId: { type: 'string', description: 'ID of the email', }, }, required: ['emailId'], }, }, { name: 'download_attachment', description: 'Download an email attachment. If savePath is provided, saves the file to disk and returns the file path and size. Otherwise returns a download URL.', inputSchema: { type: 'object', properties: { emailId: { type: 'string', description: 'ID of the email', }, attachmentId: { type: 'string', description: 'ID of the attachment', }, savePath: { type: 'string', description: `File path to save the attachment to. Paths are restricted to ${getDownloadDir() || '~/Downloads/fastmail-mcp/'} (configurable via FASTMAIL_DOWNLOAD_DIR). Path traversal outside this directory is rejected for security. Parent directories will be created automatically.`, }, }, required: ['emailId', 'attachmentId'], }, }, { 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)', }, }, }, }, { name: 'get_thread', description: 'Get all emails in a conversation thread', inputSchema: { type: 'object', properties: { threadId: { type: 'string', description: 'ID of the thread/conversation', }, }, required: ['threadId'], }, }, { name: 'get_mailbox_stats', description: 'Get statistics for a mailbox (unread count, total emails, etc.)', inputSchema: { type: 'object', properties: { mailboxId: { type: 'string', description: 'ID of the mailbox (optional, defaults to all mailboxes)', }, }, }, }, { name: 'get_account_summary', description: 'Get overall account summary with statistics', inputSchema: { type: 'object', properties: {}, }, }, { name: 'bulk_mark_read', description: 'Mark multiple emails as read/unread', inputSchema: { type: 'object', properties: { emailIds: { type: 'array', items: { type: 'string' }, description: 'Array of email IDs to mark', }, read: { type: 'boolean', description: 'true to mark as read, false as unread', default: true, }, }, required: ['emailIds'], }, }, { name: 'bulk_pin', description: 'Pin or unpin multiple emails', inputSchema: { type: 'object', properties: { emailIds: { type: 'array', items: { type: 'string' }, description: 'Array of email IDs to pin/unpin', }, pinned: { type: 'boolean', description: 'true to pin, false to unpin', default: true, }, }, required: ['emailIds'], }, }, { name: 'bulk_move', description: 'Move multiple emails to a mailbox', inputSchema: { type: 'object', properties: { emailIds: { type: 'array', items: { type: 'string' }, description: 'Array of email IDs to move', }, targetMailboxId: { type: 'string', description: 'ID of target mailbox', }, }, required: ['emailIds', 'targetMailboxId'], }, }, { name: 'bulk_delete', description: 'Delete multiple emails (move to trash)', inputSchema: { type: 'object', properties: { emailIds: { type: 'array', items: { type: 'string' }, description: 'Array of email IDs to delete', }, }, required: ['emailIds'], }, }, { name: 'bulk_add_labels', description: 'Add labels to multiple emails simultaneously', inputSchema: { type: 'object', properties: { emailIds: { type: 'array', items: { type: 'string' }, description: 'Array of email IDs to add labels to', }, mailboxIds: { type: 'array', items: { type: 'string' }, description: 'Array of mailbox IDs to add as labels', }, }, required: ['emailIds', 'mailboxIds'], }, }, { name: 'bulk_remove_labels', description: 'Remove labels from multiple emails simultaneously', inputSchema: { type: 'object', properties: { emailIds: { type: 'array', items: { type: 'string' }, description: 'Array of email IDs to remove labels from', }, mailboxIds: { type: 'array', items: { type: 'string' }, description: 'Array of mailbox IDs to remove as labels', }, }, required: ['emailIds', 'mailboxIds'], }, }, { name: 'check_function_availability', description: 'Check which MCP functions are available based on account permissions', inputSchema: { type: 'object', properties: {}, }, }, { name: 'test_bulk_operations', description: 'Test bulk operations by finding recent emails and performing safe operations (mark read/unread)', inputSchema: { type: 'object', properties: { dryRun: { type: 'boolean', description: 'If true, only shows what would be done without making changes (default: true)', default: true, }, limit: { type: 'number', description: 'Number of emails to test with (default: 3, max: 10)', default: 3, }, }, }, }, ], }; }); - src/jmap-client.ts:884-911 (helper)Core implementation in JmapClient.addLabels - builds a JMAP Email/set patch adding mailboxIds and sends the request.
async addLabels(emailId: string, mailboxIds: string[]): Promise<void> { const session = await this.getSession(); // Build patch object to add specific mailboxIds const patch: Record<string, any> = {}; mailboxIds.forEach(mailboxId => { patch[`mailboxIds/${mailboxId}`] = true; }); const request: JmapRequest = { using: ['urn:ietf:params:jmap:core', 'urn:ietf:params:jmap:mail'], methodCalls: [ ['Email/set', { accountId: session.accountId, update: { [emailId]: patch } }, 'addLabels'] ] }; const response = await this.makeRequest(request); const result = this.getMethodResult(response, 0); if (result.notUpdated && result.notUpdated[emailId]) { throw new Error('Failed to add labels to email.'); } }