/**
* @fileoverview MCP Teams Module - Microsoft Teams integration.
* Provides chat messaging, channel operations, and online meeting management.
* Uses Microsoft Graph Teams API with support for chats, channels, and meetings.
*/
const ErrorService = require('../../core/error-service.cjs');
const MonitoringService = require('../../core/monitoring-service.cjs');
const TEAMS_CAPABILITIES = [
// Chat operations
'listChats',
'getChatMessages',
'sendChatMessage',
// Team & channel operations
'listJoinedTeams',
'listTeamChannels',
'getChannelMessages',
'sendChannelMessage',
'replyToMessage',
// Meeting operations
'createOnlineMeeting',
'getOnlineMeeting',
'getMeetingByJoinUrl',
'listOnlineMeetings',
// Transcript operations
'getMeetingTranscripts',
'getMeetingTranscriptContent'
];
// Log module initialization
MonitoringService.info('Teams Module initialized', {
serviceName: 'teams-module',
capabilities: TEAMS_CAPABILITIES.length,
timestamp: new Date().toISOString()
}, 'teams');
const TeamsModule = {
/**
* Module ID
*/
id: 'teams',
/**
* Module name
*/
name: 'Microsoft Teams',
/**
* Module capabilities
*/
capabilities: TEAMS_CAPABILITIES,
/**
* Service dependencies (injected during init)
*/
services: null,
/**
* Helper method to redact sensitive data from objects before logging
* @param {object} data - The data object to redact
* @returns {object} Redacted copy of the data
* @private
*/
redactSensitiveData(data) {
if (!data || typeof data !== 'object') {
return data;
}
const result = Array.isArray(data) ? [...data] : { ...data };
const sensitiveFields = [
'body', 'content', 'email', 'emailAddress', 'address',
'from', 'to', 'subject', 'message', 'joinUrl'
];
for (const key in result) {
if (Object.prototype.hasOwnProperty.call(result, key)) {
if (sensitiveFields.includes(key.toLowerCase())) {
if (typeof result[key] === 'string') {
result[key] = result[key].substring(0, 20) + '...';
} else if (Array.isArray(result[key])) {
result[key] = `[${result[key].length} items]`;
} else if (typeof result[key] === 'object' && result[key] !== null) {
result[key] = '{...}';
}
} else if (typeof result[key] === 'object' && result[key] !== null) {
result[key] = this.redactSensitiveData(result[key]);
}
}
}
return result;
},
/**
* Initialize the teams module with dependencies
* @param {object} services - Service dependencies (teamsService)
* @param {string} userId - User ID for context
* @param {string} sessionId - Session ID for context
* @returns {TeamsModule} This module for chaining
*/
init(services, userId, sessionId) {
this.services = services;
MonitoringService.info('Teams Module services initialized', {
hasTeamsService: !!services?.teamsService,
timestamp: new Date().toISOString()
}, 'teams', null, userId);
return this;
},
// ========================================================================
// CHAT OPERATIONS
// ========================================================================
/**
* List user's chats
* @param {object} options - Query options { limit, filter }
* @param {object} req - Express request
* @param {string} userId - User ID
* @param {string} sessionId - Session ID
* @returns {Promise<Array<object>>} Array of chats
*/
async listChats(options = {}, req, userId, sessionId) {
const startTime = Date.now();
const { teamsService } = this.services || {};
if (process.env.NODE_ENV === 'development') {
MonitoringService.debug('Teams module: listChats started', {
limit: options.limit,
timestamp: new Date().toISOString()
}, 'teams');
}
if (!teamsService || typeof teamsService.getChats !== 'function') {
const mcpError = ErrorService.createError(
'teams',
'TeamsService not available',
'error',
{ method: 'listChats', moduleId: 'teams' }
);
MonitoringService.logError(mcpError);
throw mcpError;
}
try {
const chats = await teamsService.getChats(options, req, userId, sessionId);
const executionTime = Date.now() - startTime;
if (userId) {
MonitoringService.info('Listed chats via module', {
chatCount: chats.length,
executionTimeMs: executionTime,
timestamp: new Date().toISOString()
}, 'teams', null, userId);
}
return chats;
} catch (error) {
MonitoringService.error('Teams module: listChats failed', {
error: error.message,
executionTimeMs: Date.now() - startTime,
timestamp: new Date().toISOString()
}, 'teams', null, userId);
throw error;
}
},
/**
* Get messages from a chat
* @param {string} chatId - Chat ID
* @param {object} options - Query options { limit }
* @param {object} req - Express request
* @param {string} userId - User ID
* @param {string} sessionId - Session ID
* @returns {Promise<Array<object>>} Array of messages
*/
async getChatMessages(chatId, options = {}, req, userId, sessionId) {
const startTime = Date.now();
const { teamsService } = this.services || {};
if (!teamsService || typeof teamsService.getChatMessages !== 'function') {
const mcpError = ErrorService.createError(
'teams',
'TeamsService not available',
'error',
{ method: 'getChatMessages', moduleId: 'teams' }
);
MonitoringService.logError(mcpError);
throw mcpError;
}
try {
const messages = await teamsService.getChatMessages(chatId, options, req, userId, sessionId);
const executionTime = Date.now() - startTime;
if (userId) {
MonitoringService.info('Retrieved chat messages via module', {
chatId: chatId.substring(0, 20) + '...',
messageCount: messages.length,
executionTimeMs: executionTime,
timestamp: new Date().toISOString()
}, 'teams', null, userId);
}
return messages;
} catch (error) {
MonitoringService.error('Teams module: getChatMessages failed', {
error: error.message,
executionTimeMs: Date.now() - startTime,
timestamp: new Date().toISOString()
}, 'teams', null, userId);
throw error;
}
},
/**
* Send a message to a chat
* @param {string} chatId - Chat ID
* @param {object} messageData - Message content { content, contentType }
* @param {object} req - Express request
* @param {string} userId - User ID
* @param {string} sessionId - Session ID
* @returns {Promise<object>} Created message
*/
async sendChatMessage(chatId, messageData, req, userId, sessionId) {
const startTime = Date.now();
const { teamsService } = this.services || {};
if (!teamsService || typeof teamsService.sendChatMessage !== 'function') {
const mcpError = ErrorService.createError(
'teams',
'TeamsService not available',
'error',
{ method: 'sendChatMessage', moduleId: 'teams' }
);
MonitoringService.logError(mcpError);
throw mcpError;
}
try {
const message = await teamsService.sendChatMessage(chatId, messageData, req, userId, sessionId);
const executionTime = Date.now() - startTime;
if (userId) {
MonitoringService.info('Sent chat message via module', {
chatId: chatId.substring(0, 20) + '...',
messageId: message.id,
executionTimeMs: executionTime,
timestamp: new Date().toISOString()
}, 'teams', null, userId);
}
return message;
} catch (error) {
MonitoringService.error('Teams module: sendChatMessage failed', {
error: error.message,
executionTimeMs: Date.now() - startTime,
timestamp: new Date().toISOString()
}, 'teams', null, userId);
throw error;
}
},
// ========================================================================
// TEAM & CHANNEL OPERATIONS
// ========================================================================
/**
* List user's joined teams
* @param {object} options - Query options { limit }
* @param {object} req - Express request
* @param {string} userId - User ID
* @param {string} sessionId - Session ID
* @returns {Promise<Array<object>>} Array of teams
*/
async listJoinedTeams(options = {}, req, userId, sessionId) {
const startTime = Date.now();
const { teamsService } = this.services || {};
if (!teamsService || typeof teamsService.getJoinedTeams !== 'function') {
const mcpError = ErrorService.createError(
'teams',
'TeamsService not available',
'error',
{ method: 'listJoinedTeams', moduleId: 'teams' }
);
MonitoringService.logError(mcpError);
throw mcpError;
}
try {
const teams = await teamsService.getJoinedTeams(options, req, userId, sessionId);
const executionTime = Date.now() - startTime;
if (userId) {
MonitoringService.info('Listed joined teams via module', {
teamCount: teams.length,
executionTimeMs: executionTime,
timestamp: new Date().toISOString()
}, 'teams', null, userId);
}
return teams;
} catch (error) {
MonitoringService.error('Teams module: listJoinedTeams failed', {
error: error.message,
executionTimeMs: Date.now() - startTime,
timestamp: new Date().toISOString()
}, 'teams', null, userId);
throw error;
}
},
/**
* List channels in a team
* @param {string} teamId - Team ID
* @param {object} options - Query options
* @param {object} req - Express request
* @param {string} userId - User ID
* @param {string} sessionId - Session ID
* @returns {Promise<Array<object>>} Array of channels
*/
async listTeamChannels(teamId, options = {}, req, userId, sessionId) {
const startTime = Date.now();
const { teamsService } = this.services || {};
if (!teamsService || typeof teamsService.getTeamChannels !== 'function') {
const mcpError = ErrorService.createError(
'teams',
'TeamsService not available',
'error',
{ method: 'listTeamChannels', moduleId: 'teams' }
);
MonitoringService.logError(mcpError);
throw mcpError;
}
try {
const channels = await teamsService.getTeamChannels(teamId, options, req, userId, sessionId);
const executionTime = Date.now() - startTime;
if (userId) {
MonitoringService.info('Listed team channels via module', {
teamId: teamId.substring(0, 20) + '...',
channelCount: channels.length,
executionTimeMs: executionTime,
timestamp: new Date().toISOString()
}, 'teams', null, userId);
}
return channels;
} catch (error) {
MonitoringService.error('Teams module: listTeamChannels failed', {
error: error.message,
executionTimeMs: Date.now() - startTime,
timestamp: new Date().toISOString()
}, 'teams', null, userId);
throw error;
}
},
/**
* Get messages from a channel
* @param {string} teamId - Team ID
* @param {string} channelId - Channel ID
* @param {object} options - Query options { limit }
* @param {object} req - Express request
* @param {string} userId - User ID
* @param {string} sessionId - Session ID
* @returns {Promise<Array<object>>} Array of messages
*/
async getChannelMessages(teamId, channelId, options = {}, req, userId, sessionId) {
const startTime = Date.now();
const { teamsService } = this.services || {};
if (!teamsService || typeof teamsService.getChannelMessages !== 'function') {
const mcpError = ErrorService.createError(
'teams',
'TeamsService not available',
'error',
{ method: 'getChannelMessages', moduleId: 'teams' }
);
MonitoringService.logError(mcpError);
throw mcpError;
}
try {
const messages = await teamsService.getChannelMessages(teamId, channelId, options, req, userId, sessionId);
const executionTime = Date.now() - startTime;
if (userId) {
MonitoringService.info('Retrieved channel messages via module', {
teamId: teamId.substring(0, 20) + '...',
channelId: channelId.substring(0, 20) + '...',
messageCount: messages.length,
executionTimeMs: executionTime,
timestamp: new Date().toISOString()
}, 'teams', null, userId);
}
return messages;
} catch (error) {
MonitoringService.error('Teams module: getChannelMessages failed', {
error: error.message,
executionTimeMs: Date.now() - startTime,
timestamp: new Date().toISOString()
}, 'teams', null, userId);
throw error;
}
},
/**
* Send a message to a channel
* @param {string} teamId - Team ID
* @param {string} channelId - Channel ID
* @param {object} messageData - Message content { content, contentType, subject }
* @param {object} req - Express request
* @param {string} userId - User ID
* @param {string} sessionId - Session ID
* @returns {Promise<object>} Created message
*/
async sendChannelMessage(teamId, channelId, messageData, req, userId, sessionId) {
const startTime = Date.now();
const { teamsService } = this.services || {};
if (!teamsService || typeof teamsService.sendChannelMessage !== 'function') {
const mcpError = ErrorService.createError(
'teams',
'TeamsService not available',
'error',
{ method: 'sendChannelMessage', moduleId: 'teams' }
);
MonitoringService.logError(mcpError);
throw mcpError;
}
try {
const message = await teamsService.sendChannelMessage(teamId, channelId, messageData, req, userId, sessionId);
const executionTime = Date.now() - startTime;
if (userId) {
MonitoringService.info('Sent channel message via module', {
teamId: teamId.substring(0, 20) + '...',
channelId: channelId.substring(0, 20) + '...',
messageId: message.id,
executionTimeMs: executionTime,
timestamp: new Date().toISOString()
}, 'teams', null, userId);
}
return message;
} catch (error) {
MonitoringService.error('Teams module: sendChannelMessage failed', {
error: error.message,
executionTimeMs: Date.now() - startTime,
timestamp: new Date().toISOString()
}, 'teams', null, userId);
throw error;
}
},
/**
* Reply to a message in a channel
* @param {string} teamId - Team ID
* @param {string} channelId - Channel ID
* @param {string} messageId - Parent message ID
* @param {object} replyData - Reply content { content, contentType }
* @param {object} req - Express request
* @param {string} userId - User ID
* @param {string} sessionId - Session ID
* @returns {Promise<object>} Created reply
*/
async replyToMessage(teamId, channelId, messageId, replyData, req, userId, sessionId) {
const startTime = Date.now();
const { teamsService } = this.services || {};
if (!teamsService || typeof teamsService.replyToMessage !== 'function') {
const mcpError = ErrorService.createError(
'teams',
'TeamsService not available',
'error',
{ method: 'replyToMessage', moduleId: 'teams' }
);
MonitoringService.logError(mcpError);
throw mcpError;
}
try {
const reply = await teamsService.replyToMessage(teamId, channelId, messageId, replyData, req, userId, sessionId);
const executionTime = Date.now() - startTime;
if (userId) {
MonitoringService.info('Replied to message via module', {
teamId: teamId.substring(0, 20) + '...',
channelId: channelId.substring(0, 20) + '...',
parentMessageId: messageId.substring(0, 20) + '...',
replyId: reply.id,
executionTimeMs: executionTime,
timestamp: new Date().toISOString()
}, 'teams', null, userId);
}
return reply;
} catch (error) {
MonitoringService.error('Teams module: replyToMessage failed', {
error: error.message,
executionTimeMs: Date.now() - startTime,
timestamp: new Date().toISOString()
}, 'teams', null, userId);
throw error;
}
},
// ========================================================================
// MEETING OPERATIONS
// ========================================================================
/**
* Create an online meeting
* @param {object} meetingData - Meeting details { subject, startDateTime, endDateTime, participants, lobbyBypassSettings }
* @param {object} req - Express request
* @param {string} userId - User ID
* @param {string} sessionId - Session ID
* @returns {Promise<object>} Created meeting with join URL
*/
async createOnlineMeeting(meetingData, req, userId, sessionId) {
const startTime = Date.now();
const { teamsService } = this.services || {};
if (!teamsService || typeof teamsService.createOnlineMeeting !== 'function') {
const mcpError = ErrorService.createError(
'teams',
'TeamsService not available',
'error',
{ method: 'createOnlineMeeting', moduleId: 'teams' }
);
MonitoringService.logError(mcpError);
throw mcpError;
}
try {
const meeting = await teamsService.createOnlineMeeting(meetingData, req, userId, sessionId);
const executionTime = Date.now() - startTime;
if (userId) {
MonitoringService.info('Created online meeting via module', {
meetingId: meeting.id,
subject: meetingData.subject?.substring(0, 30),
hasJoinUrl: !!meeting.joinUrl,
executionTimeMs: executionTime,
timestamp: new Date().toISOString()
}, 'teams', null, userId);
}
return meeting;
} catch (error) {
MonitoringService.error('Teams module: createOnlineMeeting failed', {
error: error.message,
executionTimeMs: Date.now() - startTime,
timestamp: new Date().toISOString()
}, 'teams', null, userId);
throw error;
}
},
/**
* Get online meeting details
* @param {string} meetingId - Meeting ID
* @param {object} req - Express request
* @param {string} userId - User ID
* @param {string} sessionId - Session ID
* @returns {Promise<object>} Meeting details
*/
async getOnlineMeeting(meetingId, req, userId, sessionId) {
const startTime = Date.now();
const { teamsService } = this.services || {};
if (!teamsService || typeof teamsService.getOnlineMeeting !== 'function') {
const mcpError = ErrorService.createError(
'teams',
'TeamsService not available',
'error',
{ method: 'getOnlineMeeting', moduleId: 'teams' }
);
MonitoringService.logError(mcpError);
throw mcpError;
}
try {
const meeting = await teamsService.getOnlineMeeting(meetingId, req, userId, sessionId);
const executionTime = Date.now() - startTime;
if (userId) {
MonitoringService.info('Retrieved online meeting via module', {
meetingId: meeting.id,
executionTimeMs: executionTime,
timestamp: new Date().toISOString()
}, 'teams', null, userId);
}
return meeting;
} catch (error) {
MonitoringService.error('Teams module: getOnlineMeeting failed', {
error: error.message,
executionTimeMs: Date.now() - startTime,
timestamp: new Date().toISOString()
}, 'teams', null, userId);
throw error;
}
},
/**
* Find meeting by join URL
* @param {string} joinUrl - Meeting join URL
* @param {object} req - Express request
* @param {string} userId - User ID
* @param {string} sessionId - Session ID
* @returns {Promise<object>} Meeting details
*/
async getMeetingByJoinUrl(joinUrl, req, userId, sessionId) {
const startTime = Date.now();
const { teamsService } = this.services || {};
if (!teamsService || typeof teamsService.getMeetingByJoinUrl !== 'function') {
const mcpError = ErrorService.createError(
'teams',
'TeamsService not available',
'error',
{ method: 'getMeetingByJoinUrl', moduleId: 'teams' }
);
MonitoringService.logError(mcpError);
throw mcpError;
}
try {
const meeting = await teamsService.getMeetingByJoinUrl(joinUrl, req, userId, sessionId);
const executionTime = Date.now() - startTime;
if (userId) {
MonitoringService.info('Found meeting by join URL via module', {
meetingId: meeting.id,
executionTimeMs: executionTime,
timestamp: new Date().toISOString()
}, 'teams', null, userId);
}
return meeting;
} catch (error) {
MonitoringService.error('Teams module: getMeetingByJoinUrl failed', {
error: error.message,
executionTimeMs: Date.now() - startTime,
timestamp: new Date().toISOString()
}, 'teams', null, userId);
throw error;
}
},
/**
* List online meetings
* @param {object} options - Query options { limit }
* @param {object} req - Express request
* @param {string} userId - User ID
* @param {string} sessionId - Session ID
* @returns {Promise<Array<object>>} Array of meetings
*/
async listOnlineMeetings(options = {}, req, userId, sessionId) {
const startTime = Date.now();
const { teamsService } = this.services || {};
if (!teamsService || typeof teamsService.listOnlineMeetings !== 'function') {
const mcpError = ErrorService.createError(
'teams',
'TeamsService not available',
'error',
{ method: 'listOnlineMeetings', moduleId: 'teams' }
);
MonitoringService.logError(mcpError);
throw mcpError;
}
try {
const meetings = await teamsService.listOnlineMeetings(options, req, userId, sessionId);
const executionTime = Date.now() - startTime;
if (userId) {
MonitoringService.info('Listed online meetings via module', {
meetingCount: meetings.length,
executionTimeMs: executionTime,
timestamp: new Date().toISOString()
}, 'teams', null, userId);
}
return meetings;
} catch (error) {
MonitoringService.error('Teams module: listOnlineMeetings failed', {
error: error.message,
executionTimeMs: Date.now() - startTime,
timestamp: new Date().toISOString()
}, 'teams', null, userId);
throw error;
}
},
/**
* Get transcripts for an online meeting
* @param {string} meetingId - Meeting ID
* @param {object} req - Express request
* @param {string} userId - User ID
* @param {string} sessionId - Session ID
* @returns {Promise<Array<object>>} Array of transcripts
*/
async getMeetingTranscripts(meetingId, req, userId, sessionId) {
const startTime = Date.now();
const { teamsService } = this.services || {};
if (!teamsService || typeof teamsService.getMeetingTranscripts !== 'function') {
const mcpError = ErrorService.createError(
'teams',
'TeamsService not available',
'error',
{ method: 'getMeetingTranscripts', moduleId: 'teams' }
);
MonitoringService.logError(mcpError);
throw mcpError;
}
try {
const transcripts = await teamsService.getMeetingTranscripts(meetingId, req, userId, sessionId);
const executionTime = Date.now() - startTime;
if (userId) {
MonitoringService.info('Retrieved meeting transcripts via module', {
meetingId: meetingId.substring(0, 20) + '...',
transcriptCount: transcripts.length,
executionTimeMs: executionTime,
timestamp: new Date().toISOString()
}, 'teams', null, userId);
}
return transcripts;
} catch (error) {
MonitoringService.error('Teams module: getMeetingTranscripts failed', {
error: error.message,
executionTimeMs: Date.now() - startTime,
timestamp: new Date().toISOString()
}, 'teams', null, userId);
throw error;
}
},
/**
* Get transcript content for a specific transcript
* @param {string} meetingId - Meeting ID
* @param {string} transcriptId - Transcript ID
* @param {object} req - Express request
* @param {string} userId - User ID
* @param {string} sessionId - Session ID
* @returns {Promise<object>} Transcript content with parsed entries
*/
async getMeetingTranscriptContent(meetingId, transcriptId, req, userId, sessionId) {
const startTime = Date.now();
const { teamsService } = this.services || {};
if (!teamsService || typeof teamsService.getMeetingTranscriptContent !== 'function') {
const mcpError = ErrorService.createError(
'teams',
'TeamsService not available',
'error',
{ method: 'getMeetingTranscriptContent', moduleId: 'teams' }
);
MonitoringService.logError(mcpError);
throw mcpError;
}
try {
const content = await teamsService.getMeetingTranscriptContent(meetingId, transcriptId, req, userId, sessionId);
const executionTime = Date.now() - startTime;
if (userId) {
MonitoringService.info('Retrieved meeting transcript content via module', {
meetingId: meetingId.substring(0, 20) + '...',
transcriptId: transcriptId.substring(0, 20) + '...',
entryCount: content.entryCount,
executionTimeMs: executionTime,
timestamp: new Date().toISOString()
}, 'teams', null, userId);
}
return content;
} catch (error) {
MonitoringService.error('Teams module: getMeetingTranscriptContent failed', {
error: error.message,
executionTimeMs: Date.now() - startTime,
timestamp: new Date().toISOString()
}, 'teams', null, userId);
throw error;
}
},
// ========================================================================
// INTENT HANDLER
// ========================================================================
/**
* Handle Teams intents from MCP
* @param {string} intent - The intent to handle
* @param {object} params - Intent parameters
* @param {object} context - Execution context { req, userId, sessionId }
* @returns {Promise<object>} Intent result
*/
async handleIntent(intent, params = {}, context = {}) {
const { req, userId, sessionId } = context;
MonitoringService.debug('Teams module handling intent', {
intent,
hasParams: Object.keys(params).length > 0,
timestamp: new Date().toISOString()
}, 'teams');
switch (intent) {
// Chat intents
case 'listChats':
return this.listChats(params, req, userId, sessionId);
case 'getChatMessages':
return this.getChatMessages(params.chatId, params, req, userId, sessionId);
case 'sendChatMessage':
return this.sendChatMessage(params.chatId, params, req, userId, sessionId);
// Team & channel intents
case 'listJoinedTeams':
return this.listJoinedTeams(params, req, userId, sessionId);
case 'listTeamChannels':
return this.listTeamChannels(params.teamId, params, req, userId, sessionId);
case 'getChannelMessages':
return this.getChannelMessages(params.teamId, params.channelId, params, req, userId, sessionId);
case 'sendChannelMessage':
return this.sendChannelMessage(params.teamId, params.channelId, params, req, userId, sessionId);
case 'replyToMessage':
return this.replyToMessage(params.teamId, params.channelId, params.messageId, params, req, userId, sessionId);
// Meeting intents
case 'createOnlineMeeting':
return this.createOnlineMeeting(params, req, userId, sessionId);
case 'getOnlineMeeting':
return this.getOnlineMeeting(params.meetingId, req, userId, sessionId);
case 'getMeetingByJoinUrl':
return this.getMeetingByJoinUrl(params.joinUrl, req, userId, sessionId);
case 'listOnlineMeetings':
return this.listOnlineMeetings(params, req, userId, sessionId);
// Transcript intents
case 'getMeetingTranscripts':
return this.getMeetingTranscripts(params.meetingId, req, userId, sessionId);
case 'getMeetingTranscriptContent':
return this.getMeetingTranscriptContent(params.meetingId, params.transcriptId, req, userId, sessionId);
default:
const error = ErrorService.createError(
'teams',
`Unknown teams intent: ${intent}`,
'warning',
{ intent, availableIntents: TEAMS_CAPABILITIES }
);
MonitoringService.logError(error);
throw error;
}
}
};
module.exports = TeamsModule;