gmail-mcp-server
Version:
Gmail MCP Server with on-demand authentication for SIYA/Claude Desktop. Complete Gmail integration with multi-user support and OAuth2 security.
203 lines (202 loc) • 8.11 kB
JavaScript
/**
* Threading & Intelligence Utilities for Gmail MCP
* - Thread reconstruction (including forwards/replies)
* - Conversation context extraction
* - Email classification & urgency detection
* - Proactive alert/reminder suggestion
*/
export class ThreadReconstructor {
/**
* Reconstructs a conversation thread given an email and all emails
*/
reconstructConversation(emailId, allEmails) {
// Find the target email
const target = allEmails.find(e => e.id === emailId);
if (!target)
return [];
// Strategy 1: Use threadId
const threadEmails = allEmails.filter(e => e.threadId === target.threadId);
// Strategy 2: Subject line analysis (Re:, Fwd:)
const baseSubject = this.normalizeSubject(target.subject);
const subjectRelated = allEmails.filter(e => this.normalizeSubject(e.subject) === baseSubject);
// Strategy 3: Content analysis for forwarded emails
const forwarded = allEmails.filter(e => this.containsForwardedContent(e.snippet || ''));
// Merge and deduplicate
const allRelated = [...threadEmails, ...subjectRelated, ...forwarded];
const unique = Object.values(allRelated.reduce((acc, e) => { acc[e.id] = e; return acc; }, {}));
// Sort chronologically
return unique.sort((a, b) => (new Date(a.internalDate).getTime() - new Date(b.internalDate).getTime()));
}
normalizeSubject(subject) {
return subject.replace(/^(re:|fwd:|fw:)/i, '').trim().toLowerCase();
}
containsForwardedContent(content) {
const patterns = [
/Begin forwarded message:/i,
/---------- Forwarded message ---------/i,
/From:.*?Date:.*?Subject:/is
];
return patterns.some(p => p.test(content));
}
}
export class ConversationManager {
/**
* Extracts context for a conversation thread
*/
async preserveContext(emails) {
const sorted = emails.sort((a, b) => (new Date(a.internalDate).getTime() - new Date(b.internalDate).getTime()));
const participants = Array.from(new Set(sorted.flatMap(e => this.extractParticipants(e))));
const timeline = sorted.map(e => ({ id: e.id, date: new Date(e.internalDate), subject: e.subject }));
const topics = this.extractTopics(sorted);
const references = this.extractReferences(sorted);
const metadata = {
totalMessages: sorted.length,
duration: this.calculateDuration(sorted),
lastActivity: new Date(sorted[sorted.length - 1].internalDate)
};
return {
threadId: sorted[0].threadId,
participants,
timeline,
topics,
references,
metadata
};
}
extractParticipants(email) {
const headers = email.payload?.headers || [];
const from = headers.find((h) => h.name === 'From')?.value;
const to = headers.find((h) => h.name === 'To')?.value;
const cc = headers.find((h) => h.name === 'Cc')?.value;
return [from, ...(to ? to.split(',') : []), ...(cc ? cc.split(',') : [])].filter(Boolean).map(s => s.trim());
}
extractTopics(emails) {
// Simple keyword extraction from subjects
const allSubjects = emails.map(e => e.subject).join(' ');
const keywords = allSubjects.match(/\b\w{5,}\b/g) || [];
return Array.from(new Set(keywords.map(w => w.toLowerCase()))).slice(0, 10);
}
extractReferences(emails) {
// Find URLs, document names, and attachment names
const urls = [];
const docs = [];
const attachments = [];
for (const e of emails) {
const content = e.snippet || '';
const urlMatches = content.match(/https?:\/\/\S+/g) || [];
urls.push(...urlMatches);
// Simulate document/attachment extraction
if (content.match(/pdf|docx|xls|statement|notice|assessment/i)) {
docs.push(e.subject);
}
if (e.payload?.parts) {
for (const part of e.payload.parts) {
if (part.filename)
attachments.push(part.filename);
}
}
}
return {
documents: Array.from(new Set(docs)),
urls: Array.from(new Set(urls)),
attachments: Array.from(new Set(attachments))
};
}
calculateDuration(emails) {
if (emails.length < 2)
return '0 days';
const start = new Date(emails[0].internalDate).getTime();
const end = new Date(emails[emails.length - 1].internalDate).getTime();
const days = Math.round((end - start) / (1000 * 60 * 60 * 24));
return `${days} days`;
}
}
export class EmailIntelligence {
constructor() {
this.governmentDomains = [
'insight.gov.in', 'incometax.gov.in', 'cbdt.gov.in',
'gst.gov.in', 'rbi.org.in'
];
this.urgencyKeywords = [
'final opportunity', 'last chance', 'immediate action',
'deadline', 'penalty', 'legal action', 'compliance'
];
this.legalKeywords = [
'notice', 'assessment', 'compliance', 'section', 'act'
];
}
classifyEmail(email) {
let score = 0;
let category = 'GENERAL';
let reason = '';
const from = email.payload?.headers?.find((h) => h.name === 'From')?.value || '';
const content = `${email.subject} ${email.snippet}`.toLowerCase();
// Government sender
if (this.governmentDomains.some(domain => from.includes(domain))) {
score += 50;
category = 'GOVERNMENT';
reason += 'Government sender. ';
}
// Urgency
const urgencyMatches = this.urgencyKeywords.filter(kw => content.includes(kw));
score += urgencyMatches.length * 20;
if (urgencyMatches.length)
reason += `Urgency: ${urgencyMatches.join(', ')}. `;
// Legal/tax
const legalMatches = this.legalKeywords.filter(kw => content.includes(kw));
score += legalMatches.length * 10;
if (legalMatches.length)
reason += `Legal: ${legalMatches.join(', ')}. `;
// Set importance
let importance = 'LOW';
let actionRequired = false;
if (score >= 70) {
importance = 'CRITICAL';
actionRequired = true;
}
else if (score >= 40) {
importance = 'HIGH';
}
// Extract deadline (simple pattern)
const deadlineMatch = content.match(/by (\d{1,2} [a-zA-Z]+ \d{4})/);
let deadline = null;
if (deadlineMatch) {
deadline = new Date(deadlineMatch[1]);
reason += `Deadline detected: ${deadlineMatch[1]}. `;
}
// Related docs
const relatedDocs = content.match(/PAN [A-Z]{5}\d{4}[A-Z]/g) || [];
return {
importance,
category,
actionRequired,
deadline,
relatedDocuments: relatedDocs,
detectedEntities: relatedDocs,
reason: reason.trim()
};
}
}
export class ProactiveReminderSystem {
/**
* Suggests reminders for important emails (e.g., deadlines)
*/
createSmartReminders(email, classification) {
const reminders = [];
if (classification.category === 'GOVERNMENT' && classification.deadline) {
// 7 days before
reminders.push({
reminderDate: new Date(classification.deadline.getTime() - 7 * 24 * 60 * 60 * 1000),
type: 'DEADLINE',
context: { originalDeadline: classification.deadline }
});
// 1 day before
reminders.push({
reminderDate: new Date(classification.deadline.getTime() - 24 * 60 * 60 * 1000),
type: 'DEADLINE',
context: { originalDeadline: classification.deadline }
});
}
return reminders;
}
}