EdgeDefense

Scan your home network and local machine for security risks, open ports, weak Wi-Fi, unknown devices with a trust score and clear explanations.

Documentation

EdgeDefense

EdgeDefense MCP

Ask Claude about your home network. Runs on your machine — no account, no admin rights, and nothing sent anywhere except the one speed test you have to ask for.


License: MIT Python 3.10+ MCP

Server-specific docs live in mcp-server/README.md.

What this is

You know something is connected to your wifi. You have no idea what it is. Most tools answer that with a dashboard full of numbers you have to interpret yourself.

This one lets you just ask. Point Claude (or Cursor, or any MCP client) at your network and talk to it in plain English: what's connected, what that unknown thing at 192.168.1.47 probably is, and whether anything looks wrong.

It answers the other half of the question too — why is this slow? — which is usually a weak Wi-Fi signal or a crowded channel rather than anything to do with your internet plan:

"Why is my wifi slow?" "Is the problem my wifi or my ISP?" "How fast is my internet actually?" "Why do my video calls break up when someone else is downloading?"

Everything happens on your computer, with one exception you invoke yourself: measuring download speed means transferring real data with a server on the internet, so edgedefense_speed_test does exactly that and nothing else does. Nothing is uploaded, nothing is logged to a server, and there is nothing to sign up for.

Why it's different

Most network scanners hand you a table of IP addresses, MAC addresses and open ports, and leave the interpretation to you. That works if you already know what port 23 means and why it matters. This gives you a conversation instead — you ask a normal question, you get a normal answer, and if something is flagged you can ask why and get an explanation in plain language, including what the check genuinely can't tell you.

Two promises make it safe to install on a whim:

It needs no special permissions. Everything above works as a regular user. No sudo, no "run as administrator", no driver to install.

It never phones home. Not for updates, not for analytics, not even to look up who made a device — that database ships inside the package. There is exactly one tool that talks to the internet, edgedefense_speed_test, and only because measuring download speed is impossible without transferring real data. It is the only one, it never runs on its own, and it is marked as reaching outside your machine so your client can tell you before it runs. You can verify all of this yourself in about ten minutes; the whole thing is a few thousand lines with zero runtime dependencies.

It is also read-only by design. There is deliberately no "block this device" button. A tool you installed sixty seconds ago should not be able to disconnect things from your network.

Tools

ToolWhat you get
edgedefense_scan_networkFinds everything on your network and summarises it in plain English. Start here.
edgedefense_whats_changedTells you what's new on your network, what vanished, and what ports opened since last scan
edgedefense_list_devicesLists what's connected — filter to just the unknown ones, or just the ones with problems
edgedefense_get_device_detailEverything known about one device: what it probably is, who made it, what it's running
edgedefense_name_deviceAssigns a friendly name to a device so you can track it
edgedefense_local_securityChecks the Wi-Fi security, DNS, and open ports on the machine running the server
edgedefense_get_trust_scoreA single 0–100 score for your network, with the reasons behind it
edgedefense_explain_findingTurns any flagged issue into a plain-English explanation of what it means and what to do

And for when the network is working, but badly:

ToolWhat you get
edgedefense_network_statsLive upload/download rate per adapter, packet errors, Wi-Fi signal strength, and how many neighbours are crowding your channel. Start here for "why is it slow".
edgedefense_latency_checkRound trip to your router, jitter, packet loss, and how long each of your DNS servers takes to answer
edgedefense_speed_testActual download and upload speed in Mbps, plus bufferbloat. ⚠️ The one tool that contacts the internet

The first two are the interesting ones. "My internet is slow" is usually a weak Wi-Fi signal or a crowded channel, and a speed test cannot tell you that — it just gives you a smaller number. edgedefense_network_stats tells you the signal is −78 dBm and four neighbours are sitting on your channel, which is an answer you can act on.

edgedefense_speed_test also reports bufferbloat: how far latency climbs while the connection is saturated. That, not bandwidth, is usually why a video call falls apart the moment someone else starts a download — and it is fixed in your router's settings rather than by paying for a faster plan.

Install

Step 1 — install the server. Requires Python 3.10 or newer.

pip install "edgedefense-core @ git+https://github.com/splashxmoon/edgemcp.git#subdirectory=core-engine" "edgedefense-mcp @ git+https://github.com/splashxmoon/edgemcp.git#subdirectory=mcp-server"

Step 2 — tell your client about it.

Claude Code

claude mcp add edgedefense -- edgedefense-mcp

Codex CLI

codex mcp add edgedefense -- edgedefense-mcp

Claude Desktopclaude_desktop_config.json

{
  "mcpServers": {
    "edgedefense": {
      "command": "edgedefense-mcp"
    }
  }
}

Cursor~/.cursor/mcp.json

{
  "mcpServers": {
    "edgedefense": {
      "command": "edgedefense-mcp"
    }
  }
}

Codex CLI (manual) — ~/.codex/config.toml

[mcp_servers.edgedefense]
command = "edgedefense-mcp"
args = []

VS Code / Copilot.vscode/mcp.json

{
  "servers": {
    "edgedefense": {
      "command": "edgedefense-mcp"
    }
  }
}

Restart your client afterwards. Any other MCP client works too — the command is edgedefense-mcp, it takes no arguments, and it speaks stdio.

Remote connector (claude.ai "Add custom connector")

That dialog asks for a URL rather than a command, which means the server has to be reachable over HTTP.

The server must still run on a machine in your home. It identifies devices by reading the local address table and listening for the announcements devices broadcast on the local link — so it can only ever see the network it is sitting on. Hosting it on a VPS would work perfectly and tell you about the VPS's network, which is not what you want. The arrangement that makes sense is a tunnel from a public hostname back to a machine at home.

Keeping it running, and shipping updates without breaking the URL

Once other people are using the URL, two things about the simple setup start to hurt:

  1. A quick tunnel's hostname is random, and changes on every restart.
  2. serve_remote.py stops the tunnel when it stops the server, so restarting to pick up new code silently hands you a different address.

Together those mean a routine deploy breaks every saved connector URL — and it breaks quietly, because the old address just stops answering.

scripts/serve_forever.py is the supervised version that avoids both:

python scripts/serve_forever.py --hostname mcp-tunnel.example.com

It starts the tunnel once and leaves it alone. Every five minutes it fetches the tracked branch, and when new commits land it fast-forwards and restarts only the server. The tunnel keeps running throughout, so the public address — and every URL anyone has saved — survives the update. Push to main, and within five minutes the remote server is serving the new code at the same link.

A few deliberate choices:

  • --ff-only. A supervisor that silently creates merge commits on an unattended machine is a way to lose work. A diverged branch is reported and the current code keeps serving.
  • The tunnel is restarted only if it actually dies. Restarting a healthy tunnel is the one thing guaranteed to break the URL.
  • --check-every 0 disables auto-updating if you would rather deploy by hand.

Without --hostname it falls back to a quick tunnel and prints a warning: the address is then only stable for as long as that tunnel process lives.

Getting a public URL

Start with a quick tunnel. It needs no DNS changes at all and proves the whole thing works in about a minute:

cloudflared tunnel --url http://127.0.0.1:8765

That prints a https://<random>.trycloudflare.com URL. Pass it as --public-url and use it in your client. The URL changes each run, which is fine for testing.

For a stable address on your own domain, note two things. A subdomain like mcp.example.com is yours automatically — subdomains are not bought separately, you just create a DNS record. But cloudflared tunnel route dns only works if the domain's nameservers point at Cloudflare. If your domain is still on your registrar's nameservers, either move the zone to Cloudflare (re-creating every existing record first — MX records especially, or email stops arriving) or use a tunnel provider that gives you a CNAME target you can paste into your current DNS panel.

With browser sign-in (recommended)

Connecting opens a sign-in page on your own domain, and your client ends up holding a revocable token instead of a secret URL. The authorization server runs inside this process — there is no hosted login service and no account.

export EDGEDEFENSE_PASSPHRASE='choose-something-long'
edgedefense-mcp --http --oauth --public-url https://mcp.edgedefenseai.com --port 8765
cloudflared tunnel --url http://127.0.0.1:8765

Then paste just the endpoint into the connector dialog, leaving both OAuth fields empty:

https://mcp.edgedefenseai.com/mcp

The client registers itself, discovers the authorization server, and opens the sign-in page. Enter the passphrase and it connects. Restarting the server revokes every issued token.

--public-url must match exactly what the browser sees, since the OAuth metadata and the redirect back to your client are built from it.

Sign in with Google instead of a passphrase

Rather than sharing one passphrase, you can sign in with Google and allow specific addresses.

Create an OAuth client in the Google Cloud Console (APIs & Services → Credentials → Create credentials → OAuth client ID → Web application), and add this exact redirect URI:

https://mcp.edgedefenseai.com/auth/google/callback

Then:

export EDGEDEFENSE_GOOGLE_CLIENT_ID='...apps.googleusercontent.com'
export EDGEDEFENSE_GOOGLE_CLIENT_SECRET='...'
edgedefense-mcp --http --oauth \
  --public-url https://mcp.edgedefenseai.com \
  --allow-email you@gmail.com \
  --no-passphrase

--allow-email is required and repeatable. Signing in with Google proves who someone is; it does not decide whether they may read your network, and without an allowlist any Google account would satisfy the check. Omit --no-passphrase to offer both methods on the sign-in page.

One honest caveat. With Google sign-in enabled, this server contacts Google while somebody is signing in — that is unavoidable for any identity provider. Scanning still makes no outbound request of any kind, and no network data is ever sent anywhere. If you would rather the server never talk to anything, use the passphrase — and leave edgedefense_speed_test alone, which is the only other tool that reaches outside your machine.

With a token in the URL (simpler)

No browser step; the secret lives in the URL instead.

edgedefense-mcp --http --port 8765 --token "$(python -c 'import secrets;print(secrets.token_urlsafe(32))')" --allow-host mcp.edgedefenseai.com

Point a tunnel at it:

cloudflared tunnel --url http://127.0.0.1:8765

Then paste the printed endpoint into the connector dialog:

https://mcp.edgedefenseai.com/t/<your-token>/mcp

Leave the OAuth fields empty. The dialog has nowhere to put a header, so the token travels in the path instead.

The URL is a password. Anyone holding it gets a full inventory of your home network. Don't put it anywhere you wouldn't put a password, and rotate it by restarting with a new --token.

Binding to anything other than loopback without a token is refused; the server generates one and prints it rather than exposing your network anonymously. --allow-host must name the hostname clients will actually use, or the DNS-rebinding check rejects the request. edgedefense-mcp --help lists the rest.

Where is the Claude Desktop config file?
PlatformPath
macOS~/Library/Application Support/Claude/claude_desktop_config.json
Windows%APPDATA%\Claude\claude_desktop_config.json
Linux~/.config/Claude/claude_desktop_config.json
Client says "command not found" or the server won't start

Desktop apps often don't see the same PATH your terminal does, which matters if you installed into a virtual environment. Find the real location:

# macOS / Linux
which edgedefense-mcp

# Windows
where edgedefense-mcp

Then use that full path in the config instead of the bare name:

{
  "mcpServers": {
    "edgedefense": {
      "command": "/full/path/to/edgedefense-mcp"
    }
  }
}

Running it through Python directly also works, and avoids the PATH question entirely:

{
  "mcpServers": {
    "edgedefense": {
      "command": "/full/path/to/python",
      "args": ["-m", "edgedefense_mcp"]
    }
  }
}

Try it

Paste any of these:

Scan my home network and tell me what's connected.
What's my network trust score, and why?
Is anything unusual connected right now?

Good follow-ups once you have results: "What is that device at 192.168.1.47?" · "Which devices couldn't you identify?" · "Why is that a problem?"

A first scan takes about ten seconds.


Architecture

Below this line is for people reading the code rather than using the tool.

This is a monorepo with a deliberate split between shared detection logic and the products built on top of it.

edgemcp/
├── core-engine/     Shared discovery, identification and scoring. Imported, never forked.
├── mcp-server/      The free, open-source MCP server. Ships independently.
└── core-app/        The paid Core/Home product. Not present yet.

Why the split exists

core-engine/ is the only place detection logic lives. Both mcp-server/ and the future core-app/ import it the same way; neither reimplements any of it.

This is decided up front on purpose. Retrofitting a shared engine after two codebases have already diverged is expensive and entirely avoidable, and the cheapest moment to draw the line is before the second consumer exists.

mcp-server/ is a separate folder rather than a subfolder of the engine because it ships independently: its own packaging, its own public repository, its own README, its own MIT licence.

The hard boundary

mcp-server/ is public. Anything reachable from it is public.

core-engine/ therefore contains only generic, safe-to-open-source logic: device discovery, ARP and mDNS scanning, vendor lookup, and scoring arithmetic.

The trained ML detection pipeline behind the paid product is not in this repository, is not imported by anything here, and lives in a separate private package. This is a standing architectural constraint, not a future TODO.

Two guarantees that constrain every change

  1. Nothing phones home. No outbound network request is made by any shipped code path, for any purpose, including analytics. Vendor lookups read a bundled database on disk. There are exactly two exceptions, both deliberate and both narrow:

    • core-engine/scripts/update_oui.py, a maintainer-run script that is not part of the distributed package and is documented as such in its own docstring.
    • core-engine/src/edgedefense_core/perf/speedtest.py, which transfers data to and from a public speed test service. Throughput cannot be measured any other way. It is confined to that one module, is imported by nothing else, never runs as part of a scan, and its tool is the only one carrying openWorldHint: true. A test asserts that it stays the only one — if you add a second outbound tool, test_only_the_speed_test_reaches_outside_this_machine fails, and that failure is the point.
  2. Needs no elevated privileges. Device discovery, identification, port fingerprinting, the trust score and every performance check all run as an ordinary user on Windows, macOS and Linux. Latency measurement shells out to the system ping rather than opening a raw socket, precisely so it does not need root.

Both are load-bearing for the product's premise. A privacy tool that quietly calls an external API loses the argument permanently the first time a technical user runs a packet capture on it.

Current scope

In: Zero-permission discovery, identification, and performance measurement.

Out, by decision rather than by omission: the ML pipeline, packet-capture traffic analysis, destructive tools such as block_device, and Core/Home product features. A free tool installed sixty seconds ago should not be able to disconnect things from your network; enforcement belongs in a product you have decided to trust.

Packet capture is out for a harder reason than the others: it needs elevated privileges on every platform, which would break the "no admin rights" promise that makes this safe to install on a whim. Anything requiring sudo or a capture driver belongs in the paid product, not here.

Development

pip install -e ./core-engine
pip install -e ./mcp-server

Run the tests:

cd core-engine && python -m pytest -q
cd ../mcp-server && python -m pytest -q

Verify the evaluation questions are answerable from tool output:

cd mcp-server && python evaluation/run_evaluation.py

Then point an MCP client at it — see mcp-server/README.md for install and configuration.

Licence

MIT, across the repository.