UNPKG

basics-courses-mcp

Version:

Interactive programming courses from Basics - MCP server for Cursor

128 lines 5.1 kB
// Device credentials management for MCP server // Hybrid approach with in-memory cache + file persistence import { promises as fs } from 'fs'; import { existsSync, mkdirSync } from 'fs'; import * as path from 'path'; import * as os from 'os'; import { randomBytes } from 'crypto'; // In-memory cache for credentials // This ensures persistence within the same MCP server session let credentialsCache = null; // Track if credentials were explicitly cleared to prevent reload let credentialsCleared = false; async function getDeviceCredentialsPath() { const cacheDir = path.join(os.homedir(), '.cache', 'basics'); // Create the directory if it doesn't exist if (!existsSync(cacheDir)) { mkdirSync(cacheDir, { recursive: true }); } return path.join(cacheDir, '.device_credentials'); } async function getDeviceCredentials() { // If credentials were explicitly cleared, don't reload from file if (credentialsCleared) { console.error('[device-credentials] Credentials were cleared, not reloading'); return null; } // First check in-memory cache if (credentialsCache) { console.error('[device-credentials] Returning credentials from cache:', credentialsCache.email); return credentialsCache; } // If not in cache, try to load from file try { const credentialsPath = await getDeviceCredentialsPath(); console.error('[device-credentials] Checking path:', credentialsPath); if (!existsSync(credentialsPath)) { console.error('[device-credentials] No credentials file found'); return null; } const fileContent = await fs.readFile(credentialsPath, 'utf-8'); const parsed = JSON.parse(fileContent); if (typeof parsed.email === 'string' && typeof parsed.deviceId === 'string' && typeof parsed.userProfileId === 'string') { // Store in cache for subsequent calls credentialsCache = parsed; console.error('[device-credentials] Loaded credentials from file:', credentialsCache.email); return credentialsCache; } console.error('[device-credentials] Invalid credentials format in file'); return null; } catch (error) { console.error('[device-credentials] Error reading credentials:', error); return null; } } export async function saveDeviceCredentials(userProfileId, email) { try { const credentialsPath = await getDeviceCredentialsPath(); console.error('[device-credentials] Saving credentials for:', email, 'to path:', credentialsPath); // Generate a unique device ID using crypto module const deviceId = randomBytes(16).toString('hex'); const credentials = { email, deviceId, userProfileId, savedAt: new Date().toISOString() }; // Save to cache immediately credentialsCache = credentials; // Reset the cleared flag since we're saving new credentials credentialsCleared = false; const toWrite = JSON.stringify(credentials, null, 2); await fs.writeFile(credentialsPath, toWrite, 'utf-8'); // Set file permissions to 600 (read/write for owner only) await fs.chmod(credentialsPath, 0o600); console.error('[device-credentials] Successfully saved credentials'); } catch (error) { console.error('[device-credentials] Error saving credentials:', error); throw error; } } export async function getDeviceId() { const credentials = await getDeviceCredentials(); return credentials?.deviceId || null; } export async function getDeviceEmail() { const credentials = await getDeviceCredentials(); return credentials?.email || null; } export async function getDeviceAuthCode() { const credentials = await getDeviceCredentials(); return credentials?.authCode || null; } export async function getDeviceUserProfileId() { const credentials = await getDeviceCredentials(); return credentials?.userProfileId || null; } export async function clearDeviceCredentials() { try { // Clear from cache FIRST - this is critical credentialsCache = null; credentialsCleared = true; // Prevent reload from file console.error('[device-credentials] Cache cleared and reload prevented'); const credentialsPath = await getDeviceCredentialsPath(); if (existsSync(credentialsPath)) { await fs.unlink(credentialsPath); console.error('[device-credentials] Credentials file deleted:', credentialsPath); } } catch (error) { console.error('[device-credentials] Error clearing credentials:', error); throw error; } } // Initialize credentials on module load // This ensures we load any existing credentials when the MCP server starts (async () => { try { await getDeviceCredentials(); } catch (error) { // Ignore initialization errors } })(); //# sourceMappingURL=device-credentials.js.map