UNPKG

ms365-mcp-server

Version:

Microsoft 365 MCP Server for managing Microsoft 365 email through natural language interactions with full OAuth2 authentication support

196 lines (195 loc) 5.74 kB
import * as fs from 'fs'; import * as path from 'path'; import * as os from 'os'; import { logger } from './api.js'; // Configuration directory const CONFIG_DIR = path.join(os.homedir(), '.outlook-mcp'); const CREDENTIALS_FILE = path.join(CONFIG_DIR, 'credentials.json'); const TOKEN_FILE = path.join(CONFIG_DIR, 'token.json'); const MSAL_CACHE_FILE = path.join(CONFIG_DIR, 'msal-cache.json'); // File permissions const FILE_MODE = 0o600; // Owner read/write only const DIR_MODE = 0o700; // Owner read/write/execute only /** * Credential store for Outlook MCP server * Uses ~/.outlook-mcp/ for secure storage */ export class OutlookCredentialStore { constructor() { this.ensureConfigDir(); } /** * Ensure configuration directory exists with proper permissions */ ensureConfigDir() { try { if (!fs.existsSync(CONFIG_DIR)) { fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: DIR_MODE }); logger.log(`Created config directory: ${CONFIG_DIR}`); } } catch (error) { logger.error('Failed to create config directory:', error); } } /** * Get the config directory path */ getConfigDir() { return CONFIG_DIR; } /** * Save credentials to file with secure permissions */ async saveCredentials(credentials) { try { this.ensureConfigDir(); const data = JSON.stringify(credentials, null, 2); fs.writeFileSync(CREDENTIALS_FILE, data, { mode: FILE_MODE }); logger.log('Saved credentials to file'); } catch (error) { logger.error('Failed to save credentials:', error); throw new Error('Failed to save credentials'); } } /** * Get stored credentials */ async getCredentials() { try { if (!fs.existsSync(CREDENTIALS_FILE)) { return null; } const data = fs.readFileSync(CREDENTIALS_FILE, 'utf8'); return JSON.parse(data); } catch (error) { logger.error('Failed to read credentials:', error); return null; } } /** * Save tokens to file with secure permissions */ async saveTokens(tokens) { try { this.ensureConfigDir(); const data = JSON.stringify(tokens, null, 2); fs.writeFileSync(TOKEN_FILE, data, { mode: FILE_MODE }); logger.log(`Saved tokens (expires: ${new Date(tokens.expiresOn).toLocaleString()})`); } catch (error) { logger.error('Failed to save tokens:', error); throw new Error('Failed to save tokens'); } } /** * Get stored tokens */ async getTokens() { try { if (!fs.existsSync(TOKEN_FILE)) { return null; } const data = fs.readFileSync(TOKEN_FILE, 'utf8'); return JSON.parse(data); } catch (error) { logger.error('Failed to read tokens:', error); return null; } } /** * Save MSAL cache */ async saveMsalCache(cacheData) { try { this.ensureConfigDir(); fs.writeFileSync(MSAL_CACHE_FILE, cacheData, { mode: FILE_MODE }); } catch (error) { logger.error('Failed to save MSAL cache:', error); } } /** * Get MSAL cache */ async getMsalCache() { try { if (!fs.existsSync(MSAL_CACHE_FILE)) { return null; } return fs.readFileSync(MSAL_CACHE_FILE, 'utf8'); } catch (error) { logger.error('Failed to read MSAL cache:', error); return null; } } /** * Clear all stored data */ async clearAll() { try { if (fs.existsSync(TOKEN_FILE)) { fs.unlinkSync(TOKEN_FILE); logger.log('Deleted token file'); } if (fs.existsSync(CREDENTIALS_FILE)) { fs.unlinkSync(CREDENTIALS_FILE); logger.log('Deleted credentials file'); } if (fs.existsSync(MSAL_CACHE_FILE)) { fs.unlinkSync(MSAL_CACHE_FILE); logger.log('Deleted MSAL cache file'); } logger.log('Cleared all stored authentication data'); } catch (error) { logger.error('Failed to clear stored data:', error); throw new Error('Failed to clear stored data'); } } /** * Clear only tokens (keep credentials) */ async clearTokens() { try { if (fs.existsSync(TOKEN_FILE)) { fs.unlinkSync(TOKEN_FILE); logger.log('Deleted token file'); } if (fs.existsSync(MSAL_CACHE_FILE)) { fs.unlinkSync(MSAL_CACHE_FILE); logger.log('Deleted MSAL cache file'); } } catch (error) { logger.error('Failed to clear tokens:', error); } } /** * Check if tokens exist */ hasTokens() { return fs.existsSync(TOKEN_FILE); } /** * Check if credentials exist */ hasCredentials() { return fs.existsSync(CREDENTIALS_FILE); } /** * Get storage location info */ getStorageInfo() { return { directory: CONFIG_DIR, hasTokens: this.hasTokens(), hasCredentials: this.hasCredentials() }; } } export const outlookCredentialStore = new OutlookCredentialStore();