What this is
Connects Claude Desktop to a local nmap installation via a Model Context Protocol (MCP) server, so scans can be requested conversationally (“scan localhost for open ports”) instead of typing raw nmap commands. Claude picks the flags, runs the scan through the MCP tool, and helps interpret the results.
Repo used: PhialsBasement/nmap-mcp-server (community-maintained, not an Anthropic product — evaluated and patched locally, see below).
Scope/authorization note: this only scans targets I already have explicit authorization to scan (home network, my own boxes, or pre-approved client/work targets). Claude executing a scan on request doesn’t create new authorization — the same rules that applied before this tool existed still apply.
Installation (what actually worked)
cd ~
git clone https://github.com/PhialsBasement/nmap-mcp-server.git
cd nmap-mcp-server
npm install
npm install zod@3.25.28 # see "Problems encountered" below for why this specific version
npm run build
Verify the build succeeded and the server runs standalone (it should hang silently with NMAP server running on stdio — that’s correct, Ctrl+C to stop):
node dist/index.js
Claude Desktop config
File: ~/.config/Claude/claude_desktop_config.json
Add (merge into existing JSON, don’t replace the whole file):
"mcpServers": {
"nmap": {
"command": "node",
"args": ["/home/aztechguy/nmap-mcp-server/dist/index.js"]
}
}
Fully quit Claude Desktop (killall claude-desktop, don’t just close the window) and relaunch. Confirm connection status under Settings → Developer → Local MCP servers — should show nmap with no red “failed” badge.
⚠️ Editing this file by hand is error-prone — a single missing comma caused a full parse failure once, and Claude Desktop’s response to a broken config is to silently regenerate a clean default (wiping the mcpServers block, not just erroring). Always validate before restarting:
python3 -m json.tool ~/.config/Claude/claude_desktop_config.json
If that prints cleanly, safe to restart. If it errors, fix it before touching the app.
Problems encountered during setup (in order)
npx -y mcp-nmap-server failed — npm error could not determine executable to run. This npm package doesn’t expose a proper bin entry for npx to launch by name. Fix: clone the repo and reference the built dist/index.js directly via node, instead of trying to run it by package name.
TypeScript build failed — zod-to-json-schema expected an older zod internal type signature than what npm install pulled in by default (a newer major version). Fix attempt #1: pinned zod@3.23.8 — too old, broke a different way (see #3).
Runtime crash: ERR_PACKAGE_PATH_NOT_EXPORTED — the 3.23.8 pin was below zod-to-json-schema’s actual stated minimum (^3.25.28 || ^4), so a required internal export path (./v3) didn’t exist yet at that version. Fix: npm install zod@3.25.28 — the actual minimum compatible version. Build succeeded, server ran cleanly.
Lesson for next time: when a peer dependency warning names an exact minimum version, use that version — don’t guess a nearby-older one to dodge a build error, it just moves the failure to runtime instead.
** Known npm audit finding — accepted risk, with reasoning **
@modelcontextprotocol/sdk <=1.25.1
Severity: high
- ReDoS vulnerability (GHSA-8r9q-7v3j-jr4g)
- No DNS rebinding protection by default (GHSA-w48q-cv73-mx4w)
Decision: not patched via npm audit fix --force for now. Reasoning:
{/id*}, {?tags*}). This server only exposes a tool (run_nmap_scan) — a different code path. The vulnerable pattern isn’t present in what’s actually used here.npm audit fix --force would bump the SDK outside this repo’s tested dependency range, on a project that’s already shown one broken build from a dependency mismatch. Given the fragile dependency tree (see “Problems encountered” above), stacking a second forced version change risks breaking a now-working setup without closing any exposure that matters for how this runs.Revisit if: this server is ever reconfigured to use HTTP/SSE transport instead of stdio, or exposes MCP resources (not just tools) in the future — at that point the DNS rebinding advisory becomes directly relevant and should be patched before that change ships.
** First real-world test results (2026-07-05)**
Ran quick and version scans against localhost to validate the setup. Findings:
| Port | Service | Status |
|---|---|---|
| 139, 445 | Samba 4 | Expected — file sharing |
| 631 | CUPS 2.4 | Expected — print system |
| 3389 | xrdp (Terminal Service) | Found unexpectedly still open — systemctl disable alone doesn’t stop an already-running service. Required systemctl stop xrdp too. Also found duplicate/mistyped firewall rules (3389 + typo’d 33389, and IPv6 equivalents) — cleaned up. |
| 902 | VMware Authentication Daemon | Expected — backing the Kali and Windows (Quicken) VMs |
| 8080 | Python SimpleHTTPServer (3.12.3) | Expected — Antigravity Options trading dashboard |
Takeaway: the tool caught a real, previously-unnoticed issue on the first real use — a stale RDP listener plus a duplicated/mistyped firewall rule. Good validation that this setup does what it’s meant to.
** Quick reference — useful scan types **
| Ask Claude to… | Underlying scanType |
|---|---|
| “quick scan of X” | quick |
| “full scan of X” | full |
| “check what’s actually running on X’s open ports” | version |
** Next steps / ideas**
ufw allow from <subnet> to any port 3389) if remote-desktop convenience becomes worth it again, instead of fully starting/stopping the service each time.