Skip to main content
Glama

Gmail & PostgreSQL MCP Server

by marouanemkm
gmail.service.ts9.18 kB
import { google } from "googleapis"; import type { OAuth2Client } from "google-auth-library"; import type { Tool } from "@modelcontextprotocol/sdk/types.js"; import type { ListEmailsArgs, ReadEmailArgs, SendEmailArgs, EmailData, GmailMessageFormat, GmailMessageItem, GmailHeader, GmailMessagePart, } from "../types/gmail.types.js"; export class GmailService { private gmail: ReturnType<typeof google.gmail> | null = null; private oauth2Client: OAuth2Client | null = null; constructor() { this.initializeGmail(); } private initializeGmail(): void { const clientId = process.env.GMAIL_CLIENT_ID; const clientSecret = process.env.GMAIL_CLIENT_SECRET; const redirectUri = process.env.GMAIL_REDIRECT_URI || "urn:ietf:wg:oauth:2.0:oob"; const refreshToken = process.env.GMAIL_REFRESH_TOKEN; if (!clientId || !clientSecret) { console.warn("[Gmail] Client ID or Secret not configured"); return; } this.oauth2Client = new google.auth.OAuth2(clientId, clientSecret, redirectUri); if (refreshToken) { this.oauth2Client.setCredentials({ refresh_token: refreshToken }); this.gmail = google.gmail({ version: "v1", auth: this.oauth2Client }); } else { console.warn("[Gmail] Refresh token not configured"); } } async getTools(): Promise<Tool[]> { if (!this.gmail) { return []; } return [ { name: "gmail_list_emails", description: "List emails from Gmail inbox. Can filter by query, maxResults, and labelIds.", inputSchema: { type: "object", properties: { query: { type: "string", description: 'Search query string (e.g., "from:example@gmail.com subject:test")', }, maxResults: { type: "number", description: "Maximum number of emails to return (default: 10)", default: 10, }, labelIds: { type: "array", items: { type: "string" }, description: 'Array of label IDs to filter by (e.g., ["INBOX", "UNREAD"])', }, }, }, }, { name: "gmail_read_email", description: "Read a specific email by its message ID", inputSchema: { type: "object", properties: { messageId: { type: "string", description: "The ID of the email message to read", }, format: { type: "string", enum: ["full", "metadata", "minimal", "raw"], description: "Format of the email response (default: full)", default: "full", }, }, required: ["messageId"], }, }, { name: "gmail_send_email", description: "Send an email via Gmail", inputSchema: { type: "object", properties: { to: { type: "string", description: "Recipient email address", }, subject: { type: "string", description: "Email subject", }, body: { type: "string", description: "Email body (plain text)", }, htmlBody: { type: "string", description: "Email body (HTML, optional)", }, cc: { type: "string", description: "CC email address (optional)", }, bcc: { type: "string", description: "BCC email address (optional)", }, }, required: ["to", "subject", "body"], }, }, { name: "gmail_get_labels", description: "Get list of all Gmail labels", inputSchema: { type: "object", properties: {}, }, }, ]; } async handleTool(name: string, args: Record<string, unknown>): Promise<{ content: Array<{ type: string; text: string }> }> { if (!this.gmail) { throw new Error("Gmail service not initialized. Please check your Gmail credentials."); } try { switch (name) { case "gmail_list_emails": return await this.listEmails(args as unknown as ListEmailsArgs); case "gmail_read_email": return await this.readEmail(args as unknown as ReadEmailArgs); case "gmail_send_email": return await this.sendEmail(args as unknown as SendEmailArgs); case "gmail_get_labels": return await this.getLabels(); default: throw new Error(`Unknown Gmail tool: ${name}`); } } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; throw new Error(`Gmail operation failed: ${errorMessage}`); } } private async listEmails(args: ListEmailsArgs): Promise<{ content: Array<{ type: string; text: string }> }> { const { query, maxResults = 10, labelIds } = args; const response = await this.gmail!.users.messages.list({ userId: "me", q: query, maxResults, labelIds, }); const messages = response.data.messages || []; const emailList = messages.map((msg: GmailMessageItem) => ({ id: msg.id || null, threadId: msg.threadId || null, })); return { content: [ { type: "text", text: JSON.stringify(emailList, null, 2), }, ], }; } private async readEmail(args: ReadEmailArgs): Promise<{ content: Array<{ type: string; text: string }> }> { const { messageId, format = "full" } = args; const response = await this.gmail!.users.messages.get({ userId: "me", id: messageId, format: format as GmailMessageFormat, }); const message = response.data; const emailData: EmailData = { id: message.id || null, threadId: message.threadId || null, labelIds: message.labelIds || null, snippet: message.snippet || null, }; // Parse headers if (message.payload?.headers) { const headers: Record<string, string> = {}; message.payload.headers.forEach((header: GmailHeader) => { if (header.name && header.value) { headers[header.name.toLowerCase()] = header.value; } }); emailData.headers = headers; } // Extract body if (message.payload?.body?.data) { emailData.body = Buffer.from(message.payload.body.data, "base64").toString("utf-8"); } else if (message.payload?.parts) { const bodyParts: string[] = []; message.payload.parts.forEach((part: GmailMessagePart) => { if (part.body?.data) { bodyParts.push(Buffer.from(part.body.data, "base64").toString("utf-8")); } }); emailData.body = bodyParts.join("\n---\n"); } return { content: [ { type: "text", text: JSON.stringify(emailData, null, 2), }, ], }; } private async sendEmail(args: SendEmailArgs): Promise<{ content: Array<{ type: string; text: string }> }> { const { to, subject, body, htmlBody, cc, bcc } = args; const toLine = `To: ${to}`; const ccLine = cc ? `Cc: ${cc}` : ""; const bccLine = bcc ? `Bcc: ${bcc}` : ""; const subjectLine = `Subject: ${subject}`; const contentType = htmlBody ? "text/html" : "text/plain"; const emailBody = htmlBody || body; const rawMessage = [toLine, ccLine, bccLine, subjectLine, `Content-Type: ${contentType}; charset=utf-8`, "", emailBody] .filter((line) => line !== "") .join("\r\n"); const encodedMessage = Buffer.from(rawMessage).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); const response = await this.gmail!.users.messages.send({ userId: "me", requestBody: { raw: encodedMessage, }, }); return { content: [ { type: "text", text: JSON.stringify( { success: true, messageId: response.data.id, threadId: response.data.threadId, }, null, 2 ), }, ], }; } private async getLabels(): Promise<{ content: Array<{ type: string; text: string }> }> { const response = await this.gmail!.users.labels.list({ userId: "me", }); return { content: [ { type: "text", text: JSON.stringify(response.data.labels || [], null, 2), }, ], }; } async readResource(uri: string): Promise<{ contents: Array<{ uri: string; mimeType: string; text: string }> }> { if (uri === "gmail://inbox") { const emails = await this.listEmails({ maxResults: 20 }); return { contents: [ { uri, mimeType: "application/json", text: emails.content[0].text, }, ], }; } throw new Error(`Unknown Gmail resource: ${uri}`); } async cleanup(): Promise<void> { // Cleanup if needed } }

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/marouanemkm/gmail-mcp-server'

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