NetMap — Network Diagram MCP
Allows launching PostgreSQL clients (e.g., psql, DBeaver) from the network diagram by clicking on PostgreSQL ports, using URL protocol handlers.
Allows launching Redis clients (e.g., redis-cli, RedisInsight) from the network diagram by clicking on Redis ports, using URL protocol handlers.
@den.dance/network-diagram-mcp
Visual network topology editor with AI agent integration via the Model Context Protocol. Open-source successor to netViz (netViz Inc., 1990 → CA Technologies → discontinued 2012).
Try it live → https://map.den.dance/

What makes it different
One-click ops dashboard. Click any web port (80 / 443 / 8080 / …) on a node card to open the service in a new tab. Click an SSH / Postgres / Redis port → in-app picker offers your installed clients (iTerm / Windows Terminal / psql / DBeaver / redis-cli / RDM / …) and launches the one you choose. The picker re-detects every click — newly installed clients show up immediately, never a silent locked-in default.
AI agents edit the map. 44 MCP tools — your Claude / Cursor / Claude Desktop session can build a diagram for you, search across nodes / ports / notes, run smart auto-layouts, export PNG / PDF — all by talking to the open browser tab.
nmap -oXimport. Run a network scan, hand the XML to the agent, get a map with nodes auto-typed from OS fingerprint + port profile (router / switch / firewall / server / printer / …).CSV inventory import. Drop in your asset spreadsheet — auto-detects columns (name / ip / type / ports / notes; aliases like
hostname/descriptionaccepted).Operator-grade node cards. Per-node: open ports with service names, Docker services, DNS domains, free-form notes. All searchable cross-entity via the agent.
Smart auto-layout. Force-directed (deterministic via seed), cluster-by-type, cluster-by-connection.
Multi-sheet, local-first. Multiple maps in one workspace, everything in
localStorage. No login, no cloud. Export JSON / PNG / PDF.
How it works
This package is a stdio → WebSocket bridge. It runs locally as an MCP server; the live NetMap browser tab connects to it over WebSocket. The agent talks to the bridge over MCP; the bridge forwards calls to the browser, which updates React state in real time.
LLM agent (Claude Code / Claude Desktop / Cursor)
│ MCP protocol (stdio)
▼
@den.dance/network-diagram-mcp ← this package, runs locally
│ WebSocket ws://localhost:47821
▼
NetMap in browser (https://map.den.dance/) ← React state updates live⚠️ Requires an open NetMap browser tab. Open https://map.den.dance/ — the MCP bridge is enabled by default (v1.2+), so the connection comes up as soon as the tab is open. Without an open tab the bridge has no peer and every tool call will time out. (To opt out:
Settings → ⚙ → Integrations → AI Agent (MCP) → Enable WebSocket connection.)
Install
Claude Desktop
Add to claude_desktop_config.json:
{
"mcpServers": {
"netmap": {
"command": "npx",
"args": ["@den.dance/network-diagram-mcp"]
}
}
}Claude Code
claude mcp add netmap -- npx @den.dance/network-diagram-mcpWith a custom port:
claude mcp add netmap -e NETMAP_MCP_PORT=12345 -- npx @den.dance/network-diagram-mcpThen open https://map.den.dance/ — the bridge is enabled by default (v1.2+). Settings → Integrations → AI Agent (MCP) if you need to inspect or toggle the connection. The toolbar will show a 🟢 MCP online badge when the browser and server are connected.
Verify the agent side:
claude mcp listYou should see netmap in the list.
Environment variables
Var | Default | Description |
|
| Local WebSocket port the bridge listens on. Must match the URL configured in NetMap's Settings → Integrations → AI Agent (MCP). |
URL Protocol Handlers + in-app client picker (v1.1+, picker since v1.2)
This package also ships an OS-level agent that registers handlers for ssh://,
postgres://, and redis:// URLs. Click any such link in a browser or terminal
and the agent spawns your installed client (psql / redis-cli / iTerm /
Windows Terminal / kitty / DBeaver / TablePlus / RedisInsight / …).
Inside NetMap (v1.2+): when the serve daemon is running, clicking an
SSH / Postgres / Redis port button on a node card opens an in-app picker of
installed clients on your machine and launches the one you pick — every click
re-detects, so newly installed clients show up immediately. Without the daemon,
port-clicks fall back to the OS default URL handler.
Install once per machine:
# Linux: ~/.local/share/applications/netmap-<scheme>-handler.desktop + xdg-mime
# macOS: ~/Library/Application Support/NetMap/handlers/NetMap<Scheme>Handler.app + lsregister
# Windows: HKCU\Software\Classes\<scheme> (per-user, no admin)
npx @den.dance/network-diagram-mcp install --allOther CLI subcommands:
npx @den.dance/network-diagram-mcp install ssh # single scheme
npx @den.dance/network-diagram-mcp uninstall postgres # remove registration
npx @den.dance/network-diagram-mcp list # what's registered (JSON)
npx @den.dance/network-diagram-mcp detect ssh # which clients are available
npx @den.dance/network-diagram-mcp serve # daemon: WS + HTTP on :47821Click handling is daemon-less — OS routes the link to a short-lived
bin/handler.js process which parses the URL, picks the first installed
client (priority order: CLI → popular GUI → cross-platform power), and spawns
it. The optional serve daemon adds HTTP endpoints (/status, /detect,
/exec) for browser-side integration with a Bearer-token-gated /exec.
npx @den.dance/network-diagram-mcp with no args still runs the original
stdio MCP server (back-compat — Smithery / Claude Desktop probes are
unaffected).
Top-5 clients per scheme (priority order, first-installed wins):
Scheme | Clients |
| iTerm2 → Windows Terminal → GNOME Terminal → Terminal.app → kitty |
| psql → TablePlus → DBeaver → Beekeeper Studio → pgAdmin 4 |
| redis-cli → RedisInsight → Another Redis DM → Medis → RDM (legacy) |
postgresql:// aliases to postgres; rediss:// is preserved and triggers
redis-cli --tls.
Available tools
State & layout
Tool | Description |
| Return all nodes, connections, stickies, notes |
| Clear the entire map (requires |
| Naive auto-arrange ( |
| Smart auto-layout: |
Nodes
Tool | Description |
| Add a node ( |
| Update node fields by id |
| Delete a node and its connections |
| Move node to new coordinates |
Connections
Tool | Description |
| Add connection ( |
| Update |
| Delete a connection by id |
Sticky notes
Tool | Description |
| Add a sticky note ( |
| Update |
| Delete a sticky note by id |
| Move sticky to new coordinates |
Sheets (multi-sheet)
Tool | Description |
| List all sheets with metadata and active sheet id |
| Get nodes / connections / stickies for a sheet (default: active) |
| Create a new empty sheet and switch to it |
| Switch active sheet by id |
| Rename a sheet |
| Delete a sheet (requires |
View / canvas
Tool | Description |
| Set zoom level (0.1–3.0) |
| Pan canvas to absolute pixel position |
| Auto-fit all nodes into viewport |
Lock / protection
Tool | Description |
| Lock ( |
Notes & settings
Tool | Description |
| Get sheet-level notes text |
| Set sheet-level notes text |
| Get app settings (sshMode, showGrid, etc.) |
| Update app settings |
Search
Tool | Description |
| Structured node-only filter by |
| Full-text cross-entity search. Looks across nodes (name / ip / notes), stickies (text), connection labels, and ports (port number + service name). Optional |
| Return every node of a single type with FULL field data (ports, dockerServices, domains, ips, notes, parentServer, …). Use this when you need the complete objects, not the stripped projection from |
Example — find everything matching postgres anywhere in the map:
// → call
{ "q": "postgres" }
// → result
{
"nodes": [{ "id": "n1", "name": "db-primary", "type": "server", "ip": "10.0.0.5" }],
"stickies": [{ "id": "s2", "text": "TODO: upgrade postgres 15 → 16" }],
"connections": [{ "id": "c4", "label": "postgres replication", "from": "n1", "to": "n2" }],
"ports": [{ "nodeId": "n1", "nodeName": "db-primary", "port": 5432, "protocol": "tcp", "service": "postgresql" }],
"total": 4
}map_search and map_get_nodes_by_type are read-only — they work on locked sheets.
Import
Tool | Description |
| Replace active-sheet content with a provided |
| Parse |
| Import nodes from CSV. Auto-detects columns from the header row ( |
Example — turn a 2-host nmap scan into a map in one call:
// → call
{
"xml": "<?xml version=\"1.0\"?>\n<nmaprun>\n <host>\n <address addr=\"10.0.0.1\" addrtype=\"ipv4\"/>\n <hostnames><hostname name=\"web.local\" type=\"user\"/></hostnames>\n <ports>\n <port protocol=\"tcp\" portid=\"22\"><state state=\"open\"/><service name=\"ssh\"/></port>\n <port protocol=\"tcp\" portid=\"80\"><state state=\"open\"/><service name=\"http\"/></port>\n </ports>\n </host>\n <host>\n <address addr=\"10.0.0.2\" addrtype=\"ipv4\"/>\n <os><osmatch name=\"Cisco IOS router\" accuracy=\"98\"/></os>\n </host>\n</nmaprun>"
}
// → result
{ "count": 2, "ids": ["…", "…"] }Provide {"hosts": [...]} instead of xml when you already have parsed host data — hosts takes priority when both are given.
Example — turn a CSV inventory snippet into a map:
// → call
{
"csv": "name,ip,type,ports,notes\ndb-primary,10.0.0.5,server,\"22/tcp,5432/tcp\",Postgres 15\nrouter-main,10.0.0.1,router,22/tcp,Edge router"
}
// → result
{ "count": 2, "ids": ["…", "…"] }For non-standard headers, pass an explicit columns mapping (-1 means "absent"):
{
"csv": "Host,Address\nfoo,10.0.0.99",
"columns": { "name": 0, "ip": 1, "type": -1, "ports": -1, "notes": -1 }
}map_import_nmap and map_import_csv are mutations — blocked on locked sheets.
Layout
map_suggest_layout repositions every node according to a chosen algorithm; result shape:
{ ok: true, algorithm, changed: <node count> }.
// Force-directed (organic; reproducible with seed)
{ "algorithm": "force", "iterations": 200, "seed": 42 }
// Group nodes into horizontal lanes by type
{ "algorithm": "cluster-by-type" }
// Place each connected component in its own x-zone
{ "algorithm": "cluster-by-connection" }Force-directed is O(n²) per iteration — fine up to a few hundred nodes; bring iterations down
for larger scenes. map_suggest_layout is a mutation — blocked on locked sheets.
Export (JSON / PNG / PDF)
Tool | Description |
| Export a single sheet as a JSON object (auto-connections included). Legacy entry — still works. |
| Multi-format export: |
// JSON
{ "format": "json" }
// → { format: "json", data: { nodes, connections, stickies, notes, auto_connections } }
// PNG (max 30 s; agent must persist the base64 to a file)
{ "format": "png" }
// → { format: "png", filename: "netmap-<sheetId>.png",
// base64: "iVBORw0KGgoAAAANSUhEUg…",
// mimeType: "image/png" }
// PDF
{ "format": "pdf" }
// → { format: "pdf", filename: "netmap-<sheetId>.pdf",
// base64: "JVBERi0xLjQK…",
// mimeType: "application/pdf" }PNG / PDF capture renders the live workspace via html-to-image + jsPDF, so the MCP server raises
the per-command timeout to 30 s for this tool. map_export is read-only — works on locked sheets.
Connection status indicator
A badge appears next to the NetMap version in the toolbar (click it to open Settings):
Badge | Meaning |
🟢 MCP online | Connected — agent can edit the map |
🟡 MCP | Connecting to server |
🟠 MCP retry N/10 | Retrying, up to 10 attempts × 30 sec |
🔴 MCP error | Gave up — start the server, then toggle off / on to retry |
(no badge) | Disabled in settings |
Detailed status (with URL and hints) is shown inside Settings → Integrations → AI Agent (MCP).
Running from source (for contributors)
git clone https://github.com/den-indance/network-diagram-mcp.git
cd network-diagram-mcp
npm install
node index.jsOverride port: NETMAP_MCP_PORT=12345 node index.js.
Register the local build in Claude Code instead of npm:
claude mcp add netmap-dev -- node /absolute/path/to/network-diagram-mcp/index.jsLicense
MIT — see LICENSE.
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/den-indance/network-diagram-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server