outlook-mcp
Version:
Comprehensive MCP server for Claude to access Microsoft Outlook and Teams via Microsoft Graph API - including Email, Calendar, Contacts, Tasks, Teams, Chats, and Online Meetings
313 lines (283 loc) • 11.3 kB
JavaScript
#!/usr/bin/env node
const http = require('http');
const url = require('url');
const querystring = require('querystring');
const https = require('https');
const fs = require('fs');
const path = require('path');
// Load environment variables from .env file
require('dotenv').config();
// Log to console
console.log('Starting Outlook Authentication Server');
// Authentication configuration
const AUTH_CONFIG = {
clientId: process.env.MS_CLIENT_ID || '', // Set your client ID as an environment variable
clientSecret: process.env.MS_CLIENT_SECRET || '', // Set your client secret as an environment variable
redirectUri: 'http://localhost:3333/auth/callback',
scopes: [
'offline_access',
'User.Read',
'Mail.Read',
'Mail.Send',
'Calendars.Read',
'Calendars.ReadWrite',
'Contacts.Read'
],
tokenStorePath: path.join(process.env.HOME || process.env.USERPROFILE, '.outlook-mcp-tokens.json')
};
// Create HTTP server
const server = http.createServer((req, res) => {
const parsedUrl = url.parse(req.url, true);
const pathname = parsedUrl.pathname;
console.log(`Request received: ${pathname}`);
if (pathname === '/auth/callback') {
const query = parsedUrl.query;
if (query.error) {
console.error(`Authentication error: ${query.error} - ${query.error_description}`);
res.writeHead(400, { 'Content-Type': 'text/html' });
res.end(`
<html>
<head>
<title>Authentication Error</title>
<style>
body { font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; }
h1 { color: #d9534f; }
.error-box { background-color: #f8d7da; border: 1px solid #f5c6cb; padding: 15px; border-radius: 4px; }
</style>
</head>
<body>
<h1>Authentication Error</h1>
<div class="error-box">
<p><strong>Error:</strong> ${query.error}</p>
<p><strong>Description:</strong> ${query.error_description || 'No description provided'}</p>
</div>
<p>Please close this window and try again.</p>
</body>
</html>
`);
return;
}
if (query.code) {
console.log('Authorization code received, exchanging for tokens...');
// Exchange code for tokens
exchangeCodeForTokens(query.code)
.then((tokens) => {
console.log('Token exchange successful');
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(`
<html>
<head>
<title>Authentication Successful</title>
<style>
body { font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; }
h1 { color: #5cb85c; }
.success-box { background-color: #d4edda; border: 1px solid #c3e6cb; padding: 15px; border-radius: 4px; }
</style>
</head>
<body>
<h1>Authentication Successful!</h1>
<div class="success-box">
<p>You have successfully authenticated with Microsoft Graph API.</p>
<p>The access token has been saved securely.</p>
</div>
<p>You can now close this window and return to Claude.</p>
</body>
</html>
`);
})
.catch((error) => {
console.error(`Token exchange error: ${error.message}`);
res.writeHead(500, { 'Content-Type': 'text/html' });
res.end(`
<html>
<head>
<title>Token Exchange Error</title>
<style>
body { font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; }
h1 { color: #d9534f; }
.error-box { background-color: #f8d7da; border: 1px solid #f5c6cb; padding: 15px; border-radius: 4px; }
</style>
</head>
<body>
<h1>Token Exchange Error</h1>
<div class="error-box">
<p>${error.message}</p>
</div>
<p>Please close this window and try again.</p>
</body>
</html>
`);
});
} else {
console.error('No authorization code provided');
res.writeHead(400, { 'Content-Type': 'text/html' });
res.end(`
<html>
<head>
<title>Missing Authorization Code</title>
<style>
body { font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; }
h1 { color: #d9534f; }
.error-box { background-color: #f8d7da; border: 1px solid #f5c6cb; padding: 15px; border-radius: 4px; }
</style>
</head>
<body>
<h1>Missing Authorization Code</h1>
<div class="error-box">
<p>No authorization code was provided in the callback.</p>
</div>
<p>Please close this window and try again.</p>
</body>
</html>
`);
}
} else if (pathname === '/auth') {
// Handle the /auth route - redirect to Microsoft's OAuth authorization endpoint
console.log('Auth request received, redirecting to Microsoft login...');
// Verify credentials are set
if (!AUTH_CONFIG.clientId || !AUTH_CONFIG.clientSecret) {
res.writeHead(500, { 'Content-Type': 'text/html' });
res.end(`
<html>
<head>
<title>Configuration Error</title>
<style>
body { font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; }
h1 { color: #d9534f; }
.error-box { background-color: #f8d7da; border: 1px solid #f5c6cb; padding: 15px; border-radius: 4px; }
code { background: #f4f4f4; padding: 2px 4px; border-radius: 4px; }
</style>
</head>
<body>
<h1>Configuration Error</h1>
<div class="error-box">
<p>Microsoft Graph API credentials are not set. Please set the following environment variables:</p>
<ul>
<li><code>MS_CLIENT_ID</code></li>
<li><code>MS_CLIENT_SECRET</code></li>
</ul>
</div>
</body>
</html>
`);
return;
}
// Get client_id from query parameters or use the default
const query = parsedUrl.query;
const clientId = query.client_id || AUTH_CONFIG.clientId;
// Build the authorization URL
const authParams = {
client_id: clientId,
response_type: 'code',
redirect_uri: AUTH_CONFIG.redirectUri,
scope: AUTH_CONFIG.scopes.join(' '),
response_mode: 'query',
state: Date.now().toString() // Simple state parameter for security
};
const authUrl = `https://login.microsoftonline.com/common/oauth2/v2.0/authorize?${querystring.stringify(authParams)}`;
console.log(`Redirecting to: ${authUrl}`);
// Redirect to Microsoft's login page
res.writeHead(302, { 'Location': authUrl });
res.end();
} else if (pathname === '/') {
// Root path - provide instructions
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(`
<html>
<head>
<title>Outlook Authentication Server</title>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
h1 { color: #0078d4; }
.info-box { background-color: #e7f6fd; border: 1px solid #b3e0ff; padding: 15px; border-radius: 4px; }
code { background: #f4f4f4; padding: 2px 4px; border-radius: 4px; }
</style>
</head>
<body>
<h1>Outlook Authentication Server</h1>
<div class="info-box">
<p>This server is running to handle Microsoft Graph API authentication callbacks.</p>
<p>Don't navigate here directly. Instead, use the <code>authenticate</code> tool in Claude to start the authentication process.</p>
<p>Make sure you've set the <code>MS_CLIENT_ID</code> and <code>MS_CLIENT_SECRET</code> environment variables.</p>
</div>
<p>Server is running at http://localhost:3333</p>
</body>
</html>
`);
} else {
// Not found
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found');
}
});
function exchangeCodeForTokens(code) {
return new Promise((resolve, reject) => {
const postData = querystring.stringify({
client_id: AUTH_CONFIG.clientId,
client_secret: AUTH_CONFIG.clientSecret,
code: code,
redirect_uri: AUTH_CONFIG.redirectUri,
grant_type: 'authorization_code',
scope: AUTH_CONFIG.scopes.join(' ')
});
const options = {
hostname: 'login.microsoftonline.com',
path: '/common/oauth2/v2.0/token',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': Buffer.byteLength(postData)
}
};
const req = https.request(options, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
if (res.statusCode >= 200 && res.statusCode < 300) {
try {
const tokenResponse = JSON.parse(data);
// Calculate expiration time (current time + expires_in seconds)
const expiresAt = Date.now() + (tokenResponse.expires_in * 1000);
// Add expires_at for easier expiration checking
tokenResponse.expires_at = expiresAt;
// Save tokens to file
fs.writeFileSync(AUTH_CONFIG.tokenStorePath, JSON.stringify(tokenResponse, null, 2), 'utf8');
console.log(`Tokens saved to ${AUTH_CONFIG.tokenStorePath}`);
resolve(tokenResponse);
} catch (error) {
reject(new Error(`Error parsing token response: ${error.message}`));
}
} else {
reject(new Error(`Token exchange failed with status ${res.statusCode}: ${data}`));
}
});
});
req.on('error', (error) => {
reject(error);
});
req.write(postData);
req.end();
});
}
// Start server
const PORT = 3333;
server.listen(PORT, () => {
console.log(`Authentication server running at http://localhost:${PORT}`);
console.log(`Waiting for authentication callback at ${AUTH_CONFIG.redirectUri}`);
console.log(`Token will be stored at: ${AUTH_CONFIG.tokenStorePath}`);
if (!AUTH_CONFIG.clientId || !AUTH_CONFIG.clientSecret) {
console.log('\n⚠️ WARNING: Microsoft Graph API credentials are not set.');
console.log(' Please set the MS_CLIENT_ID and MS_CLIENT_SECRET environment variables.');
}
});
// Handle termination
process.on('SIGINT', () => {
console.log('Authentication server shutting down');
process.exit(0);
});
process.on('SIGTERM', () => {
console.log('Authentication server shutting down');
process.exit(0);
});