Skip to main content
Glama

Web Proxy MCP Server

by mako10k
proxy-server-ssl.js32.1 kB
/** * HTTP/HTTPS Proxy Server with SSL Bumping * Enhanced proxy server with HTTPS interception capabilities */ import http from 'http'; import https from 'https'; import { URL } from 'url'; import net from 'net'; import tls from 'tls'; import fs from 'fs/promises'; import assert from 'assert'; export class ProxyServerWithSSL { constructor(targetManager, trafficAnalyzer = null, sslManager = null) { // Contract: targetManager is required and must have necessary methods assert(targetManager, 'targetManager is required'); assert(typeof targetManager.shouldProxy === 'function', 'targetManager must have shouldProxy method'); assert(typeof targetManager.findTarget === 'function', 'targetManager must have findTarget method'); assert(typeof targetManager.generatePacFile === 'function', 'targetManager must have generatePacFile method'); // Contract: trafficAnalyzer if provided must have addEntry method if (trafficAnalyzer) { assert(typeof trafficAnalyzer.addEntry === 'function', 'trafficAnalyzer must have addEntry method'); } // Contract: sslManager if provided must have required SSL methods if (sslManager) { assert(typeof sslManager.generateServerCertificate === 'function', 'sslManager must have generateServerCertificate method'); assert(typeof sslManager.getCACertificate === 'function', 'sslManager must have getCACertificate method'); } this.targetManager = targetManager; this.trafficAnalyzer = trafficAnalyzer; this.sslManager = sslManager; this.server = null; this.port = null; this.host = null; this.running = false; this.sslBumpingEnabled = false; // SSL certificate cache this.certificateCache = new Map(); // Performance metrics this.metrics = { requestCount: 0, proxyCount: 0, directCount: 0, errorCount: 0, sslBumpingCount: 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 * @param {Object} options - Server options */ async start(port = 8080, host = 'localhost', options = {}) { // Pre-conditions assert(typeof port === 'number' && port > 0 && port <= 65535, 'Port must be a valid number between 1 and 65535'); assert(typeof host === 'string' && host.length > 0, 'Host must be a non-empty string'); assert(typeof options === 'object', 'Options must be an object'); if (this.running) { throw new Error('Proxy server is already running'); } this.port = port; this.host = host; this.sslBumpingEnabled = options.enableSSLBumping || false; this.metrics.startTime = new Date(); // Contract: SSL Bumping requires sslManager if (this.sslBumpingEnabled) { if (!this.sslManager) { throw new Error('SSL Bumping enabled but sslManager not provided'); } await this._initializeSSL(); } this.server = http.createServer(); // Handle HTTP requests (consolidated handler) this.server.on('request', (req, res) => { // Serve PAC file and CA certificate first if (req.url === '/proxy.pac') { this._servePacFile(req, res); return; } if (req.url === '/ca.crt') { this._serveCACertificate(req, res); return; } // Handle regular HTTP proxy requests this._handleHttpRequest(req, res); }); // Handle HTTPS CONNECT method for tunneling this.server.on('connect', (req, clientSocket, head) => { this._handleHttpsConnect(req, clientSocket, head); }); // Handle WebSocket upgrade requests this.server.on('upgrade', (req, clientSocket, head) => { this._handleWebSocketUpgrade(req, clientSocket, head); }); 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}`); if (this.sslBumpingEnabled) { console.log(`🔒 SSL Bumping: ENABLED`); this._logCAInstallationInstructions(); } else { console.log(`🔒 SSL Bumping: DISABLED (HTTPS tunneled only)`); } 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 { let url; let shouldProxy = false; // Check if this is a proxy request with full URL (http://example.com/path) if (clientReq.url.startsWith('http://') || clientReq.url.startsWith('https://')) { // Full URL proxy request pattern: GET http://example.com/path HTTP/1.1 url = new URL(clientReq.url); shouldProxy = this._shouldProxyRequest(url.hostname); } else { // Relative path proxy request pattern: GET /path HTTP/1.1 // Extract hostname from Host header const hostHeader = clientReq.headers.host; if (!hostHeader) { throw new Error('Missing Host header for relative path request'); } // Parse hostname and port from Host header const [hostname, port] = hostHeader.split(':'); shouldProxy = this._shouldProxyRequest(hostname); if (shouldProxy) { // Reconstruct full URL for proxy request const protocol = clientReq.connection.encrypted ? 'https:' : 'http:'; url = new URL(`${protocol}//${hostHeader}${clientReq.url}`); } } if (!shouldProxy) { this.metrics.directCount++; this._sendDirectResponse(clientRes, clientReq.url); 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 with optional SSL bumping * @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++; this._createDirectTunnel(clientSocket, hostname, port); return; } this.metrics.proxyCount++; if (this.sslBumpingEnabled && this._shouldBumpSSL(hostname)) { this._performSSLBumping(req, clientSocket, head, hostname, port, startTime); } else { this._createProxyTunnel(req, clientSocket, head, hostname, port, startTime); } } catch (error) { this.metrics.errorCount++; console.error('HTTPS CONNECT error:', error.message); clientSocket.end(); } } /** * Handle WebSocket upgrade requests * @private */ _handleWebSocketUpgrade(req, clientSocket, head) { const startTime = Date.now(); this.metrics.requestCount++; try { let hostname, port = 80; // Parse hostname from request if (req.url.startsWith('ws://') || req.url.startsWith('wss://')) { const url = new URL(req.url); hostname = url.hostname; port = url.port || (url.protocol === 'wss:' ? 443 : 80); } else { // Use Host header for relative WebSocket URLs const hostHeader = req.headers.host; if (!hostHeader) { console.error('Missing Host header for WebSocket upgrade'); clientSocket.end(); return; } [hostname, port = 80] = hostHeader.split(':'); } const shouldProxy = this._shouldProxyRequest(hostname); if (!shouldProxy) { this.metrics.directCount++; this._createDirectWebSocketTunnel(clientSocket, hostname, port, req, head); return; } this.metrics.proxyCount++; this._createProxyWebSocketTunnel(clientSocket, hostname, port, req, head, startTime); } catch (error) { this.metrics.errorCount++; console.error('WebSocket upgrade error:', error.message); clientSocket.end(); } } /** * Create proxy WebSocket tunnel * @private */ _createProxyWebSocketTunnel(clientSocket, hostname, port, req, head, startTime) { const targetSocket = net.createConnection(port, hostname, () => { // Forward the original upgrade request const requestHeaders = Object.keys(req.headers) .map(key => `${key}: ${req.headers[key]}`) .join('\r\n'); const upgradeRequest = [ `${req.method} ${req.url} HTTP/${req.httpVersion}`, requestHeaders, '', '' ].join('\r\n'); targetSocket.write(upgradeRequest); if (head && head.length > 0) { targetSocket.write(head); } }); targetSocket.on('error', (error) => { console.error(`WebSocket proxy tunnel error to ${hostname}:${port}:`, error.message); clientSocket.end(); }); // Bidirectional pipe for WebSocket data clientSocket.pipe(targetSocket); targetSocket.pipe(clientSocket); clientSocket.on('error', () => targetSocket.end()); targetSocket.on('error', () => clientSocket.end()); // Log WebSocket connection console.log(`🔌 WebSocket proxy tunnel: ${hostname}:${port}`); // Capture WebSocket traffic if enabled if (this._shouldCaptureTraffic(hostname)) { this._captureWebSocketTraffic(req, hostname, startTime); } } /** * Create direct WebSocket tunnel (bypass proxy) * @private */ _createDirectWebSocketTunnel(clientSocket, hostname, port, req, head) { const targetSocket = net.createConnection(port, hostname, () => { // Forward the original upgrade request const requestHeaders = Object.keys(req.headers) .map(key => `${key}: ${req.headers[key]}`) .join('\r\n'); const upgradeRequest = [ `${req.method} ${req.url} HTTP/${req.httpVersion}`, requestHeaders, '', '' ].join('\r\n'); targetSocket.write(upgradeRequest); if (head && head.length > 0) { targetSocket.write(head); } }); targetSocket.on('error', (error) => { console.error(`WebSocket direct tunnel error to ${hostname}:${port}:`, error.message); clientSocket.end(); }); // Bidirectional pipe for WebSocket data clientSocket.pipe(targetSocket); targetSocket.pipe(clientSocket); clientSocket.on('error', () => targetSocket.end()); targetSocket.on('error', () => clientSocket.end()); console.log(`🔌 WebSocket direct tunnel: ${hostname}:${port}`); } /** * Capture WebSocket traffic * @private */ _captureWebSocketTraffic(req, hostname, startTime) { const responseTime = Date.now() - startTime; const trafficEntry = { id: `ws-${Date.now()}-${Math.random().toString(36).substring(7)}`, timestamp: new Date().toISOString(), method: 'WEBSOCKET', url: `ws://${hostname}${req.url}`, hostname: hostname, responseTime: responseTime, headers: this._shouldCaptureTraffic(hostname) ? req.headers : null, userAgent: req.headers['user-agent'] || 'Unknown', remoteAddress: req.connection.remoteAddress }; this.trafficLog.push(trafficEntry); console.log(`📊 WebSocket traffic captured: ${hostname}`); } /** * Perform SSL bumping (man-in-the-middle) * @private */ async _performSSLBumping(req, clientSocket, head, hostname, port, startTime) { try { this.metrics.sslBumpingCount++; // Get or generate certificate for this domain const { key, cert } = await this._getCertificateForDomain(hostname); // Send 200 Connection Established to client clientSocket.write('HTTP/1.1 200 Connection Established\r\n\r\n'); // Create TLS server for client connection const tlsOptions = { key, cert, // Allow legacy certificates for testing secureProtocol: 'TLSv1_2_method' }; const tlsServer = tls.createSecureContext(tlsOptions); const tlsSocket = new tls.TLSSocket(clientSocket, { isServer: true, secureContext: tlsServer }); tlsSocket.on('secure', () => { console.log(`🔓 SSL bumping established for ${hostname}`); // Create HTTP server to handle decrypted requests this._handleDecryptedHTTPS(tlsSocket, hostname, port, startTime); }); tlsSocket.on('error', (error) => { console.error(`SSL bumping error for ${hostname}:`, error.message); clientSocket.end(); }); } catch (error) { console.error('SSL bumping setup failed:', error.message); // Fallback to normal tunneling this._createProxyTunnel(req, clientSocket, head, hostname, port, startTime); } } /** * Handle decrypted HTTPS traffic * @private */ _handleDecryptedHTTPS(tlsSocket, hostname, port, startTime) { let buffer = ''; tlsSocket.on('data', async (data) => { buffer += data.toString(); // Check if we have a complete HTTP request const headerEnd = buffer.indexOf('\r\n\r\n'); if (headerEnd === -1) return; // Wait for more data const headerPart = buffer.substring(0, headerEnd); const bodyPart = buffer.substring(headerEnd + 4); // Parse HTTP request const lines = headerPart.split('\r\n'); const requestLine = lines[0]; const [method, path, version] = requestLine.split(' '); // Build headers object const headers = {}; for (let i = 1; i < lines.length; i++) { const [key, value] = lines[i].split(': '); if (key && value) { headers[key.toLowerCase()] = value; } } // Ensure Host header if (!headers.host) { headers.host = hostname; } // Create URL for this request const url = new URL(`https://${hostname}:${port}${path}`); try { // Forward to actual server await this._forwardDecryptedRequest( tlsSocket, method, url, headers, bodyPart, startTime ); } catch (error) { console.error('Error forwarding decrypted request:', error.message); this._sendErrorToTLSSocket(tlsSocket, 502, 'Bad Gateway'); } // Reset buffer for next request buffer = ''; }); tlsSocket.on('error', (error) => { console.error('TLS socket error:', error.message); }); } /** * Forward decrypted HTTPS request to actual server * @private */ async _forwardDecryptedRequest(tlsSocket, method, url, headers, body, startTime) { const options = { hostname: url.hostname, port: url.port || 443, path: url.pathname + url.search, method, headers: { ...headers } }; // Remove proxy-specific headers delete options.headers['proxy-connection']; delete options.headers['proxy-authorization']; const proxyReq = https.request(options, (proxyRes) => { const responseTime = Date.now() - startTime; this._updateMetrics(responseTime); // Capture decrypted traffic if (this.trafficAnalyzer && this._shouldCaptureTraffic(url.hostname)) { this._captureDecryptedTraffic(method, url, headers, proxyRes, responseTime, body); } // Build HTTP response let responseHeaders = ''; responseHeaders += `HTTP/1.1 ${proxyRes.statusCode} ${proxyRes.statusMessage}\r\n`; for (const [key, value] of Object.entries(proxyRes.headers)) { responseHeaders += `${key}: ${value}\r\n`; } responseHeaders += '\r\n'; // Send response headers tlsSocket.write(responseHeaders); // Forward response body proxyRes.on('data', (chunk) => { tlsSocket.write(chunk); }); proxyRes.on('end', () => { // Response complete - keep connection alive for next request this.metrics.bytesTransferred += parseInt(proxyRes.headers['content-length'] || '0'); }); }); proxyReq.on('error', (error) => { console.error('Decrypted proxy request error:', error.message); this._sendErrorToTLSSocket(tlsSocket, 502, 'Bad Gateway'); }); // Send request body if present if (body) { proxyReq.write(body); } proxyReq.end(); } /** * Create direct tunnel (no proxying) * @private */ _createDirectTunnel(clientSocket, hostname, port) { const serverSocket = net.connect(port, hostname, () => { clientSocket.write('HTTP/1.1 200 Connection established\r\n\r\n'); serverSocket.pipe(clientSocket); clientSocket.pipe(serverSocket); }); serverSocket.on('error', (error) => { console.error('Direct tunnel error:', error.message); clientSocket.end(); }); } /** * Create proxy tunnel (no SSL bumping) * @private */ _createProxyTunnel(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); 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('Proxy tunnel error:', error.message); clientSocket.end(); }); } /** * Get or generate certificate for domain * @private */ async _getCertificateForDomain(hostname) { // Check cache first if (this.certificateCache.has(hostname)) { return this.certificateCache.get(hostname); } try { // Generate certificate for this domain const certInfo = await this.sslManager.generateServerCertificate(hostname, [ hostname, `*.${hostname}` // Include wildcard ]); // Read certificate and key const cert = await fs.readFile(certInfo.certPath, 'utf-8'); const key = await fs.readFile(certInfo.keyPath, 'utf-8'); const result = { cert, key }; // Cache for future use this.certificateCache.set(hostname, result); console.log(`📜 Generated SSL certificate for ${hostname}`); return result; } catch (error) { console.error(`Failed to generate certificate for ${hostname}:`, error.message); throw error; } } /** * Initialize SSL components * @private */ async _initializeSSL() { if (!this.sslManager) { throw new Error('SSL Manager not provided but SSL bumping is enabled'); } const status = await this.sslManager.initialize(); if (!status.caExists) { console.log('🔧 Creating Certificate Authority for SSL bumping...'); await this.sslManager.createCA('default', { description: 'Web Proxy MCP Server SSL Bumping CA' }); } console.log(`🔒 SSL Manager initialized with CA: ${status.caName}`); } /** * Check if SSL bumping should be performed for this hostname * @private */ _shouldBumpSSL(hostname) { // Only bump SSL for monitored domains that have traffic capture enabled const target = this.targetManager.findTarget(hostname); return target && (target.captureHeaders || target.captureBody); } /** * Serve CA certificate for download * @private */ async _serveCACertificate(req, res) { try { // Check if headers already sent if (res.headersSent) { console.warn('Headers already sent for CA certificate request'); return; } if (!this.sslManager) { res.writeHead(404, { 'Content-Type': 'text/plain' }); res.end('SSL Manager not available'); return; } const caCert = await this.sslManager.getCACertificate(); res.writeHead(200, { 'Content-Type': 'application/x-x509-ca-cert', 'Content-Disposition': `attachment; filename="${this.sslManager.currentCA}.crt"`, 'Cache-Control': 'no-cache' }); res.end(caCert.certContent); } catch (error) { console.error('Error serving CA certificate:', error); if (!res.headersSent) { res.writeHead(500, { 'Content-Type': 'text/plain' }); res.end(`Error serving CA certificate: ${error.message}`); } } } /** * Log CA installation instructions * @private */ async _logCAInstallationInstructions() { if (!this.sslManager) return; try { const caCert = await this.sslManager.getCACertificate(); console.log('\n' + '='.repeat(80)); console.log('🔒 SSL BUMPING ACTIVE - CA CERTIFICATE INSTALLATION REQUIRED'); console.log('='.repeat(80)); console.log(`\n📥 Download CA Certificate:`); console.log(` curl -o proxy-ca.crt http://${this.host}:${this.port}/ca.crt`); console.log(` OR browse to: http://${this.host}:${this.port}/ca.crt`); console.log(`\n📁 CA Certificate Location:`); console.log(` ${caCert.certPath}`); console.log('\n' + caCert.installationInstructions); console.log('\n' + '='.repeat(80)); } catch (error) { console.error('Failed to display CA installation instructions:', error.message); } } /** * Capture decrypted HTTPS traffic * @private */ _captureDecryptedTraffic(method, url, requestHeaders, proxyRes, responseTime, requestBody) { const target = this.targetManager.findTarget(url.hostname); if (!target) return; const entry = { timestamp: new Date().toISOString(), url: url.href, method, domain: url.hostname, statusCode: proxyRes.statusCode, responseTime, sslBumped: true, headers: target.captureHeaders ? { request: { ...requestHeaders }, response: { ...proxyRes.headers } } : undefined, requestBody: target.captureBody && requestBody ? requestBody : undefined }; // Note: Response body capture would require additional buffering if (target.captureBody) { entry.bodyCaptured = false; entry.note = 'Response body capture requires additional implementation'; } this.trafficAnalyzer.addEntry(entry); } /** * Send error response to TLS socket * @private */ _sendErrorToTLSSocket(tlsSocket, statusCode, message) { const response = `HTTP/1.1 ${statusCode} ${message}\r\nContent-Type: text/plain\r\n\r\n${message}`; tlsSocket.write(response); } // ... (继续原有的方法) /** * Proxy HTTP request through our server * @private */ async _proxyHttpRequest(clientReq, clientRes, url, startTime) { // Pre-conditions assert(clientReq, 'clientReq is required'); assert(clientRes, 'clientRes is required'); assert(url, 'url is required'); assert(typeof url.hostname === 'string', 'url.hostname must be a string'); assert(typeof startTime === 'number', 'startTime must be a number'); 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); // Contract: Ensure traffic capture is attempted for monitored targets const shouldCapture = this._shouldCaptureTraffic(url.hostname); console.log(`🔍 Traffic capture check for ${url.hostname}: shouldCapture=${shouldCapture}, hasAnalyzer=${!!this.trafficAnalyzer}`); if (this.trafficAnalyzer && shouldCapture) { try { this._captureTraffic(clientReq, proxyRes, url, responseTime); console.log(`📊 Traffic capture attempted for ${url.hostname}`); } catch (error) { console.error(`Failed to capture traffic for ${url.hostname}:`, error.message); } } else { console.log(`❌ Traffic capture skipped for ${url.hostname}: analyzer=${!!this.trafficAnalyzer}, shouldCapture=${shouldCapture}`); } // 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); } /** * Serve PAC file * @private */ _servePacFile(req, res) { try { // Check if headers already sent if (res.headersSent) { console.warn('Headers already sent for PAC file request'); return; } 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); } catch (error) { console.error('Error serving PAC file:', error); if (!res.headersSent) { res.writeHead(500, { 'Content-Type': 'text/plain' }); res.end('Error serving PAC file'); } } } /** * Check if request should be proxied * @private */ _shouldProxyRequest(hostname) { return this.targetManager.shouldProxy(hostname); } /** * Check if traffic should be captured * @private */ _shouldCaptureTraffic(hostname) { // Pre-conditions assert(typeof hostname === 'string', 'hostname must be a string'); assert(hostname.length > 0, 'hostname cannot be empty'); const target = this.targetManager.findTarget(hostname); console.log(`🔍 Traffic capture check for ${hostname}: shouldCapture=${target ? (target.captureHeaders || target.captureBody) : false}, hasAnalyzer=${!!this.trafficAnalyzer}`); const result = target ? (target.captureHeaders || target.captureBody) : false; // Post-condition: result must be boolean assert(typeof result === 'boolean', 'shouldCaptureTraffic must return boolean'); if (!result) { console.log(`❌ Traffic capture skipped for ${hostname}: analyzer=${!!this.trafficAnalyzer}, shouldCapture=${result}`); } return result; } /** * Capture HTTP traffic * @private */ _captureTraffic(clientReq, proxyRes, url, responseTime) { // Pre-conditions assert(clientReq, 'clientReq is required'); assert(proxyRes, 'proxyRes is required'); assert(url, 'url is required'); assert(typeof url.hostname === 'string', 'url.hostname must be a string'); assert(typeof responseTime === 'number', 'responseTime must be a number'); // Contract: trafficAnalyzer must be available for capture if (!this.trafficAnalyzer) { console.warn('Traffic capture skipped: trafficAnalyzer not available'); return; } const target = this.targetManager.findTarget(url.hostname); if (!target) { console.debug(`Traffic capture skipped: ${url.hostname} not a monitored target`); return; } const entry = { timestamp: new Date().toISOString(), url: url.href, method: clientReq.method, domain: url.hostname, statusCode: proxyRes.statusCode, responseTime, sslBumped: false, headers: target.captureHeaders ? { request: { ...clientReq.headers }, response: { ...proxyRes.headers } } : undefined }; // Capture body if enabled (requires buffering) if (target.captureBody) { entry.bodyCaptured = false; entry.note = 'Body capture requires buffering implementation'; } try { this.trafficAnalyzer.addEntry(entry); console.log(`📊 Traffic captured: ${clientReq.method} ${url.href} -> ${proxyRes.statusCode}`); } catch (error) { console.error('Failed to add traffic entry:', error.message); throw new Error(`Traffic capture failed: ${error.message}`); } // Post-condition: Verify entry was added assert(this.trafficAnalyzer.entries && this.trafficAnalyzer.entries.length > 0, 'Traffic entry should have been added'); } /** * 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, sslBumped: false }; 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(), sslBumpingEnabled: this.sslBumpingEnabled, caName: this.sslManager ? this.sslManager.currentCA : null, 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, sslBumpingCount: 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