ms365-mcp-server
Version:
Microsoft 365 MCP Server for managing Microsoft 365 email through natural language interactions with full OAuth2 authentication support
997 lines (996 loc) ⢠160 kB
JavaScript
/**
* Microsoft 365 MCP Server
* A comprehensive server implementation using Model Context Protocol for Microsoft 365 API
*/
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import express from 'express';
import cors from 'cors';
import path from 'path';
import { fileURLToPath } from 'url';
import fs from 'fs/promises';
import crypto from 'crypto';
import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, ReadResourceRequestSchema, ListResourceTemplatesRequestSchema, GetPromptRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import { logger } from './utils/api.js';
import { MS365Operations } from './utils/ms365-operations.js';
import { multiUserMS365Auth } from './utils/multi-user-auth.js';
import { outlookAuth } from './utils/outlook-auth.js';
import { IntelligenceEngine } from './utils/intelligence-engine.js';
import { ProactiveIntelligence } from './utils/proactive-intelligence.js';
import { DocumentWorkflow } from './utils/document-workflow.js';
import { ContextAwareSearch } from './utils/context-aware-search.js';
import { LargeMailboxSearch } from './utils/large-mailbox-search.js';
import { BatchTestRunner } from './utils/batch-test-scenarios.js';
import { performanceMonitor } from './utils/batch-performance-monitor.js';
import { SearchBatchPipeline } from './utils/search-batch-pipeline.js';
// Create singleton MS365Operations instance
const ms365Ops = new MS365Operations();
// Create singleton intelligence services
const intelligenceEngine = new IntelligenceEngine(ms365Ops);
const proactiveIntelligence = new ProactiveIntelligence(ms365Ops);
const documentWorkflow = new DocumentWorkflow();
const contextAwareSearch = new ContextAwareSearch(ms365Ops);
const largeMailboxSearch = new LargeMailboxSearch(ms365Ops, contextAwareSearch, intelligenceEngine, proactiveIntelligence);
const batchTestRunner = new BatchTestRunner(ms365Ops);
const searchBatchPipeline = new SearchBatchPipeline(ms365Ops);
let ms365Config = {
setupAuth: false,
resetAuth: false,
debug: false,
nonInteractive: false,
multiUser: false,
login: false,
logout: false,
verifyLogin: false,
serverUrl: process.env.SERVER_URL || 'http://localhost:55000'
};
function parseArgs() {
const args = process.argv.slice(2);
const config = {
setupAuth: false,
resetAuth: false,
debug: false,
nonInteractive: false,
multiUser: false,
login: false,
logout: false,
verifyLogin: false,
serverUrl: process.env.SERVER_URL || 'http://localhost:55000'
};
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 === '--multi-user')
config.multiUser = 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 === '--server-url' && i + 1 < args.length) {
config.serverUrl = args[++i];
}
}
return config;
}
const server = new Server({
name: "ms365-mcp-server",
version: "1.0.0"
}, {
capabilities: {
resources: {
read: true,
list: true,
templates: true
},
tools: {
list: true,
call: true
},
prompts: {
list: true,
get: true
},
resourceTemplates: {
list: true
}
}
});
logger.log('Server started with version 1.1.16');
// 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 365.
*/
server.setRequestHandler(ListToolsRequestSchema, async () => {
const baseTools = [
{
name: "send_email",
description: "Send an email message with support for HTML content, attachments (via file paths or Base64), and international characters. Supports both plain text and HTML emails with proper formatting. NEW: File path attachments with automatic MIME detection and Base64 conversion.",
inputSchema: {
type: "object",
properties: {
userId: {
type: "string",
description: "User ID for multi-user authentication (required if using multi-user mode)"
},
to: {
oneOf: [
{ type: "string" },
{ type: "array", items: { type: "string" } }
],
description: "Recipient email address (string) or list of recipient email addresses (array)"
},
cc: {
oneOf: [
{ type: "string" },
{ type: "array", items: { type: "string" } }
],
description: "CC recipient email address (string) or list of CC recipient email addresses (array) - optional"
},
bcc: {
oneOf: [
{ type: "string" },
{ type: "array", items: { type: "string" } }
],
description: "BCC recipient email address (string) or list of BCC recipient email addresses (array) - optional"
},
subject: {
type: "string",
description: "Email subject line with full support for international characters"
},
body: {
type: "string",
description: "Email content (text or HTML based on bodyType)"
},
bodyType: {
type: "string",
enum: ["text", "html"],
description: "Content type of the email body (default: text)",
default: "text"
},
replyTo: {
type: "string",
description: "Reply-to email address (optional)"
},
importance: {
type: "string",
enum: ["low", "normal", "high"],
description: "Email importance level (default: normal)",
default: "normal"
},
attachments: {
type: "array",
items: {
type: "object",
properties: {
name: {
type: "string",
description: "Name of the attachment file"
},
contentBytes: {
type: "string",
description: "Base64 encoded content of the attachment (required if filePath not provided)"
},
filePath: {
type: "string",
description: "ABSOLUTE PATH (full path) to the file to attach (alternative to contentBytes). MUST start with '/' - relative paths like './file.pdf' will NOT work. Supports Unicode filenames, spaces, and special characters. Example: '/Users/name/Documents/file with spaces.pdf'"
},
contentType: {
type: "string",
description: "MIME type of the attachment (optional, auto-detected from file extension if not provided). Supports PDF, Office docs, images, text files, etc."
}
},
required: ["name"]
},
description: "List of email attachments (optional). Each attachment can use either 'filePath' (ABSOLUTE PATH - must start with '/') or 'contentBytes' (Base64 encoded data). File paths support automatic MIME detection and Unicode filenames. IMPORTANT: Relative paths like './file.pdf' will fail."
}
},
required: ["to", "subject"]
}
},
{
name: "manage_email",
description: "š GRAPH API COMPLIANT EMAIL MANAGEMENT: Advanced email operations with Microsoft Graph API compliance. ā
FIXED: Separated $filter and $search operations to respect Graph API limitations. ā
OPTIMIZED: Dynamic timeouts (30-60s), intelligent strategy selection, and proper field restrictions. š§ Actions: search, search_to_me, list, mark, move, delete, draft operations, threading support",
inputSchema: {
type: "object",
properties: {
userId: {
type: "string",
description: "User ID for multi-user authentication (required if using multi-user mode)"
},
action: {
type: "string",
enum: ["read", "search", "search_batched", "list", "mark", "move", "delete", "search_to_me", "draft", "update_draft", "send_draft", "list_drafts", "reply_draft", "forward_draft"],
description: "Action to perform: read (get email by ID), search (find emails), search_batched (find emails in batches of 50, up to 5 batches for better performance), list (folder contents), mark (read/unread), move (to folder), delete (permanently), search_to_me (emails addressed to you), draft (create standalone draft - NOTE: Use reply_draft/forward_draft for threaded drafts), update_draft (modify existing draft), send_draft (send saved draft), list_drafts (list draft emails), reply_draft (create threaded reply draft that appears in conversation), forward_draft (create threaded forward draft that appears in conversation)"
},
messageId: {
type: "string",
description: "Email ID (required for: read, mark, move, delete)"
},
includeAttachments: {
type: "boolean",
description: "Include attachment info when reading email",
default: false
},
isRead: {
type: "boolean",
description: "Mark as read (true) or unread (false) - used with mark action"
},
destinationFolderId: {
type: "string",
description: "Destination folder for move action (e.g., 'archive', 'drafts', 'deleteditems')"
},
folderId: {
type: "string",
description: "Folder to list emails from (default: inbox) - used with list action",
default: "inbox"
},
query: {
type: "string",
description: "General search query using natural language or specific terms"
},
from: {
type: "string",
description: "Search emails from specific sender (supports partial names like 'John' or 'Smith')"
},
to: {
type: "string",
description: "Search emails to specific recipient"
},
cc: {
type: "string",
description: "Search emails with specific CC recipient"
},
subject: {
type: "string",
description: "Search emails with specific subject"
},
after: {
type: "string",
description: "Search emails after date (format: YYYY-MM-DD)"
},
before: {
type: "string",
description: "Search emails before date (format: YYYY-MM-DD)"
},
hasAttachment: {
type: "boolean",
description: "Filter emails that have attachments"
},
folder: {
type: "string",
description: "Search within specific folder (default: inbox)"
},
isUnread: {
type: "boolean",
description: "Filter for unread emails only"
},
importance: {
type: "string",
enum: ["low", "normal", "high"],
description: "Filter by email importance level"
},
maxResults: {
type: "number",
description: "Maximum number of results to return. Set to 0 or omit to get ALL matching results (safety limited to 1000). For large result sets, use specific maxResults values to avoid timeouts. Default: 50",
minimum: 0,
default: 50
},
batchSize: {
type: "number",
description: "Number of results per batch for search_batched action (default: 50)",
minimum: 10,
maximum: 100,
default: 50
},
maxBatches: {
type: "number",
description: "Maximum number of batches for search_batched action (default: 5)",
minimum: 1,
maximum: 10,
default: 5
},
// Draft email parameters
draftTo: {
oneOf: [
{ type: "string" },
{ type: "array", items: { type: "string" } }
],
description: "Recipient email address (string) or list of recipient email addresses (array) - required for draft action"
},
draftCc: {
oneOf: [
{ type: "string" },
{ type: "array", items: { type: "string" } }
],
description: "CC recipient email address (string) or list of CC recipient email addresses (array) - optional for draft action"
},
draftBcc: {
oneOf: [
{ type: "string" },
{ type: "array", items: { type: "string" } }
],
description: "BCC recipient email address (string) or list of BCC recipient email addresses (array) - optional for draft action"
},
draftSubject: {
type: "string",
description: "Email subject line (required for draft action)"
},
draftBody: {
type: "string",
description: "Email content (required for draft action)"
},
draftBodyType: {
type: "string",
enum: ["text", "html"],
description: "Content type of the email body for draft (default: text)",
default: "text"
},
draftImportance: {
type: "string",
enum: ["low", "normal", "high"],
description: "Email importance level for draft (default: normal)",
default: "normal"
},
draftAttachments: {
type: "array",
items: {
type: "object",
properties: {
name: {
type: "string",
description: "Name of the attachment file"
},
contentBytes: {
type: "string",
description: "Base64 encoded content of the attachment (required if filePath not provided)"
},
filePath: {
type: "string",
description: "ABSOLUTE PATH (full path) to the file to attach (alternative to contentBytes). MUST start with '/' - relative paths like './file.pdf' will NOT work. Supports Unicode filenames, spaces, and special characters. Example: '/Users/name/Documents/file with spaces.pdf'"
},
contentType: {
type: "string",
description: "MIME type of the attachment (optional, auto-detected from file extension if not provided). Supports PDF, Office docs, images, text files, etc."
}
},
required: ["name"]
},
description: "List of email attachments for draft (optional). Each attachment can use either 'filePath' (ABSOLUTE PATH - must start with '/') or 'contentBytes' (Base64 encoded data). File paths support automatic MIME detection and Unicode filenames. IMPORTANT: Relative paths like './file.pdf' will fail."
},
// Additional draft parameters
draftId: {
type: "string",
description: "Draft email ID (required for update_draft and send_draft actions)"
},
originalMessageId: {
type: "string",
description: "Original message ID for reply_draft and forward_draft actions"
},
replyToAll: {
type: "boolean",
description: "Reply to all recipients (used with reply_draft action)",
default: false
},
conversationId: {
type: "string",
description: "Conversation ID for threading drafts (optional)"
}
},
required: ["action"]
}
},
{
name: "get_attachment",
description: "Download and retrieve attachment information from an email. Returns attachment data, filename, and metadata.",
inputSchema: {
type: "object",
properties: {
userId: {
type: "string",
description: "User ID for multi-user authentication (required if using multi-user mode)"
},
messageId: {
type: "string",
description: "Microsoft 365 message ID containing the attachment"
},
attachmentId: {
type: "string",
description: "Attachment ID to download"
}
},
required: ["messageId", "attachmentId"]
}
},
{
name: "list_folders",
description: "List all mail folders in the mailbox including subfolders. Returns folder hierarchy with names, IDs, paths, and item counts for navigation and organization. Now includes child folders inside parent folders like Inbox subfolders.",
inputSchema: {
type: "object",
properties: {
userId: {
type: "string",
description: "User ID for multi-user authentication (required if using multi-user mode)"
},
parentFolderId: {
type: "string",
description: "Optional: List only child folders of this parent folder ID. If not specified, returns complete folder hierarchy."
},
searchName: {
type: "string",
description: "Optional: Search for folders by name (case-insensitive partial match)"
}
}
}
},
{
name: "manage_contacts",
description: "UNIFIED CONTACT MANAGEMENT: Get all contacts or search contacts by name/email. Combines contact operations in one tool.",
inputSchema: {
type: "object",
properties: {
userId: {
type: "string",
description: "User ID for multi-user authentication (required if using multi-user mode)"
},
action: {
type: "string",
enum: ["list", "search"],
description: "Action: list (get all contacts) or search (find specific contacts)",
default: "list"
},
query: {
type: "string",
description: "Search query for contact name or email address (required for search action)"
},
maxResults: {
type: "number",
description: "Maximum number of contacts to return (default: 100 for list, 50 for search, max: 500)",
minimum: 1,
maximum: 500
}
}
}
},
{
name: "batch_operations",
description: "š NATIVE GRAPH BATCHING: Leverage Microsoft Graph's native JSON batching API to perform up to 20 operations in a single HTTP request. Dramatically improves performance for multiple email operations. Based on official Microsoft Graph batching documentation.",
inputSchema: {
type: "object",
properties: {
userId: {
type: "string",
description: "User ID for multi-user authentication (required if using multi-user mode)"
},
action: {
type: "string",
enum: ["batch_get_emails", "batch_email_operations", "batch_folder_operations", "get_folder_with_emails"],
description: "Batch action: batch_get_emails (get multiple emails efficiently), batch_email_operations (mark/move/delete multiple emails), batch_folder_operations (multiple folder operations), get_folder_with_emails (get folder info and recent emails together)"
},
messageIds: {
type: "array",
items: { type: "string" },
description: "List of message IDs for batch_get_emails and batch_email_operations actions"
},
includeAttachments: {
type: "boolean",
description: "Include attachment info when getting emails (batch_get_emails action)",
default: false
},
operations: {
type: "array",
items: {
type: "object",
properties: {
id: {
type: "string",
description: "Unique ID for this operation (for tracking results)"
},
operation: {
type: "string",
enum: ["mark", "move", "delete"],
description: "Operation type: mark (read/unread), move (to folder), delete (permanently)"
},
messageId: {
type: "string",
description: "Message ID to operate on"
},
params: {
type: "object",
properties: {
isRead: {
type: "boolean",
description: "Mark as read (true) or unread (false) for mark operation"
},
destinationFolderId: {
type: "string",
description: "Destination folder ID for move operation"
}
},
description: "Parameters for the operation"
}
},
required: ["id", "operation", "messageId"]
},
description: "List of email operations to perform in batch (max 20 operations)"
},
folderId: {
type: "string",
description: "Folder ID for get_folder_with_emails action"
},
emailCount: {
type: "number",
description: "Number of recent emails to get with folder info (default: 10, max: 50)",
minimum: 1,
maximum: 50,
default: 10
}
},
required: ["action"]
}
},
{
name: "ai_email_assistant",
description: "š¤ AI EMAIL ASSISTANT: GRAPH API COMPLIANT intelligent email companion with OPTIMIZED LARGE MAILBOX SUPPORT. ā
FIXED: Proper $filter/$search separation, field restrictions, and timeout handling. Features: Dynamic timeouts (up to 3 minutes), progressive search tiers, Graph API compliance, smart fallbacks. š BEST FOR: Large mailboxes (20k+ emails), complex searches, timeout-sensitive operations.",
inputSchema: {
type: "object",
properties: {
userId: {
type: "string",
description: "User ID for multi-user authentication (required if using multi-user mode)"
},
action: {
type: "string",
enum: ["search", "classify", "process_attachments"],
description: "Action: 'search' (intelligent search with AI), 'classify' (smart email classification), 'process_attachments' (intelligent attachment processing)"
},
query: {
type: "string",
description: "Natural language search query (e.g., 'tax notice from few weeks', 'government emails urgent', 'related to project alpha') - required for search action"
},
// Search options
enableCrossReference: {
type: "boolean",
description: "Enable cross-reference detection to find related emails (search action)",
default: true
},
enableFuzzySearch: {
type: "boolean",
description: "Enable fuzzy matching for better search results (search action)",
default: true
},
enableThreadReconstruction: {
type: "boolean",
description: "Enable thread reconstruction for forwarded email chains (search action)",
default: true
},
enableContextAware: {
type: "boolean",
description: "Enable context-aware search with time/sender understanding (search action)",
default: true
},
folder: {
type: "string",
description: "Search within specific folder (search action, default: all folders)"
},
corpusSize: {
type: "number",
description: "Number of recent emails to search through (search action, default: 200, max: 1000)",
minimum: 10,
maximum: 1000,
default: 200
},
useLargeMailboxStrategy: {
type: "boolean",
description: "Enable intelligent multi-tier search for large mailboxes (20k+ emails) - automatically enabled for large mailboxes",
default: false
},
timeWindowDays: {
type: "number",
description: "Time window in days for large mailbox search (default: 90 for optimal performance, max: 365 for comprehensive search)",
minimum: 30,
maximum: 1095,
default: 90
},
// Classification options
classifyAction: {
type: "string",
enum: ["classify_recent", "classify_folder", "classify_batch", "get_summary"],
description: "Classification action: classify_recent (last 50 emails), classify_folder (specific folder), classify_batch (specific emails), get_summary (classification summary)"
},
folderId: {
type: "string",
description: "Folder ID for classify_folder action (e.g., 'inbox', 'sent')"
},
messageIds: {
type: "array",
items: { type: "string" },
description: "List of message IDs for classify_batch action or specific email for processing"
},
category: {
type: "string",
enum: ["government", "tax", "legal", "financial", "healthcare", "insurance", "deadline", "invoice", "security"],
description: "Filter by specific category (classify action)"
},
priority: {
type: "string",
enum: ["critical", "high", "medium", "low"],
description: "Filter by priority level (classify action)"
},
// Attachment processing options
messageId: {
type: "string",
description: "Email message ID containing attachments (process_attachments action)"
},
attachmentIds: {
type: "array",
items: { type: "string" },
description: "Specific attachment IDs to process (process_attachments action, optional)"
},
autoDecrypt: {
type: "boolean",
description: "Attempt to automatically decrypt password-protected files (process_attachments action)",
default: true
},
extractText: {
type: "boolean",
description: "Extract text content from documents (process_attachments action)",
default: true
},
customPasswords: {
type: "array",
items: { type: "string" },
description: "Additional passwords to try for decryption (process_attachments action)"
},
// General options
maxResults: {
type: "number",
description: "Maximum number of results to return",
minimum: 1,
maximum: 200,
default: 50
}
},
required: ["action"]
}
},
{
name: "performance_testing",
description: "š¬ PERFORMANCE TESTING & BENCHMARKING: Comprehensive testing suite to benchmark traditional vs native batched operations. Compare performance, monitor improvements, and generate detailed reports showing HTTP call reduction and speed improvements.",
inputSchema: {
type: "object",
properties: {
userId: {
type: "string",
description: "User ID for multi-user authentication (required if using multi-user mode)"
},
action: {
type: "string",
enum: ["run_tests", "benchmark_bulk_retrieval", "benchmark_bulk_operations", "benchmark_folder_operations", "benchmark_mixed_operations", "generate_report", "clear_metrics"],
description: "Testing action: run_tests (comprehensive test suite), benchmark_* (specific performance tests), generate_report (create performance report), clear_metrics (reset all metrics)"
},
testEmailCount: {
type: "number",
description: "Number of emails to use for testing (default: 10, max: 20 for safety)",
minimum: 5,
maximum: 20,
default: 10
},
includeAttachments: {
type: "boolean",
description: "Include attachment operations in bulk retrieval tests",
default: true
},
folderId: {
type: "string",
description: "Folder ID to test (default: inbox)",
default: "inbox"
},
operationType: {
type: "string",
enum: ["mark", "move"],
description: "Type of operation for bulk operations test (default: mark)",
default: "mark"
},
generateDetailedLog: {
type: "boolean",
description: "Generate detailed performance logs",
default: true
}
},
required: ["action"]
}
},
{
name: "search_batch_pipeline",
description: "š SEARCH-TO-BATCH PIPELINE: Seamlessly combine search and batch operations for maximum efficiency. Find emails with search, then efficiently process them with native batching. Perfect for bulk operations on search results.",
inputSchema: {
type: "object",
properties: {
userId: {
type: "string",
description: "User ID for multi-user authentication (required if using multi-user mode)"
},
action: {
type: "string",
enum: ["search_and_retrieve", "search_and_mark_read", "search_and_mark_unread", "search_and_move", "search_and_delete", "quick_workflows"],
description: "Pipeline action: search_and_retrieve (search + get full details), search_and_mark_read (search + mark as read), search_and_mark_unread (search + mark as unread), search_and_move (search + move to folder), search_and_delete (search + delete), quick_workflows (pre-built workflows)"
},
// Search criteria
query: {
type: "string",
description: "Search query for finding emails"
},
from: {
type: "string",
description: "Search emails from specific sender"
},
to: {
type: "string",
description: "Search emails to specific recipient"
},
subject: {
type: "string",
description: "Search emails with specific subject"
},
folder: {
type: "string",
description: "Search within specific folder"
},
after: {
type: "string",
description: "Search emails after date (YYYY-MM-DD)"
},
before: {
type: "string",
description: "Search emails before date (YYYY-MM-DD)"
},
hasAttachment: {
type: "boolean",
description: "Filter emails with attachments"
},
isUnread: {
type: "boolean",
description: "Filter for unread emails"
},
importance: {
type: "string",
enum: ["low", "normal", "high"],
description: "Filter by importance level"
},
maxResults: {
type: "number",
description: "Maximum results to find and process (default: 50, max: 100)",
minimum: 1,
maximum: 100,
default: 50
},
// Batch operation parameters
includeAttachments: {
type: "boolean",
description: "Include attachments in search_and_retrieve action",
default: false
},
destinationFolderId: {
type: "string",
description: "Destination folder ID for search_and_move action"
},
// Quick workflow parameters
workflowType: {
type: "string",
enum: ["mark_sender_read", "move_by_subject", "delete_old", "get_attachments"],
description: "Pre-built workflow type for quick_workflows action"
},
senderEmail: {
type: "string",
description: "Sender email for mark_sender_read workflow"
},
subjectText: {
type: "string",
description: "Subject text for move_by_subject workflow"
},
beforeDate: {
type: "string",
description: "Date for delete_old workflow (YYYY-MM-DD)"
}
},
required: ["action"]
}
}
];
// Add multi-user specific tools
const multiUserTools = [
{
name: "authenticate_user",
description: "Start authentication flow for a new user. Returns authentication URL that user needs to visit for Microsoft 365 access.",
inputSchema: {
type: "object",
properties: {
userEmail: {
type: "string",
description: "User's email address (optional, for identification)"
}
}
}
},
{
name: "remove_my_session",
description: "Remove your own authentication session (user-specific, secure).",
inputSchema: {
type: "object",
properties: {
userId: {
type: "string",
description: "Your User ID to remove"
}
},
required: ["userId"]
}
}
];
// Consolidated authentication tools
const authTools = [
{
name: "authenticate",
description: "UNIFIED AUTHENTICATION: Handle all Microsoft 365 authentication needs via OAuth2 redirect flow. Supports login (opens browser), status checking, and logout.",
inputSchema: {
type: "object",
properties: {
action: {
type: "string",
enum: ["login", "status", "logout"],
description: "Auth action: login (OAuth redirect auth - opens browser), status (check auth status), logout (clear tokens)",
default: "login"
},
force: {
type: "boolean",
description: "Force new authentication even if already authenticated (for login action)",
default: false
}
}
}
}
];
if (ms365Config.multiUser) {
return { tools: [...baseTools, ...multiUserTools] };
}
// For single-user mode, always include auth tools
return { tools: [...baseTools, ...authTools] };
});
/**
* Handle tool execution requests
*/
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
// Debug logging for MCP protocol issues
logger.log(`š§ MCP Tool Called: ${name}`);
logger.log(`š§ Args type: ${typeof args}`);
logger.log(`š§ Args content:`, JSON.stringify(args, null, 2));
// Handle string-encoded JSON arguments (common MCP client issue)
let processedArgs = args;
if (typeof args === 'string') {
try {
processedArgs = JSON.parse(args);
logger.log(`š§ Parsed string args to object:`, JSON.stringify(processedArgs, null, 2));
}
catch (parseError) {
logger.error(`ā Failed to parse string arguments: ${parseError}`);
throw new Error(`Invalid JSON in arguments: ${parseError}`);
}
}
// Helper function to validate and normalize bodyType
const normalizeBodyType = (bodyType) => {
if (!bodyType)
return 'text';
const normalized = bodyType.toString().toLowerCase().trim();
if (normalized === 'html')
return 'html';
if (normalized === 'text')
return 'text';
// Invalid value provided - log warning and default to text
logger.log(`ā ļø Invalid bodyType '${bodyType}' provided. Valid values are 'text' or 'html'. Defaulting to 'text'.`);
return 'text';
};
try {
switch (name) {
// ============ UNIFIED AUTHENTICATION TOOL ============
case "authenticate":
const action = processedArgs?.action || 'login';
switch (action) {
case "login":
try {
// Check if already authenticated
if (await outlookAuth.isAuthenticated() && !processedArgs?.force) {
const user = await outlookAuth.getCurrentUser();
return {
content: [
{
type: "text",
text: `ā
Already authenticated with Microsoft 365!\n\nš¤ User: ${user || 'authenticated-user'}\n\nš” Use force: true to re-authenticate.`
}
]
};
}
// Start OAuth redirect flow
const authResult = await outlookAuth.authenticate();
const currentUser = await outlookAuth.getCurrentUser();
return {
content: [
{
type: "text",
text: `ā
Authentication successful!\n\nš¤ User: ${currentUser || 'authenticated-user'}\nš Status: Valid\n\nš You can now use all Microsoft 365 email features!`
}
]
};
}
catch (error) {
// Check if this is an auth required error with URL (MCP context)
if (error.code === 'AUTH_REQUIRED' && error.authUrl) {
return {
content: [
{
type: "text",
text: `š Microsoft 365 Authentication Required\n\nš± Please authenticate using the CLI:\n\nms365-mcp-server --login\n\nOr visit this URL:\n${error.authUrl}`
}
]
};
}
return {
content: [
{
type: "text",
text: `ā Authentication failed: ${error.message}\n\nš” Try using CLI: ms365-mcp-server --login`
}
]
};
}
case "status":
const authStatus = await outlookAuth.getAuthenticationStatus();
let statusText = `š Microsoft 365 Authentication Status\n\n`;
statusText += `š Authentication: ${authStatus.authenticated ? 'ā
Valid' : 'ā Not authenticated'}\n`;
statusText += `š¤ Current User: ${authStatus.username || 'None'}\n`;
if (authStatus.authenticated) {
statusText += `\nā° Token expires: ${authStatus.expiresAt}`;
statusText += `\nā±ļø Expires in: ${authStatus.expiresIn} minutes`;
}
else {