Skip to main content
Glama
index.js25.2 kB
#!/usr/bin/env node // index.js - WireMCP Secure Server const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js'); const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js'); const axios = require('axios'); const https = require('https'); // Import secure modules const config = require('./config'); const audit = require('./audit'); const rateLimit = require('./rateLimit'); const tshark = require('./tshark'); const { validators, validatePcapPath, sanitizePacketData, sanitizeError } = require('./validators'); // Redirect console.log to stderr (MCP requirement) const originalConsoleLog = console.log; console.log = (...args) => console.error(...args); // Initialize MCP server const server = new McpServer({ name: config.server.name, version: config.server.version, }); // Secure axios instance for threat intelligence const secureAxios = axios.create({ timeout: config.threatIntel.sources.urlhaus.timeout, maxContentLength: config.threatIntel.sources.urlhaus.maxSize, maxBodyLength: config.threatIntel.sources.urlhaus.maxSize, httpsAgent: new https.Agent({ rejectUnauthorized: true, minVersion: 'TLSv1.2' }) }); // Threat intelligence cache const threatCache = { data: null, timestamp: null, ttl: config.threatIntel.cacheTTL * 1000 }; /** * Fetch threat intelligence data with caching */ async function getThreatData() { const now = Date.now(); // Return cached data if still valid if (threatCache.data && threatCache.timestamp && (now - threatCache.timestamp) < threatCache.ttl) { console.error('[THREAT] Using cached threat data'); return threatCache.data; } // Fetch fresh data console.error('[THREAT] Fetching fresh threat intelligence...'); try { const response = await secureAxios.get(config.threatIntel.sources.urlhaus.url); const ipRegex = /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g; const ips = [...new Set( (response.data.match(ipRegex) || []) .filter(ip => { const parts = ip.split('.'); return parts.every(part => parseInt(part) <= 255); }) )]; // Update cache threatCache.data = ips; threatCache.timestamp = now; console.error(`[THREAT] Loaded ${ips.length} threat IPs`); return ips; } catch (error) { console.error(`[THREAT] Failed to fetch data: ${error.message}`); // Return cached data even if expired, if available if (threatCache.data) { console.error('[THREAT] Using expired cache due to fetch failure'); return threatCache.data; } return []; } } /** * Helper to handle tool execution with common error handling */ async function executeTool(toolName, args, handler, clientId = 'unknown') { const startTime = Date.now(); try { // Check rate limit rateLimit.checkRateLimit(clientId); // Execute tool const result = await handler(args, clientId); // Log successful execution const duration = Date.now() - startTime; await audit.logToolExecution(toolName, args, clientId, duration); return result; } catch (error) { // Log error await audit.logToolError(toolName, args, error, clientId); // Return sanitized error return { content: [{ type: 'text', text: `Error: ${sanitizeError(error, config.server.environment === 'development')}` }], isError: true }; } } // ============================================================================= // TOOL 1: Capture Packets // ============================================================================= server.tool( 'capture_packets', 'Capture live network traffic and analyze packet data (requires elevated privileges)', { interface: validators.capturePackets.shape.interface, duration: validators.capturePackets.shape.duration }, async (args) => { return executeTool('capture_packets', args, async (validatedArgs, clientId) => { // Validate input const { interface: iface, duration } = validators.capturePackets.parse(validatedArgs); // Acquire capture slot await rateLimit.acquireCaptureSlot(clientId); let tempFile = null; try { // Capture packets console.error(`[CAPTURE] Starting capture on ${iface} for ${duration}s`); tempFile = await tshark.capture(iface, duration); // Read and parse packets const packets = await tshark.readPcap(tempFile, [ 'frame.number', 'frame.time', 'ip.src', 'ip.dst', 'tcp.srcport', 'tcp.dstport', 'udp.srcport', 'udp.dstport', 'tcp.flags', 'http.request.method', 'http.response.code' ]); // Sanitize sensitive data const sanitized = sanitizePacketData(packets); // Enforce size limit let jsonString = JSON.stringify(sanitized); if (jsonString.length > config.security.maxOutputSize) { const ratio = config.security.maxOutputSize / jsonString.length; const trimCount = Math.floor(sanitized.length * ratio); jsonString = JSON.stringify(sanitized.slice(0, trimCount)); console.error(`[CAPTURE] Trimmed output from ${sanitized.length} to ${trimCount} packets`); } return { content: [{ type: 'text', text: `Captured ${packets.length} packets from ${iface} (${duration}s)\n\n${jsonString}` }] }; } finally { // Always cleanup if (tempFile) await tshark.cleanupFile(tempFile); rateLimit.releaseCaptureSlot(); } }); } ); // ============================================================================= // TOOL 2: Get Summary Statistics // ============================================================================= server.tool( 'get_summary_stats', 'Get protocol hierarchy statistics from live network traffic', { interface: validators.getSummaryStats.shape.interface, duration: validators.getSummaryStats.shape.duration }, async (args) => { return executeTool('get_summary_stats', args, async (validatedArgs, clientId) => { const { interface: iface, duration } = validators.getSummaryStats.parse(validatedArgs); await rateLimit.acquireCaptureSlot(clientId); let tempFile = null; try { console.error(`[STATS] Capturing on ${iface} for ${duration}s`); tempFile = await tshark.capture(iface, duration); const stats = await tshark.getStats(tempFile, 'phs'); return { content: [{ type: 'text', text: `Protocol Hierarchy Statistics (${iface}, ${duration}s):\n\n${stats}` }] }; } finally { if (tempFile) await tshark.cleanupFile(tempFile); rateLimit.releaseCaptureSlot(); } }); } ); // ============================================================================= // TOOL 3: Get Conversations // ============================================================================= server.tool( 'get_conversations', 'Get TCP/UDP conversation statistics from live network traffic', { interface: validators.getConversations.shape.interface, duration: validators.getConversations.shape.duration }, async (args) => { return executeTool('get_conversations', args, async (validatedArgs, clientId) => { const { interface: iface, duration } = validators.getConversations.parse(validatedArgs); await rateLimit.acquireCaptureSlot(clientId); let tempFile = null; try { console.error(`[CONV] Capturing on ${iface} for ${duration}s`); tempFile = await tshark.capture(iface, duration); const tcpStats = await tshark.getStats(tempFile, 'conv,tcp'); return { content: [{ type: 'text', text: `TCP Conversation Statistics (${iface}, ${duration}s):\n\n${tcpStats}` }] }; } finally { if (tempFile) await tshark.cleanupFile(tempFile); rateLimit.releaseCaptureSlot(); } }); } ); // ============================================================================= // TOOL 4: Check Threats // ============================================================================= server.tool( 'check_threats', 'Capture traffic and check IP addresses against URLhaus threat intelligence', { interface: validators.checkThreats.shape.interface, duration: validators.checkThreats.shape.duration }, async (args) => { return executeTool('check_threats', args, async (validatedArgs, clientId) => { if (!config.threatIntel.enabled) { return { content: [{ type: 'text', text: 'Threat intelligence is disabled in configuration' }] }; } const { interface: iface, duration } = validators.checkThreats.parse(validatedArgs); await rateLimit.acquireCaptureSlot(clientId); let tempFile = null; try { console.error(`[THREAT] Capturing on ${iface} for ${duration}s`); tempFile = await tshark.capture(iface, duration); // Extract IPs const ipData = await tshark.extractFields(tempFile, ['ip.src', 'ip.dst']); const ips = [...new Set( ipData.split('\n') .flatMap(line => line.split('\t')) .filter(ip => ip && ip !== 'unknown' && /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(ip)) )]; console.error(`[THREAT] Found ${ips.length} unique IPs`); // Get threat data const threatIPs = await getThreatData(); // Check for matches const threats = ips.filter(ip => threatIPs.includes(ip)); if (threats.length > 0) { await audit.logSecurityEvent( 'THREATS_DETECTED', { threats, totalIPs: ips.length }, clientId ); } const output = `Analyzed ${ips.length} unique IP addresses from ${iface}\n\n` + `Captured IPs:\n${ips.slice(0, 50).join('\n')}${ips.length > 50 ? '\n... and more' : ''}\n\n` + `URLhaus Threat Check:\n${ threats.length > 0 ? `⚠️ THREATS DETECTED: ${threats.length} IP(s) found in URLhaus blacklist:\n${threats.join('\n')}` : `✓ No threats detected (checked against ${threatIPs.length} known malicious IPs)` }`; return { content: [{ type: 'text', text: output }] }; } finally { if (tempFile) await tshark.cleanupFile(tempFile); rateLimit.releaseCaptureSlot(); } }); } ); // ============================================================================= // TOOL 5: Check IP Threats // ============================================================================= server.tool( 'check_ip_threats', 'Check a specific IP address against URLhaus threat intelligence', { ip: validators.checkIpThreats.shape.ip }, async (args) => { return executeTool('check_ip_threats', args, async (validatedArgs, clientId) => { if (!config.threatIntel.enabled) { return { content: [{ type: 'text', text: 'Threat intelligence is disabled in configuration' }] }; } const { ip } = validators.checkIpThreats.parse(validatedArgs); console.error(`[THREAT] Checking IP: ${ip}`); const threatIPs = await getThreatData(); const isThreat = threatIPs.includes(ip); if (isThreat) { await audit.logSecurityEvent( 'MALICIOUS_IP_CHECKED', { ip, source: 'URLhaus' }, clientId ); } const output = `IP Address: ${ip}\n\n` + `URLhaus Threat Check:\n${ isThreat ? `⚠️ THREAT DETECTED: This IP is listed in the URLhaus blacklist` : `✓ No threat detected (checked against ${threatIPs.length} known malicious IPs)` }`; return { content: [{ type: 'text', text: output }] }; }); } ); // ============================================================================= // TOOL 6: Analyze PCAP // ============================================================================= server.tool( 'analyze_pcap', 'Analyze an existing PCAP file and extract comprehensive packet data', { pcapPath: validators.analyzePcap.shape.pcapPath, includeUrls: validators.analyzePcap.shape.includeUrls, includeProtocols: validators.analyzePcap.shape.includeProtocols }, async (args) => { return executeTool('analyze_pcap', args, async (validatedArgs) => { const validated = validators.analyzePcap.parse(validatedArgs); const pcapPath = validatePcapPath(validated.pcapPath); console.error(`[ANALYZE] Processing: ${pcapPath}`); // Read packets const packets = await tshark.readPcap(pcapPath, [ 'frame.number', 'ip.src', 'ip.dst', 'tcp.srcport', 'tcp.dstport', 'udp.srcport', 'udp.dstport', 'http.host', 'http.request.uri', 'frame.protocols' ]); // Extract unique IPs const ips = [...new Set(packets.flatMap(p => [ p._source?.layers['ip.src']?.[0], p._source?.layers['ip.dst']?.[0] ]).filter(ip => ip))]; // Extract URLs if requested let urls = []; if (validated.includeUrls) { urls = packets .filter(p => p._source?.layers['http.host'] && p._source?.layers['http.request.uri']) .map(p => `http://${p._source.layers['http.host'][0]}${p._source.layers['http.request.uri'][0]}`) .slice(0, 100); // Limit to 100 URLs } // Extract protocols if requested let protocols = []; if (validated.includeProtocols) { protocols = [...new Set( packets.map(p => p._source?.layers['frame.protocols']?.[0]) .filter(p => p) )]; } // Sanitize and limit packet data const sanitized = sanitizePacketData(packets); let jsonString = JSON.stringify(sanitized); if (jsonString.length > config.security.maxOutputSize) { const ratio = config.security.maxOutputSize / jsonString.length; const trimCount = Math.floor(sanitized.length * ratio); jsonString = JSON.stringify(sanitized.slice(0, trimCount)); console.error(`[ANALYZE] Trimmed from ${sanitized.length} to ${trimCount} packets`); } const output = `PCAP Analysis: ${path.basename(pcapPath)}\n\n` + `Total Packets: ${packets.length}\n` + `Unique IPs: ${ips.length}\n${ips.slice(0, 20).join('\n')}${ips.length > 20 ? '\n... and more' : ''}\n\n` + (urls.length > 0 ? `URLs Found: ${urls.length}\n${urls.slice(0, 20).join('\n')}${urls.length > 20 ? '\n... and more' : ''}\n\n` : '') + (protocols.length > 0 ? `Protocols: ${protocols.join(', ')}\n\n` : '') + `Packet Data (JSON):\n${jsonString}`; return { content: [{ type: 'text', text: output }] }; }); } ); // ============================================================================= // TOOL 7: Extract Credentials (Restricted) // ============================================================================= server.tool( 'extract_credentials', 'Extract potential credentials from PCAP file (RESTRICTED - requires explicit authorization)', { pcapPath: validators.extractCredentials.shape.pcapPath, includeHashes: validators.extractCredentials.shape.includeHashes }, async (args) => { return executeTool('extract_credentials', args, async (validatedArgs, clientId) => { // Check if feature is enabled if (!config.security.enableCredentialExtraction) { return { content: [{ type: 'text', text: '⚠️ CREDENTIAL EXTRACTION DISABLED\n\n' + 'This feature is disabled in the configuration for security reasons.\n' + 'To enable: Set ENABLE_CREDENTIAL_EXTRACTION=true in environment.' }] }; } const validated = validators.extractCredentials.parse(validatedArgs); const pcapPath = validatePcapPath(validated.pcapPath); console.error(`[CREDENTIALS] Extracting from: ${pcapPath}`); // Extract plaintext credentials const plaintextData = await tshark.extractFields(pcapPath, [ 'http.authbasic', 'ftp.request.command', 'ftp.request.arg', 'telnet.data', 'frame.number' ]); const credentials = { plaintext: [], encrypted: [] }; // Parse plaintext credentials const lines = plaintextData.split('\n').filter(line => line.trim()); lines.forEach(line => { const [authBasic, ftpCmd, ftpArg, telnetData, frameNumber] = line.split('\t'); // HTTP Basic Auth if (authBasic) { try { const [username, password] = Buffer.from(authBasic, 'base64').toString().split(':'); credentials.plaintext.push({ type: 'HTTP Basic Auth', username: username || '', password: password || '', frame: frameNumber }); } catch (e) { // Invalid base64, skip } } // FTP if (ftpCmd === 'USER') { credentials.plaintext.push({ type: 'FTP', username: ftpArg || '', password: '', frame: frameNumber }); } if (ftpCmd === 'PASS') { const lastUser = credentials.plaintext.findLast(c => c.type === 'FTP' && !c.password); if (lastUser) lastUser.password = ftpArg || ''; } }); // Extract Kerberos if includeHashes is true if (validated.includeHashes) { const kerberosData = await tshark.extractFields(pcapPath, [ 'kerberos.CNameString', 'kerberos.realm', 'kerberos.cipher', 'frame.number' ]); const kerbLines = kerberosData.split('\n').filter(line => line.trim()); kerbLines.forEach(line => { const [cname, realm, cipher, frameNumber] = line.split('\t'); if (cipher && cname) { credentials.encrypted.push({ type: 'Kerberos', username: cname, realm: realm || 'unknown', hashPreview: cipher.substring(0, 32) + '...', frame: frameNumber }); } }); } // Log the extraction await audit.logCredentialExtraction( clientId, pcapPath, credentials.plaintext.length + credentials.encrypted.length ); const output = `⚠️ CREDENTIAL EXTRACTION REPORT\n` + `File: ${path.basename(pcapPath)}\n\n` + `Plaintext Credentials: ${credentials.plaintext.length}\n` + (credentials.plaintext.length > 0 ? credentials.plaintext.map(c => ` • ${c.type}: ${c.username}:${c.password} (Frame ${c.frame})` ).join('\n') + '\n' : '') + `\nEncrypted/Hashed Credentials: ${credentials.encrypted.length}\n` + (credentials.encrypted.length > 0 ? credentials.encrypted.map(c => ` • ${c.type}: ${c.username}@${c.realm} (Frame ${c.frame})` ).join('\n') + '\n' : '') + `\n⚠️ WARNING: This operation has been logged for audit purposes.`; return { content: [{ type: 'text', text: output }] }; }); } ); // ============================================================================= // TOOL 8: Get Server Status (NEW) // ============================================================================= server.tool( 'get_status', 'Get server status, rate limits, and configuration information', {}, async (args) => { return executeTool('get_status', args, async (_, clientId) => { const status = rateLimit.getStatus(clientId); const recentAudit = config.audit.enabled ? await audit.getRecentEntries(10) : []; const output = `WireMCP Secure Server Status\n\n` + `Version: ${config.server.version}\n` + `Environment: ${config.server.environment}\n\n` + `Rate Limiting:\n` + ` • Requests in window: ${status.requestsInWindow}/${status.maxRequests}\n` + ` • Remaining: ${status.remaining}\n` + ` • Window: ${status.windowMs/1000}s\n` + ` • Reset at: ${status.resetAt || 'N/A'}\n\n` + `Resources:\n` + ` • Active captures: ${status.activeCaptureCount}/${status.maxConcurrentCaptures}\n` + ` • Max capture duration: ${config.security.maxCaptureDuration}s\n` + ` • Max output size: ${(config.security.maxOutputSize/1024/1024).toFixed(2)}MB\n\n` + `Security:\n` + ` • Audit logging: ${config.audit.enabled ? 'enabled' : 'disabled'}\n` + ` • Threat intelligence: ${config.threatIntel.enabled ? 'enabled' : 'disabled'}\n` + ` • Credential extraction: ${config.security.enableCredentialExtraction ? 'enabled' : 'disabled'}\n` + ` • Data sanitization: ${config.security.sanitizeSensitiveData ? 'enabled' : 'disabled'}\n\n` + (recentAudit.length > 0 ? `Recent Activity (last ${recentAudit.length}):\n${recentAudit.map(e => ` • ${new Date(e.timestamp).toLocaleString()}: ${e.action}` ).join('\n')}` : 'Recent Activity: None'); return { content: [{ type: 'text', text: output }] }; }); } ); // ============================================================================= // Add prompts for better LLM integration // ============================================================================= server.prompt( 'network_analysis', { interface: validators.capturePackets.shape.interface.optional(), duration: validators.capturePackets.shape.duration.optional() }, ({ interface: iface = config.network.defaultInterface, duration = 10 }) => ({ messages: [{ role: 'user', content: { type: 'text', text: `Please analyze network traffic on interface ${iface} for ${duration} seconds and provide insights about: 1. Types of traffic and protocols observed 2. Notable communication patterns 3. Key endpoints and services 4. Any security concerns or anomalies 5. Recommendations for further investigation` } }] }) ); server.prompt( 'threat_hunt', { interface: validators.checkThreats.shape.interface.optional() }, ({ interface: iface = config.network.defaultInterface }) => ({ messages: [{ role: 'user', content: { type: 'text', text: `Perform a threat hunting analysis on interface ${iface}: 1. Capture network traffic for analysis 2. Check all IP addresses against threat intelligence 3. Identify any suspicious patterns or anomalies 4. Assess the overall security posture 5. Provide actionable security recommendations` } }] }) ); // ============================================================================= // Start Server // ============================================================================= async function startServer() { try { console.error('='.repeat(60)); console.error(`WireMCP Secure Server v${config.server.version}`); console.error('='.repeat(60)); // Initialize tshark await tshark.init(); console.error('[INIT] tshark initialized successfully'); // Ensure temp directory exists await tshark.ensureTempDir(); console.error('[INIT] Temp directory ready'); // Clean up old temp files await tshark.cleanupOldFiles(); // Start MCP server await server.connect(new StdioServerTransport()); console.error('[INIT] MCP server connected and ready'); console.error('='.repeat(60)); // Schedule periodic cleanup setInterval(async () => { await tshark.cleanupOldFiles(); }, 3600000); // Every hour } catch (error) { console.error(`[FATAL] Failed to start server: ${error.message}`); await audit.log('SYSTEM', 'startup_failed', { error: error.message }, 'system', 'error'); process.exit(1); } } // Handle graceful shutdown process.on('SIGINT', async () => { console.error('\n[SHUTDOWN] Received SIGINT, shutting down gracefully...'); await audit.log('SYSTEM', 'shutdown', { reason: 'SIGINT' }); await audit.flush(); process.exit(0); }); process.on('SIGTERM', async () => { console.error('\n[SHUTDOWN] Received SIGTERM, shutting down gracefully...'); await audit.log('SYSTEM', 'shutdown', { reason: 'SIGTERM' }); await audit.flush(); process.exit(0); }); // Start the server startServer();

Latest Blog Posts

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/anishphilip012git/WireMCP-Secure'

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