Skip to main content
Glama
gmail.ts11 kB
import { google } from "googleapis"; import { z } from "zod"; // Gmail schemas export const sendEmailSchema = z.object({ to: z.string().describe("Recipient email address"), subject: z.string().describe("Email subject"), body: z.string().describe("Email body content"), cc: z.string().optional().describe("CC email addresses (comma-separated)"), bcc: z.string().optional().describe("BCC email addresses (comma-separated)"), }); export const readEmailsSchema = z.object({ query: z .string() .optional() .describe( "Gmail search query (e.g., 'is:unread', 'from:example@gmail.com')" ), maxResults: z .number() .min(1) .max(100) .default(10) .describe("Maximum number of emails to return"), labelIds: z .array(z.string()) .optional() .describe("Array of label IDs to filter by"), }); export const getEmailSchema = z.object({ messageId: z.string().describe("Gmail message ID"), }); export const createLabelSchema = z.object({ name: z.string().describe("Label name"), messageListVisibility: z .enum(["show", "hide"]) .default("show") .describe("Message list visibility"), labelListVisibility: z .enum(["labelShow", "labelHide"]) .default("labelShow") .describe("Label list visibility"), }); // Markdown formatting helpers function formatEmailListToMarkdown(emails: any[]): string { if (!emails.length) return "No emails found."; let markdown = `# Inbox (${emails.length} emails)\n\n`; emails.forEach((email, index) => { const date = new Date(email.date).toLocaleDateString(); const from = email.from.replace(/[<>]/g, ''); const subject = email.subject || '(No Subject)'; const snippet = email.snippet || ''; markdown += `## ${index + 1}. ${subject}\n`; markdown += `From: ${from} \n`; markdown += `Date: ${date} \n`; markdown += `ID: \`${email.id}\`\n\n`; if (snippet) { markdown += `${snippet}\n\n`; } markdown += `---\n\n`; }); return markdown; } function formatEmailToMarkdown(email: any): string { const date = new Date(email.date).toLocaleDateString(); const from = email.from.replace(/[<>]/g, ''); const to = email.to.replace(/[<>]/g, ''); const subject = email.subject || '(No Subject)'; let markdown = `# ${subject}\n\n`; markdown += `From: ${from} \n`; markdown += `To: ${to} \n`; if (email.cc) markdown += `CC: ${email.cc.replace(/[<>]/g, '')} \n`; if (email.bcc) markdown += `BCC: ${email.bcc.replace(/[<>]/g, '')} \n`; markdown += `Date: ${date} \n`; markdown += `Message ID: \`${email.id}\`\n\n`; if (email.body) { markdown += `---\n\n${email.body}\n`; } return markdown; } function formatLabelsToMarkdown(labels: any[]): string { if (!labels.length) return "No labels found."; let markdown = `# Gmail Labels (${labels.length})\n\n`; const systemLabels = labels.filter(label => label.type === 'system'); const userLabels = labels.filter(label => label.type === 'user'); if (systemLabels.length) { markdown += `## System Labels\n\n`; systemLabels.forEach(label => { markdown += `- ${label.name} (\`${label.id}\`)`; if (label.messagesUnread > 0) { markdown += ` - ${label.messagesUnread} unread`; } markdown += `\n`; }); markdown += `\n`; } if (userLabels.length) { markdown += `## Custom Labels\n\n`; userLabels.forEach(label => { markdown += `- ${label.name} (\`${label.id}\`)`; if (label.messagesTotal) { markdown += ` - ${label.messagesTotal} total`; if (label.messagesUnread > 0) { markdown += `, ${label.messagesUnread} unread`; } } markdown += `\n`; }); } return markdown; } // OAuth2 Authentication helper function createGmailAuth() { const clientId = process.env.GOOGLE_CLIENT_ID; const clientSecret = process.env.GOOGLE_CLIENT_SECRET; const redirectUri = process.env.GOOGLE_REDIRECT_URI || "http://localhost:3000/oauth2callback"; if (!clientId || !clientSecret) { throw new Error( "GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET are required. Run oauth-setup.js to configure." ); } const oauth2Client = new google.auth.OAuth2( clientId, clientSecret, redirectUri ); const accessToken = process.env.GOOGLE_ACCESS_TOKEN; const refreshToken = process.env.GOOGLE_REFRESH_TOKEN; if (!accessToken || !refreshToken) { throw new Error("OAuth2 tokens missing. Run oauth-setup.js to get tokens."); } oauth2Client.setCredentials({ access_token: accessToken, refresh_token: refreshToken, }); return oauth2Client; } // Send email function export async function sendEmail(params: z.infer<typeof sendEmailSchema>) { try { const auth = createGmailAuth(); const gmail = google.gmail({ version: "v1", auth }); // Create email content const email = []; email.push(`To: ${params.to}`); if (params.cc) email.push(`Cc: ${params.cc}`); if (params.bcc) email.push(`Bcc: ${params.bcc}`); email.push(`Subject: ${params.subject}`); email.push(""); email.push(params.body); const rawEmail = Buffer.from(email.join("\n")).toString("base64url"); const response = await gmail.users.messages.send({ userId: "me", requestBody: { raw: rawEmail, }, }); return { content: [ { type: "text" as const, text: `# Email Sent Successfully ✅\n\nMessage ID: \`${response.data.id}\` \nThread ID: \`${response.data.threadId}\` \nTo: ${params.to} \nSubject: ${params.subject}`, }, ], }; } catch (error) { return { content: [ { type: "text" as const, text: `Error sending email: ${ error instanceof Error ? error.message : String(error) }`, }, ], }; } } // Read emails function export async function readEmails(params: z.infer<typeof readEmailsSchema>) { try { const auth = createGmailAuth(); const gmail = google.gmail({ version: "v1", auth }); const listParams: any = { userId: "me", maxResults: params.maxResults, }; if (params.query) listParams.q = params.query; if (params.labelIds) listParams.labelIds = params.labelIds; const response = await gmail.users.messages.list(listParams); if (!response.data.messages || response.data.messages.length === 0) { return { content: [ { type: "text" as const, text: "# No Emails Found\n\nNo emails found matching your search criteria.", }, ], }; } // Get detailed information for each message const emailDetails = await Promise.all( response.data.messages .slice(0, params.maxResults) .map(async (message) => { const detail = await gmail.users.messages.get({ userId: "me", id: message.id!, format: "metadata", metadataHeaders: ["From", "To", "Subject", "Date"], }); const headers = detail.data.payload?.headers || []; const getHeader = (name: string) => headers.find((h) => h.name?.toLowerCase() === name.toLowerCase()) ?.value || ""; return { id: message.id, threadId: message.threadId, from: getHeader("From"), to: getHeader("To"), subject: getHeader("Subject"), date: getHeader("Date"), snippet: detail.data.snippet, labelIds: detail.data.labelIds, }; }) ); return { content: [ { type: "text" as const, text: formatEmailListToMarkdown(emailDetails), }, ], }; } catch (error) { return { content: [ { type: "text" as const, text: `Error reading emails: ${ error instanceof Error ? error.message : String(error) }`, }, ], }; } } // Get specific email function export async function getEmail(params: z.infer<typeof getEmailSchema>) { try { const auth = createGmailAuth(); const gmail = google.gmail({ version: "v1", auth }); const response = await gmail.users.messages.get({ userId: "me", id: params.messageId, format: "full", }); const message = response.data; const headers = message.payload?.headers || []; const getHeader = (name: string) => headers.find((h) => h.name?.toLowerCase() === name.toLowerCase()) ?.value || ""; // Extract body content let body = ""; if (message.payload?.body?.data) { body = Buffer.from(message.payload.body.data, "base64").toString(); } else if (message.payload?.parts) { // Handle multipart messages for (const part of message.payload.parts) { if (part.mimeType === "text/plain" && part.body?.data) { body = Buffer.from(part.body.data, "base64").toString(); break; } } } // Truncate body if it exceeds 30000 characters const truncatedBody = body.length > 20000 ? body.substring(0, 20000) + "\n\n[Email body truncated - content too long]" : body; const emailDetail = { id: message.id, threadId: message.threadId, from: getHeader("From"), to: getHeader("To"), cc: getHeader("Cc"), bcc: getHeader("Bcc"), subject: getHeader("Subject"), date: getHeader("Date"), body: truncatedBody, snippet: message.snippet, labelIds: message.labelIds, }; return { content: [ { type: "text" as const, text: formatEmailToMarkdown(emailDetail), }, ], }; } catch (error) { return { content: [ { type: "text" as const, text: `Error getting email: ${ error instanceof Error ? error.message : String(error) }`, }, ], }; } } // Get Gmail labels function export async function getLabels() { try { const auth = createGmailAuth(); const gmail = google.gmail({ version: "v1", auth }); const response = await gmail.users.labels.list({ userId: "me", }); const labels = response.data.labels?.map((label) => ({ id: label.id, name: label.name, type: label.type, messagesTotal: label.messagesTotal, messagesUnread: label.messagesUnread, threadsTotal: label.threadsTotal, threadsUnread: label.threadsUnread, })); return { content: [ { type: "text" as const, text: formatLabelsToMarkdown(labels || []), }, ], }; } catch (error) { return { content: [ { type: "text" as const, text: `Error getting labels: ${ error instanceof Error ? error.message : String(error) }`, }, ], }; } }

Implementation Reference

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/CaptainCrouton89/maps-mcp'

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