Skip to main content
Glama
server.py30.8 kB
"""MCP server implementation for Gmail functionality.""" from typing import Optional, List from mcp.server.fastmcp import FastMCP from .auth_manager import AuthManager from .gmail_client import GmailClient from .models import EmailRequest, DraftRequest, EmailResponse, DraftInfo, UserInfo from .resources.html_email_templates import HTML_EMAIL_TEMPLATES from .resources.email_signatures import EMAIL_SIGNATURES, get_signature_template from .resources.subject_line_guidelines import ( get_subject_line_suggestions, validate_subject_line, ) from .resources.email_security_guidelines import get_security_guidelines_by_category from .resources.email_etiquette import EMAIL_ETIQUETTE_GUIDELINES # Initialize MCP server mcp = FastMCP("Gmail MCP Server") # Global auth manager auth_manager = AuthManager() # Add prompts for enhanced email composition guidance @mcp.prompt() def professional_email_composer( email_type: str = "general", recipient_relationship: str = "professional", urgency: str = "normal", ) -> str: """Professional Email Composer - Guide users through structured email creation. Args: email_type: Type of email (general, request, announcement, follow_up, meeting, introduction) recipient_relationship: Relationship to recipient (professional, personal, unknown, executive) urgency: Urgency level (low, normal, high, urgent) """ tone_guidance = { "professional": "formal and respectful", "personal": "friendly but professional", "unknown": "polite and professional", "executive": "concise and direct", } structure_templates = { "general": """ **Professional Email Structure:** 1. **Subject Line**: Clear, specific, actionable - Example: "Request for Q4 Budget Review Meeting" 2. **Greeting**: - Formal: "Dear [Name]," or "Hello [Name]," - Professional: "Hi [Name]," 3. **Opening** (1-2 sentences): - State purpose immediately - Provide context if needed 4. **Body** (2-3 paragraphs max): - Main points with clear structure - Use bullet points for multiple items - Include specific details and deadlines 5. **Call to Action**: - Clearly state what you need - Include timeline/deadline 6. **Professional Closing**: - "Best regards," "Thank you," or "Sincerely," - Your full name and title """, "request": """ **Request Email Template:** 1. **Subject**: "Request: [Specific Action Needed]" 2. **Opening**: State your request clearly in first sentence 3. **Context**: Brief background (2-3 sentences max) 4. **Specific Ask**: - Exactly what you need - Why it's needed - When it's needed 5. **Make it Easy**: - Provide options if applicable - Offer to follow up - Thank them in advance """, "meeting": """ **Meeting Request Structure:** 1. **Subject**: "Meeting Request: [Topic] - [Proposed Date/Time]" 2. **Purpose**: Clear meeting objective 3. **Details**: - Duration: [X minutes/hours] - Attendees: [List key participants] - Location/Link: [In-person/virtual details] 4. **Agenda**: - Key discussion points - Desired outcomes 5. **Alternatives**: Offer 2-3 time options 6. **Preparation**: Any materials to review beforehand """, } urgency_notes = { "low": "Take time to craft thoughtfully", "normal": "Standard professional timeline", "high": "Mention priority in subject line", "urgent": "Call attention to urgency appropriately - avoid overuse", } tone = tone_guidance.get(recipient_relationship, "professional and respectful") template = structure_templates.get(email_type, structure_templates["general"]) urgency_note = urgency_notes.get(urgency, "Standard professional timeline") return f""" # Professional Email Composition Guide ## Email Type: {email_type.title()} ## Recipient Relationship: {recipient_relationship.title()} ## Urgency: {urgency.title()} **Recommended Tone**: {tone} **Timing Guidance**: {urgency_note} {template} ## Best Practices: - Keep emails under 150 words when possible - Use active voice - Proofread before sending - Include clear next steps - Consider recipient's time zone ## Security Reminders: - Use BCC for multiple external recipients - Double-check recipient addresses - Avoid sensitive information in subject lines """ @mcp.prompt() def follow_up_email_generator( original_context: str, time_since_last: str = "1 week", follow_up_type: str = "polite_reminder", ) -> str: """Generate appropriate follow-up emails based on context. Args: original_context: Brief description of original email/request time_since_last: How long since last communication (e.g., "3 days", "1 week", "2 weeks") follow_up_type: Type of follow-up (polite_reminder, status_check, escalation, thank_you) """ templates = { "polite_reminder": f""" # Polite Follow-Up Email **Context**: {original_context} **Time Elapsed**: {time_since_last} ## Template: **Subject**: "Following up: [Original Subject]" Hi [Name], I hope this email finds you well. I wanted to follow up on my [email/request] from {time_since_last} ago regarding {original_context}. I understand you have a busy schedule, and I don't want this to get lost in your inbox. When you have a moment, I'd appreciate any updates you might have. [Optional: Restate key ask or deadline if relevant] Thank you for your time and consideration. Best regards, [Your name] """, "status_check": f""" # Status Check Follow-Up **Context**: {original_context} ## Template: **Subject**: "Status Update: [Original Subject]" Hi [Name], I hope you're doing well. I'm following up on {original_context} to check on the current status. Could you please provide a brief update on: - Current progress - Any challenges or blockers - Revised timeline if needed I'm happy to discuss this further or provide any additional information that might be helpful. Thank you, [Your name] """, "escalation": f""" # Escalation Follow-Up (Use Carefully) **Context**: {original_context} **⚠️ Warning**: Only use after multiple polite attempts ## Template: **Subject**: "Urgent: Follow-up needed on [Original Subject]" Hi [Name], I've reached out a few times regarding {original_context} but haven't received a response. This matter requires attention because [brief explanation of impact/deadline]. I understand priorities shift, but I need to understand the status so I can plan accordingly. Could we schedule a brief call this week to discuss? I'm available [provide specific times] and am happy to work around your schedule. Thank you, [Your name] """, } return templates.get(follow_up_type, templates["polite_reminder"]) @mcp.prompt() def meeting_request_composer( meeting_purpose: str, duration_minutes: int = 30, attendee_count: int = 2, meeting_type: str = "discussion", ) -> str: """Compose professional meeting requests with all necessary details. Args: meeting_purpose: Main reason for the meeting duration_minutes: Expected duration in minutes (15, 30, 60, etc.) attendee_count: Number of expected attendees meeting_type: Type of meeting (discussion, presentation, decision, brainstorm, check_in) """ templates = { "discussion": f""" # Discussion Meeting Request **Purpose**: {meeting_purpose} **Duration**: {duration_minutes} minutes **Attendees**: {attendee_count} people ## Email Template: **Subject**: "Meeting Request: {meeting_purpose} - [Propose 2-3 specific time slots]" Hi [Name(s)], I'd like to schedule a {duration_minutes}-minute meeting to discuss {meeting_purpose}. **Proposed Times** (please let me know what works best): - Option 1: [Day, Date] at [Time] - Option 2: [Day, Date] at [Time] - Option 3: [Day, Date] at [Time] **Agenda**: - [Key discussion point 1] - [Key discussion point 2] - [Key discussion point 3] - Next steps and action items **Meeting Details**: - Location: [Conference room/Virtual link] - Duration: {duration_minutes} minutes - Attendees: [List names if multiple people] **Preparation**: - [Any materials to review beforehand] - [Questions to consider in advance] Please confirm which time works for you, or suggest alternatives if none of these fit your schedule. Thank you, [Your name] """, "decision": f""" # Decision-Making Meeting **Purpose**: {meeting_purpose} ## Template: **Subject**: "Decision Needed: {meeting_purpose} - Meeting Request" Hi [Name(s)], I'm requesting a {duration_minutes}-minute meeting to make a decision regarding {meeting_purpose}. **Background**: [2-3 sentence context] **Decision Required**: [Specific decision to be made] **Options to Consider**: 1. [Option 1 with brief pros/cons] 2. [Option 2 with brief pros/cons] 3. [Option 3 if applicable] **Information Needed**: - [Key data points or input needed] - [Stakeholder perspectives to consider] **Proposed Meeting Times**: [List 3 options] **Attendees**: [Decision makers and key stakeholders] Please come prepared with your thoughts on the options above. The goal is to reach a decision by the end of our meeting. Best regards, [Your name] """, } return templates.get(meeting_type, templates["discussion"]) @mcp.prompt() def draft_strategy_advisor( email_purpose: str, urgency: str = "normal", stakeholder_count: int = 1, complexity: str = "medium", ) -> str: """Advise whether to save as draft or send immediately based on email characteristics. Args: email_purpose: Main purpose of the email (request, announcement, sensitive_topic, routine_update) urgency: How urgent the email is (low, normal, high, urgent) stakeholder_count: Number of people affected by or involved in the email complexity: Complexity of the topic (simple, medium, complex) """ draft_scenarios = { "high_risk": { "triggers": [ "sensitive", "legal", "budget", "layoff", "termination", "complaint", ], "advice": "**RECOMMENDATION: Save as Draft**\n\nThis appears to be a high-risk communication. Consider:\n- Having a colleague review before sending\n- Legal/HR review if applicable\n- Sleeping on it overnight\n- Alternative communication methods (call/meeting)", }, "complex_multi_stakeholder": { "advice": "**RECOMMENDATION: Save as Draft**\n\nFor complex topics with multiple stakeholders:\n- Review for clarity and completeness\n- Consider if all stakeholders need same message\n- Check if anyone should be added/removed from recipients\n- Verify timing is appropriate for all recipients" }, "urgent_simple": { "advice": "**RECOMMENDATION: Send Immediately**\n\nFor urgent, straightforward communications:\n- Quick proofread for typos\n- Confirm recipients are correct\n- Send promptly to maintain urgency" }, "routine_update": { "advice": "**RECOMMENDATION: Send After Review**\n\nFor routine communications:\n- Quick review for completeness\n- Ensure subject line is clear\n- Check if timing is appropriate\n- Send when ready" }, } # Determine recommendation based on inputs if any( trigger in email_purpose.lower() for trigger in draft_scenarios["high_risk"]["triggers"] ): recommendation = draft_scenarios["high_risk"]["advice"] elif complexity == "complex" and stakeholder_count > 3: recommendation = draft_scenarios["complex_multi_stakeholder"]["advice"] elif urgency in ["high", "urgent"] and complexity == "simple": recommendation = draft_scenarios["urgent_simple"]["advice"] else: recommendation = draft_scenarios["routine_update"]["advice"] checklist = { "before_sending": [ "✅ Subject line clearly describes the content", "✅ Recipients are correct and complete", "✅ Tone is appropriate for all recipients", "✅ Call-to-action is clear (if needed)", "✅ Attachments are included (if referenced)", "✅ No sensitive information exposed inappropriately", "✅ Grammar and spelling checked", ], "draft_review": [ "⏱️ Wait at least 15 minutes before final review", "👁️ Read from recipient's perspective", "🎯 Verify the main message is clear", "📱 Consider how it looks on mobile", "🔄 Check if a simpler approach would work better", "👥 Consider if this should be a meeting/call instead", ], } timing_advice = { "urgent": "Send within 1 hour if truly urgent", "high": "Send within 4 hours, but review carefully first", "normal": "Send within 24 hours after appropriate review", "low": "Can wait for optimal timing (avoid late nights/weekends)", } return f""" # Draft Strategy Recommendation ## Email Analysis: - **Purpose**: {email_purpose} - **Urgency**: {urgency} - **Stakeholders**: {stakeholder_count} people - **Complexity**: {complexity} {recommendation} ## Pre-Send Checklist: {chr(10).join(checklist["before_sending"])} ## Draft Review Process: {chr(10).join(checklist["draft_review"])} ## Timing Guidance: **{urgency.title()} Priority**: {timing_advice.get(urgency, timing_advice["normal"])} ## Alternative Communication Methods to Consider: - **Phone call**: For urgent matters or sensitive topics - **Video meeting**: For complex discussions needing back-and-forth - **In-person**: For highly sensitive or relationship-critical conversations - **Instant message**: For quick questions or updates (if available) Remember: When in doubt, saving as draft and reviewing later is almost always the safer choice. """ @mcp.prompt() def email_review_checklist( email_type: str = "general", recipient_type: str = "internal", has_attachments: bool = False, ) -> str: """Provide comprehensive checklist for reviewing emails before sending. Args: email_type: Type of email (general, meeting_request, urgent, announcement, sensitive) recipient_type: Type of recipients (internal, external, mixed, executive) has_attachments: Whether email includes attachments """ base_checklist = [ "📝 **Content Review**:", " ✅ Subject line is clear and specific", " ✅ Main message is stated upfront", " ✅ All necessary information is included", " ✅ Call-to-action is clear (if needed)", " ✅ Tone is appropriate for recipients", "", "👥 **Recipients**:", " ✅ All necessary recipients included", " ✅ No unnecessary recipients", " ✅ Used BCC for external bulk recipients (if applicable)", " ✅ Double-checked email addresses", "", "🔤 **Language & Format**:", " ✅ Grammar and spelling checked", " ✅ Professional formatting (bullets, paragraphs)", " ✅ No ALL CAPS or excessive punctuation", " ✅ Appropriate greeting and closing", "", "🔒 **Security & Privacy**:", " ✅ No sensitive information in subject line", " ✅ Appropriate level of confidentiality", " ✅ No accidental reply-all risks", " ✅ Checked for auto-complete errors", ] # Add attachment-specific items if has_attachments: attachment_items = [ "", "📎 **Attachments**:", " ✅ All referenced attachments are attached", " ✅ Attachments are the correct versions", " ✅ File names are professional and clear", " ✅ File sizes are reasonable", " ✅ Attachments scanned for malware", " ✅ Sensitive files are password-protected (if needed)", ] base_checklist.extend(attachment_items) # Add type-specific items type_specific = { "meeting_request": [ "", "📅 **Meeting-Specific**:", " ✅ Date, time, and duration specified", " ✅ Time zone clarified (if needed)", " ✅ Location or meeting link included", " ✅ Agenda or purpose stated", " ✅ Preparation requirements mentioned", " ✅ Multiple time options provided (if requesting)", ], "urgent": [ "", "⚡ **Urgent Email**:", " ✅ Urgency is genuinely justified", " ✅ Deadline or time-sensitivity explained", " ✅ Consider if phone call would be better", " ✅ Marked urgent/priority appropriately", " ✅ Backup plan mentioned (if applicable)", ], "announcement": [ "", "📢 **Announcement**:", " ✅ Key information highlighted clearly", " ✅ Effective date/timeline specified", " ✅ Next steps or actions outlined", " ✅ Contact for questions identified", " ✅ Impact on recipients addressed", ], "sensitive": [ "", "⚠️ **Sensitive Content**:", " ✅ Recipients have legitimate need to know", " ✅ Confidentiality level appropriate", " ✅ Legal/compliance review completed (if needed)", " ✅ Consider if email is appropriate medium", " ✅ Potential misunderstandings addressed", ], } if email_type in type_specific: base_checklist.extend(type_specific[email_type]) # Add recipient-specific items recipient_specific = { "external": [ "", "🌐 **External Recipients**:", " ✅ Company branding/signature included", " ✅ Professional email address used", " ✅ Industry jargon minimized", " ✅ Legal disclaimers included (if required)", " ✅ Contact information provided", ], "executive": [ "", "👔 **Executive Recipients**:", " ✅ Message is concise and action-oriented", " ✅ Key points summarized upfront", " ✅ Supporting details in appendix/attachment", " ✅ Clear recommendations provided", " ✅ Next steps explicitly stated", ], "mixed": [ "", "👥 **Mixed Recipients**:", " ✅ Content appropriate for all recipient types", " ✅ External recipients' privacy protected", " ✅ Internal jargon explained or avoided", " ✅ Confidentiality level appropriate for least secure recipient", ], } if recipient_type in recipient_specific: base_checklist.extend(recipient_specific[recipient_type]) final_steps = [ "", "🚀 **Final Review**:", " ✅ Read email from recipient's perspective", " ✅ Checked email on mobile preview (if possible)", " ✅ Timing is appropriate for recipients", " ✅ Considered if response is actually needed", "", "💡 **Pro Tips**:", " • Wait 5-10 minutes after writing before final review", " • Read out loud to catch awkward phrasing", " • Ask: 'Would I want to receive this email?'", " • Consider the recipient's current workload/priorities", ] base_checklist.extend(final_steps) return f""" # Email Review Checklist **Email Type**: {email_type.replace("_", " ").title()} **Recipients**: {recipient_type.replace("_", " ").title()} **Has Attachments**: {"Yes" if has_attachments else "No"} {chr(10).join(base_checklist)} --- **Remember**: A few extra minutes of review can save hours of clarification emails and prevent misunderstandings! """ def get_authenticated_client() -> Optional[GmailClient]: """Get authenticated Gmail client for current user.""" credentials = auth_manager.get_credentials() if not credentials: return None return GmailClient(credentials) @mcp.tool() async def send_email( to: str, subject: str, body: str, cc: Optional[str] = None, bcc: Optional[str] = None, html_body: Optional[str] = None, ctx=None, ) -> EmailResponse: """Send an email via Gmail. Args: to: Recipient email address subject: Email subject line body: Email body (plain text) cc: CC recipients (optional) bcc: BCC recipients (optional) html_body: HTML version of email body (optional) """ client = get_authenticated_client() if not client: raise Exception( "No authenticated user. Please login first with: gmail-mcp --login" ) if ctx: await ctx.info(f"Sending email to {to}") try: # Validate email request email_req = EmailRequest( to=to, subject=subject, body=body, cc=cc, bcc=bcc, html_body=html_body ) result = client.send_email( to=email_req.to, subject=email_req.subject, body=email_req.body, cc=email_req.cc, bcc=email_req.bcc, html_body=email_req.html_body, ) if ctx: await ctx.info(f"Email sent successfully with ID: {result['id']}") return EmailResponse(**result) except Exception as e: if ctx: await ctx.error(f"Failed to send email: {str(e)}") raise Exception(f"Failed to send email: {str(e)}") @mcp.tool() async def create_draft( to: str, subject: str, body: str, cc: Optional[str] = None, bcc: Optional[str] = None, html_body: Optional[str] = None, ctx=None, ) -> EmailResponse: """Create an email draft. Args: to: Recipient email address subject: Email subject line body: Email body (plain text) cc: CC recipients (optional) bcc: BCC recipients (optional) html_body: HTML version of email body (optional) """ client = get_authenticated_client() if not client: raise Exception( "No authenticated user. Please login first with: gmail-mcp --login" ) if ctx: await ctx.info(f"Creating draft for {to}") try: draft_req = DraftRequest( to=to, subject=subject, body=body, cc=cc, bcc=bcc, html_body=html_body ) result = client.create_draft( to=draft_req.to, subject=draft_req.subject, body=draft_req.body, cc=draft_req.cc, bcc=draft_req.bcc, html_body=draft_req.html_body, ) if ctx: await ctx.info(f"Draft created with ID: {result['id']}") return EmailResponse(**result) except Exception as e: if ctx: await ctx.error(f"Failed to create draft: {str(e)}") raise Exception(f"Failed to create draft: {str(e)}") @mcp.tool() async def send_draft(draft_id: str, ctx=None) -> EmailResponse: """Send an existing email draft. Args: draft_id: ID of the draft to send """ client = get_authenticated_client() if not client: raise Exception( "No authenticated user. Please login first with: gmail-mcp --login" ) if ctx: await ctx.info(f"Sending draft {draft_id}") try: result = client.send_draft(draft_id) if ctx: await ctx.info(f"Draft sent with message ID: {result['id']}") return EmailResponse(**result) except Exception as e: if ctx: await ctx.error(f"Failed to send draft: {str(e)}") raise Exception(f"Failed to send draft: {str(e)}") @mcp.tool() async def list_drafts(max_results: int = 10, ctx=None) -> List[DraftInfo]: """List email drafts. Args: max_results: Maximum number of drafts to return (default: 10) """ client = get_authenticated_client() if not client: raise Exception( "No authenticated user. Please login first with: gmail-mcp --login" ) if ctx: await ctx.info(f"Listing up to {max_results} drafts") try: drafts = client.list_drafts(max_results) result = [DraftInfo(**draft) for draft in drafts] if ctx: await ctx.info(f"Found {len(result)} drafts") return result except Exception as e: if ctx: await ctx.error(f"Failed to list drafts: {str(e)}") raise Exception(f"Failed to list drafts: {str(e)}") @mcp.tool() async def get_user_info(ctx=None) -> UserInfo: """Get current authenticated user information.""" client = get_authenticated_client() if not client: raise Exception( "No authenticated user. Please login first with: gmail-mcp --login" ) try: user_info = client.get_user_info() result = UserInfo(**user_info) if ctx: await ctx.info(f"Current user: {result.email}") return result except Exception as e: if ctx: await ctx.error(f"Failed to get user info: {str(e)}") raise Exception(f"Failed to get user info: {str(e)}") # Resource access tools @mcp.resource("template://html_email/{template_name}") def get_html_template(template_name: str) -> str: """Get HTML email template by name. Available templates: professional_announcement, meeting_invitation, project_update, newsletter """ template = HTML_EMAIL_TEMPLATES.get(template_name) if not template: available = list(HTML_EMAIL_TEMPLATES.keys()) raise ValueError( f"Template '{template_name}' not found. Available: {', '.join(available)}" ) return template @mcp.resource("template://signature/{signature_type}") def get_signature_resource(signature_type: str) -> str: """Get email signature template by type. Available types: standard_professional, detailed_professional, consultant_freelancer, sales_business_development, executive_minimal, startup_founder, academic_researcher, creative_professional, nonprofit_social_impact, tech_developer, legal_professional, healthcare_medical, holiday_seasonal """ return get_signature_template(signature_type) @mcp.resource("guidelines://security/{category}") def get_security_resource(category: str) -> dict: """Get email security guidelines by category. Available categories: recipient_management, sensitive_information, phishing_prevention, attachment_security, compliance_legal, access_control, external_communication """ guidelines = get_security_guidelines_by_category(category) if not guidelines: available = [ "recipient_management", "sensitive_information", "phishing_prevention", "attachment_security", "compliance_legal", "access_control", "external_communication", ] raise ValueError( f"Category '{category}' not found. Available: {', '.join(available)}" ) return guidelines @mcp.resource("guidelines://etiquette/{category}") def get_etiquette_resource(category: str) -> dict: """Get email etiquette guidelines by category. Available categories: tone_and_language, timing_and_response, structure_and_formatting, professional_courtesy, cultural_considerations, meeting_email_etiquette """ guidelines = EMAIL_ETIQUETTE_GUIDELINES.get(category) if not guidelines: available = list(EMAIL_ETIQUETTE_GUIDELINES.keys()) raise ValueError( f"Category '{category}' not found. Available: {', '.join(available)}" ) return guidelines @mcp.tool() async def get_subject_line_help( email_type: str = "general", industry: str = None, ctx=None ) -> dict: """Get subject line suggestions and best practices. Args: email_type: Type of email (action_required, meeting_requests, status_updates, etc.) industry: Optional industry for specialized templates (sales, marketing, hr, etc.) """ try: suggestions = get_subject_line_suggestions(email_type, industry) if ctx: await ctx.info( f"Generated subject line suggestions for {email_type} emails" ) return suggestions except Exception as e: if ctx: await ctx.error(f"Failed to get subject line help: {str(e)}") raise Exception(f"Failed to get subject line help: {str(e)}") @mcp.tool() async def validate_subject_line_tool(subject: str, ctx=None) -> dict: """Validate a subject line against best practices. Args: subject: Subject line to validate """ try: validation = validate_subject_line(subject) if ctx: await ctx.info(f"Validated subject line: '{subject}'") return validation except Exception as e: if ctx: await ctx.error(f"Failed to validate subject line: {str(e)}") raise Exception(f"Failed to validate subject line: {str(e)}") @mcp.tool() async def get_email_templates(template_type: str = "html", ctx=None) -> dict: """Get available email templates. Args: template_type: Type of templates to retrieve (html, signature) """ try: if template_type == "html": templates = { name: "HTML email template" for name in HTML_EMAIL_TEMPLATES.keys() } elif template_type == "signature": templates = { name: "Email signature template" for name in EMAIL_SIGNATURES.keys() } else: raise ValueError( f"Unknown template type: {template_type}. Use 'html' or 'signature'" ) if ctx: await ctx.info(f"Retrieved {len(templates)} {template_type} templates") return { "template_type": template_type, "available_templates": templates, "usage": "Use get_html_template() or get_signature_template() to retrieve specific templates", } except Exception as e: if ctx: await ctx.error(f"Failed to get templates: {str(e)}") raise Exception(f"Failed to get templates: {str(e)}")

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

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