Nmap MCP Server Setup (Claude Desktop)

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)

  1. npx -y mcp-nmap-server failednpm 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.

  2. TypeScript build failedzod-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).

  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:

  • ReDoS advisory applies to MCP resource handlers using exploded URI template patterns ({/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.
  • DNS rebinding advisory applies to MCP servers using HTTP/SSE transport (a bound network port a malicious webpage could rebind to). This server runs over stdio — Claude Desktop spawns it as a direct child process, communicating over stdin/stdout pipes. There’s no network listener to rebind to.
  • Realistic exploitability for this specific config: an attacker would need existing code-execution on this box to exploit either issue — at which point they don’t need this vulnerability, they already have the machine.
  • 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 opensystemctl 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**

  • Extend this to point at the Kali VM (bridged network) once comfortable with the local-only setup.
  • Consider firewall-scoping xrdp to LAN-only (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.