MCP server providing Microsoft Outlook integration via Graph API: - Email: list, search, read, send, thread reconstruction - Calendar: list, create, decline, cancel, delete events - Folders: list, create, move emails - Inbox rules: list, create, reorder - MSAL device code flow auth with persistent token cache - Test mode with mock data - Comprehensive README with setup, config, and tool reference 20 MCP tools across 5 modules. Node.js >= 14. MIT license.
204 lines
6.8 KiB
JavaScript
204 lines
6.8 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Outlook MCP Server - Main entry point
|
|
*
|
|
* A Model Context Protocol server that provides access to
|
|
* Microsoft Outlook through the Microsoft Graph API.
|
|
*
|
|
* INSTRUCTIONS FOR AI MODELS:
|
|
* This server provides comprehensive Outlook integration with the following capabilities:
|
|
*
|
|
* 🔐 AUTHENTICATION (Required First):
|
|
* - Use `check-auth-status()` to verify authentication
|
|
* - Use `authenticate()` if not authenticated (follow the provided URL)
|
|
*
|
|
* 📧 EMAIL MANAGEMENT:
|
|
* - `list-emails()` - List emails with advanced date filtering. Results include conversationId.
|
|
* - `search-emails({ from, subject, query, unreadOnly, hasAttachments })` - Search emails. Results include conversationId.
|
|
* - `read-email({ id })` - Read full email content (body auto-cleaned)
|
|
* - `read-emails({ ids: [id1, id2] })` - Read multiple emails at once (max: 10, bodies auto-cleaned)
|
|
* - `get-email-thread({ conversationId })` - Fetch a complete deduplicated thread (quoted replies stripped). Use conversationId from list-emails or search-emails. Fallback: pass ids array of specific message IDs.
|
|
* - `send-email({ to, subject, body })` - Send new emails
|
|
*
|
|
* 💡 RECOMMENDED EMAIL WORKFLOW:
|
|
* 1. search-emails() or list-emails() → get conversationId from results
|
|
* 2. get-email-thread({ conversationId }) → read the full thread efficiently
|
|
*
|
|
* 📅 CALENDAR MANAGEMENT:
|
|
* - `list-events()` - List calendar events
|
|
* - `create-event({ subject, start, end })` - Create meetings
|
|
* - `decline-event()`, `cancel-event()` - Respond to invitations
|
|
*
|
|
* 📁 FOLDER MANAGEMENT:
|
|
* - `list-folders()` - List mail folders
|
|
* - `create-folder({ name })` - Create new folders
|
|
* - `move-emails({ emailIds, targetFolder })` - Organize emails
|
|
*
|
|
* 📋 EMAIL RULES:
|
|
* - `list-rules()` - List inbox rules
|
|
* - `create-rule({ name, conditions, actions })` - Automate email handling
|
|
*
|
|
* 💡 KEY FEATURES:
|
|
* - Date filtering: Use dateRange ("today", "last7days") or dateFrom/dateTo
|
|
* - High limits: Up to 500 emails/events (WARNING: may consume significant tokens)
|
|
* - Comprehensive search: Filter by sender, subject, attachments, read status
|
|
* - Full automation: Create rules for automatic email organization
|
|
*
|
|
* 📖 For complete documentation, see MCP_TOOLS_GUIDE.md
|
|
*/
|
|
const { Server } = require("@modelcontextprotocol/sdk/server/index.js");
|
|
const { StdioServerTransport } = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
const config = require('./config');
|
|
const { authTools } = require('./auth');
|
|
const { calendarTools } = require('./calendar');
|
|
const { emailTools } = require('./email');
|
|
const { folderTools } = require('./folder');
|
|
const { rulesTools } = require('./rules');
|
|
const { threadTool } = require('./tools/get-email-thread');
|
|
|
|
// Log startup information
|
|
console.error(`STARTING ${config.SERVER_NAME.toUpperCase()} MCP SERVER`);
|
|
console.error(`Test mode is ${config.USE_TEST_MODE ? 'enabled' : 'disabled'}`);
|
|
if (config.DEBUG_MODE) {
|
|
console.error(`[DEBUG] Current Working Directory: ${process.cwd()}`);
|
|
console.error(`[DEBUG] MS_CLIENT_ID: ${process.env.MS_CLIENT_ID ? 'SET' : 'NOT SET'}`);
|
|
console.error(`[DEBUG] USE_TEST_MODE: ${process.env.USE_TEST_MODE ? 'SET' : 'NOT SET'}`);
|
|
}
|
|
|
|
// Combine all tools
|
|
const TOOLS = [
|
|
...authTools,
|
|
...calendarTools,
|
|
...emailTools,
|
|
...folderTools,
|
|
...rulesTools,
|
|
threadTool
|
|
];
|
|
|
|
// Create server with tools capabilities
|
|
const server = new Server(
|
|
{ name: config.SERVER_NAME, version: config.SERVER_VERSION },
|
|
{
|
|
capabilities: {
|
|
tools: TOOLS.reduce((acc, tool) => {
|
|
acc[tool.name] = {};
|
|
return acc;
|
|
}, {})
|
|
}
|
|
}
|
|
);
|
|
|
|
// Handle all requests
|
|
server.fallbackRequestHandler = async (request) => {
|
|
try {
|
|
const { method, params, id } = request;
|
|
console.error(`REQUEST: ${method} [${id}]`);
|
|
|
|
// Initialize handler
|
|
if (method === "initialize") {
|
|
console.error(`INITIALIZE REQUEST: ID [${id}]`);
|
|
return {
|
|
protocolVersion: "2024-11-05",
|
|
capabilities: {
|
|
tools: TOOLS.reduce((acc, tool) => {
|
|
acc[tool.name] = {};
|
|
return acc;
|
|
}, {})
|
|
},
|
|
serverInfo: {
|
|
name: config.SERVER_NAME,
|
|
version: config.SERVER_VERSION,
|
|
description: "Comprehensive Outlook integration with email, calendar, folders, and rules management. See MCP_TOOLS_GUIDE.md for complete documentation."
|
|
}
|
|
};
|
|
}
|
|
|
|
// Tools list handler
|
|
if (method === "tools/list") {
|
|
console.error(`TOOLS LIST REQUEST: ID [${id}]`);
|
|
console.error(`TOOLS COUNT: ${TOOLS.length}`);
|
|
console.error(`TOOLS NAMES: ${TOOLS.map(t => t.name).join(', ')}`);
|
|
|
|
return {
|
|
tools: TOOLS.map(tool => ({
|
|
name: tool.name,
|
|
description: tool.description,
|
|
inputSchema: tool.inputSchema
|
|
}))
|
|
};
|
|
}
|
|
|
|
// Required empty responses for other capabilities
|
|
if (method === "resources/list") return { resources: [] };
|
|
if (method === "prompts/list") return { prompts: [] };
|
|
|
|
// Tool call handler
|
|
if (method === "tools/call") {
|
|
try {
|
|
const { name, arguments: args = {} } = params || {};
|
|
|
|
console.error(`TOOL CALL: ${name}`);
|
|
|
|
// Find the tool handler
|
|
const tool = TOOLS.find(t => t.name === name);
|
|
|
|
if (tool && tool.handler) {
|
|
return await tool.handler(args);
|
|
}
|
|
|
|
// Tool not found
|
|
return {
|
|
error: {
|
|
code: -32601,
|
|
message: `Tool not found: ${name}`
|
|
}
|
|
};
|
|
} catch (error) {
|
|
console.error(`Error in tools/call:`, error);
|
|
return {
|
|
error: {
|
|
code: -32603,
|
|
message: `Error processing tool call: ${error.message}`
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
// For any other method, return method not found
|
|
return {
|
|
error: {
|
|
code: -32601,
|
|
message: `Method not found: ${method}`
|
|
}
|
|
};
|
|
} catch (error) {
|
|
console.error(`Error in fallbackRequestHandler:`, error);
|
|
return {
|
|
error: {
|
|
code: -32603,
|
|
message: `Error processing request: ${error.message}`
|
|
}
|
|
};
|
|
}
|
|
};
|
|
|
|
// Make the script executable
|
|
process.on('SIGTERM', () => {
|
|
console.error('SIGTERM received, exiting.');
|
|
process.exit(0);
|
|
});
|
|
|
|
process.on('exit', () => {
|
|
// Any cleanup needed when the main server exits
|
|
});
|
|
|
|
// Start the server
|
|
const transport = new StdioServerTransport();
|
|
server.connect(transport)
|
|
.then(() => {
|
|
console.error(`${config.SERVER_NAME} connected and listening`);
|
|
})
|
|
.catch(error => {
|
|
console.error(`Connection error: ${error.message}`);
|
|
process.exit(1);
|
|
});
|