basics-courses-mcp
Version:
Interactive programming courses from Basics - MCP server for Cursor
128 lines • 5.1 kB
JavaScript
// 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