outlook-mcp/index.js
Seton Carmichael a7886b5b2b Initial commit: Outlook MCP Server v1.0.0
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.
2026-06-21 19:39:31 -04:00

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);
});