nmapScan
Perform network scanning to discover hosts, open ports, services, and operating systems on target systems for security assessment and penetration testing.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| target | Yes | ||
| ports | No | ||
| fastScan | No | ||
| topPorts | No | ||
| scanTechnique | No | ||
| udpScan | No | ||
| serviceVersionDetection | No | ||
| versionIntensity | No | ||
| osDetection | No | ||
| defaultScripts | No | ||
| scripts | No | ||
| scriptArgs | No | ||
| timingTemplate | No | ||
| skipHostDiscovery | No | ||
| verbose | No | ||
| rawOptions | No | ||
| userModeHint | No |
Implementation Reference
- src/index.ts:674-686 (schema)Zod schema defining input parameters and description for the nmapScan MCP tool.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:687-687 (registration)Registration of the nmapScan tool using server.tool() with schema and handler reference.server.tool("nmapScan", nmapScanToolSchema.shape, async (args /*: z.infer<typeof nmapScanToolSchema> */ /*, extra */) => {
- src/index.ts:687-773 (handler)Main handler function for nmapScan tool: destructures args, builds Nmap options from structured params or rawOptions, executes scan, formats output for student/professional modes, handles errors.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:175-255 (helper)Core helper that spawns nmap process, streams output, parses Nmap XML to ScanData structure, manages active scans and progress.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/logger.ts:22-70 (helper)Helper in logger.ts to format nmap ScanData into user-friendly markdown for logging scan results.function formatScanDataToMarkdown(target: string, options: string[], data: ScanData | string | null): string { // Use template literals for easier string construction and readability let markdown = `## Scan Details - ${new Date().toISOString()}\\n\\n`; markdown += `* **Target:** \\\`${target}\\\`\\n`; // Escaped backticks for markdown code format markdown += `* **Options:** \\\`${options.length > 0 ? options.join(' ') : 'default'}\\\`\\n\\n`; if (typeof data === 'string') { // Handle error messages or simple string output markdown += `**Result:** Error or simple message\\n\\\`\\\`\\\`\\n${data}\\n\\\`\\\`\\\`\\n`; // Escaped backticks for markdown code block } else if (data && Object.keys(data).length > 0) { markdown += `**Results Summary:**\\n\\n`; for (const ip in data) { // Ensure we are accessing valid Host data if (!data[ip] || typeof data[ip] !== 'object') continue; const hostData = data[ip] as Host; // Type assertion based on our d.ts markdown += `### Host: ${hostData.hostname ? `${hostData.hostname} (${ip})` : ip}\\n\\n`; if (hostData.mac) { markdown += `* **MAC Address:** ${hostData.mac}\\n`; } if (hostData.osNmap) { markdown += `* **OS Guess:** ${hostData.osNmap}\\n`; } if (hostData.ports && hostData.ports.length > 0) { const openPorts = hostData.ports.filter(port => port.state === 'open'); if (openPorts.length > 0) { markdown += `* **Open Ports:**\\n`; markdown += ` | Port ID | Protocol | State | Service | Reason |\\n`; markdown += ` |---------|----------|-------|---------|--------|\\n`; openPorts.forEach(port => { // Use optional chaining for potentially missing service details markdown += ` | ${port.portId || 'N/A'} | ${port.protocol || 'N/A'} | ${port.state || 'N/A'} | ${port.service?.name || 'N/A'} | ${port.reason || 'N/A'} |\\n`; }); } else { markdown += `* No open ports found.\\n`; } } else { markdown += `* Port scanning not performed or no ports reported.\\n`; } markdown += `\\n`; // Add space between hosts } } else { markdown += `**Result:** No relevant data returned from scan.\\n`; } markdown += `\\n---\\n\\n`; // Separator return markdown; }