/** * Authentication-related tools for the Outlook MCP server */ const config = require('../config'); const tokenManager = require('./token-manager'); const { analyzeTokenScopes, expectedSharedScopes } = require('../utils/mailbox'); async function handleAbout() { const sharedLine = config.ENABLE_SHARED_MAILBOXES ? 'Shared mailbox support: enabled (optional mailbox param + list-mailboxes). Requires Mail.*.Shared scopes and re-auth after upgrade.' : 'Shared mailbox support: disabled (OUTLOOK_ENABLE_SHARED_MAILBOXES=false).'; return { content: [{ type: "text", text: `Outlook Assistant MCP Server v${config.SERVER_VERSION}\n\nProvides access to Microsoft Outlook email, calendar, folders, and rules through Microsoft Graph API.\n${sharedLine}` }] }; } /** * Initiates device code flow via MSAL. Returns the code and URL to the user * immediately; MSAL polls internally until the user completes sign-in. */ async function handleAuthenticate(args) { if (config.USE_TEST_MODE) { tokenManager.createTestTokens(); return { content: [{ type: "text", text: 'Successfully authenticated with Microsoft Graph API (test mode)' }] }; } let flowResult; try { flowResult = await tokenManager.initiateDeviceCodeFlow(); } catch (err) { console.error('[authenticate] Failed to initiate device code flow:', err.message); return { content: [{ type: "text", text: `Failed to start authentication: ${err.message}` }] }; } const { deviceCodeInfo, tokenPromise } = flowResult; const { userCode, verificationUri, expiresIn } = deviceCodeInfo; const minutesRemaining = Math.floor(expiresIn / 60); // MSAL polls internally — just log completion when it resolves tokenPromise .then(result => { if (result) console.error('[authenticate] Sign-in completed — MSAL cached tokens.'); }) .catch(err => { console.error(`[authenticate] Device code flow ended: ${err.message}`); }); const scopeHint = config.ENABLE_SHARED_MAILBOXES ? `\nThis login requests shared-mailbox scopes: ${expectedSharedScopes().join(', ')}.\nAfter signing in, call check-auth-status and confirm those scopes are present.` : ''; return { content: [{ type: "text", text: [ `To sign in to Microsoft, please:`, ``, ` 1. Open: ${verificationUri}`, ` 2. Enter code: ${userCode}`, ``, `You have ${minutesRemaining} minutes to complete sign-in.`, `After signing in, call check-auth-status to confirm.`, scopeHint ].filter(Boolean).join('\n') }] }; } async function handleCheckAuthStatus() { console.error(`[CHECK-AUTH-STATUS] Checking for valid token...`); try { const token = await tokenManager.getAccessToken(); if (!token) { console.error('[CHECK-AUTH-STATUS] No valid token found'); return { content: [{ type: "text", text: "Not authenticated" }] }; } console.error('[CHECK-AUTH-STATUS] Valid token acquired'); const lines = ['Authenticated and ready']; if (config.ENABLE_SHARED_MAILBOXES) { const analysis = analyzeTokenScopes(token); if (analysis.missing.length === 0) { lines.push('Shared mailbox scopes: OK (' + analysis.present.join(', ') + ')'); } else if (analysis.present.length === 0) { lines.push('Shared mailbox scopes: MISSING (' + analysis.missing.join(', ') + ')'); lines.push('Re-run authenticate (device code) after adding these delegated permissions in Entra and granting admin consent.'); } else { lines.push('Shared mailbox scopes: PARTIAL'); lines.push(' present: ' + analysis.present.join(', ')); lines.push(' missing: ' + analysis.missing.join(', ')); lines.push('Re-run authenticate so the token picks up the missing scopes.'); } if (analysis.scp) { lines.push('Token scp: ' + analysis.scp); } } else { lines.push('Shared mailbox feature disabled (OUTLOOK_ENABLE_SHARED_MAILBOXES=false).'); } return { content: [{ type: "text", text: lines.join('\n') }] }; } catch (e) { console.error('[CHECK-AUTH-STATUS] Error:', e.message); return { content: [{ type: "text", text: "Not authenticated" }] }; } } const authTools = [ { name: "about", description: "Returns information about this Outlook Assistant server", inputSchema: { type: "object", properties: {}, required: [] }, handler: handleAbout }, { name: "authenticate", description: "Authenticate with Microsoft Graph API using device code flow. Returns a short code and URL. IMPORTANT: Show the user the code and URL — they must visit the URL on any device and enter the code to complete sign-in. After they sign in, call check-auth-status to confirm. After upgrading to shared-mailbox support, re-authenticate so Mail.*.Shared scopes appear on the token.", inputSchema: { type: "object", properties: { force: { anyOf: [{ type: "boolean" }, { type: "string" }], description: "Force re-authentication even if already authenticated" } }, required: [] }, handler: handleAuthenticate }, { name: "check-auth-status", description: "Check the current authentication status with Microsoft Graph API, including whether shared-mailbox scopes are present on the access token", inputSchema: { type: "object", properties: {}, required: [] }, handler: handleCheckAuthStatus } ]; module.exports = { authTools, handleAbout, handleAuthenticate, handleCheckAuthStatus };