Skip to main content
Glama

Superjolt MCP Server

by scoritz
storage.service.ts5.5 kB
import { Injectable, Logger } from '@nestjs/common'; import * as os from 'os'; import * as path from 'path'; import * as fs from 'fs'; const SERVICE_NAME = 'superjolt-cli'; interface StorageOptions { secure?: boolean; // Use keytar if available encrypt?: boolean; // Future: encrypt file storage } @Injectable() export class StorageService { private readonly logger = new Logger(StorageService.name); private readonly configDir = path.join(os.homedir(), '.config', 'superjolt'); private keytarAvailable: boolean | null = null; private keytar: any = null; constructor() { // Ensure config directory exists this.ensureConfigDir(); } private async loadKeytar(): Promise<boolean> { if (this.keytarAvailable !== null) { return this.keytarAvailable; } try { this.keytar = require('keytar'); this.keytarAvailable = true; return true; } catch { this.keytarAvailable = false; return false; } } /** * Get a value from storage * @param key Storage key * @param options Storage options * @returns The stored value or null if not found */ async get(key: string, options: StorageOptions = {}): Promise<string | null> { if (options.secure && (await this.loadKeytar())) { try { const value = await this.keytar.getPassword(SERVICE_NAME, key); if (value) { return value; } } catch (error) { this.logger.debug(`Failed to get secure value for ${key}:`, error); } } // Fallback to file storage const filePath = this.getFilePath(key); if (fs.existsSync(filePath)) { try { const content = fs.readFileSync(filePath, 'utf-8').trim(); return content; } catch (error) { this.logger.debug(`Failed to read file for ${key}:`, error); } } return null; } /** * Set a value in storage * @param key Storage key * @param value Value to store * @param options Storage options */ async set( key: string, value: string, options: StorageOptions = {}, ): Promise<void> { if (options.secure && (await this.loadKeytar())) { try { await this.keytar.setPassword(SERVICE_NAME, key, value); // If keytar succeeded, remove any file fallback const filePath = this.getFilePath(key); if (fs.existsSync(filePath)) { fs.unlinkSync(filePath); } return; } catch (error) { this.logger.debug(`Failed to set secure value for ${key}:`, error); // Fall through to file storage } } // Fallback to file storage const filePath = this.getFilePath(key); fs.writeFileSync(filePath, value, { encoding: 'utf-8', mode: options.secure ? 0o600 : 0o644, // Restrict permissions for secure items }); } /** * Delete a value from storage * @param key Storage key * @param options Storage options */ async delete(key: string, options: StorageOptions = {}): Promise<void> { // Try to delete from keytar if it was secure if (options.secure && (await this.loadKeytar())) { try { await this.keytar.deletePassword(SERVICE_NAME, key); } catch { // Ignore keytar errors } } // Also delete file if it exists const filePath = this.getFilePath(key); if (fs.existsSync(filePath)) { fs.unlinkSync(filePath); } } /** * Get JSON data from storage * @param key Storage key * @returns Parsed JSON or null */ async getJson<T>(key: string): Promise<T | null> { const value = await this.get(key); if (!value) return null; try { return JSON.parse(value); } catch (error) { this.logger.debug(`Failed to parse JSON for ${key}:`, error); return null; } } /** * Set JSON data in storage * @param key Storage key * @param data Data to store */ async setJson<T>(key: string, data: T): Promise<void> { const value = JSON.stringify(data, null, 2); await this.set(key, value); } /** * Check if a key exists in storage * @param key Storage key * @param options Storage options */ async exists(key: string, options: StorageOptions = {}): Promise<boolean> { const value = await this.get(key, options); return value !== null; } /** * List all keys in file storage (not keytar) */ listKeys(): string[] { if (!fs.existsSync(this.configDir)) { return []; } try { const files = fs.readdirSync(this.configDir); return files.filter((file) => !file.startsWith('.')); } catch { return []; } } /** * Clear all storage (use with caution!) */ async clearAll(): Promise<void> { // Clear file storage if (fs.existsSync(this.configDir)) { const files = fs.readdirSync(this.configDir); for (const file of files) { if (!file.startsWith('.')) { fs.unlinkSync(path.join(this.configDir, file)); } } } // Note: We don't clear keytar as we don't know all keys this.logger.warn('Cleared all file storage (keytar storage unchanged)'); } private getFilePath(key: string): string { // Sanitize key to be filesystem-safe const safeKey = key.replace(/[^a-zA-Z0-9-_]/g, '_'); return path.join(this.configDir, safeKey); } private ensureConfigDir(): void { if (!fs.existsSync(this.configDir)) { fs.mkdirSync(this.configDir, { recursive: true }); } } }

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/scoritz/superjolt'

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