UNPKG

msexchange-mcp

Version:

MCP server for Microsoft Exchange/Outlook email operations via Microsoft Graph API

300 lines 10.7 kB
import { logger } from '../utils/logger.js'; import * as fs from 'fs/promises'; import * as path from 'path'; export class AliasManager { static instance; aliasData; cacheFile; // Pre-configured common aliases static DEFAULT_ALIASES = { 'florian': [ { name: 'florian', email: 'florian.semrau@allianz.de', displayName: 'Florian Semrau' }, { name: 'florian', email: 'florian@semrau.de' }, ], 'mads': [ { name: 'mads', email: 'mads.schwartz@example.com', displayName: 'Mads Schwartz' }, ], 'doss': [ { name: 'doss', email: 'mads.schwartz@example.com', displayName: 'Mads Schwartz (Doss)' }, ], 'justinas': [ { name: 'justinas', email: 'justinas@example.com', displayName: 'Justinas Lekavičius' }, ], 'jl': [ { name: 'jl', email: 'justinas@example.com', displayName: 'JL (Justinas)' }, ], 'noewe': [ { name: 'noewe', email: 'team@noewe.com', displayName: 'Noewe Team' }, { name: 'noewe', email: 'support@noewe.com', displayName: 'Noewe Support' }, ], }; // Known relationships static DEFAULT_RELATIONSHIPS = { 'doss': ['mads', 'mads.schwartz'], 'mads': ['doss'], 'jl': ['justinas', 'justinas.lekavicius'], 'justinas': ['jl'], }; constructor() { this.cacheFile = path.join(process.cwd(), '.cache', 'email-aliases.json'); this.aliasData = { aliases: { ...AliasManager.DEFAULT_ALIASES }, relationships: { ...AliasManager.DEFAULT_RELATIONSHIPS }, lastUpdated: new Date(), }; } static getInstance() { if (!AliasManager.instance) { AliasManager.instance = new AliasManager(); } return AliasManager.instance; } async initialize() { try { await this.loadCache(); } catch (error) { logger.info('No alias cache found, using defaults'); } } async loadCache() { try { const data = await fs.readFile(this.cacheFile, 'utf-8'); const cached = JSON.parse(data); cached.lastUpdated = new Date(cached.lastUpdated); this.aliasData = cached; } catch (error) { // Cache doesn't exist or is invalid } } async saveCache() { try { await fs.mkdir(path.dirname(this.cacheFile), { recursive: true }); await fs.writeFile(this.cacheFile, JSON.stringify(this.aliasData, null, 2)); } catch (error) { logger.error('Failed to save alias cache', { error }); } } /** * Learn aliases from email history */ async learnFromEmails(mailService, accountId = 'default') { try { // Get recent emails to learn from const messages = await mailService.queryMessages(accountId, { top: 100, orderby: 'receivedDateTime desc', select: 'from,toRecipients,ccRecipients', }); for (const message of messages) { // Learn from sender if (message.from) { this.addEmailToAliases(message.from.emailAddress.address, message.from.emailAddress.name); } // Learn from recipients const allRecipients = [ ...(message.toRecipients || []), ...(message.ccRecipients || []), ]; for (const recipient of allRecipients) { if (recipient.emailAddress) { this.addEmailToAliases(recipient.emailAddress.address, recipient.emailAddress.name); } } } this.aliasData.lastUpdated = new Date(); await this.saveCache(); } catch (error) { logger.error('Failed to learn from emails', { error }); } } addEmailToAliases(email, displayName) { if (!email) return; const normalizedEmail = email.toLowerCase(); const names = this.extractNamesFromEmail(normalizedEmail, displayName); for (const name of names) { if (!this.aliasData.aliases[name]) { this.aliasData.aliases[name] = []; } const existing = this.aliasData.aliases[name].find(a => a.email.toLowerCase() === normalizedEmail); if (existing) { existing.lastSeen = new Date(); existing.frequency = (existing.frequency || 0) + 1; if (displayName && !existing.displayName) { existing.displayName = displayName; } } else { this.aliasData.aliases[name].push({ name, email: normalizedEmail, displayName, lastSeen: new Date(), frequency: 1, }); } } } extractNamesFromEmail(email, displayName) { const names = new Set(); // Extract from email address const [localPart] = email.split('@'); const parts = localPart.split(/[._-]/); for (const part of parts) { if (part.length > 2) { names.add(part.toLowerCase()); } } // Extract from display name if (displayName) { const nameParts = displayName.toLowerCase().split(/\s+/); for (const part of nameParts) { if (part.length > 2 && !part.includes('@')) { names.add(part); } } // Add initials if we have first and last name if (nameParts.length >= 2) { const initials = nameParts.map(p => p[0]).join(''); if (initials.length >= 2) { names.add(initials); } } } return Array.from(names); } /** * Resolve a name or partial name to email addresses */ resolveAlias(nameOrEmail) { const query = nameOrEmail.toLowerCase(); const results = new Set(); // If it's already an email, return it if (query.includes('@')) { results.add(nameOrEmail); return Array.from(results); } // Direct match if (this.aliasData.aliases[query]) { for (const alias of this.aliasData.aliases[query]) { results.add(alias.email); } } // Check relationships if (this.aliasData.relationships[query]) { for (const related of this.aliasData.relationships[query]) { if (this.aliasData.aliases[related]) { for (const alias of this.aliasData.aliases[related]) { results.add(alias.email); } } } } // Fuzzy match - contains query for (const [name, aliases] of Object.entries(this.aliasData.aliases)) { if (name.includes(query) || query.includes(name)) { for (const alias of aliases) { results.add(alias.email); } } } // Check display names for (const aliases of Object.values(this.aliasData.aliases)) { for (const alias of aliases) { if (alias.displayName?.toLowerCase().includes(query)) { results.add(alias.email); } } } return Array.from(results); } /** * Get all known variations of a name */ getNameVariations(name) { const variations = new Set(); const query = name.toLowerCase(); variations.add(name); // Add direct aliases if (this.aliasData.aliases[query]) { for (const alias of this.aliasData.aliases[query]) { variations.add(alias.name); if (alias.displayName) { variations.add(alias.displayName); } } } // Add related names if (this.aliasData.relationships[query]) { for (const related of this.aliasData.relationships[query]) { variations.add(related); } } return Array.from(variations); } /** * Get display name for an email address */ getDisplayName(email) { const normalizedEmail = email.toLowerCase(); for (const aliases of Object.values(this.aliasData.aliases)) { for (const alias of aliases) { if (alias.email.toLowerCase() === normalizedEmail) { return alias.displayName || alias.name; } } } return undefined; } /** * Add a manual alias mapping */ addAlias(name, email, displayName) { const normalizedName = name.toLowerCase(); if (!this.aliasData.aliases[normalizedName]) { this.aliasData.aliases[normalizedName] = []; } const existing = this.aliasData.aliases[normalizedName].find(a => a.email.toLowerCase() === email.toLowerCase()); if (!existing) { this.aliasData.aliases[normalizedName].push({ name: normalizedName, email: email.toLowerCase(), displayName, lastSeen: new Date(), frequency: 0, }); this.saveCache().catch(error => logger.error('Failed to save alias', { error })); } } /** * Add a relationship between names */ addRelationship(name1, name2) { const n1 = name1.toLowerCase(); const n2 = name2.toLowerCase(); if (!this.aliasData.relationships[n1]) { this.aliasData.relationships[n1] = []; } if (!this.aliasData.relationships[n2]) { this.aliasData.relationships[n2] = []; } if (!this.aliasData.relationships[n1].includes(n2)) { this.aliasData.relationships[n1].push(n2); } if (!this.aliasData.relationships[n2].includes(n1)) { this.aliasData.relationships[n2].push(n1); } this.saveCache().catch(error => logger.error('Failed to save relationship', { error })); } /** * Get all aliases for debugging/display */ getAllAliases() { return this.aliasData; } } //# sourceMappingURL=aliasManager.js.map