Skip to main content
Glama

MCP Bridge Server

user.ts8.49 kB
import { createHash } from 'crypto'; import { promises as fs } from 'fs'; import { join } from 'path'; import { PersistentStorage } from '../storage/persistentStorage.js'; import { StorageData } from '../storage/types.js'; import { UserIdentity, UserPreferences, IdentityError, IdentityErrorType, ValidationResult, ValidationRules } from './types.js'; import { defaultProvider as machineIdProvider } from './platform/index.js'; /** * User identity manager options */ interface UserManagerOptions { storage: PersistentStorage; validationRules?: ValidationRules; userDataPath?: string; } /** * User identity manager */ export class UserManager { private storage: PersistentStorage; private validationRules: ValidationRules; private userDataPath: string; private initialized: boolean = false; constructor(options: UserManagerOptions) { this.storage = options.storage; this.validationRules = options.validationRules || {}; this.userDataPath = options.userDataPath || join(this.storage.directory, 'users'); } /** * Initialize user manager */ public async initialize(): Promise<void> { if (this.initialized) { return; } try { // Create user data directory if it doesn't exist await fs.mkdir(this.userDataPath, { recursive: true }); this.initialized = true; } catch (error) { throw new IdentityError( IdentityErrorType.USER_INVALID, 'Failed to initialize user manager', error ); } } /** * Create new user identity */ public async createUser(machineId?: string): Promise<UserIdentity> { if (!this.initialized) { throw new IdentityError( IdentityErrorType.USER_INVALID, 'User manager not initialized' ); } try { // Get and validate machine ID const currentMachineId = machineId || await machineIdProvider.getId(); if (!machineIdProvider.validate(currentMachineId)) { throw new IdentityError( IdentityErrorType.USER_INVALID, 'Invalid machine ID' ); } // Generate user ID const userId = await this.generateUserId(currentMachineId); // Create user identity const user: UserIdentity = { id: userId, machineIds: [currentMachineId], preferences: this.getDefaultPreferences(), created: new Date(), lastSeen: new Date(), sessions: [] }; // Validate user if (!this.validateUser(user)) { throw new IdentityError( IdentityErrorType.USER_INVALID, 'Invalid user data' ); } // Store user await this.storage.write( join(this.userDataPath, `${userId}.json`), user ); return user; } catch (error) { if (error instanceof IdentityError) { throw error; } throw new IdentityError( IdentityErrorType.USER_INVALID, 'Failed to create user', error ); } } /** * Get user by ID */ public async getUser(userId: string): Promise<UserIdentity> { try { const user = await this.storage.read<UserIdentity>( join(this.userDataPath, `${userId}.json`) ); if (!this.validateUser(user)) { throw new IdentityError( IdentityErrorType.USER_INVALID, 'Invalid user data' ); } return user; } catch (error) { if (error instanceof IdentityError) { throw error; } throw new IdentityError( IdentityErrorType.USER_NOT_FOUND, 'User not found', error ); } } /** * Get user by machine ID */ public async getUserByMachine(machineId: string): Promise<UserIdentity> { try { // List all user files const userFiles = await fs.readdir(this.userDataPath); // Find user with matching machine ID for (const file of userFiles) { if (!file.endsWith('.json')) continue; const user = await this.storage.read<UserIdentity>( join(this.userDataPath, file) ); if (user.machineIds.includes(machineId)) { return user; } } throw new IdentityError( IdentityErrorType.USER_NOT_FOUND, 'No user found for machine ID' ); } catch (error) { if (error instanceof IdentityError) { throw error; } throw new IdentityError( IdentityErrorType.USER_NOT_FOUND, 'Failed to find user', error ); } } /** * Update user */ public async updateUser(userId: string, updates: Partial<UserIdentity>): Promise<UserIdentity> { try { const user = await this.getUser(userId); // Apply updates const updated: UserIdentity = { ...user, ...updates, lastSeen: new Date() }; // Validate updated user if (!this.validateUser(updated)) { throw new IdentityError( IdentityErrorType.USER_INVALID, 'Invalid user data' ); } // Store updated user await this.storage.write( join(this.userDataPath, `${userId}.json`), updated ); return updated; } catch (error) { if (error instanceof IdentityError) { throw error; } throw new IdentityError( IdentityErrorType.USER_INVALID, 'Failed to update user', error ); } } /** * Delete user */ public async deleteUser(userId: string): Promise<void> { try { await this.storage.delete( join(this.userDataPath, `${userId}.json`) ); } catch (error) { throw new IdentityError( IdentityErrorType.USER_NOT_FOUND, 'Failed to delete user', error ); } } /** * Generate user ID */ private async generateUserId(machineId: string): Promise<string> { // Create hash from machine ID and timestamp const hash = createHash('sha256') .update(`${machineId}:${Date.now()}`) .digest('hex'); // Use first 16 characters as user ID return hash.slice(0, 16); } /** * Get default user preferences */ private getDefaultPreferences(): UserPreferences { return { defaultClientType: 'claude', autoStartClients: false, clientSettings: { claude: { startupTimeout: 30000, healthCheckInterval: 30000, maxRetries: 3 }, cline: { startupTimeout: 30000, healthCheckInterval: 30000, maxRetries: 3 } } }; } /** * Validate user data */ private validateUser(user: UserIdentity): boolean { try { // Basic structure validation if (!user || typeof user !== 'object') return false; if (!user.id || typeof user.id !== 'string') return false; if (!Array.isArray(user.machineIds)) return false; if (!user.preferences || typeof user.preferences !== 'object') return false; if (!(user.created instanceof Date)) return false; if (!(user.lastSeen instanceof Date)) return false; if (!Array.isArray(user.sessions)) return false; // ID format validation if (this.validationRules.userId?.pattern) { if (!this.validationRules.userId.pattern.test(user.id)) { return false; } } // Machine ID validation if (user.machineIds.length === 0) return false; for (const machineId of user.machineIds) { if (typeof machineId !== 'string' || !machineIdProvider.validate(machineId)) { return false; } } // Preferences validation const prefs = user.preferences; if (!prefs.defaultClientType || !['claude', 'cline'].includes(prefs.defaultClientType)) { return false; } if (!prefs.clientSettings) return false; for (const [clientType, settings] of Object.entries(prefs.clientSettings)) { if (!['claude', 'cline'].includes(clientType)) return false; if (typeof settings !== 'object') return false; if (typeof settings.startupTimeout !== 'number' || settings.startupTimeout <= 0) return false; if (typeof settings.healthCheckInterval !== 'number' || settings.healthCheckInterval <= 0) return false; if (typeof settings.maxRetries !== 'number' || settings.maxRetries <= 0) return false; } return true; } catch (error) { return false; } } }

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/glassBead-tc/SubspaceDomain'

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