netexpert-mcp / docs local host · LAN · diagnostics v0.1.0

netexpert-mcp_

A cross-platform Model Context Protocol server for local-network diagnostics and host security posture. Windows, Linux, macOS — one normalized JSON envelope. It answers what a home lab actually asks: what's on my network, is my link healthy, is this machine safe, and why is X slow.

npx -y netexpert-mcp
Tools
31
Runtime
Node ≥ 20
Platforms
win32 · linux · darwin
Transports
stdio · HTTP
License
MIT

Scope

01 / 06

Five tool groups, one diagnostic ladder

Small primitives the model composes — plus two composite tools whose value is interpretation, not data. Everything returns the same shape regardless of OS; the model never sees raw netsh or ip output.

GroupCovers
netConnectivity. Ping with honest TCP fallback, traceroute, HTTP timing split (dns/connect/ttfb), port check & bounded scan, TLS certificate grading, public IP — and an optional, opt-in nmap deep scan.
dnsName resolution. Lookups across record types, reverse PTR, which resolvers the OS actually uses, propagation diffs — and the killer check: system resolver vs trusted DoH on known-good domains.
lanDevices & inventory. ARP cache, RFC1918-gated sweeps, device inventory enriched with IEEE-OUI vendor and PTR hostname, dated snapshots, new-device diffs against baselines, and a confirm-gated Wake-on-LAN.
wifiLink health. Current link (band, channel, RSSI, PHY rate), AP survey — and wifi.health, which interprets: RSSI quality bands, 2.4 GHz channel overlap, band mismatch, each with a concrete next step.
secHost posture. Active sockets with owning PID, listening-port exposure flags, firewall state per platform, a bounded speed test — and sec.exposure, the headline composite: exposed listeners × firewall × risky-port knowledge.

Safety

02 / 06

Bounded probes, gated mutations

Three tool classes. Reads run free; network probes carry hard caps and token-bucket rate limits; the one mutating tool plans first.

R
read

Local state: interfaces, ARP cache, sockets, firewall, snapshots — 15 tools, no gate.

N
probe

Network probes: ping, sweep, scan, http, ssl, DoH — 15 tools, each behind hard caps (≤256 ports, ≤1024 hosts, byte ceilings, per-probe timeouts) and a token-bucket rate limit. Breach ⇒ RATE_LIMITED with retryAfterMs, never a retry loop.

M
mutate

One mutating tool: lan.wol — a single UDP magic packet, planned before it is sent.

→
wol flow
1 · plan

Call lan.wol with a MAC. Nothing is sent. You get a PlannedAction summary and a confirmToken.

2 · decline

Do nothing. The token expires in 10 minutes; the network was never touched.

3 · approve

Re-call with the same MAC and the confirmToken. The service validates: single-use, action-bound — parameters frozen at plan time. Then one broadcast.

Fig. 1 — the three classes. Only class M carries a confirmation gate.

No shell, ever. Every subprocess spawns via arg arrays (execFile-style, shell: false); every input passes zod validation before it reaches the OS. No raw sockets, no Npcap, no Python — ICMP is the OS binary, ARP sweeps are ping-then-read-cache.

LAN tools stay on the LAN. lan.* sweeps and the nmap adapter accept RFC1918 targets only; public IPs are rejected with a redirect to the bounded alternatives. The inverted-SSRF rule you'd expect from a web tool would be wrong here — public targets are the point of net.* and dns.*, so the boundary is drawn per namespace, not per host.

Caps are ceilings, not suggestions. Config values are upper bounds; tool inputs may go lower, never higher. config.json refuses any key containing password, token, secret, or credential — credentials never ride in a config file.

Structure

03 / 06

Four layers, one envelope

Every tool declares its backend chain; the platform registry resolves what actually runs on this OS. net.health reports the resolved capability matrix so the model never calls a tool that would 404.

L1  node stdlib        net / dns / tls / http / dgram — identical on all platforms
L2  systeminformation  interfaces, WiFi, connections, stats — well-known npm
L3  platform commands   argv arrays + pure parsers (arp, ping, netsh, iw, …)
L4  optional binaries   nmap adapter — capability-detected, opt-in, never required
src/
├── server.ts              entrypoint: stdio + optional stateless HTTP (/mcp, /health)
├── config.ts              zod-validated config; NETEXPERT_* env overrides; no secrets
├── exec/
│   ├── executor.ts         CommandRunner interface + LocalExecutor (execFile, arg arrays)
│   └── platform.ts         per-platform command maps: win32 / linux / darwin
├── parse/                  pure parsers (arp, ping, traceroute) — fixture-tested
├── safety/
│   ├── caps.ts             hard bounds + token-bucket rate limiter
│   └── confirm.ts          ConfirmationTokenService — the dry-run/confirm core
├── services/              one module per tool group, each takes ServiceContext
│   ├── coreService         net.*        (health ping trace httpcheck portcheck portscan ssl publicip)
│   ├── dnsService          dns.*        (lookup reverse resolvers propagation hijackcheck)
│   ├── lanService          lan.*        (interfaces gateway arp sweep devices hostname snapshot newdevices wol)
│   ├── wifiService         wifi.*       (status networks health)
│   ├── secService          sec.*        (connections listening firewall exposure speedtest)
│   ├── nmapService         net.deepscan (opt-in adapter)
│   └── util.ts             envelope builders, ServiceContext, exec helpers
├── data/oui.json           40,099 IEEE OUI entries — committed, never fetched at runtime
└── mcp/
    ├── createServer.ts      McpServer wrapper
    └── toolset.ts          all 31 tool registrations

Closed error alphabet. Seven codes, full stop: VALIDATION · BACKEND_MISSING · EXEC_FAILED · TIMEOUT · CAP_EXCEEDED · RATE_LIMITED · CONFIRMATION_REQUIRED. Every failure carries a hint — an actionable next step, not a shrug.

Parsers are pure and fixture-driven. Real captured output per platform (tests/fixtures/win32/arp-a.txt, linux/ip-j-neigh.json, …) feeds unit tests through the same CommandRunner interface production uses — a fake runner, zero real network in the test suite.

Honest backends. When ICMP is unavailable, net.ping says backend: "tcp-connect" rather than pretending. sec.speedtest reports a lower bound with complete: false when the transfer hits its cap, instead of failing on slow links.

Index

04 / 06

The complete ledger — 31 tools

read  runs directly   probe  capped & rate-limited   gated  plans first

net.healthVersion, platform, capability matrix with install hints, config caps.read
net.pingICMP loss/latency (min/avg/max/mdev); honest TCP-connect fallback.probe
net.traceTraceroute hops: ttl, ip, latency — normalized across platforms.probe
net.httpcheckGET/HEAD timing split: dns / connect / ttfb / total, status, headers.probe
net.portcheckSingle TCP connect: open/closed + latency.probe
net.portscanBounded connect scan, default top common ports, cap 256.probe
net.sslTLS cert: subject, SANs, issuer, expiry, days-left, grade.probe
net.publicipPublic IPv4 + ASN/org, via configurable endpoints.probe
net.deepscanOptional nmap adapter — opt-in via config, RFC1918 targets only.probe
dns.lookupA/AAAA/CNAME/MX/TXT/NS/SRV/PTR with optional nameserver override.probe
dns.reversePTR names for an IP.probe
dns.resolversWhich DNS servers the OS actually uses, per platform.read
dns.propagationSame record at system + trusted resolvers + DoH — diff table.probe
dns.hijackcheckKnown-good domains: system vs trusted DoH; mismatch ⇒ Finding.probe
lan.interfacesNICs: name, MAC, addresses, type, virtual flag.read
lan.gatewayDefault gateway + interface + local subnet CIDR.read
lan.arpParsed neighbor cache, stale entries filtered.read
lan.sweepPing-probe an RFC1918 subnet (cap 1024 hosts) → live set.probe
lan.devicesInventory: ARP ∩ RFC1918, OUI vendor + PTR hostname enrichment.read
lan.hostnameAll names for an IP: PTR + NetBIOS (win).probe
lan.snapshotSave device inventory to dated snapshots; or list them.read
lan.newdevicesDiff live vs baseline ⇒ Findings; first run auto-saves baseline.read
lan.wolWake-on-LAN magic packet — dry-run plan → confirmToken → broadcast.gated
wifi.statusCurrent link: SSID, band, channel, RSSI, PHY rate, quality 0–100.read
wifi.networksNearby AP survey with band inference.read
wifi.healthInterprets: RSSI bands, channel overlap, band mismatch ⇒ Findings.read
sec.connectionsActive sockets with owning PID/process.read
sec.listeningListeners per process; flags non-loopback exposures.read
sec.firewallFirewall state: profiles (win) / ufw→nft (linux) / socketfilterfw (mac).read
sec.exposureHeadline composite: exposed listeners × firewall × risky ports ⇒ Findings.read
sec.speedtestBounded Cloudflare download; lower-bound reporting on slow links.probe
31 / 31 shown fifteen read · fifteen probe · one gated

Setup

05 / 06

Configuration & wiring

Zero-config first run — defaults are sane and everything below is optional. The server reads ~/.netexpert-mcp/config.json (override with NETEXPERT_MCP_CONFIG); NETEXPERT_* env variables win over the file.

# ~/.netexpert-mcp/config.json — every key optional
{
  "defaults":  { "pingCount": 4, "traceMaxHops": 20 },
  "resolvers": { "trusted": ["1.1.1.1", "8.8.8.8"],
                 "dohUrl": "https://cloudflare-dns.com/dns-query" },
  "caps":      { "portscan": { "maxPorts": 256 }, "sweep": { "maxHosts": 1024 } },
  "rate":      { "sweep": 30, "portscan": 10, "speedtest": 60, "httpCheck": 2 },
  "nmap":      { "enabled": false }
}
// caps are ceilings — inputs may go lower, never higher.
// keys containing password / token / secret / credential are rejected.

Client wiring (stdio)

# official package — no clone, no build
npx netexpert-mcp               # or: npm i -g netexpert-mcp && netexpert-mcp

# client config
{
  "mcpServers": {
    "netexpert": {
      "command": "npx",
      "args": ["-y", "netexpert-mcp"]
    }
  }
}

Optional HTTP transport

NETEXPERT_HTTP_PORT=3100 netexpert-mcp

POST 127.0.0.1:3100/mcp      # JSON-RPC, stateless (fresh dispatch per request)
GET  127.0.0.1:3100/health   # liveness

Optional binaries

nmap. Only net.deepscan. Install (winget install Insecure.Nmap / sudo apt install nmap), then set "nmap": { "enabled": true } in config — deep scans are opt-in by design. Absence produces an install hint, never a crash.

OUI database. src/data/oui.json (40k IEEE MA-L entries) is committed — never fetched at runtime. Regenerate on demand: node scripts/gen-oui.mjs.

Monitoring without a daemon

netexpert stays request/response. Recurring checks are the client's job — e.g. watch_create-style recipes (where your client supports them):

new-device tripwire   lan.newdevices    every 15 min   fire on Finding[]
gateway reachability   net.ping <gw>      every 60 s      fire on lossPct > 0
dns integrity          dns.hijackcheck   hourly          fire on sev=high
wifi degradation      wifi.health       every 10 min    fire on severity ≥ med

Judgment

06 / 06

The persona — hands meet judgment

The server provides hands; docs/persona.md provides the judgment — a rule pack for AI clients. The triage ladder (link → ARP → IP/DNS → port → app, always naming the layer that exonerated or implicated), the baselines for "suspicious", the Finding{severity, title, evidence, nextStep} report format, and composition rules with the ugnas-mcp server for the NAS half.

Every finding ends with the concrete next tool call. No bare data dumps, no guessing: if a backend is missing, the hint is reported verbatim; if the ping fell back to TCP, it says "TCP probe", not "ping".