msexchange-mcp
Version:
MCP server for Microsoft Exchange/Outlook email operations via Microsoft Graph API
300 lines • 10.7 kB
JavaScript
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