@kyaniiii/google-search-mcp
Version:
MCP server for Google Search with API key rotation
137 lines • 5.03 kB
JavaScript
import * as fs from 'fs';
import * as path from 'path';
export class GlobalConfigManager {
configPath;
config;
constructor() {
const homeDir = process.env.USERPROFILE || process.env.HOME || '';
this.configPath = path.join(homeDir, '.google-search-mcp.json');
this.loadConfig();
}
loadConfig() {
try {
if (fs.existsSync(this.configPath)) {
const data = fs.readFileSync(this.configPath, 'utf8');
this.config = JSON.parse(data);
console.error(`[INFO] Loaded global config from: ${this.configPath}`);
// Note: If you manually edit the config file, you must restart Claude Desktop
// for the changes to take effect as the config is only loaded once at startup
// Migrate old format if needed
this.migrateIfNeeded();
// Reset daily usage if needed
this.resetDailyUsageIfNeeded();
}
else {
console.error('[WARN] No global config found. Use setup command to configure.');
this.config = {
keys: [],
lastUpdated: new Date().toISOString(),
version: '1.0.0'
};
}
}
catch (error) {
console.error(`[ERROR] Error loading global config: ${error}`);
this.config = {
keys: [],
lastUpdated: new Date().toISOString(),
version: '1.0.0'
};
}
}
migrateIfNeeded() {
if (!this.config.version) {
this.config.version = '1.0.0';
this.saveConfig();
}
}
saveConfig() {
try {
this.config.lastUpdated = new Date().toISOString();
fs.writeFileSync(this.configPath, JSON.stringify(this.config, null, 2));
console.error(`[DEBUG] Global config saved to: ${this.configPath}`);
}
catch (error) {
console.error(`[ERROR] Error saving global config: ${error}`);
}
}
setupKeys(apiKeys, searchEngineIds) {
this.config.keys = [];
for (let i = 0; i < apiKeys.length; i++) {
this.config.keys.push({
id: `key_${i + 1}`,
apiKey: apiKeys[i].trim(),
searchEngineId: searchEngineIds[i]?.trim() || searchEngineIds[0]?.trim() || '',
dailyUsage: 0,
dailyLimit: 100,
lastReset: new Date().toISOString().split('T')[0],
isActive: true
});
}
this.saveConfig();
console.error(`[INFO] ${this.config.keys.length} API keys configured globally`);
}
getAvailableKey() {
this.resetDailyUsageIfNeeded();
// Find a key with available quota
for (const key of this.config.keys) {
if (key.isActive && key.dailyUsage < key.dailyLimit) {
return key;
}
}
return null; // All keys are exhausted
}
incrementUsage(keyId) {
const key = this.config.keys.find(k => k.id === keyId);
if (key) {
key.dailyUsage++;
this.saveConfig(); // Persist immediately
console.error(`[INFO] Key ${keyId}: ${key.dailyUsage}/${key.dailyLimit} requests used`);
}
}
getQuotaStatus() {
this.resetDailyUsageIfNeeded();
const totalUsed = this.config.keys.reduce((sum, key) => sum + key.dailyUsage, 0);
const totalLimit = this.config.keys.reduce((sum, key) => sum + key.dailyLimit, 0);
const keysStatus = this.config.keys.map(key => ({
id: key.id,
used: key.dailyUsage,
limit: key.dailyLimit,
remaining: key.dailyLimit - key.dailyUsage,
active: key.isActive
}));
return { totalUsed, totalLimit, keysStatus };
}
resetDailyUsageIfNeeded() {
const today = new Date().toISOString().split('T')[0];
let hasChanges = false;
for (const key of this.config.keys) {
if (key.lastReset !== today) {
key.dailyUsage = 0;
key.lastReset = today;
key.isActive = true; // Re-enable disabled keys
hasChanges = true;
console.error(`[INFO] Reset quota for ${key.id}`);
}
}
if (hasChanges) {
this.saveConfig();
}
}
disableKey(keyId, reason) {
const key = this.config.keys.find(k => k.id === keyId);
if (key) {
key.isActive = false;
this.saveConfig();
console.error(`[WARN] Key ${keyId} disabled: ${reason}`);
}
}
hasValidConfig() {
return this.config.keys.length > 0 &&
this.config.keys.some(k => k.apiKey && k.searchEngineId);
}
getConfigPath() {
return this.configPath;
}
}
//# sourceMappingURL=global-config.js.map