nmapScan
Perform network discovery and security auditing with advanced scanning options. Identify open ports, services, OS, and vulnerabilities for penetration testing. Integrates with the Pentest MCP server for streamlined security operations.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| defaultScripts | No | ||
| fastScan | No | ||
| osDetection | No | ||
| ports | No | ||
| rawOptions | No | ||
| scanTechnique | No | ||
| scriptArgs | No | ||
| scripts | No | ||
| serviceVersionDetection | No | ||
| skipHostDiscovery | No | ||
| target | Yes | ||
| timingTemplate | No | ||
| topPorts | No | ||
| udpScan | No | ||
| userModeHint | No | ||
| verbose | No | ||
| versionIntensity | No |
Implementation Reference
- src/index.ts:687-773 (handler)Primary MCP tool handler for 'nmapScan'. Parses structured input args to build Nmap CLI options, validates combinations, executes scan, handles student/professional output formatting with suggestions.server.tool("nmapScan", nmapScanToolSchema.shape, async (args /*: z.infer<typeof nmapScanToolSchema> */ /*, extra */) => { const { target, ports, fastScan, topPorts, scanTechnique, udpScan, serviceVersionDetection, versionIntensity, osDetection, defaultScripts, scripts, scriptArgs, timingTemplate, skipHostDiscovery, verbose, rawOptions, userModeHint } = args; console.error(`Received nmapScan request:`, args); if (currentUserSession.mode === UserMode.UNKNOWN) { if (userModeHint) { currentUserSession.mode = userModeHint; /* log */ } else { currentUserSession.mode = UserMode.STUDENT; /* log */ } } try { const constructedOptions: string[] = []; const validationErrors: string[] = []; // Restore detailed option building logic if (skipHostDiscovery) constructedOptions.push('-Pn'); let portSpecCount = 0; if (ports) portSpecCount++; if (fastScan) portSpecCount++; if (topPorts) portSpecCount++; if (portSpecCount > 1) validationErrors.push("Use only one of ports, fastScan, or topPorts."); else if (ports) constructedOptions.push('-p', ports); else if (fastScan) constructedOptions.push('-F'); else if (topPorts) constructedOptions.push('--top-ports', String(topPorts)); if (scanTechnique) { switch (scanTechnique) { case 'SYN': constructedOptions.push('-sS'); break; case 'Connect': constructedOptions.push('-sT'); break; case 'ACK': constructedOptions.push('-sA'); break; case 'Window': constructedOptions.push('-sW'); break; case 'Maimon': constructedOptions.push('-sM'); break; case 'FIN': constructedOptions.push('-sF'); break; case 'Xmas': constructedOptions.push('-sX'); break; case 'Null': constructedOptions.push('-sN'); break; case 'Proto': constructedOptions.push('-sO'); break; } } if (udpScan) constructedOptions.push('-sU'); if (serviceVersionDetection) { constructedOptions.push('-sV'); if (versionIntensity !== undefined) constructedOptions.push('--version-intensity', String(versionIntensity)); } else if (versionIntensity !== undefined) validationErrors.push("Cannot set intensity without -sV."); if (osDetection) constructedOptions.push('-O'); if (defaultScripts && scripts) validationErrors.push("Cannot use both -sC and --script."); else if (defaultScripts) constructedOptions.push('-sC'); else if (scripts && scripts.length > 0) constructedOptions.push('--script', scripts.join(',')); if (scriptArgs) { if (!defaultScripts && !scripts) validationErrors.push("Cannot use scriptArgs without scripts."); else constructedOptions.push('--script-args', scriptArgs); } if (timingTemplate) constructedOptions.push(`-${timingTemplate}`); if (verbose) constructedOptions.push('-v'); if (rawOptions) constructedOptions.push(...rawOptions); if (validationErrors.length > 0) throw new Error(`Invalid params: ${validationErrors.join('; ')}`); // Privilege Warning (remains valid) const needsPrivileges = constructedOptions.some(opt => opt === '-sS' || opt === '-O'); if (needsPrivileges) console.warn("Nmap options may require elevated privileges."); const results = await runNmapScan(target, constructedOptions); // Restore detailed response formatting let responseContent: any[] = []; let suggestions: string[] = []; if (typeof results === 'string') { responseContent.push({ type: "text", text: results }); } else if (results) { if (currentUserSession.mode === UserMode.STUDENT) { const { explanation, suggestions: studentSuggestions } = formatResultsForStudent(target, constructedOptions, results); responseContent.push({ type: "text", text: explanation }); suggestions.push(...studentSuggestions); } else { // Professional Mode responseContent.push({ type: "text", text: JSON.stringify(results, null, 2) }); const foundPorts: { [key: string]: Set<string> } = {}; Object.entries(results).forEach(([ip, host]) => { const typedHost = host as nmap.Host; if (!foundPorts[ip]) foundPorts[ip] = new Set(); typedHost.ports?.forEach(port => { if (port.state === 'open') foundPorts[ip].add(port.portId); }); }); for (const ip in foundPorts) { /* ... pro suggestions logic ... */ } if (suggestions.length === 0 && Object.keys(foundPorts).length > 0) suggestions.push("Scan complete."); } } else { responseContent.push({ type: "text", text: "Nmap scan returned no data." }); } if (suggestions.length > 0) responseContent.push({ type: "text", text: "\n**Suggestions:**\n* " + suggestions.join("\n* ") }); return { content: responseContent }; } catch (error: any) { return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true }; } });
- src/index.ts:674-686 (schema)Zod schema defining all input parameters and validation for the nmapScan tool, mapping to common Nmap flags and options.const nmapScanToolSchema = z.object({ target: z.string(), ports: z.string().optional(), fastScan: z.boolean().optional(), topPorts: z.number().int().optional(), scanTechnique: z.enum(['SYN', 'Connect', 'ACK', 'Window', 'Maimon', 'FIN', 'Xmas', 'Null', 'Proto']).optional(), udpScan: z.boolean().optional(), serviceVersionDetection: z.boolean().optional(), versionIntensity: z.number().int().optional(), osDetection: z.boolean().optional(), defaultScripts: z.boolean().optional(), scripts: z.array(z.string()).optional(), scriptArgs: z.string().optional(), timingTemplate: z.enum(['T0', 'T1', 'T2', 'T3', 'T4', 'T5']).optional(), skipHostDiscovery: z.boolean().optional(), verbose: z.boolean().optional(), rawOptions: z.array(z.string()).optional(), userModeHint: z.enum([UserMode.STUDENT, UserMode.PROFESSIONAL]).optional() }).describe( "Run an Nmap scan to discover hosts and services. Use this before other " + "tools to identify attack surface. Options map directly to Nmap flags. " + "Note that SYN scans or OS detection (e.g. `-sS`, `-O`) require elevated " + "privileges. Example: `{\"target\":\"192.168.1.0/24\", \"scanTechnique\":\"SYN\", \"serviceVersionDetection\":true}`" );
- src/index.ts:175-255 (helper)Key helper function that spawns the actual nmap process via child_process.spawn, parses XML output into typed ScanData, tracks scan progress from stderr, manages active scans map.async function runNmapScan(target: string, options: string[] = []): Promise<ScanData | null> { const scanId = `scan-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; const progress: ScanProgress = { scanId, target, startTime: Date.now(), progress: 0, status: 'initializing' }; // trackProgress(progress); // Commented out console.error(`Executing nmap scan: target=${target}, options=${options.join(' ')}`); if (!target) { throw new Error("Error: Target is required."); } return new Promise((resolve, reject) => { const args = [...options, '-oX', '-', target]; const nmapProcess = spawn('nmap', args); activeScans.set(scanId, { process: nmapProcess, progress }); // Track active scan let stdoutData = ''; let stderrData = ''; nmapProcess.stdout.on('data', (data) => { stdoutData += data.toString(); }); nmapProcess.stderr.on('data', (data) => { stderrData += data.toString(); // Update progress based on stderr const output = data.toString(); if (output.includes('Initiating')) { progress.status = 'scanning'; progress.progress = 10; } else if (output.includes('Completed SYN Stealth Scan')) { progress.progress = 40; } else if (output.includes('Initiating Service scan')) { progress.currentStage = 'Service detection'; progress.progress = 50; } else if (output.includes('Completed Service scan')) { progress.progress = 70; } else if (output.includes('Initiating OS detection')) { progress.currentStage = 'OS detection'; progress.progress = 80; } // trackProgress(progress); // Commented out }); nmapProcess.on('error', (error) => { activeScans.delete(scanId); progress.status = 'failed'; // trackProgress(progress); // Commented out reject(error); }); let parsedScanData: ScanData | null = null; nmapProcess.on('close', async (code) => { activeScans.delete(scanId); console.error(`Nmap process exited with code ${code}`); if (stderrData) { console.warn(`Nmap stderr: ${stderrData}`); } let resultForLog: ScanData | string | null = null; if (code === 0 && stdoutData) { try { const parser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: "@", allowBooleanAttributes: true, parseAttributeValue: true, isArray: (name: string) => ['host', 'port', 'hostname', 'osmatch', 'cpe'].includes(name) }); const parsedXml = parser.parse(stdoutData); parsedScanData = {}; // Init // ... (Transformation logic) ... resultForLog = parsedScanData; // Log the parsed data on success } catch (parseError: any) { console.error("Failed to parse Nmap XML output:", parseError); resultForLog = `XML Parse Error: ${parseError.message}\n${stdoutData}`; // Log error + raw data parsedScanData = null; // Don't log twice, the main log call handles this based on code reject(new Error(`Failed to parse Nmap XML output: ${parseError.message}`)); return; } } else if (code !== 0) { resultForLog = stderrData || `Nmap failed: ${code}`; // Log stderr on failure } if (currentUserSession.mode === UserMode.PROFESSIONAL) { // Correct call with 3 arguments await logScanResult(target, options, resultForLog); } if (code !== 0) { const errorMsg = `Nmap scan failed with exit code ${code}.${stderrData ? " Stderr: " + stderrData : ""}`; reject(new Error(errorMsg)); } else { resolve(parsedScanData); // Resolve with parsed data (or null) } }); }); }
- src/index.ts:441-497 (helper)Helper for formatting nmap results in student mode with detailed explanations and pedagogical suggestions for next pentest steps.function formatResultsForStudent(target: string, options: string[], results: ScanData): { explanation: string, suggestions: string[] } { let explanation = `Scan results for **${target}** (options: ${options.join(' ') || 'default'}):\n\n`; const suggestions: string[] = []; let foundOpenPorts = false; for (const ip in results) { const hostData = results[ip] as Host; explanation += `**Host:** ${hostData.hostname ? `${hostData.hostname} (${ip})` : ip}\n`; if (hostData.mac) { explanation += `* MAC Address: ${hostData.mac} (This is the hardware address, useful for identifying devices on a local network).\n`; } if (hostData.osNmap) { explanation += `* Operating System Guess: ${hostData.osNmap} (Nmap tries to guess the OS based on network responses).\n`; if (!options.includes('-O')) { suggestions.push(`Try adding \`-O\` to the options for a more dedicated OS detection scan on ${ip}.`); } } if (hostData.ports && hostData.ports.length > 0) { const openPorts = hostData.ports.filter(port => port.state === 'open'); if (openPorts.length > 0) { foundOpenPorts = true; explanation += `* **Open Ports Found:**\n`; openPorts.forEach(port => { explanation += ` * **Port ${port.portId}/${port.protocol}:** State is **${port.state}**. Service detected: **${port.service?.name || 'unknown'}**. Reason: ${port.reason}\n`; if (port.portId === '80' || port.portId === '443') { suggestions.push(`Port ${port.portId} (${port.service?.name}) is open on ${ip}. Try exploring it with a web browser or tools like \`curl\`.`); suggestions.push(`Consider running \`nmapScan\` with scripts: \`options: ["-sV", "-sC", "-p${port.portId}"]\` on ${ip} to get more service info.`); } if (port.portId === '22') { suggestions.push(`SSH (Port 22) is open on ${ip}. You could try connecting if you have credentials, or check for common vulnerabilities (\`options: ["-sV", "--script=ssh-auth-methods"]\`).`); } if (port.portId === '21' || port.portId === '23') { // FTP/Telnet suggestions.push(`${port.service?.name} (Port ${port.portId}) on ${ip} is often insecure. Check for anonymous login or default credentials (\`options: ["-sV", "--script=ftp-anon"]\` for FTP).`); } if (port.portId === '3389') { // RDP suggestions.push(`RDP (Port 3389) on ${ip} allows remote desktop access. Check for weak passwords or vulnerabilities.`); } }); } else { explanation += `* No *open* ports were detected in the scanned range for ${ip}. Filtered ports might still exist.\n`; } } else { explanation += `* Port scanning was not performed or no ports were reported for ${ip}.\n`; } explanation += `\n`; } if (!foundOpenPorts) { suggestions.push("No open ports found with the current options. Try scanning all ports (\`-p-\` ) or using different scan types like SYN scan (\`-sS\`, requires root/admin) or UDP scan (\`-sU\`)."); } if (!options.includes('-sV') && foundOpenPorts) { suggestions.push("Run with \`-sV\` option to try and determine the version of the services running on the open ports."); } return { explanation, suggestions }; }
- src/index.ts:595-616 (registration)Registers 'nmapScan' as a supported tool capability in the MCP server constructor.server = new McpServer({ name: "pentest-mcp", version: "0.5.0", // Explicitly declare capabilities to enable discovery capabilities: { resources: { "mode": {}, "clientReport": {} }, tools: { "setMode": {}, "generateWordlist": {}, "cancelScan": {}, "createClientReport": {}, "nmapScan": {}, "runJohnTheRipper": {}, "runHashcat": {}, "gobuster": {}, "nikto": {} }, // prompts: {} // No prompts defined }