Skip to main content
Glama

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
NameRequiredDescriptionDefault
targetYes
portsNo
fastScanNo
topPortsNo
scanTechniqueNo
udpScanNo
serviceVersionDetectionNo
versionIntensityNo
osDetectionNo
defaultScriptsNo
scriptsNo
scriptArgsNo
timingTemplateNo
skipHostDiscoveryNo
verboseNo
rawOptionsNo
userModeHintNo

Implementation Reference

  • 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 */) => {
  • 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 }; } });
  • 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) } }); }); }
  • 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; }

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/DMontgomery40/pentest-mcp'

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