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
JavaScript
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();