Skip to main content
Glama
preferences-manager.js8.16 kB
/** * Internal Preferences Manager for the Gemini MCP Server. * Handles self-contained preferences storage with automatic migration * from external preferences files. * * @author Assistant */ const fs = require('fs'); const path = require('path'); const os = require('os'); const { log } = require('../utils/logger'); const { ensureDirectoryExists } = require('../utils/file-utils'); const StorageAdapter = require('./storage-adapter'); const { validateStorageType, ensureStoragePathProvided, validateStoragePath } = require('../utils/storage-validation'); class PreferencesManager { constructor(serverDataDir = null, storageType = 'json', storagePath = null) { validateStorageType(storageType); // Use the provided data directory or default to ../../../data this.dataDir = serverDataDir || path.join(__dirname, '..', '..', 'data'); this.preferencesFile = storagePath || path.join(this.dataDir, 'tool-preferences.json'); // Validate storage path (e.g., create directories if needed) ensureStoragePathProvided(this.preferencesFile, storageType); validateStoragePath(this.preferencesFile); this.cache = new Map(); this.storage = new StorageAdapter(storageType, this.preferencesFile); this.migrationCompleted = false; this.initialized = false; // Add initialized flag } /** * Initializes the preferences manager, ensuring data directory exists * and migrating external preferences if needed. */ async initialize() { if (this.initialized) { log('PreferencesManager already initialized.', 'preferences-manager'); return; } try { await this.ensureDataDirectory(); await this.migrateExternalPreferences(); await this.loadPreferences(); this.initialized = true; // Set initialized to true after successful initialization log('PreferencesManager initialized successfully', 'preferences-manager'); } catch (error) { log(`Error initializing PreferencesManager: ${error.message}`, 'preferences-manager'); throw error; } } /** * Ensures the data directory exists. */ async ensureDataDirectory() { ensureDirectoryExists(this.dataDir, 'preferences-manager'); } /** * Migrates external preferences from the old location to internal storage. * This provides backwards compatibility for existing users. */ async migrateExternalPreferences() { const externalPath = path.join(os.homedir(), 'Claude', 'tool-preferences.json'); // Check if external file exists but internal doesn't if (fs.existsSync(externalPath) && !fs.existsSync(this.preferencesFile)) { try { log('Migrating external preferences to internal storage...', 'preferences-manager'); // Read external preferences const externalData = JSON.parse(fs.readFileSync(externalPath, 'utf8')); // Save to internal storage await this.savePreferences(externalData); log(`Migration complete. External file preserved at: ${externalPath}`, 'preferences-manager'); log('You can safely delete the external file if no longer needed.', 'preferences-manager'); this.migrationCompleted = true; } catch (error) { log(`Error migrating external preferences: ${error.message}`, 'preferences-manager'); log('Continuing with fresh preferences...', 'preferences-manager'); } } } /** * Saves preferences to internal storage. * @param {Object} preferences - The preferences object to save */ async savePreferences(preferences) { try { // Ensure preferences has the correct structure const dataToSave = { patterns: preferences.patterns || {}, lastUpdated: preferences.lastUpdated || new Date().toISOString(), migrationInfo: { migrated: this.migrationCompleted, migrationDate: this.migrationCompleted ? new Date().toISOString() : null, }, }; await this.storage.write('preferences', dataToSave); // Update cache this.cache.set('preferences', dataToSave); log('Preferences saved to internal storage', 'preferences-manager'); } catch (error) { log(`Error saving preferences: ${error.message}`, 'preferences-manager'); throw error; } } /** * Loads preferences from internal storage into cache. */ async loadPreferences() { try { const exists = await this.storage.exists('preferences'); if (exists) { const data = await this.storage.read('preferences'); this.cache.set('preferences', data); log('Loaded preferences from internal storage', 'preferences-manager'); } else { // Initialize with default structure const defaultPreferences = { patterns: {}, lastUpdated: new Date().toISOString(), }; this.cache.set('preferences', defaultPreferences); log('Initialized with default preferences', 'preferences-manager'); } } catch (error) { log(`Error loading preferences: ${error.message}`, 'preferences-manager'); // Initialize with defaults on error this.cache.set('preferences', { patterns: {}, lastUpdated: new Date().toISOString(), }); } } /** * Gets the current preferences from cache. * @returns {Object} The preferences object */ getPreferences() { return this.cache.get('preferences') || { patterns: {}, lastUpdated: null }; } /** * Updates preferences in cache and storage. * @param {Object} updates - Partial preferences to update */ async updatePreferences(updates) { const current = this.getPreferences(); const updated = { ...current, ...updates, lastUpdated: new Date().toISOString(), }; await this.savePreferences(updated); } /** * Gets patterns for a specific context. * @param {string} contextKey - The context key (e.g., 'consciousness', 'code') * @returns {Array} Array of patterns for the context */ getPatterns(contextKey) { const preferences = this.getPreferences(); return preferences.patterns[contextKey] || []; } /** * Adds a pattern for a specific context. * @param {string} contextKey - The context key * @param {Object} pattern - The pattern to add */ async addPattern(contextKey, pattern) { const preferences = this.getPreferences(); if (!preferences.patterns[contextKey]) { preferences.patterns[contextKey] = []; } preferences.patterns[contextKey].push(pattern); // Keep only last 100 patterns per context if (preferences.patterns[contextKey].length > 100) { preferences.patterns[contextKey] = preferences.patterns[contextKey].slice(-100); } await this.savePreferences(preferences); } /** * Clears all preferences (useful for testing). */ async clearPreferences() { const emptyPreferences = { patterns: {}, lastUpdated: new Date().toISOString(), }; await this.savePreferences(emptyPreferences); log('Preferences cleared', 'preferences-manager'); } /** * Exports preferences to a specific path (useful for backups). * @param {string} exportPath - Path to export preferences to */ async exportPreferences(exportPath) { try { const preferences = this.getPreferences(); fs.writeFileSync(exportPath, JSON.stringify(preferences, null, 2)); log(`Preferences exported to: ${exportPath}`, 'preferences-manager'); } catch (error) { log(`Error exporting preferences: ${error.message}`, 'preferences-manager'); throw error; } } /** * Imports preferences from a specific path. * @param {string} importPath - Path to import preferences from */ async importPreferences(importPath) { try { const data = JSON.parse(fs.readFileSync(importPath, 'utf8')); await this.savePreferences(data); log(`Preferences imported from: ${importPath}`, 'preferences-manager'); } catch (error) { log(`Error importing preferences: ${error.message}`, 'preferences-manager'); throw error; } } } module.exports = PreferencesManager;

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Garblesnarff/gemini-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server