msteams-mcp-server
Version:
Microsoft Teams MCP Server - Complete Teams integration for Claude Desktop and MCP clients with secure OAuth2 authentication and comprehensive team management
1,909 lines ⢠96.2 kB
JavaScript
/**
* Microsoft Teams MCP Server
* A comprehensive server implementation using Model Context Protocol for Microsoft Teams API
*/
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, ReadResourceRequestSchema, ListResourceTemplatesRequestSchema, GetPromptRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import { logger } from './utils/api.js';
import { teamsOperations } from './utils/teams-operations.js';
import { teamsAuth } from './utils/teams-auth.js';
let teamsConfig = {
setupAuth: false,
resetAuth: false,
debug: false,
nonInteractive: false,
login: false,
logout: false,
verifyLogin: false,
checkPermissions: false,
adminConsentHelp: false,
azureSetup: false,
clientId: undefined,
tenantId: undefined,
clientSecret: undefined,
};
function parseArgs() {
const args = process.argv.slice(2);
const config = {
setupAuth: false,
resetAuth: false,
debug: false,
nonInteractive: false,
login: false,
logout: false,
verifyLogin: false,
checkPermissions: false,
adminConsentHelp: false,
azureSetup: false,
clientId: undefined,
tenantId: undefined,
clientSecret: undefined,
};
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (arg === '--setup-auth')
config.setupAuth = true;
else if (arg === '--reset-auth')
config.resetAuth = true;
else if (arg === '--debug')
config.debug = true;
else if (arg === '--non-interactive' || arg === '-n')
config.nonInteractive = true;
else if (arg === '--login')
config.login = true;
else if (arg === '--logout')
config.logout = true;
else if (arg === '--verify-login')
config.verifyLogin = true;
else if (arg === '--check-permissions')
config.checkPermissions = true;
else if (arg === '--admin-consent-help')
config.adminConsentHelp = true;
else if (arg === '--azure-setup')
config.azureSetup = true;
else if (arg === '--client-id') {
if (i + 1 < args.length) {
config.clientId = args[i + 1];
i++; // Skip the next argument since it's the value
}
}
else if (arg === '--tenant-id') {
if (i + 1 < args.length) {
config.tenantId = args[i + 1];
i++; // Skip the next argument since it's the value
}
}
else if (arg === '--client-secret') {
if (i + 1 < args.length) {
config.clientSecret = args[i + 1];
i++; // Skip the next argument since it's the value
}
}
else if (arg === '--redirect-uri') {
if (i + 1 < args.length) {
config.redirectUri = args[i + 1];
i++; // Skip the next argument since it's the value
}
}
else if (arg === '--user-id') {
if (i + 1 < args.length) {
config.userId = args[i + 1];
i++; // Skip the next argument since it's the value
}
}
}
return config;
}
const server = new Server({
name: "msteams-mcp-server",
version: "1.0.25"
}, {
capabilities: {
resources: {
read: true,
list: true,
templates: true
},
tools: {
list: true,
call: true
},
prompts: {
list: true,
get: true
},
resourceTemplates: {
list: true
}
}
});
logger.log('Microsoft Teams MCP Server started with version 1.0.23');
// Set up the resource listing request handler
server.setRequestHandler(ListResourcesRequestSchema, async () => {
logger.log('Received list resources request');
return { resources: [] };
});
/**
* Handler for reading resource information.
*/
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
logger.log('Received read resource request: ' + JSON.stringify(request));
throw new Error("Resource reading not implemented");
});
/**
* Handler for listing available prompts.
*/
server.setRequestHandler(ListPromptsRequestSchema, async () => {
logger.log('Received list prompts request');
return { prompts: [] };
});
/**
* Handler for getting a specific prompt.
*/
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
logger.log('Received get prompt request: ' + JSON.stringify(request));
throw new Error("Prompt getting not implemented");
});
/**
* Handler for listing available resource templates.
*/
server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
logger.log('Received list resource templates request');
return { resourceTemplates: [] };
});
/**
* List available tools for interacting with Microsoft Teams.
*/
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "teams_authenticate",
description: "Handle Microsoft Teams authentication (login, logout, status check)",
inputSchema: {
type: "object",
properties: {
action: {
type: "string",
enum: ["login", "logout", "status"],
description: "Authentication action to perform: login (start auth), logout (clear tokens), status (check current state)"
}
},
required: ["action"]
}
},
{
name: "manage_teams",
description: "Comprehensive team management - list, search, create, or get details about teams",
inputSchema: {
type: "object",
properties: {
action: {
type: "string",
enum: ["list", "search", "create", "get", "get_members"],
description: "Action to perform on teams"
},
teamId: {
type: "string",
description: "Team ID for get, get_members actions"
},
query: {
type: "string",
description: "Search query for teams (name or description)"
},
visibility: {
type: "string",
enum: ["Private", "Public"],
description: "Filter teams by visibility"
},
maxResults: {
type: "integer",
description: "Maximum number of results to return",
default: 10
},
displayName: {
type: "string",
description: "Name for new team (create action)"
},
description: {
type: "string",
description: "Description for new team (create action)"
},
members: {
type: "array",
items: { type: "string" },
description: "Array of user IDs or emails for team members (create action)"
},
owners: {
type: "array",
items: { type: "string" },
description: "Array of user IDs or emails for team owners (create action)"
}
},
required: ["action"]
}
},
{
name: "manage_channels",
description: "Channel management - list, search, create channels in teams",
inputSchema: {
type: "object",
properties: {
action: {
type: "string",
enum: ["list", "search", "create"],
description: "Action to perform on channels"
},
teamId: {
type: "string",
description: "Team ID (required for all actions)"
},
query: {
type: "string",
description: "Search query for channels (name or description)"
},
membershipType: {
type: "string",
enum: ["standard", "private", "shared"],
description: "Filter channels by membership type"
},
maxResults: {
type: "integer",
description: "Maximum number of results to return",
default: 10
},
displayName: {
type: "string",
description: "Name for new channel (create action)"
},
description: {
type: "string",
description: "Description for new channel (create action)"
}
},
required: ["action", "teamId"]
}
},
{
name: "send_message",
description: "Send a message to a Teams channel",
inputSchema: {
type: "object",
properties: {
teamId: {
type: "string",
description: "ID of the team containing the channel"
},
channelId: {
type: "string",
description: "ID of the channel to send message to"
},
content: {
type: "string",
description: "Message content to send"
},
contentType: {
type: "string",
enum: ["text", "html"],
description: "Content type of the message",
default: "text"
},
mentions: {
type: "array",
items: {
type: "object",
properties: {
id: { type: "string" },
displayName: { type: "string" }
}
},
description: "Array of user mentions in the message"
}
},
required: ["teamId", "channelId", "content"]
}
},
{
name: "manage_messages",
description: "Message management - get or search messages in channels",
inputSchema: {
type: "object",
properties: {
action: {
type: "string",
enum: ["get", "search"],
description: "Action to perform on messages"
},
teamId: {
type: "string",
description: "ID of the team containing the channel"
},
channelId: {
type: "string",
description: "ID of the channel"
},
query: {
type: "string",
description: "Search query for messages (search action)"
},
fromDate: {
type: "string",
description: "Start date for message search (ISO format)"
},
toDate: {
type: "string",
description: "End date for message search (ISO format)"
},
maxResults: {
type: "integer",
description: "Maximum number of results to return",
default: 20
}
},
required: ["action", "teamId", "channelId"]
}
},
{
name: "manage_members",
description: "Team member management - add or remove team members",
inputSchema: {
type: "object",
properties: {
action: {
type: "string",
enum: ["add", "remove"],
description: "Action to perform on team members"
},
teamId: {
type: "string",
description: "ID of the team"
},
userId: {
type: "string",
description: "User ID or email to add/remove"
},
memberId: {
type: "string",
description: "Member ID for removal (use with remove action)"
},
roles: {
type: "array",
items: {
type: "string",
enum: ["owner", "member"]
},
description: "Roles for the user (add action)",
default: ["member"]
}
},
required: ["action", "teamId"]
}
},
{
name: "search_users",
description: "Search for users in the organization",
inputSchema: {
type: "object",
properties: {
query: {
type: "string",
description: "Search query (name or email)"
},
maxResults: {
type: "integer",
description: "Maximum number of results to return",
default: 10
}
},
required: ["query"]
}
},
{
name: "send_direct_message",
description: "Send a direct message to a user",
inputSchema: {
type: "object",
properties: {
userId: {
type: "string",
description: "User ID to send message to"
},
displayName: {
type: "string",
description: "Display name of user (used to find user ID if userId not provided)"
},
content: {
type: "string",
description: "Message content to send"
},
contentType: {
type: "string",
enum: ["text", "html"],
description: "Content type of the message",
default: "text"
},
mentions: {
type: "array",
items: {
type: "object",
properties: {
id: { type: "string" },
displayName: { type: "string" }
}
},
description: "Array of user mentions in the message"
}
},
required: ["content"]
}
},
{
name: "get_direct_messages",
description: "Get direct messages from a chat with a user",
inputSchema: {
type: "object",
properties: {
userId: {
type: "string",
description: "User ID to get messages from"
},
displayName: {
type: "string",
description: "Display name of user (used to find user ID if userId not provided)"
},
maxResults: {
type: "integer",
description: "Maximum number of messages to return",
default: 20
}
},
required: []
}
},
{
name: "manage_reactions",
description: "Add or remove reactions from messages",
inputSchema: {
type: "object",
properties: {
action: {
type: "string",
enum: ["add", "remove"],
description: "Action to perform: add or remove reaction"
},
messageType: {
type: "string",
enum: ["channel", "chat"],
description: "Type of message: channel message or chat/direct message"
},
teamId: {
type: "string",
description: "Team ID (required for channel messages)"
},
channelId: {
type: "string",
description: "Channel ID (required for channel messages)"
},
chatId: {
type: "string",
description: "Chat ID (required for chat messages, can be obtained from direct message operations)"
},
messageId: {
type: "string",
description: "ID of the message to react to"
},
reactionType: {
type: "string",
enum: ["like", "heart", "laugh", "surprised", "sad", "angry"],
description: "Type of reaction"
}
},
required: ["action", "messageType", "messageId", "reactionType"]
}
},
{
name: "manage_files",
description: "Upload, download, or list files in teams and channels",
inputSchema: {
type: "object",
properties: {
action: {
type: "string",
enum: ["upload", "download", "list"],
description: "Action to perform: upload file, download file, or list files"
},
location: {
type: "string",
enum: ["team", "channel"],
description: "Location for file operations: team drive or channel files"
},
teamId: {
type: "string",
description: "Team ID (required for all actions)"
},
channelId: {
type: "string",
description: "Channel ID (required for channel file operations)"
},
fileName: {
type: "string",
description: "Name for the file (upload action)"
},
fileContent: {
type: "string",
description: "Base64 encoded file content (upload action)"
},
mimeType: {
type: "string",
description: "MIME type of the file (upload action)"
},
driveId: {
type: "string",
description: "Drive ID (required for download action)"
},
itemId: {
type: "string",
description: "File item ID (required for download action)"
},
maxResults: {
type: "integer",
description: "Maximum number of files to return (list action)",
default: 20
}
},
required: ["action", "location", "teamId"]
}
},
{
name: "manage_replies",
description: "Reply to messages or get message replies in channels and chats",
inputSchema: {
type: "object",
properties: {
action: {
type: "string",
enum: ["reply", "get_replies"],
description: "Action to perform: reply to message or get existing replies"
},
messageType: {
type: "string",
enum: ["channel", "chat"],
description: "Type of message: channel message or chat/direct message"
},
teamId: {
type: "string",
description: "Team ID (required for channel messages)"
},
channelId: {
type: "string",
description: "Channel ID (required for channel messages)"
},
chatId: {
type: "string",
description: "Chat ID (required for chat messages)"
},
messageId: {
type: "string",
description: "ID of the message to reply to or get replies from"
},
content: {
type: "string",
description: "Reply content (reply action)"
},
contentType: {
type: "string",
enum: ["text", "html"],
description: "Content type of the reply",
default: "text"
},
maxResults: {
type: "integer",
description: "Maximum number of replies to return (get_replies action)",
default: 20
}
},
required: ["action", "messageType", "messageId"]
}
},
{
name: "manage_group_chats",
description: "Create and manage group chats, add/remove members",
inputSchema: {
type: "object",
properties: {
action: {
type: "string",
enum: ["create", "list", "get_members", "add_members", "remove_member"],
description: "Action to perform on group chats"
},
chatId: {
type: "string",
description: "Group chat ID (required for get_members, add_members, remove_member actions)"
},
topic: {
type: "string",
description: "Topic/name for the group chat (create action)"
},
members: {
type: "array",
items: { type: "string" },
description: "Array of user IDs to add to the group chat (create/add_members actions)"
},
membershipId: {
type: "string",
description: "Membership ID to remove (remove_member action)"
},
maxResults: {
type: "integer",
description: "Maximum number of group chats to return (list action)",
default: 20
}
},
required: ["action"]
}
},
{
name: "manage_meetings",
description: "Create and manage online meetings and calendar events",
inputSchema: {
type: "object",
properties: {
action: {
type: "string",
enum: ["create_meeting", "list_meetings", "get_calendar", "create_event"],
description: "Action to perform on meetings and calendar"
},
subject: {
type: "string",
description: "Meeting/event subject (create actions)"
},
startDateTime: {
type: "string",
description: "Start date and time in ISO 8601 format (create actions)"
},
endDateTime: {
type: "string",
description: "End date and time in ISO 8601 format (create actions)"
},
attendees: {
type: "array",
items: { type: "string" },
description: "Array of attendee email addresses (create actions)"
},
content: {
type: "string",
description: "Meeting/event description (create actions)"
},
isRecordingEnabled: {
type: "boolean",
description: "Whether recording is enabled (create_meeting action)",
default: false
},
startTime: {
type: "string",
description: "Filter start time for calendar events (get_calendar action)"
},
endTime: {
type: "string",
description: "Filter end time for calendar events (get_calendar action)"
},
maxResults: {
type: "integer",
description: "Maximum number of results to return",
default: 20
}
},
required: ["action"]
}
},
{
name: "manage_presence",
description: "Get and set user presence/status information",
inputSchema: {
type: "object",
properties: {
action: {
type: "string",
enum: ["get_presence", "set_presence", "get_bulk_presence"],
description: "Action to perform on user presence"
},
userId: {
type: "string",
description: "User ID to get presence for (get_presence action, optional for current user)"
},
userIds: {
type: "array",
items: { type: "string" },
description: "Array of user IDs to get presence for (get_bulk_presence action)"
},
availability: {
type: "string",
enum: ["Available", "Busy", "DoNotDisturb", "BeRightBack", "Away"],
description: "Availability status to set (set_presence action)"
},
activity: {
type: "string",
enum: ["Available", "Busy", "DoNotDisturb", "BeRightBack", "Away", "InAMeeting", "InACall"],
description: "Activity status to set (set_presence action)"
},
expirationDuration: {
type: "string",
description: "How long to keep the status (ISO 8601 duration, e.g., 'PT1H' for 1 hour)"
}
},
required: ["action"]
}
}
]
};
});
/**
* Helper function to get emoji for reaction types
*/
function getReactionEmoji(reactionType) {
const emojiMap = {
'like': 'š',
'heart': 'ā¤ļø',
'laugh': 'š',
'surprised': 'š®',
'sad': 'š¢',
'angry': 'š '
};
return emojiMap[reactionType.toLowerCase()] || 'š';
}
/**
* Helper function to safely extract arguments with defaults
*/
function safeGetArgs(args, defaultValues) {
if (!args || typeof args !== 'object') {
return defaultValues;
}
const result = { ...defaultValues };
for (const key in defaultValues) {
if (args[key] !== undefined) {
result[key] = args[key];
}
}
return result;
}
/**
* Handle tool execution requests
*/
server.setRequestHandler(CallToolRequestSchema, async (request) => {
logger.log('Received call tool request: ' + JSON.stringify(request));
try {
switch (request.params.name) {
case "teams_authenticate": {
const args = safeGetArgs(request.params.arguments, { action: '' });
if (!args.action) {
throw new Error("Action is required for authentication");
}
switch (args.action) {
case "login":
try {
// Check if already authenticated
const authStatus = await teamsAuth.getAuthenticationStatus();
if (authStatus.isAuthenticated) {
return {
content: [{
type: "text",
text: `ā
**Already authenticated with Microsoft Teams!**\n\n**User:** ${authStatus.user?.displayName || 'Unknown'}\n\nš You can now use Teams commands to manage your teams, channels, and messages.`
}]
};
}
// Start OAuth redirect authentication
try {
const result = await teamsAuth.authenticate();
return {
content: [{
type: "text",
text: `ā
**Successfully authenticated with Microsoft Teams!**\n\n**User:** ${result.account?.name || 'Unknown'}\n\nš You can now use Teams commands to manage your teams, channels, and messages.`
}]
};
}
catch (authError) {
// Handle OAuth redirect required error
if (authError.authUrl) {
return {
content: [{
type: "text",
text: `š **Microsoft Teams Authentication Required**\n\n` +
`š **Please complete authentication:**\n` +
`1. Visit: **${authError.authUrl}**\n` +
`2. Sign in with your Microsoft account\n` +
`3. Complete the authorization process\n\n` +
`š” **Direct link:** [Click here to authenticate](${authError.authUrl})\n\n` +
`š **Next steps:**\n` +
`- Complete authentication in your browser\n` +
`- The system will automatically detect completion\n` +
`- Try your Teams command again after authentication\n\n` +
`ā ļø **If you're having trouble:**\n` +
`- Make sure you're signed in to the correct Microsoft account\n` +
`- Check that your organization allows Teams access\n` +
`- Ensure you have the necessary permissions for Teams operations`
}]
};
}
// General authentication error
throw authError;
}
}
catch (error) {
return {
content: [{
type: "text",
text: `ā **Authentication failed:** ${error.message}\n\n` +
`š” **Troubleshooting steps:**\n` +
`1. **Try terminal authentication:** Run \`msteams-mcp-server --login\` from your terminal\n` +
`2. **Check network connection:** Ensure you can access https://login.microsoftonline.com\n` +
`3. **Verify permissions:** Make sure your Azure app has Teams permissions\n` +
`4. **Reset authentication:** Try \`msteams-mcp-server --reset-auth\` then retry\n\n` +
`š **Need help?** Check the README for detailed setup instructions.`
}]
};
}
case "logout":
try {
await teamsAuth.logout();
return {
content: [{
type: "text",
text: "ā
**Successfully logged out from Microsoft Teams.**\n\nšļø All stored authentication tokens have been cleared.\n\nš Use the authenticate tool with action 'login' to sign in again."
}]
};
}
catch (error) {
return {
content: [{
type: "text",
text: `ā **Logout failed:** ${error.message}`
}]
};
}
case "status":
try {
const authStatus = await teamsAuth.getAuthenticationStatus();
if (authStatus.isAuthenticated) {
return {
content: [{
type: "text",
text: `ā
**Authenticated with Microsoft Teams**\n\n**User:** ${authStatus.user?.displayName || 'Unknown'}\n**Status:** Ready to use Teams commands\n\nš You can now manage your teams, channels, and messages!`
}]
};
}
else {
return {
content: [{
type: "text",
text: `ā **Not authenticated with Microsoft Teams**\n\n**Status:** ${authStatus.message}\n\nš **Next steps:**\n- Run the authenticate tool with action 'login'\n- Or use 'msteams-mcp-server --login' from terminal`
}]
};
}
}
catch (error) {
return {
content: [{
type: "text",
text: `ā **Error checking authentication status:** ${error.message}`
}]
};
}
default:
throw new Error(`Unknown authentication action: ${args.action}`);
}
}
case "manage_teams": {
const args = safeGetArgs(request.params.arguments, {
action: '',
teamId: '',
query: '',
visibility: '',
maxResults: 10,
displayName: '',
description: '',
members: [],
owners: []
});
if (!args.action) {
throw new Error("Action is required for team management");
}
switch (args.action) {
case "list":
const teams = await teamsOperations.getUserTeams(args.maxResults || 10);
return {
content: [{
type: "text",
text: `š **Your Teams (${teams.length})**\n\n${teams.map(team => `**${team.displayName}** (${team.visibility})\n` +
`ID: ${team.id}\n` +
`Description: ${team.description || 'No description'}\n` +
`Created: ${new Date(team.createdDateTime).toLocaleDateString()}\n` +
`š [Open in Teams](${team.webUrl})\n`).join('\n')}`
}]
};
case "search":
if (!args.query) {
throw new Error("Query is required for team search");
}
const searchResults = await teamsOperations.searchTeams({
query: args.query,
visibility: args.visibility,
maxResults: args.maxResults
});
return {
content: [{
type: "text",
text: `š **Team Search Results for "${args.query}" (${searchResults.length})**\n\n${searchResults.map(team => `**${team.displayName}** (${team.visibility})\n` +
`ID: ${team.id}\n` +
`Description: ${team.description || 'No description'}\n` +
`š [Open in Teams](${team.webUrl})\n`).join('\n')}`
}]
};
case "create":
if (!args.displayName) {
throw new Error("Display name is required for team creation");
}
const newTeam = await teamsOperations.createTeam({
displayName: args.displayName,
description: args.description,
visibility: args.visibility || 'Private',
members: args.members,
owners: args.owners
});
return {
content: [{
type: "text",
text: `ā
**Team Created Successfully!**\n\n**Name:** ${newTeam.displayName}\n**ID:** ${newTeam.id}\n**Visibility:** ${newTeam.visibility}\n**Description:** ${newTeam.description || 'No description'}\n\nš [Open in Teams](${newTeam.webUrl})`
}]
};
case "get":
if (!args.teamId) {
throw new Error("Team ID is required for getting team details");
}
const team = await teamsOperations.getTeam(args.teamId);
return {
content: [{
type: "text",
text: `š **Team Details**\n\n**Name:** ${team.displayName}\n**ID:** ${team.id}\n**Visibility:** ${team.visibility}\n**Description:** ${team.description || 'No description'}\n**Created:** ${new Date(team.createdDateTime).toLocaleDateString()}\n\nš [Open in Teams](${team.webUrl})`
}]
};
case "get_members":
if (!args.teamId) {
throw new Error("Team ID is required for getting team members");
}
const members = await teamsOperations.getTeamMembers(args.teamId);
return {
content: [{
type: "text",
text: `š„ **Team Members (${members.length})**\n\n${members.map(member => `**${member.displayName}**\n` +
`Email: ${member.email}\n` +
`Roles: ${member.roles.join(', ')}\n` +
`ID: ${member.id}\n`).join('\n')}`
}]
};
default:
throw new Error(`Unknown team action: ${args.action}`);
}
}
case "manage_channels": {
const args = safeGetArgs(request.params.arguments, {
action: '',
teamId: '',
query: '',
membershipType: '',
maxResults: 10,
displayName: '',
description: ''
});
if (!args.action || !args.teamId) {
throw new Error("Action and teamId are required for channel management");
}
switch (args.action) {
case "list":
const channels = await teamsOperations.getTeamChannels(args.teamId);
return {
content: [{
type: "text",
text: `š **Team Channels (${channels.length})**\n\n${channels.map(channel => `**${channel.displayName}** (${channel.membershipType})\n` +
`ID: ${channel.id}\n` +
`Description: ${channel.description || 'No description'}\n` +
`Created: ${new Date(channel.createdDateTime).toLocaleDateString()}\n` +
`š [Open in Teams](${channel.webUrl})\n`).join('\n')}`
}]
};
case "search":
if (!args.query) {
throw new Error("Query is required for channel search");
}
const channelResults = await teamsOperations.searchChannels({
teamId: args.teamId,
query: args.query,
membershipType: args.membershipType,
maxResults: args.maxResults
});
return {
content: [{
type: "text",
text: `š **Channel Search Results for "${args.query}" (${channelResults.length})**\n\n${channelResults.map(channel => `**${channel.displayName}** (${channel.membershipType})\n` +
`ID: ${channel.id}\n` +
`Description: ${channel.description || 'No description'}\n` +
`š [Open in Teams](${channel.webUrl})\n`).join('\n')}`
}]
};
case "create":
if (!args.displayName) {
throw new Error("Display name is required for channel creation");
}
const newChannel = await teamsOperations.createChannel(args.teamId, {
displayName: args.displayName,
description: args.description,
membershipType: args.membershipType || 'standard'
});
return {
content: [{
type: "text",
text: `ā
**Channel Created Successfully!**\n\n**Name:** ${newChannel.displayName}\n**ID:** ${newChannel.id}\n**Type:** ${newChannel.membershipType}\n**Description:** ${newChannel.description || 'No description'}\n\nš [Open in Teams](${newChannel.webUrl})`
}]
};
default:
throw new Error(`Unknown channel action: ${args.action}`);
}
}
case "send_message": {
const args = safeGetArgs(request.params.arguments, {
teamId: '',
channelId: '',
content: '',
contentType: 'text',
mentions: []
});
if (!args.teamId || !args.channelId || !args.content) {
throw new Error("teamId, channelId, and content are required for sending messages");
}
const message = await teamsOperations.sendChannelMessage(args.teamId, args.channelId, {
content: args.content,
contentType: args.contentType,
mentions: args.mentions
});
return {
content: [{
type: "text",
text: `ā
**Message Sent Successfully!**\n\n**Message ID:** ${message.id}\n**Sent at:** ${new Date(message.createdDateTime).toLocaleString()}\n**Content:** ${message.body.content.substring(0, 100)}${message.body.content.length > 100 ? '...' : ''}`
}]
};
}
case "manage_messages": {
const args = safeGetArgs(request.params.arguments, {
action: '',
teamId: '',
channelId: '',
query: '',
fromDate: '',
toDate: '',
maxResults: 20
});
if (!args.action || !args.teamId || !args.channelId) {
throw new Error("Action, teamId, and channelId are required for message management");
}
switch (args.action) {
case "get":
const messages = await teamsOperations.getChannelMessages(args.teamId, args.channelId, {
maxResults: args.maxResults
});
return {
content: [{
type: "text",
text: `š¬ **Channel Messages (${messages.length})**\n\n${messages.map(msg => `**${msg.from?.user?.displayName || 'Unknown'}** - ${new Date(msg.createdDateTime).toLocaleString()}\n` +
`${msg.body.content.substring(0, 200)}${msg.body.content.length > 200 ? '...' : ''}\n` +
`Message ID: ${msg.id}\n`).join('\n')}`
}]
};
case "search":
if (!args.query) {
throw new Error("Query is required for message search");
}
const searchMessages = await teamsOperations.searchChannelMessages({
teamId: args.teamId,
channelId: args.channelId,
query: args.query,
fromDate: args.fromDate,
toDate: args.toDate,
maxResults: args.maxResults
});
return {
content: [{
type: "text",
text: `š **Message Search Results for "${args.query}" (${searchMessages.length})**\n\n${searchMessages.map(msg => `**${msg.from?.user?.displayName || 'Unknown'}** - ${new Date(msg.createdDateTime).toLocaleString()}\n` +
`${msg.body.content.substring(0, 200)}${msg.body.content.length > 200 ? '...' : ''}\n` +
`Message ID: ${msg.id}\n`).join('\n')}`
}]
};
default:
throw new Error(`Unknown message action: ${args.action}`);
}
}
case "manage_members": {
const args = safeGetArgs(request.params.arguments, {
action: '',
teamId: '',
userId: '',
memberId: '',
roles: ['member']
});
if (!args.action || !args.teamId) {
throw new Error("Action and teamId are required for member management");
}
switch (args.action) {
case "add":
if (!args.userId) {
throw new Error("userId is required for adding members");
}
await teamsOperations.addTeamMember(args.teamId, args.userId, args.roles);
return {
content: [{
type: "text",
text: `ā
**Member Added Successfully!**\n\n**User ID:** ${args.userId}\n**Roles:** ${args.roles.join(', ')}\n**Team ID:** ${args.teamId}`
}]
};
case "remove":
if (!args.memberId) {
throw new Error("memberId is required for removing members");
}
await teamsOperations.removeTeamMember(args.teamId, args.memberId);
return {
content: [{
type: "text",
text: `ā
**Member Removed Successfully!**\n\n**Member ID:** ${args.memberId}\n**Team ID:** ${args.teamId}`
}]
};
default:
throw new Error(`Unknown member action: ${args.action}`);
}
}
case "search_users": {
const args = safeGetArgs(request.params.arguments, {
query: '',
maxResults: 10
});
if (!args.query) {
throw new Error("Query is required for user search");
}
const users = await teamsOperations.searchUsers(args.query, args.maxResults);
return {
content: [{
type: "text",
text: `š¤ **User Search Results for "${args.query}" (${users.length})**\n\n${users.map(user => `**${user.displayName}**\n` +
`Email: ${user.email}\n` +
`ID: ${user.id}\n`).join('\n')}`
}]
};
}
case "send_direct_message": {
const args = safeGetArgs(request.params.arguments, {
userId: '',
displayName: '',
content: '',
contentType: 'text',
mentions: []
});
if (!args.content) {
throw new Error("Content is required for sending direct messages");
}
// Get user ID from display name if not provided
let userId = args.userId;
if (!userId && args.displayName) {
const users = await teamsOperations.searchUsers(args.displayName, 1);
if (users.length > 0) {
userId = users[0].id;
}
else {
throw new Error(`Could not find user with display name: ${args.displayName}`);
}
}
if (!userId) {
throw new Error("Either userId or displayName is required for sending direct messages");
}
const message = await teamsOperations.sendDirectMessage(userId, {
content: args.content,
contentType: args.contentType,
mentions: args.mentions
});
return {
content: [{
type: "text",
text: `ā
**Direct Message Sent Successfully!**\n\n**Message ID:** ${message.id}\n**Sent at:** ${new Date(message.createdDateTime).toLocaleString()}\n**Content:** ${message.body.content.substring(0, 100)}${message.body.content.length > 100 ? '...' : ''}`
}]
};
}
case "get_direct_messages": {
const args = safeGetArgs(request.params.arguments, {
userId: '',
displayName: '',
maxResults: 20
});
// Get user ID from display name if not provided
let userId = args.userId;
if (!userId && args.displayName) {
const users = await teamsOperations.searchUsers(args.displayName, 1);
if (users.length > 0) {
userId = users[0].id;
}
else {
throw new Error(`Could not find user with display name: ${args.displayName}`);
}
}
if (!userId) {
throw new Error("Either userId or displayName is required for getting direct messages");
}
const messages = await teamsOperations.getDirectMessages(userId, {
maxResults: args.maxResults
});
return {
content: [{
type: "text",
text: `š¬ **Direct Messages (${messages.length})**\n\n${messages.map(msg => `**${msg.from?.user?.displayName || 'Unknown'}** - ${new Date(msg.createdDateTime).toLocaleString()}\n` +
`${msg.body.content.substring(0, 200)}${msg.body.content.length > 200 ? '...' : ''}\n` +
`Message ID: ${msg.id}\n`).join('\n')}`
}]
};
}
case "manage_reactions": {
const args = safeGetArgs(request.params.arguments, {
action: '',
messageType: '',
teamId: '',
channelId: '',
chatId: '',
messageId: '',
reactionType: ''
});
if (!args.action || !args.messageType || !args.messageId || !args.reactionType) {
throw new Error("Action, messageType, messageId, and reactionType are required");
}
const validReactions = ['like', 'heart', 'laugh', 'surprised', 'sad', 'angry'];
if (!validReactions.includes(args.reactionType)) {
throw new Error(`Invalid reaction type. Must be one of: ${validReactions.join(', ')}`);
}
if (args.messageType === 'channel') {
if (!args.teamId || !args.channelId) {
throw new Error("teamId and channelId are required for channel message reactions");
}
if (args.action === 'add') {
await teamsOperations.addMessageReaction(args.teamId, args.channelId, args.messageId, args.reactionType);
return {
content: [{
type: "text",
text: `ā
**Reaction Added!**\n\n**Reaction:** ${args.reactionType} ${getReactionEmoji(args.reactionType)}\n**Message ID:** ${args.messageId}\n**Channel:** ${args.channelId}`
}]
};
}
else {
await teamsOperations.removeMessageReaction(args.teamId, args.channelId, args.messageId, args.reactionType);
return {
content: [{
type: "text",
text: `ā
**Reaction Removed!**\n\n**Reaction:** ${args.reactionType} ${getReactionEmoji(args.reactionType)}\n**Message ID:** ${args.messageId}\n**Channel:** ${args.channelId}`
}]
};
}
}
else if (args.messageType === 'chat') {
if (!args.chatId) {
throw new Error("chatId is required for chat message reactions");
}
if (args.action === 'add') {
await teamsOperations.addChatMessageReaction(args.chatId, args.messageId, args.reactionType);
return {
content: [{
type: "text",
text: `ā
**Reaction Added!**\n\n**Reaction:** ${args.reactionType} ${getReactionEmoji(args.reactionType)}\n**Message ID:** ${args.messageId}\n**Chat:** ${args.chatId}`
}]
};
}
else {
await teamsOperations.removeChatMessageReaction(args.chatId, args.messageId, args.reactionType);
return {
content: [{
type: "text",
text: `ā
**Reaction Removed!**\n\n**Reaction:** ${args.reactionType} ${getReactionEmoji(args.reactionType)}\n**Message ID:** ${args.messageId}\n**Chat:** ${args.chatId}`
}]
};
}
}
else {
throw new Error("messageType must be 'channel' or 'chat'");
}
}
case "manage_files": {
const args = safeGetArgs(request.params.arguments, {
action: '',
location: '',
teamId: '',
channelId: '',
fileName: '',
fileContent: '',
mimeType: 'application/octet-stream',
driveId: '',
itemId: '',
maxResults: 20
});
if (!args.action || !args.location || !args.teamId) {
throw new Error("Action, location, and teamId are required");
}
switch (args.action) {
case "upload":
if (!args.fileName || !args.fileContent) {
throw new Error("fileName and fileContent are required for upload");
}
try {
// Decode base64 file content
const fileBuffer = Buffer.from(args.fileContent, 'base64');
let result;
if (args.location === 'team') {
result = await teamsOperations.uploadFileToTeam(args.teamId, {
fileName: args.fileName,
fileContent: fileBuffer,
mimeType: args.mimeType
});
}
else if (args.location === 'channel') {
if (!args.channelId) {
throw new Error("channelId is required for channel file upload");
}
result = await teamsOperations.uploadFileToChannel(args.teamId, args.channelId, {
fileName: args.fileName,
fileContent: fileBuffer,
mimeType: args.mimeType
});
}
else {
throw new Error("location must be 'team' or 'channel'");
}
return {
content: [{
type: "text",
text: `ā
**File Uploaded Successfully!**\n\n**Name:** ${result.name}\n**Size:** ${(result.size / 1024).toFixed(2)} KB\n**Location:** ${args.location === 'team' ? 'Team Drive' : 'Channel Files'}\n**Created:** ${new Date(result.createdDateTime).toLocaleString()}\n\nš [Open in Teams](${result.webUrl})`
}]
};
}
catch (error) {
throw new Error(`File upload failed: ${error.message}`);
}
case "download":
if (!args.driveId || !args.itemId) {
throw new Error("driveId and itemId are required for download");
}
try {
const downloadResult = await teamsOperations.downloadFile(args.driveId, args.itemId);
const base64Content = downloadResult.content.toString('base64');
return {
content: [{
type: "text",
text: `ā
**File Downloaded Successfully!**\n\n**Name:** ${downloadResult.fileName}\n**Size:** ${(downloadResult.content.length / 1024).toFixed(2)} KB\n**MIME Type:** ${downloadResult.mimeType}\n\n**Base64 Content:**\n\`\`\`\n${base64Content.substring(0, 200)}${base64Content.length > 200 ? '...[truncated]' : ''}\n\`\`\``
}]
};
}
catch (error) {
throw new Error(`File download failed: ${error.message}`);
}
case "list":
try {
let files;
if (args.location === 'team') {
files = await teamsOperations.getTeamFiles(args.teamId, args.maxResults);
}
else if (args.location === 'channel') {
if (!args.channelId) {
throw new Error("channelId is required for channel file listing");
}
files = await teamsOperations.getChannelFiles(args.teamId, args.channelId, args.maxResults);
}
else {
throw new Error("location must be 'team' or 'channel'");
}
if (files.length === 0) {
return {
content: [{
type: "text",
text: `š **No files found in ${args.location === 'team' ? 'team drive' : 'channel files'}**`
}]
};
}
const fileList = files.map(file => {
const size = file.size ? `${(file.size / 1024).toFixed(2)} KB` : 'Unknown size';
const type = file.folder ? 'š Folder' : 'š File';
return `${type} **${file.name}**\n` +
`Size: ${size}\n` +
`Created: ${new Date(file.createdDateTime).toLocaleDateString()}\n` +
`ID: ${file.id}\n` +
`š [Open in Teams](${file.webUrl})\n`;
}).join('\n');
return {
content: [{
type: "text",
text: `š **${args.location === 'team' ? 'Team' : 'Channel'} Files (${files.length})**\n\n${fileList}`
}]
};
}
catch (error) {
throw new Error(`File listing failed: ${error.message}`);
}
default:
throw new Error(`Unknown file action: ${args.action}`);
}
}
case "manage_replies": {
const args = safeGetArgs(request.params.arguments, {
action: '',
messageType: '',
teamId: '',
channelId: '',
chatId: '',
messageId: '',
content: '',
contentType: 'text',
maxResults: 20
});
if (!args.action || !args.messageType || !args.messageId) {
throw new Error("Action, messageType, and messageId are required");
}
switch (args.action) {
case "reply":
if (!args.content) {
throw new Error("content is required for reply action");
}
if (args.messageType === 'channel') {
if (!args.teamId || !args.channelId) {
throw new Error("teamId and channelId are required for channel message replies");
}
const reply = await teamsOperations.replyToChannelMessage(args.teamId, args.channelId, args.messageId, args.content, args.contentType);
return {
content: [{
type: "text",
text: `ā
**Reply Posted Successfully!**\n\n**Reply ID:** ${reply.id}\n**Original Message:** ${args.messageId}\n**Posted at:** ${new Date(reply.createdDateTime).toLocaleString()}\n**Content:** ${reply.body.content.substring(0, 100)}${reply.body.content.length > 100 ? '...' : ''}`
}]
};
}
else if (args.messageType === 'chat') {
if (!args.chatId) {
throw new Error("chatId is required for chat message replies");
}
const reply = await teamsOperations.replyToChatMessage(args.chatId, args.messageId, args.content, args.contentType);
return {
content: [{
type: "text",
text: `ā
**Reply Posted Successfully!**\n\n**Reply ID:** ${reply.id}\n**Original Message:** ${args.messageId}\n**Posted at:** ${new Date(reply.createdDateTime).toLocaleString()}\n**Content:** ${reply.body.content.substring(0, 100)}${reply.body.content.length > 100 ? '...' : ''}`
}]
};
}
else {
throw new Error("messageType must be 'channel' or 'chat'");
}
case "get_replies":
if (args.messageType === 'channel') {
if (!args.teamId || !args.channelId) {
throw new Error("teamId and channelId are required for channel message replies");
}
const replies = await teamsOperations.getChannelMessageReplies(args.teamId, args.channelId, args.messageId, args.maxResults);
if (replies.length === 0) {
return {
content: [{
type: "text",
text: `š¬ **No replies found for message ${args.messageId}**`
}]
};
}
const replyList = replies.map(reply => `**${reply.from?.user?.displayName || 'Unknown'}** - ${new Date(reply.createdDateTime).toLocaleString()}\n` +
`${reply.body.content.substring(0, 200)}${reply.body.content.length > 200 ? '...' : ''}\n` +
`Reply ID: ${reply.id}\n`).join('\n');
return {
content: [{
type: "text",
text: `š¬ **Message Replies (${replies.length})**\n\n${replyList}`
}]
};
}
else if (args.messageType === 'chat') {
if (!args.chatId) {
throw new Error("chatId is required for chat message replies");
}
const replies = await teamsOperations.getChatMessageReplies(args.chatId, args.messageId, args.maxResults);
if (replies.length === 0) {
return {
content: [{
type: "text",
text: `š¬ **No replies found for message ${args.messageId}**`
}]
};
}
const replyList = replies.map(reply => `**${reply.from?.user?.displayName || 'Unknown'}** - ${new Date(reply.createdDateTime).toLocaleString()}\n` +
`${reply.body.content.substring(0, 200)}${reply.body.content.length > 200 ? '...' : ''}\n` +
`Reply ID: ${reply.id}\n`).join('\n');
return {
content: [{
type: "text",
text: `š¬ **Message Replies (${replies.length})**\n\n${replyList}`
}]
};
}
else {
throw new Error("messageType must be 'channel' or 'chat'");
}
default:
throw new Error(`Unknown reply action: ${args.action}`);
}
}
case "manage_group_chats": {
const args = safeGetArgs(request.params.arguments, {
action: '',
chatId: '',
topic: '',
members: [],
membershipId: '',
maxResults: 20
});
if (!args.action) {
throw new Error("Action is required");
}
switch (args.action) {
case "create":
if (!args.members || args.members.length === 0) {
throw new Error("members array is required and must not be empty for group chat creation");
}
const groupChat = await teamsOperations.createGroupChat({
topic: args.topic,
members: args.members
});
return {
content: [{
type: "text",
text: `ā
**Group Chat Created Successfully!**\n\n**Chat ID:** ${groupChat.id}\n**Topic:** ${groupChat.topic || 'No topic set'}\n**Members:** ${args.members.length}\n**Created:** ${new Date(groupChat.createdDateTime).toLocaleString()}\n\nš¬ The group chat is now ready for messaging!`
}]
};
case "list":
const chats = await teamsOperations.getGroupChats(args.maxResults);
if (chats.length === 0) {
return {
content: [{
type: "text",
text: `š„ **No group chats found**\n\nYou're not a member of any group chats yet.`
}]
};
}
const chatList = chats.map(chat => `**${chat.topic || 'Untitled Chat'}**\n` +
`ID: ${chat.id}\n` +
`Created: ${new Date(chat.createdDateTime).toLocaleDateString()}\n` +
`Last Updated: ${new Date(chat.lastUpdatedDateTime).toLocaleDateString()}\n`).join('\n');
return {
content: [{
type: "text",
text: `š„ **Your Group Chats (${chats.length})**\n\n${chatList}`
}]
};
case "get_members":
if (!args.chatId) {
throw new Error("chatId is required for getting group chat members");
}
const members = await teamsOperations.getGroupChatMembers(args.chatId);
const memberList = members.map(member => `**${member.displayName}**\n` +
`Email: ${member.email}\n` +
`ID: ${member.id}\n` +
`Roles: ${member.roles.join(', ')}\n`).join('\n');
return {
content: [{
type: "text",
text: `š„ **Group Chat Members (${members.length})**\n\n${memberList}`
}]
};
case "add_members":
if (!args.chatId) {
throw new Error("chatId is required for adding members");
}
if (!args.members || args.members.length === 0) {
throw new Error("members array is required for adding members");
}
await teamsOperations.addGroupChatMembers(args.chatId, args.members);
return {
content: [{
type: "text",
text: `ā
**Members Added Successfully!**\n\n**Added ${args.members.length} member(s) to chat ${args.chatId}**\n\nThe new members can now participate in the group chat.`
}]
};
case "remove_member":
if (!args.chatId || !args.membershipId) {
throw new Error("chatId and membershipId are required for removing members");
}
await teamsOperations.removeGroupChatMember(args.chatId, args.membershipId);
return {
content: [{
type: "text",
text: `ā
**Member Removed Successfully!**\n\n**Removed member ${args.membershipId} from chat ${args.chatId}**`
}]
};
default:
throw new Error(`Unknown group chat action: ${args.action}`);
}
}
case "manage_meetings": {
const args = safeGetArgs(request.params.arguments, {
action: '',
subject: '',
startDateTime: '',
endDateTime: '',
attendees: [],
content: '',
isRecordingEnabled: false,
startTime: '',
endTime: '',
maxResults: 20
});
if (!args.action) {
throw new Error("Action is required");
}
switch (args.action) {
case "create_meeting":
if (!args.subject || !args.startDateTime || !args.endDateTime) {
throw new Error("subject, startDateTime, and endDateTime are required for creating meetings");
}
const meeting = await teamsOperations.createOnlineMeeting({
subject: args.subject,
startDateTime: args.startDateTime,
endDateTime: args.endDateTime,
attendees: args.attendees,
content: args.content,
isRecordingEnabled: args.isRecordingEnabled
});
return {
content: [{
type: "text",
text: `ā
**Online Meeting Created Successfully!**\n\n**Subject:** ${meeting.subject}\n**Meeting ID:** ${meeting.id}\n**Start:** ${new Date(meeting.startDateTime).toLocaleString()}\n**End:** ${new Date(meeting.endDateTime).toLocaleString()}\n**Recording:** ${meeting.isRecordingEnabled ? 'Enabled' : 'Disabled'}\n\nš **Join URL:** ${meeting.joinWebUrl}\n\nš
Share this link with attendees to join the meeting!`
}]
};
case "list_meetings":
const meetings = await teamsOperations.getOnlineMeetings(args.maxResults);
if (meetings.length === 0) {
return {
content: [{
type: "text",
text: `š
**No online meetings found**\n\nYou don't have any online meetings scheduled.`
}]
};
}
const meetingList = meetings.map(meeting => `**${meeting.subject}**\n` +
`Start: ${new Date(meeting.startDateTime).toLocaleString()}\n` +
`End: ${new Date(meeting.endDateTime).toLocaleString()}\n` +
`Join: ${meeting.joinWebUrl}\n` +
`ID: ${meeting.id}\n`).join('\n');
return {
content: [{
type: "text",
text: `š
**Your Online Meetings (${meetings.length})**\n\n${meetingList}`
}]
};
case "get_calendar":
const events = await teamsOperations.getCalendarEvents(args.startTime, args.endTime, args.maxResults);
if (events.length === 0) {
return {
content: [{
type: "text",
text: `š
**No calendar events found**\n\nNo events found for the specified time range.`
}]
};
}
const eventList = events.map(event => {
const start = new Date(event.start.dateTime).toLocaleString();
const end = new Date(event.end.dateTime).toLocaleString();
const location = event.location?.displayName || 'No location';
const organizer = event.organizer.emailAddress.name;
const isOnline = event.isOnlineMeeting ? 'š Online Meeting' : 'š In-person';
const joinUrl = event.onlineMeeting?.joinUrl ? `\nJoin: ${event.onlineMeeting.joinUrl}` : '';
return `**${event.subject}** ${isOnline}\n` +
`Organizer: ${organizer}\n` +
`Start: ${start}\n` +
`End: ${end}\n` +
`Location: ${location}${joinUrl}\n`;
}).join('\n');
return {
content: [{
type: "text",
text: `š
**Calendar Events (${events.length})**\n\n${eventList}`
}]
};
default:
throw new Error(`Unknown meeting action: ${args.action}`);
}
}
case "manage_presence": {
const args = safeGetArgs(request.params.arguments, {
action: '',
userId: '',
userIds: [],
availability: '',
activity: '',
expirationDuration: ''
});
if (!args.action) {
throw new Error("Action is required");
}
switch (args.action) {
case "get_presence":
const presence = await teamsOperations.getUserPresence(args.userId);
const availabilityEmoji = {
'Available': 'š¢',
'AvailableIdle': 'š”',
'Away': 'š”',
'BeRightBack': 'š”',
'Busy': 'š“',
'BusyIdle': 'š“',
'DoNotDisturb': 'š“',
'Offline': 'ā«',
'PresenceUnknown': 'ā'
};
const emoji = availabilityEmoji[presence.availability] || 'ā';
const statusMsg = presence.statusMessage?.message?.content || 'No status message';
return {
content: [{
type: "text",
text: `š¤ **User Presence**\n\n**Status:** ${emoji} ${presence.availability}\n**Activity:** ${presence.activity}\n**Status Message:** ${statusMsg}\n**User ID:** ${presence.id}`
}]
};
case "set_presence":
if (!args.availability || !args.activity) {
throw new Error("availability and activity are required for setting presence");
}
await teamsOperations.setUserPresence({
availability: args.availability,
activity: args.activity,
expirationDuration: args.expirationDuration
});
return {
content: [{
type: "text",
text: `ā
**Presence Updated Successfully!**\n\n**Availability:** ${args.availability}\n**Activity:** ${args.activity}\n**Duration:** ${args.expirationDuration || 'PT1H (1 hour default)'}\n\nYour status has been updated and will be visible to your colleagues.`
}]
};
case "get_bulk_presence":
if (!args.userIds || args.userIds.length === 0) {
throw new Error("userIds array is required for bulk presence lookup");
}
const bulkPresence = await teamsOperations.getBulkPresence(args.userIds);
const presenceList = bulkPresence.map(p => {
const emoji = availabilityEmoji[p.availability] || 'ā';
const statusMsg = p.statusMessage?.message?.content || 'No status';
return `**${p.id}**\n${emoji} ${p.availability} - ${p.activity}\nStatus: ${statusMsg}\n`;
}).join('\n');
return {
content: [{
type: "text",
text: `š„ **Bulk User Presence (${bulkPresence.length})**\n\n${presenceList}`
}]
};
default:
throw new Error(`Unknown presence action: ${args.action}`);
}
}
default:
throw new Error(`Unknown tool: ${request.params.name}`);
}
}
catch (error) {
logger.error('Error executing tool:', error);
return {
content: [{
type: "text",
text: `ā **Error:** ${error.message}\n\nPlease check your authentication status and try again. If the error persists, check the log file for more details.`
}]
};
}
});
async function main() {
try {
teamsConfig = parseArgs();
logger.log('Parsed configuration: ' + JSON.stringify(teamsConfig));
// Set environment variables from command line arguments if provided
if (teamsConfig.clientId) {
process.env.TEAMS_CLIENT_ID = teamsConfig.clientId;
logger.log(`Set TEAMS_CLIENT_ID from command line: ${teamsConfig.clientId}`);
}
if (teamsConfig.tenantId) {
process.env.TEAMS_TENANT_ID = teamsConfig.tenantId;
logger.log(`Set TEAMS_TENANT_ID from command line: ${teamsConfig.tenantId}`);
}
if (teamsConfig.clientSecret) {
process.env.TEAMS_CLIENT_SECRET = teamsConfig.clientSecret;
logger.log(`Set TEAMS_CLIENT_SECRET from command line: [REDACTED]`);
}
if (teamsConfig.redirectUri) {
process.env.TEAMS_REDIRECT_URI = teamsConfig.redirectUri;
logger.log(`Set TEAMS_REDIRECT_URI from command line: ${teamsConfig.redirectUri}`);
}
if (teamsConfig.userId) {
process.env.USER_ID = teamsConfig.userId;
logger.log(`Set USER_ID from command line: ${teamsConfig.userId}`);
}
// Handle CLI commands
if (teamsConfig.login) {
console.log('š Starting Microsoft Teams authentication...');
logger.log('About to call teamsAuth.authenticate()');
try {
logger.log('Calling teamsAuth.authenticate()');
await teamsAuth.authenticate();
const authStatus = await teamsAuth.getAuthenticationStatus();
console.log(`ā
Successfully authenticated as: ${authStatus.user?.displayName || 'Unknown user'}`);
console.log('You can now start the MCP server with: msteams-mcp-server');
process.exit(0);
}
catch (error) {
console.error(`ā Authentication failed: ${error.message}`);
process.exit(1);
}
}
if (teamsConfig.logout) {
console.log('š Logging out from Microsoft Teams...');
try {
await teamsAuth.logout();
console.log('ā
Successfully logged out');
process.exit(0);
}
catch (error) {
console.error(`ā Logout failed: ${error.message}`);
process.exit(1);
}
}
if (teamsConfig.verifyLogin) {
console.log('š Checking authentication status...');
try {
const authStatus = await teamsAuth.getAuthenticationStatus();
if (authStatus.isAuthenticated) {
console.log(`ā
Authenticated as: ${authStatus.user?.displayName || 'Unknown user'}`);
process.exit(0);
}
else {
console.log('ā Not authenticated. Run with --login to authenticate.');
process.exit(1);
}
}
catch (error) {
console.error(`ā Error checking authentication: ${error.message}`);
process.exit(1);
}
}
if (teamsConfig.resetAuth) {
console.log('š Resetting authentication...');
try {
await teamsAuth.logout();
console.log('ā
Authentication reset successfully');
process.exit(0);
}
catch (error) {
console.error(`ā Reset failed: ${error.message}`);
process.exit(1);
}
}
// Start MCP server
const transport = new StdioServerTransport();
await server.connect(transport);
logger.log('Microsoft Teams MCP server connected via stdio transport');
}
catch (error) {
logger.error('Fatal error in main:', error);
console.error('Fatal error:', error.message);
process.exit(1);
}
}
// Handle graceful shutdown
process.on('SIGINT', async () => {
logger.log('Received SIGINT, shutting down gracefully...');
process.exit(0);
});
process.on('SIGTERM', async () => {
logger.log('Received SIGTERM, shutting down gracefully...');
process.exit(0);
});
// Start the server
main().catch((error) => {
logger.error('Unhandled error in main:', error);
console.error('Unhandled error:', error);
process.exit(1);
});