UNPKG

uhbarp-gmail-mcp-server

Version:

Gmail MCP Server for managing Gmail through natural language interactions with full OAuth2 authentication support

1,201 lines (1,197 loc) • 65 kB
/** * Gmail MCP Server * A comprehensive server implementation using Model Context Protocol for Gmail API */ import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js"; import { gmailAuth } from "./utils/gmail-auth.js"; import { gmailOperations } from "./utils/gmail-operations.js"; import { multiUserAuth } from "./utils/multi-user-auth.js"; import { logger } from './utils/api.js'; let gmailConfig; function parseArgs() { const args = process.argv.slice(2); const config = { setupAuth: false, resetAuth: false, debug: false, nonInteractive: false, multiUser: false }; for (let i = 0; i < args.length; i++) { const arg = args[i]; if (arg === '--setup-auth') { config.setupAuth = true; } else if (arg === '--reset-auth') { config.resetAuth = true; } else if (arg === '--debug' || arg === '-d') { config.debug = true; } else if (arg === '--non-interactive' || arg === '-n') { config.nonInteractive = true; } else if (arg === '--multi-user') { config.multiUser = true; } } return config; } const server = new Server({ name: "gmail-mcp-server", version: "1.0.0" }, { capabilities: { resources: { read: true, list: true }, tools: { list: true, call: true } } }); // Set up the resource listing request handler server.setRequestHandler(ListResourcesRequestSchema, async () => { logger.log('Received list resources request'); return { resources: [] }; }); /** * Handler for reading resource information. */ server.setRequestHandler(ReadResourceRequestSchema, async (request) => { logger.log('Received read resource request: ' + JSON.stringify(request)); throw new Error("Resource reading not implemented"); }); /** * List available tools for interacting with Gmail. */ server.setRequestHandler(ListToolsRequestSchema, async () => { const baseTools = [ { name: "send_email", description: "Send an email message with support for HTML content, attachments, and international characters. Supports both plain text and HTML emails with proper multipart structure.", inputSchema: { type: "object", properties: { userId: { type: "string", description: "User ID for multi-user authentication (required if using multi-user mode)" }, to: { type: "array", items: { type: "string" }, description: "List of recipient email addresses" }, cc: { type: "array", items: { type: "string" }, description: "List of CC recipient email addresses (optional)" }, bcc: { type: "array", items: { type: "string" }, description: "List of BCC recipient email addresses (optional)" }, subject: { type: "string", description: "Email subject line with full support for international characters" }, text: { type: "string", description: "Plain text content of the email (optional if html is provided)" }, html: { type: "string", description: "HTML content of the email (optional if text is provided)" }, replyTo: { type: "string", description: "Reply-to email address (optional)" }, attachments: { type: "array", items: { type: "object", properties: { filename: { type: "string", description: "Name of the attachment file" }, content: { type: "string", description: "Base64 encoded content of the attachment or file path" }, contentType: { type: "string", description: "MIME type of the attachment (optional, auto-detected if not provided)" } }, required: ["filename", "content"] }, description: "List of email attachments (optional)" } }, required: ["to", "subject"], additionalProperties: false } }, { name: "read_email", description: "Read email message by ID with advanced MIME structure handling. Returns full email content including headers, body, and attachment information.", inputSchema: { type: "object", properties: { userId: { type: "string", description: "User ID for multi-user authentication (required if using multi-user mode)" }, messageId: { type: "string", description: "Gmail message ID to retrieve" }, format: { type: "string", enum: ["minimal", "full", "raw", "metadata"], description: "Level of detail to retrieve (default: full)", default: "full" }, includeContent: { type: "boolean", description: "Whether to extract and include email content (text/html)", default: true } }, required: ["messageId"], additionalProperties: false } }, { name: "search_emails", description: "Search emails with various criteria including subject, sender, date range, and advanced Gmail search syntax. Supports complex queries and filtering.", inputSchema: { type: "object", properties: { userId: { type: "string", description: "User ID for multi-user authentication (required if using multi-user mode)" }, query: { type: "string", description: "General search query using Gmail search syntax (e.g., 'is:unread has:attachment')" }, from: { type: "string", description: "Search for emails from specific sender" }, to: { type: "string", description: "Search for emails to specific recipient" }, subject: { type: "string", description: "Search for emails with specific subject" }, after: { type: "string", description: "Search for emails after date (format: YYYY/MM/DD)" }, before: { type: "string", description: "Search for emails before date (format: YYYY/MM/DD)" }, hasAttachment: { type: "boolean", description: "Filter emails that have attachments" }, label: { type: "string", description: "Search within specific label/folder" }, isUnread: { type: "boolean", description: "Filter for unread emails only" }, maxResults: { type: "number", description: "Maximum number of results to return (default: 100, max: 500)", minimum: 1, maximum: 500, default: 100 } }, additionalProperties: false } }, { name: "list_emails", description: "List emails in inbox, sent, or custom label/folder. Returns basic email information including subjects, senders, and snippets.", inputSchema: { type: "object", properties: { userId: { type: "string", description: "User ID for multi-user authentication (required if using multi-user mode)" }, labelId: { type: "string", description: "Gmail label ID or name (default: INBOX). Common values: INBOX, SENT, DRAFT, TRASH, SPAM", default: "INBOX" }, maxResults: { type: "number", description: "Maximum number of emails to retrieve (default: 50, max: 500)", minimum: 1, maximum: 500, default: 50 } }, additionalProperties: false } }, { name: "mark_email", description: "Mark email as read or unread. Useful for managing email status and organizing your inbox.", inputSchema: { type: "object", properties: { userId: { type: "string", description: "User ID for multi-user authentication (required if using multi-user mode)" }, messageId: { type: "string", description: "Gmail message ID to mark" }, read: { type: "boolean", description: "True to mark as read, false to mark as unread" } }, required: ["messageId", "read"], additionalProperties: false } }, { name: "move_email", description: "Move email to different label/folder. Can add labels and remove existing ones in a single operation.", inputSchema: { type: "object", properties: { userId: { type: "string", description: "User ID for multi-user authentication (required if using multi-user mode)" }, messageId: { type: "string", description: "Gmail message ID to move" }, labelId: { type: "string", description: "Label ID to add to the email" }, removeLabelIds: { type: "array", items: { type: "string" }, description: "List of label IDs to remove from the email (optional)" } }, required: ["messageId", "labelId"], additionalProperties: false } }, { name: "delete_email", description: "Permanently delete an email message. Use with caution as this action cannot be undone.", inputSchema: { type: "object", properties: { userId: { type: "string", description: "User ID for multi-user authentication (required if using multi-user mode)" }, messageId: { type: "string", description: "Gmail message ID to delete" } }, required: ["messageId"], additionalProperties: false } }, { name: "get_labels", description: "List all available Gmail labels including system labels (Inbox, Sent, etc.) and user-defined labels.", inputSchema: { type: "object", properties: { userId: { type: "string", description: "User ID for multi-user authentication (required if using multi-user mode)" } }, additionalProperties: false } }, { name: "create_label", description: "Create a new Gmail label/folder for organizing emails.", inputSchema: { type: "object", properties: { userId: { type: "string", description: "User ID for multi-user authentication (required if using multi-user mode)" }, name: { type: "string", description: "Name of the new label" }, visible: { type: "boolean", description: "Whether the label should be visible in the label list (default: true)", default: true } }, required: ["name"], additionalProperties: false } }, { name: "update_label", description: "Update an existing Gmail label's name or visibility settings.", inputSchema: { type: "object", properties: { userId: { type: "string", description: "User ID for multi-user authentication (required if using multi-user mode)" }, labelId: { type: "string", description: "ID of the label to update" }, name: { type: "string", description: "New name for the label (optional)" }, visible: { type: "boolean", description: "Whether the label should be visible (optional)" } }, required: ["labelId"], additionalProperties: false } }, { name: "delete_label", description: "Delete a Gmail label. Note: This will remove the label from all emails but won't delete the emails themselves.", inputSchema: { type: "object", properties: { userId: { type: "string", description: "User ID for multi-user authentication (required if using multi-user mode)" }, labelId: { type: "string", description: "ID of the label to delete" } }, required: ["labelId"], additionalProperties: false } }, { name: "get_attachment", description: "Download and retrieve attachment information from an email. Returns attachment data, filename, and metadata.", inputSchema: { type: "object", properties: { userId: { type: "string", description: "User ID for multi-user authentication (required if using multi-user mode)" }, messageId: { type: "string", description: "Gmail message ID containing the attachment" }, attachmentId: { type: "string", description: "Attachment ID to download" } }, required: ["messageId", "attachmentId"], additionalProperties: false } }, { name: "batch_mark_read", description: "Mark multiple emails as read in a single batch operation for efficiency.", inputSchema: { type: "object", properties: { userId: { type: "string", description: "User ID for multi-user authentication (required if using multi-user mode)" }, messageIds: { type: "array", items: { type: "string" }, description: "List of Gmail message IDs to mark as read" } }, required: ["messageIds"], additionalProperties: false } }, { name: "batch_move_emails", description: "Move multiple emails to a label/folder in a single batch operation.", inputSchema: { type: "object", properties: { userId: { type: "string", description: "User ID for multi-user authentication (required if using multi-user mode)" }, messageIds: { type: "array", items: { type: "string" }, description: "List of Gmail message IDs to move" }, labelId: { type: "string", description: "Label ID to add to all emails" }, removeLabelIds: { type: "array", items: { type: "string" }, description: "List of label IDs to remove from all emails (optional)" } }, required: ["messageIds", "labelId"], additionalProperties: false } }, { name: "batch_delete_emails", description: "Delete multiple emails in a single batch operation. Use with caution as this action cannot be undone.", inputSchema: { type: "object", properties: { userId: { type: "string", description: "User ID for multi-user authentication (required if using multi-user mode)" }, messageIds: { type: "array", items: { type: "string" }, description: "List of Gmail message IDs to delete" } }, required: ["messageIds"], additionalProperties: false } } ]; // Add multi-user specific tools const multiUserTools = [ { name: "authenticate_user", description: "Start authentication flow for a new user. Returns authentication URL that user needs to visit.", inputSchema: { type: "object", properties: { userEmail: { type: "string", description: "User's email address (optional, for identification)" } }, additionalProperties: false } }, { name: "get_my_session_info", description: "Get information about your own authentication session (user-specific, secure).", inputSchema: { type: "object", properties: { userId: { type: "string", description: "Your User ID to get information for" } }, required: ["userId"], additionalProperties: false } }, { name: "remove_my_session", description: "Remove your own authentication session (user-specific, secure).", inputSchema: { type: "object", properties: { userId: { type: "string", description: "Your User ID to remove" } }, required: ["userId"], additionalProperties: false } } ]; // Add one-time authentication tool for single-user mode const oneTimeAuthTools = [ { name: "quick_authenticate", description: "One-time Gmail authentication for immediate access. Opens browser automatically and completes authentication flow.", inputSchema: { type: "object", properties: { force: { type: "boolean", description: "Force re-authentication even if already authenticated (default: false)", default: false } }, additionalProperties: false } }, { name: "get_auth_link", description: "Get a clickable Gmail authentication link without auto-opening browser. Perfect for manual authentication control.", inputSchema: { type: "object", properties: { force: { type: "boolean", description: "Force new authentication link even if already authenticated (default: false)", default: false } }, additionalProperties: false } } ]; if (gmailConfig.multiUser) { return { tools: [...baseTools, ...multiUserTools] }; } // For single-user mode, add quick authentication if not already authenticated if (!await gmailAuth.isAuthenticated()) { return { tools: [...baseTools, ...oneTimeAuthTools] }; } return { tools: baseTools }; }); /** * Handle tool calls to the Gmail API. */ server.setRequestHandler(CallToolRequestSchema, async (request) => { logger.log('Received call tool request: ' + JSON.stringify(request)); switch (request.params.name) { case "quick_authenticate": try { const args = request.params.arguments || {}; const { force = false } = args; // Check if already authenticated and not forcing re-auth if (!force && await gmailAuth.isAuthenticated()) { return { content: [ { type: "text", text: "āœ… You're already authenticated! Gmail access is ready.\n\nYou can now use all Gmail tools. If you want to re-authenticate, use the 'force' option." } ] }; } // Check if credentials are configured if (!await gmailAuth.isConfigured()) { return { content: [ { type: "text", text: "āŒ Gmail credentials not configured.\n\nPlease set up your OAuth2 credentials first:\n1. Set environment variables GMAIL_CLIENT_ID and GMAIL_CLIENT_SECRET\n2. Or run: gmail-mcp-server --setup-auth" } ], isError: true }; } try { // Perform authentication await gmailAuth.authenticate(); return { content: [ { type: "text", text: "šŸŽ‰ Gmail authentication successful!\n\nāœ… You can now use all Gmail tools:\n- send_email\n- read_email\n- search_emails\n- list_emails\n- get_labels\n- And more!\n\nAuthentication complete and ready for use." } ] }; } catch (authError) { const errorMessage = authError instanceof Error ? authError.message : 'Unknown error'; return { content: [ { type: "text", text: `āŒ Authentication failed: ${errorMessage}\n\nPlease try again or check your OAuth2 credentials.` } ], isError: true }; } } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; return { content: [ { type: "text", text: `Error during authentication: ${errorMessage}` } ], isError: true }; } case "authenticate_user": try { if (!gmailConfig.multiUser) { throw new Error("Multi-user mode not enabled. Restart server with --multi-user flag."); } const args = request.params.arguments || {}; const { userEmail } = args; const authResult = await multiUserAuth.authenticateNewUser(userEmail); return { content: [ { type: "text", text: `šŸ” Authentication started for user! User ID: ${authResult.userId} Authentication URL: ${authResult.authUrl} Server Port: ${authResult.port} šŸš€ Please visit the authentication URL to complete the process. The authentication will timeout in 10 minutes. After successful authentication, use this User ID for all Gmail operations: ${authResult.userId}` } ] }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; return { content: [ { type: "text", text: `Error starting authentication: ${errorMessage}` } ], isError: true }; } case "get_my_session_info": try { if (!gmailConfig.multiUser) { throw new Error("Multi-user mode not enabled. Restart server with --multi-user flag."); } const args = request.params.arguments || {}; const { userId } = args; if (!userId) { throw new Error("'userId' is required"); } const session = multiUserAuth.getUserSession(userId); if (!session) { return { content: [ { type: "text", text: `āŒ User session ${userId} not found.` } ] }; } const responseText = `šŸ“‹ User Session Info: User ID: ${session.userId} Email: ${session.userEmail || 'Unknown'} Authenticated: ${session.authenticated ? 'āœ…' : 'āŒ'} Token Expires: ${new Date(session.expiryDate).toLocaleString()} Status: ${session.authenticated ? 'Active' : 'Inactive'}`; return { content: [ { type: "text", text: responseText } ] }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; return { content: [ { type: "text", text: `Error getting user info: ${errorMessage}` } ], isError: true }; } case "remove_my_session": try { if (!gmailConfig.multiUser) { throw new Error("Multi-user mode not enabled. Restart server with --multi-user flag."); } const args = request.params.arguments || {}; const { userId } = args; if (!userId) { throw new Error("'userId' is required"); } const removed = multiUserAuth.removeUser(userId); return { content: [ { type: "text", text: removed ? `āœ… User session ${userId} removed successfully.` : `āŒ User session ${userId} not found.` } ] }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; return { content: [ { type: "text", text: `Error removing user session: ${errorMessage}` } ], isError: true }; } case "send_email": try { const args = request.params.arguments || {}; const { userId, to, cc, bcc, subject, text, html, replyTo, attachments } = args; if (!to || !Array.isArray(to) || to.length === 0) { throw new Error("'to' field is required and must be a non-empty array"); } if (!subject) { throw new Error("'subject' field is required"); } if (!text && !html) { throw new Error("Either 'text' or 'html' content is required"); } const emailMessage = { to, cc, bcc, subject, text, html, replyTo, attachments: attachments?.map((att) => ({ filename: att.filename, content: att.content, contentType: att.contentType, encoding: 'base64' })) }; let result; if (gmailConfig.multiUser) { if (!userId) { throw new Error("'userId' is required in multi-user mode"); } // Get user-specific Gmail client const gmail = await multiUserAuth.getGmailClientForUser(userId); // Use the multi-user Gmail operations (would need to be created) result = await gmailOperations.sendEmail(emailMessage); } else { result = await gmailOperations.sendEmail(emailMessage); } return { content: [ { type: "text", text: `Email sent successfully!\nMessage ID: ${result.id}\nThread ID: ${result.threadId}\nRecipients: ${to.join(', ')}\nSubject: ${subject}` } ] }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; return { content: [ { type: "text", text: `Error sending email: ${errorMessage}` } ], isError: true }; } case "read_email": try { const args = request.params.arguments || {}; const { userId, messageId, format = 'full', includeContent = true } = args; if (!messageId) { throw new Error("'messageId' is required"); } if (gmailConfig.multiUser && !userId) { throw new Error("'userId' is required in multi-user mode"); } const email = await gmailOperations.getEmail(messageId, format); let responseText = `Email ID: ${email.id}\nThread ID: ${email.threadId}\nSnippet: ${email.snippet}\nLabels: ${email.labelIds?.join(', ') || 'None'}\n`; if (email.payload && email.payload.headers) { const headers = email.payload.headers; const fromHeader = headers.find((h) => h.name === 'From'); const toHeader = headers.find((h) => h.name === 'To'); const subjectHeader = headers.find((h) => h.name === 'Subject'); const dateHeader = headers.find((h) => h.name === 'Date'); responseText += `From: ${fromHeader?.value || 'Unknown'}\n`; responseText += `To: ${toHeader?.value || 'Unknown'}\n`; responseText += `Subject: ${subjectHeader?.value || 'No Subject'}\n`; responseText += `Date: ${dateHeader?.value || 'Unknown'}\n`; } if (includeContent && email.payload) { const content = gmailOperations.extractEmailContent(email.payload); if (content.text) { responseText += `\n--- Text Content ---\n${content.text}\n`; } if (content.html) { responseText += `\n--- HTML Content ---\n${content.html}\n`; } if (content.attachments.length > 0) { responseText += `\n--- Attachments ---\n`; content.attachments.forEach((att, index) => { responseText += `${index + 1}. ${att.filename} (${att.mimeType}, ${att.size} bytes)\n`; }); } } return { content: [ { type: "text", text: responseText } ] }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; return { content: [ { type: "text", text: `Error reading email: ${errorMessage}` } ], isError: true }; } case "search_emails": try { const args = request.params.arguments || {}; const { userId, ...searchArgs } = args; if (gmailConfig.multiUser && !userId) { throw new Error("'userId' is required in multi-user mode"); } const searchCriteria = { query: searchArgs.query, from: searchArgs.from, to: searchArgs.to, subject: searchArgs.subject, after: searchArgs.after, before: searchArgs.before, hasAttachment: searchArgs.hasAttachment, label: searchArgs.label, isUnread: searchArgs.isUnread, maxResults: searchArgs.maxResults || 100 }; const result = await gmailOperations.searchEmails(searchCriteria); if (result.messages.length === 0) { return { content: [ { type: "text", text: "No emails found matching the search criteria." } ] }; } let responseText = `Found ${result.messages.length} emails:\n\n`; result.messages.forEach((email, index) => { const fromHeader = email.payload?.headers?.find((h) => h.name === 'From'); const subjectHeader = email.payload?.headers?.find((h) => h.name === 'Subject'); const dateHeader = email.payload?.headers?.find((h) => h.name === 'Date'); responseText += `${index + 1}. ID: ${email.id}\n`; responseText += ` From: ${fromHeader?.value || 'Unknown'}\n`; responseText += ` Subject: ${subjectHeader?.value || 'No Subject'}\n`; responseText += ` Date: ${dateHeader?.value || 'Unknown'}\n`; responseText += ` Snippet: ${email.snippet}\n`; responseText += ` Labels: ${email.labelIds?.join(', ') || 'None'}\n\n`; }); if (result.nextPageToken) { responseText += `Note: More results available. Use pagination to retrieve additional emails.`; } return { content: [ { type: "text", text: responseText } ] }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; return { content: [ { type: "text", text: `Error searching emails: ${errorMessage}` } ], isError: true }; } case "list_emails": try { const args = request.params.arguments || {}; const { userId, labelId = 'INBOX', maxResults = 50 } = args; if (gmailConfig.multiUser && !userId) { throw new Error("'userId' is required in multi-user mode"); } const result = await gmailOperations.listEmails(labelId, maxResults); if (result.messages.length === 0) { return { content: [ { type: "text", text: `No emails found in label: ${labelId}` } ] }; } let responseText = `Found ${result.messages.length} emails in ${labelId}:\n\n`; result.messages.forEach((email, index) => { const fromHeader = email.payload?.headers?.find((h) => h.name === 'From'); const subjectHeader = email.payload?.headers?.find((h) => h.name === 'Subject'); const dateHeader = email.payload?.headers?.find((h) => h.name === 'Date'); responseText += `${index + 1}. ID: ${email.id}\n`; responseText += ` From: ${fromHeader?.value || 'Unknown'}\n`; responseText += ` Subject: ${subjectHeader?.value || 'No Subject'}\n`; responseText += ` Date: ${dateHeader?.value || 'Unknown'}\n`; responseText += ` Snippet: ${email.snippet}\n\n`; }); return { content: [ { type: "text", text: responseText } ] }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; return { content: [ { type: "text", text: `Error listing emails: ${errorMessage}` } ], isError: true }; } case "mark_email": try { const args = request.params.arguments || {}; const { userId, messageId, read } = args; if (!messageId) { throw new Error("'messageId' is required"); } if (typeof read !== 'boolean') { throw new Error("'read' must be a boolean value"); } if (gmailConfig.multiUser && !userId) { throw new Error("'userId' is required in multi-user mode"); } await gmailOperations.markEmail(messageId, read); return { content: [ { type: "text", text: `Email ${messageId} marked as ${read ? 'read' : 'unread'} successfully.` } ] }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; return { content: [ { type: "text", text: `Error marking email: ${errorMessage}` } ], isError: true }; } case "move_email": try { const args = request.params.arguments || {}; const { userId, messageId, labelId, removeLabelIds } = args; if (!messageId) { throw new Error("'messageId' is required"); } if (!labelId) { throw new Error("'labelId' is required"); } if (gmailConfig.multiUser && !userId) { throw new Error("'userId' is required in multi-user mode"); } await gmailOperations.moveToLabel(messageId, labelId, removeLabelIds); return { content: [ { type: "text", text: `Email ${messageId} moved to label ${labelId} successfully.` } ] }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; return { content: [ { type: "text", text: `Error moving email: ${errorMessage}` } ], isError: true }; } case "delete_email": try { const args = request.params.arguments || {}; const { userId, messageId } = args; if (!messageId) { throw new Error("'messageId' is required"); } if (gmailConfig.multiUser && !userId) { throw new Error("'userId' is required in multi-user mode"); } await gmailOperations.deleteEmail(messageId); return { content: [ { type: "text", text: `Email ${messageId} deleted successfully.` } ] }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; return { content: [ { type: "text", text: `Error deleting email: ${errorMessage}` } ], isError: true }; } case "get_labels": try { const args = request.params.arguments || {}; const { userId } = args; if (gmailConfig.multiUser && !userId) { throw new Error("'userId' is required in multi-user mode"); } const labels = await gmailOperations.getLabels(); let responseText = `Gmail Labels (${labels.length} total):\n\n`; // Separate system and user labels const systemLabels = labels.filter(label => label.type === 'system'); const userLabels = labels.filter(label => label.type === 'user'); if (systemLabels.length > 0) { responseText += "System Labels:\n"; systemLabels.forEach(label => { responseText += `- ${label.name} (ID: ${label.id})\n`; }); responseText += "\n"; } if (userLabels.length > 0) { responseText += "User Labels:\n"; userLabels.forEach(label => { responseText += `- ${label.name} (ID: ${label.id})\n`; }); } return { content: [ { type: "text", text: responseText } ] }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; return { content: [ { type: "text", text: `Error retrieving labels: ${errorMessage}` } ], isError: true }; } case "create_label": try { const args = request.params.arguments || {}; const { userId, name, visible = true } = args; if (!name) { throw new Error("'name' is required"); } if (gmailConfig.multiUser && !userId) { throw new Error("'userId' is required in multi-user mode"); } const label = await gmailOperations.createLabel(name, visible); return { content: [ { type: "text", text: `Label "${name}" created successfully!\nLabel ID: ${label.id}\nVisible: ${visible}` } ] }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; return { content: [ { type: "text", text: `Error creating label: ${errorMessage}` } ], isError: true }; } case "update_label": try { c