Skip to main content
Glama

Web Proxy MCP Server

by mako10k
proxy-server.js10.6 kB
/** * HTTP/HTTPS Proxy Server * Core proxy server implementation with traffic capture */ import http from 'http'; import https from 'https'; import { URL } from 'url'; import net from 'net'; export class ProxyServer { constructor(targetManager, trafficAnalyzer = null) { this.targetManager = targetManager; this.trafficAnalyzer = trafficAnalyzer; this.server = null; this.port = null; this.host = null; this.running = false; // Performance metrics this.metrics = { requestCount: 0, proxyCount: 0, directCount: 0, errorCount: 0, bytesTransferred: 0, averageResponseTime: 0, startTime: null, lastRequestTime: null }; } /** * Start the proxy server * @param {number} port - Port to listen on * @param {string} host - Host to bind to */ async start(port = 8080, host = 'localhost') { if (this.running) { throw new Error('Proxy server is already running'); } this.port = port; this.host = host; this.metrics.startTime = new Date(); this.server = http.createServer(); // Handle HTTP requests this.server.on('request', (req, res) => { this._handleHttpRequest(req, res); }); // Handle HTTPS CONNECT method for tunneling this.server.on('connect', (req, clientSocket, head) => { this._handleHttpsConnect(req, clientSocket, head); }); // Serve PAC file this.server.on('request', (req, res) => { if (req.url === '/proxy.pac') { this._servePacFile(req, res); return; } }); return new Promise((resolve, reject) => { this.server.listen(port, host, (error) => { if (error) { reject(error); } else { this.running = true; console.log(`🚀 Proxy server started on ${host}:${port}`); resolve(); } }); }); } /** * Stop the proxy server */ async stop() { if (!this.running || !this.server) { return; } return new Promise((resolve) => { this.server.close(() => { this.running = false; this.server = null; console.log('✅ Proxy server stopped'); resolve(); }); }); } /** * Handle HTTP requests * @private */ async _handleHttpRequest(clientReq, clientRes) { const startTime = Date.now(); this.metrics.requestCount++; this.metrics.lastRequestTime = new Date(); try { const url = new URL(clientReq.url); const shouldProxy = this._shouldProxyRequest(url.hostname); if (!shouldProxy) { this.metrics.directCount++; this._sendDirectResponse(clientRes, url.href); return; } this.metrics.proxyCount++; await this._proxyHttpRequest(clientReq, clientRes, url, startTime); } catch (error) { this.metrics.errorCount++; console.error('Proxy error:', error.message); this._sendErrorResponse(clientRes, 500, 'Proxy Error'); } } /** * Handle HTTPS CONNECT tunneling * @private */ _handleHttpsConnect(req, clientSocket, head) { const startTime = Date.now(); this.metrics.requestCount++; try { const [hostname, port = 443] = req.url.split(':'); const shouldProxy = this._shouldProxyRequest(hostname); if (!shouldProxy) { this.metrics.directCount++; clientSocket.write('HTTP/1.1 200 Connection established\r\n\r\n'); // Create direct tunnel const serverSocket = net.connect(port, hostname, () => { clientSocket.write('HTTP/1.1 200 Connection established\r\n\r\n'); serverSocket.write(head); serverSocket.pipe(clientSocket); clientSocket.pipe(serverSocket); }); serverSocket.on('error', (error) => { console.error('HTTPS tunnel error:', error.message); clientSocket.end(); }); return; } this.metrics.proxyCount++; this._proxyHttpsConnect(req, clientSocket, head, hostname, port, startTime); } catch (error) { this.metrics.errorCount++; console.error('HTTPS CONNECT error:', error.message); clientSocket.end(); } } /** * Proxy HTTP request through our server * @private */ async _proxyHttpRequest(clientReq, clientRes, url, startTime) { const options = { hostname: url.hostname, port: url.port || (url.protocol === 'https:' ? 443 : 80), path: url.pathname + url.search, method: clientReq.method, headers: { ...clientReq.headers } }; // Remove proxy-specific headers delete options.headers['proxy-connection']; delete options.headers['proxy-authorization']; const httpModule = url.protocol === 'https:' ? https : http; const proxyReq = httpModule.request(options, (proxyRes) => { const responseTime = Date.now() - startTime; this._updateMetrics(responseTime); // Capture traffic if analyzer is available if (this.trafficAnalyzer && this._shouldCaptureTraffic(url.hostname)) { this._captureTraffic(clientReq, proxyRes, url, responseTime); } // Forward response headers clientRes.writeHead(proxyRes.statusCode, proxyRes.headers); // Forward response body proxyRes.pipe(clientRes); proxyRes.on('end', () => { this.metrics.bytesTransferred += parseInt(proxyRes.headers['content-length'] || '0'); }); }); proxyReq.on('error', (error) => { console.error('Proxy request error:', error.message); this._sendErrorResponse(clientRes, 502, 'Bad Gateway'); }); // Forward request body clientReq.pipe(proxyReq); } /** * Proxy HTTPS CONNECT tunnel * @private */ _proxyHttpsConnect(req, clientSocket, head, hostname, port, startTime) { const serverSocket = net.connect(port, hostname, () => { clientSocket.write('HTTP/1.1 200 Connection established\r\n\r\n'); serverSocket.write(head); // Set up bidirectional piping serverSocket.pipe(clientSocket); clientSocket.pipe(serverSocket); // Log the connection if traffic analyzer is available if (this.trafficAnalyzer && this._shouldCaptureTraffic(hostname)) { const responseTime = Date.now() - startTime; this._captureHttpsConnect(req, hostname, port, responseTime); this._updateMetrics(responseTime); } }); serverSocket.on('error', (error) => { console.error('HTTPS proxy error:', error.message); clientSocket.end(); }); clientSocket.on('error', (error) => { console.error('Client socket error:', error.message); serverSocket.end(); }); } /** * Serve PAC file * @private */ _servePacFile(req, res) { const pacContent = this.targetManager.generatePacFile(this.host, this.port); res.writeHead(200, { 'Content-Type': 'application/x-ns-proxy-autoconfig', 'Cache-Control': 'no-cache' }); res.end(pacContent); } /** * Check if request should be proxied * @private */ _shouldProxyRequest(hostname) { return this.targetManager.shouldProxy(hostname); } /** * Check if traffic should be captured * @private */ _shouldCaptureTraffic(hostname) { const target = this.targetManager.findTarget(hostname); return target && (target.captureHeaders || target.captureBody); } /** * Capture HTTP traffic * @private */ _captureTraffic(clientReq, proxyRes, url, responseTime) { const target = this.targetManager.findTarget(url.hostname); if (!target) return; const entry = { timestamp: new Date().toISOString(), url: url.href, method: clientReq.method, domain: url.hostname, statusCode: proxyRes.statusCode, responseTime, headers: target.captureHeaders ? { request: { ...clientReq.headers }, response: { ...proxyRes.headers } } : undefined }; // Capture body if enabled (requires buffering) if (target.captureBody) { // Note: Body capture would require more complex implementation // to buffer and forward simultaneously entry.bodyCaptured = false; entry.note = 'Body capture requires buffering implementation'; } this.trafficAnalyzer.addEntry(entry); } /** * Capture HTTPS CONNECT information * @private */ _captureHttpsConnect(req, hostname, port, responseTime) { const entry = { timestamp: new Date().toISOString(), url: `https://${hostname}:${port}`, method: 'CONNECT', domain: hostname, statusCode: 200, responseTime, tunneled: true }; this.trafficAnalyzer.addEntry(entry); } /** * Send direct response for non-proxied requests * @private */ _sendDirectResponse(res, url) { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end(`Direct access: ${url}\nNot routed through proxy (not a monitored domain)`); } /** * Send error response * @private */ _sendErrorResponse(res, statusCode, message) { res.writeHead(statusCode, { 'Content-Type': 'text/plain' }); res.end(`Proxy Error: ${message}`); } /** * Update performance metrics * @private */ _updateMetrics(responseTime) { const count = this.metrics.proxyCount + this.metrics.directCount; this.metrics.averageResponseTime = (this.metrics.averageResponseTime * (count - 1) + responseTime) / count; } /** * Get server status * @returns {Object} Server status */ getStatus() { return { running: this.running, address: this.getAddress(), uptime: this.metrics.startTime ? Math.floor((Date.now() - this.metrics.startTime.getTime()) / 1000) : 0, metrics: { ...this.metrics } }; } /** * Get server address * @returns {string} Server address */ getAddress() { return this.running ? `http://${this.host}:${this.port}` : null; } /** * Check if server is running * @returns {boolean} Running status */ isRunning() { return this.running; } /** * Get performance metrics * @returns {Object} Performance metrics */ getMetrics() { return { ...this.metrics }; } /** * Reset performance metrics */ resetMetrics() { const startTime = this.metrics.startTime; this.metrics = { requestCount: 0, proxyCount: 0, directCount: 0, errorCount: 0, bytesTransferred: 0, averageResponseTime: 0, startTime, lastRequestTime: null }; } }

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