@synvo_ai/mcp-server
Version:
Enterprise-grade MCP server for Synvo AI - File management and AI query capabilities for Claude Desktop, VSCode, and Cursor
328 lines (327 loc) ⢠10.2 kB
JavaScript
/**
* License Management Module for Synvo MCP Server
* Handles license verification, quota management, and user authentication
*/
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
import { homedir } from 'os';
import { join } from 'path';
import axios from 'axios';
import { createHash } from 'crypto';
// Configuration
const LICENSE_SERVER = process.env.LICENSE_SERVER || 'https://license.synvo.ai/v1';
const LICENSE_DIR = join(homedir(), '.synvo');
const LICENSE_FILE = join(LICENSE_DIR, 'license.json');
const CACHE_DURATION_HOURS = 24;
// Default quotas for each tier
const DEFAULT_QUOTAS = {
free: {
uploads_per_day: 10,
queries_per_day: 50,
max_file_size_mb: 5,
},
pro: {
uploads_per_day: 1000,
queries_per_day: 10000,
max_file_size_mb: 100,
},
team: {
uploads_per_day: 10000,
queries_per_day: 100000,
max_file_size_mb: 500,
},
enterprise: {
uploads_per_day: 999999,
queries_per_day: 999999,
max_file_size_mb: 5000,
},
};
/**
* Generate a unique machine identifier
* This is used to bind licenses to specific machines (optional)
*/
export function getMachineId() {
try {
const os = require('os');
const platform = process.platform;
const arch = process.arch;
const hostname = os.hostname();
const cpus = os.cpus();
// Create a fingerprint from system info
const fingerprint = [
platform,
arch,
hostname,
cpus.length,
cpus[0]?.model || 'unknown',
].join('-');
return createHash('sha256').update(fingerprint).digest('hex').substring(0, 32);
}
catch (error) {
console.error('[License] Failed to generate machine ID:', error);
return 'unknown';
}
}
/**
* Load license from local file
*/
export function loadLicense() {
try {
if (!existsSync(LICENSE_FILE)) {
return null;
}
const data = readFileSync(LICENSE_FILE, 'utf-8');
const license = JSON.parse(data);
// Validate license structure
if (!license.license_key || !license.tier || !license.cached_quota) {
console.error('[License] Invalid license file format');
return null;
}
return license;
}
catch (error) {
console.error('[License] Failed to load license:', error);
return null;
}
}
/**
* Save license to local file
*/
export function saveLicense(license) {
try {
// Ensure directory exists
if (!existsSync(LICENSE_DIR)) {
mkdirSync(LICENSE_DIR, { recursive: true });
}
// Write license file
writeFileSync(LICENSE_FILE, JSON.stringify(license, null, 2), 'utf-8');
console.error('[License] License saved successfully');
}
catch (error) {
console.error('[License] Failed to save license:', error);
throw new Error('Failed to save license file');
}
}
/**
* Delete local license file
*/
export function deleteLicense() {
try {
if (existsSync(LICENSE_FILE)) {
require('fs').unlinkSync(LICENSE_FILE);
console.error('[License] License deleted');
}
}
catch (error) {
console.error('[License] Failed to delete license:', error);
}
}
/**
* Verify license with remote server
*/
export async function verifyLicense(licenseKey) {
try {
console.error('[License] Verifying license with server...');
const response = await axios.post(`${LICENSE_SERVER}/verify`, {
license_key: licenseKey,
machine_id: getMachineId(),
version: getPackageVersion(),
}, {
timeout: 10000,
headers: {
'Content-Type': 'application/json',
},
});
console.error(`[License] Verification response: valid=${response.data.valid}, tier=${response.data.tier}`);
return response.data;
}
catch (error) {
if (axios.isAxiosError(error)) {
console.error('[License] Verification failed:', error.response?.data || error.message);
// Return error response
return {
valid: false,
tier: 'free',
expires_at: '',
quota: DEFAULT_QUOTAS.free,
error: error.response?.data?.error || 'License verification failed',
};
}
console.error('[License] Unknown verification error:', error);
throw new Error('License verification failed');
}
}
/**
* Check if license needs revalidation
*/
export function needsRevalidation(license) {
try {
const lastVerified = new Date(license.last_verified);
const now = new Date();
const hoursSinceVerification = (now.getTime() - lastVerified.getTime()) / 1000 / 60 / 60;
return hoursSinceVerification > CACHE_DURATION_HOURS;
}
catch (error) {
console.error('[License] Failed to check revalidation:', error);
return true; // Force revalidation on error
}
}
/**
* Check if license is expired
*/
export function isLicenseExpired(license) {
if (!license.expires_at) {
return false; // No expiration
}
try {
const expiresAt = new Date(license.expires_at);
const now = new Date();
return now > expiresAt;
}
catch (error) {
console.error('[License] Failed to check expiration:', error);
return false;
}
}
/**
* Get current license status (with caching and fallback)
*/
export async function getLicenseStatus() {
const license = loadLicense();
// No license ā Free tier
if (!license) {
console.error('[License] No license found, using Free tier');
return {
tier: 'free',
quota: DEFAULT_QUOTAS.free,
is_valid: true,
};
}
// Check expiration
if (isLicenseExpired(license)) {
console.error('[License] License expired, reverting to Free tier');
return {
tier: 'free',
quota: DEFAULT_QUOTAS.free,
is_valid: false,
};
}
// Check if needs revalidation
if (needsRevalidation(license)) {
console.error('[License] License needs revalidation, checking server...');
try {
const verification = await verifyLicense(license.license_key);
if (!verification.valid) {
console.error('[License] License invalid, reverting to Free tier');
return {
tier: 'free',
quota: DEFAULT_QUOTAS.free,
is_valid: false,
};
}
// Update cached license
saveLicense({
license_key: license.license_key,
tier: verification.tier,
last_verified: new Date().toISOString(),
cached_quota: verification.quota,
expires_at: verification.expires_at,
});
return {
tier: verification.tier,
quota: verification.quota,
usage: verification.usage,
is_valid: true,
expires_at: verification.expires_at,
};
}
catch (error) {
// Revalidation failed, use cached data
console.warn('[License] Revalidation failed, using cached license data');
return {
tier: license.tier,
quota: license.cached_quota,
is_valid: true, // Trust cached data for now
};
}
}
// Use cached license data
return {
tier: license.tier,
quota: license.cached_quota,
is_valid: true,
expires_at: license.expires_at,
};
}
/**
* Activate a license (CLI command)
*/
export async function activateLicense(licenseKey) {
console.log('š Verifying license key...');
// Verify with server
const verification = await verifyLicense(licenseKey);
if (!verification.valid) {
throw new Error(verification.error || 'Invalid license key');
}
// Save license
saveLicense({
license_key: licenseKey,
tier: verification.tier,
last_verified: new Date().toISOString(),
cached_quota: verification.quota,
expires_at: verification.expires_at,
});
console.log(`\nā
License activated successfully!`);
console.log(` Tier: ${verification.tier.toUpperCase()}`);
console.log(` Expires: ${verification.expires_at || 'Never'}`);
console.log(`\nš Your Quotas:`);
console.log(` Daily Uploads: ${verification.quota.uploads_per_day}`);
console.log(` Daily Queries: ${verification.quota.queries_per_day}`);
console.log(` Max File Size: ${verification.quota.max_file_size_mb}MB`);
}
/**
* Deactivate license (CLI command)
*/
export function deactivateLicense() {
const license = loadLicense();
if (!license) {
console.log('ā¹ļø No license is currently activated.');
return;
}
deleteLicense();
console.log('ā
License deactivated successfully.');
console.log(' You are now using the Free tier.');
}
/**
* Get package version
*/
function getPackageVersion() {
try {
const packageJson = require('../package.json');
return packageJson.version || '1.0.0';
}
catch (error) {
return '1.0.0';
}
}
/**
* Format quota for display
*/
export function formatQuota(quota) {
return `
Daily Uploads: ${quota.uploads_per_day === 999999 ? 'Unlimited' : quota.uploads_per_day}
Daily Queries: ${quota.queries_per_day === 999999 ? 'Unlimited' : quota.queries_per_day}
Max File Size: ${quota.max_file_size_mb}MB`;
}
/**
* Get upgrade message for free tier users
*/
export function getUpgradeMessage() {
return `
š” Upgrade to Pro for higher limits!
⢠1,000 daily uploads
⢠10,000 daily queries
⢠100MB max file size
⢠Priority support
Visit: https://synvo.ai/pricing`;
}
//# sourceMappingURL=license.js.map