Skip to main content
Glama

Web Proxy MCP Server

by mako10k
target-manager.js11.5 kB
/** * Target Manager * Manages proxy target sites and routing rules */ export class TargetManager { constructor() { this.targets = new Map(); // Map<domain, config> this.pacContent = ''; this.lastUpdated = new Date(); } /** * Add a target site for proxy monitoring * @param {string} domain - Domain to monitor (e.g., 'example.com') * @param {Object} config - Configuration options * @returns {boolean} Success status */ addTarget(domain, config = {}) { // Pre-conditions if (!domain || typeof domain !== 'string') { throw new Error('Domain is required and must be a string'); } const targetConfig = { domain: domain.toLowerCase(), enabled: config.enabled !== false, includeSubdomains: config.includeSubdomains !== false, description: config.description || '', captureHeaders: config.captureHeaders !== false, // Default to true captureBody: config.captureBody !== false, // Default to true addedAt: new Date(), ...config }; // Post-conditions: validate critical fields console.assert(typeof targetConfig.captureHeaders === 'boolean', 'captureHeaders must be boolean'); console.assert(typeof targetConfig.captureBody === 'boolean', 'captureBody must be boolean'); console.assert(typeof targetConfig.enabled === 'boolean', 'enabled must be boolean'); this.targets.set(domain.toLowerCase(), targetConfig); this._updatePacFile(); console.log(`✅ Target added: ${domain} (captureHeaders=${targetConfig.captureHeaders}, captureBody=${targetConfig.captureBody})`); return true; } /** * Remove a target site * @param {string} domain - Domain to remove * @returns {boolean} Success status */ removeTarget(domain) { if (!domain) { throw new Error('Domain is required'); } const removed = this.targets.delete(domain.toLowerCase()); if (removed) { this._updatePacFile(); } return removed; } /** * Toggle target site on/off * @param {string} domain - Domain to toggle * @returns {boolean} New enabled status */ toggleTarget(domain) { const target = this.targets.get(domain.toLowerCase()); if (!target) { throw new Error(`Target domain '${domain}' not found`); } target.enabled = !target.enabled; target.lastModified = new Date(); this._updatePacFile(); return target.enabled; } /** * Get all target sites * @param {string} statusFilter - Filter by status: 'all', 'active', 'inactive' * @returns {Array} List of target configurations */ listTargets(statusFilter = 'all') { const targets = Array.from(this.targets.values()).map(target => ({ ...target, status: target.enabled ? 'active' : 'inactive' })); if (statusFilter === 'active') { return targets.filter(t => t.enabled); } else if (statusFilter === 'inactive') { return targets.filter(t => !t.enabled); } return targets; } /** * Check if a request should be proxied * @param {string} hostname - Request hostname (not full URL) * @returns {boolean} Should proxy this request */ shouldProxy(hostname) { try { const normalizedHostname = hostname.toLowerCase(); for (const [domain, config] of this.targets) { if (!config.enabled) continue; if (config.includeSubdomains) { if (normalizedHostname === domain || normalizedHostname.endsWith(`.${domain}`)) { return true; } } else { if (normalizedHostname === domain) { return true; } } } return false; } catch (error) { console.error('Error checking proxy rule:', error); return false; } } /** * Generate PAC (Proxy Auto-Configuration) file content * @param {string} proxyHost - Proxy server host (default: localhost) * @param {number} proxyPort - Proxy server port (default: 8080) * @returns {string} PAC file content */ generatePacFile(proxyHost = 'localhost', proxyPort = 8080) { const enabledTargets = Array.from(this.targets.values()) .filter(target => target.enabled); const domainChecks = enabledTargets.map(target => { if (target.includeSubdomains) { return ` if (host === "${target.domain}" || host.endsWith(".${target.domain}")) return "PROXY ${proxyHost}:${proxyPort}";`; } else { return ` if (host === "${target.domain}") return "PROXY ${proxyHost}:${proxyPort}";`; } }).join('\n'); return `function FindProxyForURL(url, host) { host = host.toLowerCase(); // Proxy specific domains through our proxy server ${domainChecks} // All other traffic goes direct return "DIRECT"; } // Generated by Web Proxy MCP Server // Last updated: ${new Date().toISOString()} // Active targets: ${enabledTargets.length}`; } /** * Update internal PAC file content * @private */ _updatePacFile() { this.pacContent = this.generatePacFile(); this.lastUpdated = new Date(); } /** * Get current PAC file content * @returns {string} PAC file content */ getPacContent() { return this.pacContent; } /** * Get statistics about targets * @returns {Object} Statistics */ getStats() { const targets = Array.from(this.targets.values()); return { total: targets.length, enabled: targets.filter(t => t.enabled).length, disabled: targets.filter(t => !t.enabled).length, lastUpdated: this.lastUpdated, domains: targets.map(t => t.domain) }; } /** * Export all targets as JSON * @returns {string} JSON string of all targets */ exportTargets() { const exportData = { version: '1.0', exportedAt: new Date().toISOString(), targets: Object.fromEntries(this.targets) }; return JSON.stringify(exportData, null, 2); } /** * Import targets from JSON * @param {string} jsonData - JSON string to import * @returns {number} Number of targets imported */ importTargets(jsonData) { try { const data = JSON.parse(jsonData); let imported = 0; if (data.targets && typeof data.targets === 'object') { for (const [domain, config] of Object.entries(data.targets)) { this.addTarget(domain, config); imported++; } } return imported; } catch (error) { throw new Error(`Failed to import targets: ${error.message}`); } } /** * Load targets from file * @param {string} filepath - File path to load from */ async loadTargets(filepath = './data/targets.json') { try { const fs = await import('fs/promises'); const data = await fs.readFile(filepath, 'utf-8'); const parsed = JSON.parse(data); this.targets.clear(); if (parsed.targets && typeof parsed.targets === 'object') { for (const [domain, config] of Object.entries(parsed.targets)) { this.targets.set(domain.toLowerCase(), { domain: config.domain, description: config.description || '', enabled: config.enabled !== false, includeSubdomains: config.includeSubdomains !== false, captureHeaders: config.captureHeaders !== false, captureBody: config.captureBody || false, createdAt: config.createdAt ? new Date(config.createdAt) : new Date(), lastModified: config.lastModified ? new Date(config.lastModified) : new Date() }); } this._updatePacFile(); console.log(`📋 Loaded ${this.targets.size} targets from ${filepath}`); } } catch (error) { if (error.code === 'ENOENT') { console.log(`📋 No existing targets file found at ${filepath}, starting fresh`); } else { console.error(`Failed to load targets: ${error.message}`); } } } /** * Save targets to file * @param {string} filepath - File path to save to */ async saveTargets(filepath = './data/targets.json') { try { const fs = await import('fs/promises'); const path = await import('path'); // Ensure directory exists const dir = path.dirname(filepath); await fs.mkdir(dir, { recursive: true }); const exportData = { version: '1.0', savedAt: new Date().toISOString(), targets: Object.fromEntries(this.targets) }; await fs.writeFile(filepath, JSON.stringify(exportData, null, 2)); console.log(`💾 Saved ${this.targets.size} targets to ${filepath}`); } catch (error) { console.error(`Failed to save targets: ${error.message}`); throw error; } } /** * Import targets from file * @param {string} filepath - File path to import from * @param {boolean} merge - Whether to merge with existing targets * @returns {Object} Import results */ async importFromFile(filepath, merge = false) { try { const fs = await import('fs/promises'); const data = await fs.readFile(filepath, 'utf-8'); if (!merge) { this.targets.clear(); } const imported = this.importTargets(data); return { imported, merge }; } catch (error) { throw new Error(`Failed to import from file: ${error.message}`); } } /** * Export targets to file * @param {string} filepath - File path to export to * @param {Object} additionalData - Additional data to include * @returns {Object} Export results */ async exportToFile(filepath, additionalData = {}) { try { const fs = await import('fs/promises'); const path = await import('path'); // Ensure directory exists const dir = path.dirname(filepath); await fs.mkdir(dir, { recursive: true }); const exportData = { version: '1.0', exportedAt: new Date().toISOString(), targets: Object.fromEntries(this.targets), ...additionalData }; const content = JSON.stringify(exportData, null, 2); await fs.writeFile(filepath, content); return { filepath, targetCount: this.targets.size, fileSize: content.length }; } catch (error) { throw new Error(`Failed to export to file: ${error.message}`); } } /** * Find target by domain * @param {string} domain - Domain to find * @returns {Object|null} Target configuration or null */ findTarget(domain) { return this.targets.get(domain.toLowerCase()) || null; } /** * Update target configuration * @param {string} domain - Domain to update * @param {Object} updates - Updates to apply * @returns {boolean} Whether target was updated */ updateTarget(domain, updates) { const target = this.targets.get(domain.toLowerCase()); if (!target) { return false; } // Apply updates if ('enabled' in updates) target.enabled = updates.enabled; if ('description' in updates) target.description = updates.description; if ('includeSubdomains' in updates) target.includeSubdomains = updates.includeSubdomains; if ('captureHeaders' in updates) target.captureHeaders = updates.captureHeaders; if ('captureBody' in updates) target.captureBody = updates.captureBody; target.lastModified = new Date(); this._updatePacFile(); return true; } }

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/mako10k/mcp-web-proxy'

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