// Google Gmail MCP tools
import { z } from 'zod';
import type { GmailService } from '../services/gmail.js';
// ─────────────────────────────────────────────────────────────────────────────
// SCHEMAS
// ─────────────────────────────────────────────────────────────────────────────
// Common schemas
const AttachmentSchema = z.object({
filename: z.string().describe('Filename with extension'),
mimeType: z.string().describe('MIME type (e.g., application/pdf)'),
content: z.string().optional().describe('Base64 encoded content'),
driveFileId: z.string().optional().describe('Google Drive file ID (alternative to content)'),
});
// Message schemas
export const ListMessagesSchema = z.object({
q: z.string().optional().describe('Gmail search query (e.g., "from:user@example.com is:unread")'),
maxResults: z.number().optional().describe('Max messages to return (default 20)'),
labelIds: z.array(z.string()).optional().describe('Filter by label IDs'),
pageToken: z.string().optional().describe('Pagination token'),
includeSpamTrash: z.boolean().optional().describe('Include spam/trash (default false)'),
});
export const GetMessageSchema = z.object({
messageId: z.string().describe('Message ID'),
format: z.enum(['full', 'metadata', 'minimal']).optional().describe('Response format (default full)'),
});
export const SendMessageSchema = z.object({
to: z.union([z.string(), z.array(z.string())]).describe('Recipient email(s)'),
cc: z.union([z.string(), z.array(z.string())]).optional().describe('CC recipients'),
bcc: z.union([z.string(), z.array(z.string())]).optional().describe('BCC recipients'),
subject: z.string().describe('Email subject'),
body: z.string().describe('Email body'),
isHtml: z.boolean().optional().describe('Body is HTML (default false)'),
threadId: z.string().optional().describe('Thread ID for replies'),
attachments: z.array(AttachmentSchema).optional().describe('File attachments'),
});
export const ReplyToMessageSchema = z.object({
messageId: z.string().describe('Message ID to reply to'),
body: z.string().describe('Reply body'),
isHtml: z.boolean().optional().describe('Body is HTML (default false)'),
replyAll: z.boolean().optional().describe('Reply to all recipients (default false)'),
attachments: z.array(AttachmentSchema).optional().describe('File attachments'),
});
export const ForwardMessageSchema = z.object({
messageId: z.string().describe('Message ID to forward'),
to: z.union([z.string(), z.array(z.string())]).describe('Forward to email(s)'),
additionalBody: z.string().optional().describe('Additional text to include'),
});
export const TrashMessageSchema = z.object({
messageId: z.string().describe('Message ID to trash'),
});
export const DeleteMessageSchema = z.object({
messageId: z.string().describe('Message ID to permanently delete'),
});
export const ModifyMessageLabelsSchema = z.object({
messageId: z.string().describe('Message ID'),
addLabelIds: z.array(z.string()).optional().describe('Labels to add'),
removeLabelIds: z.array(z.string()).optional().describe('Labels to remove'),
});
// Thread schemas
export const ListThreadsSchema = z.object({
q: z.string().optional().describe('Gmail search query'),
maxResults: z.number().optional().describe('Max threads to return (default 20)'),
labelIds: z.array(z.string()).optional().describe('Filter by label IDs'),
pageToken: z.string().optional().describe('Pagination token'),
includeSpamTrash: z.boolean().optional().describe('Include spam/trash'),
});
export const GetThreadSchema = z.object({
threadId: z.string().describe('Thread ID'),
format: z.enum(['full', 'metadata', 'minimal']).optional().describe('Response format'),
});
export const TrashThreadSchema = z.object({
threadId: z.string().describe('Thread ID to trash'),
});
export const ModifyThreadLabelsSchema = z.object({
threadId: z.string().describe('Thread ID'),
addLabelIds: z.array(z.string()).optional().describe('Labels to add to all messages'),
removeLabelIds: z.array(z.string()).optional().describe('Labels to remove from all messages'),
});
// Draft schemas
export const ListDraftsSchema = z.object({
maxResults: z.number().optional().describe('Max drafts to return'),
pageToken: z.string().optional().describe('Pagination token'),
});
export const CreateDraftSchema = z.object({
to: z.union([z.string(), z.array(z.string())]).optional().describe('Recipient email(s)'),
cc: z.union([z.string(), z.array(z.string())]).optional().describe('CC recipients'),
bcc: z.union([z.string(), z.array(z.string())]).optional().describe('BCC recipients'),
subject: z.string().optional().describe('Email subject'),
body: z.string().optional().describe('Email body'),
isHtml: z.boolean().optional().describe('Body is HTML'),
attachments: z.array(AttachmentSchema).optional().describe('File attachments'),
});
export const UpdateDraftSchema = z.object({
draftId: z.string().describe('Draft ID to update'),
to: z.union([z.string(), z.array(z.string())]).optional().describe('Recipient email(s)'),
cc: z.union([z.string(), z.array(z.string())]).optional().describe('CC recipients'),
bcc: z.union([z.string(), z.array(z.string())]).optional().describe('BCC recipients'),
subject: z.string().optional().describe('Email subject'),
body: z.string().optional().describe('Email body'),
isHtml: z.boolean().optional().describe('Body is HTML'),
attachments: z.array(AttachmentSchema).optional().describe('File attachments'),
});
export const SendDraftSchema = z.object({
draftId: z.string().describe('Draft ID to send'),
});
export const DeleteDraftSchema = z.object({
draftId: z.string().describe('Draft ID to delete'),
});
// Label schemas
export const ListLabelsSchema = z.object({});
export const CreateLabelSchema = z.object({
name: z.string().describe('Label name'),
labelListVisibility: z.enum(['labelShow', 'labelShowIfUnread', 'labelHide']).optional()
.describe('Visibility in label list'),
messageListVisibility: z.enum(['show', 'hide']).optional()
.describe('Visibility in message list'),
backgroundColor: z.string().optional().describe('Background color (hex)'),
textColor: z.string().optional().describe('Text color (hex)'),
});
export const UpdateLabelSchema = z.object({
labelId: z.string().describe('Label ID'),
name: z.string().optional().describe('New label name'),
labelListVisibility: z.enum(['labelShow', 'labelShowIfUnread', 'labelHide']).optional(),
messageListVisibility: z.enum(['show', 'hide']).optional(),
backgroundColor: z.string().optional(),
textColor: z.string().optional(),
});
export const DeleteLabelSchema = z.object({
labelId: z.string().describe('Label ID to delete'),
});
// Attachment schema
export const GetAttachmentSchema = z.object({
messageId: z.string().describe('Message ID containing the attachment'),
attachmentId: z.string().describe('Attachment ID'),
});
// Settings schemas
export const GetVacationSettingsSchema = z.object({});
export const SetVacationSettingsSchema = z.object({
enableAutoReply: z.boolean().describe('Enable or disable auto-reply'),
responseSubject: z.string().optional().describe('Auto-reply subject'),
responseBodyPlainText: z.string().optional().describe('Auto-reply body (plain text)'),
responseBodyHtml: z.string().optional().describe('Auto-reply body (HTML)'),
restrictToContacts: z.boolean().optional().describe('Only reply to contacts'),
restrictToDomain: z.boolean().optional().describe('Only reply to same domain'),
startTime: z.number().optional().describe('Start time (epoch ms)'),
endTime: z.number().optional().describe('End time (epoch ms)'),
});
// Profile schema
export const GetGmailProfileSchema = z.object({});
// ─────────────────────────────────────────────────────────────────────────────
// TOOL DEFINITIONS
// ─────────────────────────────────────────────────────────────────────────────
export const gmailTools = [
// Messages
{
name: 'listMessages',
description: 'List Gmail messages with optional search query. Supports Gmail search syntax (from:, to:, subject:, is:unread, has:attachment, after:, before:, label:, etc.)',
inputSchema: {
type: 'object' as const,
properties: {
q: { type: 'string', description: 'Gmail search query (e.g., "from:boss@company.com is:unread")' },
maxResults: { type: 'number', description: 'Max messages (default 20)' },
labelIds: { type: 'array', items: { type: 'string' }, description: 'Filter by labels' },
pageToken: { type: 'string', description: 'Pagination token' },
includeSpamTrash: { type: 'boolean', description: 'Include spam/trash' },
},
required: [],
},
},
{
name: 'getMessage',
description: 'Get full content of a specific email message including body and attachment info',
inputSchema: {
type: 'object' as const,
properties: {
messageId: { type: 'string', description: 'Message ID' },
format: { type: 'string', enum: ['full', 'metadata', 'minimal'], description: 'Response detail level' },
},
required: ['messageId'],
},
},
{
name: 'sendMessage',
description: 'Send a new email with optional attachments',
inputSchema: {
type: 'object' as const,
properties: {
to: {
oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }],
description: 'Recipient email(s)',
},
cc: {
oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }],
description: 'CC recipients',
},
bcc: {
oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }],
description: 'BCC recipients',
},
subject: { type: 'string', description: 'Email subject' },
body: { type: 'string', description: 'Email body' },
isHtml: { type: 'boolean', description: 'Body is HTML (default false)' },
threadId: { type: 'string', description: 'Thread ID for replies' },
attachments: {
type: 'array',
items: {
type: 'object',
properties: {
filename: { type: 'string' },
mimeType: { type: 'string' },
content: { type: 'string', description: 'Base64 content' },
driveFileId: { type: 'string', description: 'Or Drive file ID' },
},
},
description: 'File attachments',
},
},
required: ['to', 'subject', 'body'],
},
},
{
name: 'replyToMessage',
description: 'Reply to an existing email, optionally reply-all',
inputSchema: {
type: 'object' as const,
properties: {
messageId: { type: 'string', description: 'Message ID to reply to' },
body: { type: 'string', description: 'Reply body' },
isHtml: { type: 'boolean', description: 'Body is HTML' },
replyAll: { type: 'boolean', description: 'Reply to all recipients' },
attachments: { type: 'array', items: { type: 'object' }, description: 'Attachments' },
},
required: ['messageId', 'body'],
},
},
{
name: 'forwardMessage',
description: 'Forward an email to other recipients',
inputSchema: {
type: 'object' as const,
properties: {
messageId: { type: 'string', description: 'Message ID to forward' },
to: {
oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }],
description: 'Forward to email(s)',
},
additionalBody: { type: 'string', description: 'Additional text to include' },
},
required: ['messageId', 'to'],
},
},
{
name: 'trashMessage',
description: 'Move a message to trash (can be recovered)',
inputSchema: {
type: 'object' as const,
properties: {
messageId: { type: 'string', description: 'Message ID to trash' },
},
required: ['messageId'],
},
},
{
name: 'deleteMessage',
description: 'Permanently delete a message (cannot be undone)',
inputSchema: {
type: 'object' as const,
properties: {
messageId: { type: 'string', description: 'Message ID to delete' },
},
required: ['messageId'],
},
},
{
name: 'modifyMessageLabels',
description: 'Add or remove labels from a message (archive, mark read/unread, categorize)',
inputSchema: {
type: 'object' as const,
properties: {
messageId: { type: 'string', description: 'Message ID' },
addLabelIds: { type: 'array', items: { type: 'string' }, description: 'Labels to add (e.g., STARRED, IMPORTANT)' },
removeLabelIds: { type: 'array', items: { type: 'string' }, description: 'Labels to remove (e.g., UNREAD, INBOX)' },
},
required: ['messageId'],
},
},
// Threads
{
name: 'listThreads',
description: 'List email threads (conversations) with optional search',
inputSchema: {
type: 'object' as const,
properties: {
q: { type: 'string', description: 'Gmail search query' },
maxResults: { type: 'number', description: 'Max threads (default 20)' },
labelIds: { type: 'array', items: { type: 'string' }, description: 'Filter by labels' },
pageToken: { type: 'string', description: 'Pagination token' },
includeSpamTrash: { type: 'boolean' },
},
required: [],
},
},
{
name: 'getThread',
description: 'Get all messages in a conversation thread',
inputSchema: {
type: 'object' as const,
properties: {
threadId: { type: 'string', description: 'Thread ID' },
format: { type: 'string', enum: ['full', 'metadata', 'minimal'] },
},
required: ['threadId'],
},
},
{
name: 'trashThread',
description: 'Move entire conversation to trash',
inputSchema: {
type: 'object' as const,
properties: {
threadId: { type: 'string', description: 'Thread ID to trash' },
},
required: ['threadId'],
},
},
{
name: 'modifyThreadLabels',
description: 'Add or remove labels from all messages in a thread',
inputSchema: {
type: 'object' as const,
properties: {
threadId: { type: 'string', description: 'Thread ID' },
addLabelIds: { type: 'array', items: { type: 'string' }, description: 'Labels to add' },
removeLabelIds: { type: 'array', items: { type: 'string' }, description: 'Labels to remove' },
},
required: ['threadId'],
},
},
// Drafts
{
name: 'listDrafts',
description: 'List email drafts',
inputSchema: {
type: 'object' as const,
properties: {
maxResults: { type: 'number', description: 'Max drafts to return' },
pageToken: { type: 'string', description: 'Pagination token' },
},
required: [],
},
},
{
name: 'createDraft',
description: 'Create a new email draft (can be sent later)',
inputSchema: {
type: 'object' as const,
properties: {
to: { oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }], description: 'Recipient(s)' },
cc: { oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }], description: 'CC' },
bcc: { oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }], description: 'BCC' },
subject: { type: 'string', description: 'Subject' },
body: { type: 'string', description: 'Body' },
isHtml: { type: 'boolean', description: 'HTML body' },
attachments: { type: 'array', items: { type: 'object' }, description: 'Attachments' },
},
required: [],
},
},
{
name: 'updateDraft',
description: 'Update an existing draft',
inputSchema: {
type: 'object' as const,
properties: {
draftId: { type: 'string', description: 'Draft ID' },
to: { oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }] },
cc: { oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }] },
bcc: { oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }] },
subject: { type: 'string' },
body: { type: 'string' },
isHtml: { type: 'boolean' },
attachments: { type: 'array', items: { type: 'object' } },
},
required: ['draftId'],
},
},
{
name: 'sendDraft',
description: 'Send a draft email',
inputSchema: {
type: 'object' as const,
properties: {
draftId: { type: 'string', description: 'Draft ID to send' },
},
required: ['draftId'],
},
},
{
name: 'deleteDraft',
description: 'Delete a draft (does not send it)',
inputSchema: {
type: 'object' as const,
properties: {
draftId: { type: 'string', description: 'Draft ID to delete' },
},
required: ['draftId'],
},
},
// Labels
{
name: 'listLabels',
description: 'List all Gmail labels (system and custom)',
inputSchema: {
type: 'object' as const,
properties: {},
required: [],
},
},
{
name: 'createLabel',
description: 'Create a new custom label',
inputSchema: {
type: 'object' as const,
properties: {
name: { type: 'string', description: 'Label name' },
labelListVisibility: { type: 'string', enum: ['labelShow', 'labelShowIfUnread', 'labelHide'] },
messageListVisibility: { type: 'string', enum: ['show', 'hide'] },
backgroundColor: { type: 'string', description: 'Background color (hex)' },
textColor: { type: 'string', description: 'Text color (hex)' },
},
required: ['name'],
},
},
{
name: 'updateLabel',
description: 'Update a label name or appearance',
inputSchema: {
type: 'object' as const,
properties: {
labelId: { type: 'string', description: 'Label ID' },
name: { type: 'string', description: 'New name' },
labelListVisibility: { type: 'string', enum: ['labelShow', 'labelShowIfUnread', 'labelHide'] },
messageListVisibility: { type: 'string', enum: ['show', 'hide'] },
backgroundColor: { type: 'string' },
textColor: { type: 'string' },
},
required: ['labelId'],
},
},
{
name: 'deleteLabel',
description: 'Delete a custom label',
inputSchema: {
type: 'object' as const,
properties: {
labelId: { type: 'string', description: 'Label ID to delete' },
},
required: ['labelId'],
},
},
// Attachments
{
name: 'getAttachment',
description: 'Download an attachment from a message (returns base64 data)',
inputSchema: {
type: 'object' as const,
properties: {
messageId: { type: 'string', description: 'Message ID' },
attachmentId: { type: 'string', description: 'Attachment ID' },
},
required: ['messageId', 'attachmentId'],
},
},
// Settings
{
name: 'getVacationSettings',
description: 'Get vacation auto-reply settings',
inputSchema: {
type: 'object' as const,
properties: {},
required: [],
},
},
{
name: 'setVacationSettings',
description: 'Configure vacation auto-reply (out of office)',
inputSchema: {
type: 'object' as const,
properties: {
enableAutoReply: { type: 'boolean', description: 'Enable/disable auto-reply' },
responseSubject: { type: 'string', description: 'Auto-reply subject' },
responseBodyPlainText: { type: 'string', description: 'Auto-reply message (plain text)' },
responseBodyHtml: { type: 'string', description: 'Auto-reply message (HTML)' },
restrictToContacts: { type: 'boolean', description: 'Only reply to contacts' },
restrictToDomain: { type: 'boolean', description: 'Only reply to same domain' },
startTime: { type: 'number', description: 'Start time (epoch ms)' },
endTime: { type: 'number', description: 'End time (epoch ms)' },
},
required: ['enableAutoReply'],
},
},
// Profile
{
name: 'getGmailProfile',
description: 'Get Gmail account info (email address, total messages)',
inputSchema: {
type: 'object' as const,
properties: {},
required: [],
},
},
];
// ─────────────────────────────────────────────────────────────────────────────
// HANDLERS
// ─────────────────────────────────────────────────────────────────────────────
export function createGmailHandlers(gmailService: GmailService) {
return {
// Messages
listMessages: async (args: z.infer<typeof ListMessagesSchema>) => {
const params = ListMessagesSchema.parse(args);
return gmailService.listMessages(params);
},
getMessage: async (args: z.infer<typeof GetMessageSchema>) => {
const params = GetMessageSchema.parse(args);
return gmailService.getMessage(params.messageId, params.format);
},
sendMessage: async (args: z.infer<typeof SendMessageSchema>) => {
const params = SendMessageSchema.parse(args);
return gmailService.sendMessage(params);
},
replyToMessage: async (args: z.infer<typeof ReplyToMessageSchema>) => {
const params = ReplyToMessageSchema.parse(args);
return gmailService.replyToMessage(params);
},
forwardMessage: async (args: z.infer<typeof ForwardMessageSchema>) => {
const params = ForwardMessageSchema.parse(args);
return gmailService.forwardMessage(params);
},
trashMessage: async (args: z.infer<typeof TrashMessageSchema>) => {
const params = TrashMessageSchema.parse(args);
return gmailService.trashMessage(params.messageId);
},
deleteMessage: async (args: z.infer<typeof DeleteMessageSchema>) => {
const params = DeleteMessageSchema.parse(args);
await gmailService.deleteMessage(params.messageId);
return { success: true, deleted: params.messageId };
},
modifyMessageLabels: async (args: z.infer<typeof ModifyMessageLabelsSchema>) => {
const params = ModifyMessageLabelsSchema.parse(args);
return gmailService.modifyMessageLabels(
params.messageId,
params.addLabelIds,
params.removeLabelIds
);
},
// Threads
listThreads: async (args: z.infer<typeof ListThreadsSchema>) => {
const params = ListThreadsSchema.parse(args);
return gmailService.listThreads(params);
},
getThread: async (args: z.infer<typeof GetThreadSchema>) => {
const params = GetThreadSchema.parse(args);
return gmailService.getThread(params.threadId, params.format);
},
trashThread: async (args: z.infer<typeof TrashThreadSchema>) => {
const params = TrashThreadSchema.parse(args);
return gmailService.trashThread(params.threadId);
},
modifyThreadLabels: async (args: z.infer<typeof ModifyThreadLabelsSchema>) => {
const params = ModifyThreadLabelsSchema.parse(args);
return gmailService.modifyThreadLabels(
params.threadId,
params.addLabelIds,
params.removeLabelIds
);
},
// Drafts
listDrafts: async (args: z.infer<typeof ListDraftsSchema>) => {
const params = ListDraftsSchema.parse(args);
return gmailService.listDrafts(params);
},
createDraft: async (args: z.infer<typeof CreateDraftSchema>) => {
const params = CreateDraftSchema.parse(args);
return gmailService.createDraft(params);
},
updateDraft: async (args: z.infer<typeof UpdateDraftSchema>) => {
const params = UpdateDraftSchema.parse(args);
const { draftId, ...options } = params;
return gmailService.updateDraft(draftId, options);
},
sendDraft: async (args: z.infer<typeof SendDraftSchema>) => {
const params = SendDraftSchema.parse(args);
return gmailService.sendDraft(params.draftId);
},
deleteDraft: async (args: z.infer<typeof DeleteDraftSchema>) => {
const params = DeleteDraftSchema.parse(args);
await gmailService.deleteDraft(params.draftId);
return { success: true, deleted: params.draftId };
},
// Labels
listLabels: async () => {
return gmailService.listLabels();
},
createLabel: async (args: z.infer<typeof CreateLabelSchema>) => {
const params = CreateLabelSchema.parse(args);
return gmailService.createLabel(params);
},
updateLabel: async (args: z.infer<typeof UpdateLabelSchema>) => {
const params = UpdateLabelSchema.parse(args);
const { labelId, ...options } = params;
return gmailService.updateLabel(labelId, options);
},
deleteLabel: async (args: z.infer<typeof DeleteLabelSchema>) => {
const params = DeleteLabelSchema.parse(args);
await gmailService.deleteLabel(params.labelId);
return { success: true, deleted: params.labelId };
},
// Attachments
getAttachment: async (args: z.infer<typeof GetAttachmentSchema>) => {
const params = GetAttachmentSchema.parse(args);
return gmailService.getAttachment(params.messageId, params.attachmentId);
},
// Settings
getVacationSettings: async () => {
return gmailService.getVacationSettings();
},
setVacationSettings: async (args: z.infer<typeof SetVacationSettingsSchema>) => {
const params = SetVacationSettingsSchema.parse(args);
return gmailService.setVacationSettings(params);
},
// Profile
getGmailProfile: async () => {
return gmailService.getProfile();
},
};
}