Skip to main content
Glama
README.md
# mcp-dns-insights

Capture the DNS queries leaving your Mac and serve them to Claude over the
[Model Context Protocol](https://modelcontextprotocol.io) so Claude can analyse
your traffic patterns and hand back a structured JSON overview.

It answers questions like:

- *Which domains does my machine talk to the most?*
- *How much of my traffic is advertising, analytics, or telemetry?*
- *When am I busiest, and were there any unusual bursts of activity?*

## How it works

```
 ┌────────────┐     ┌──────────────┐     ┌───────────────┐     ┌────────┐
 │  tcpdump   │ --> │   capture    │ --> │   SQLite db   │ <-- │  MCP   │ <── Claude
 │ (port 53)  │     │   daemon     │     │  (dns.db)     │     │ server │
 └────────────┘     └──────────────┘     └───────────────┘     └────────┘
```

1. A **capture daemon** runs `tcpdump`, parses each DNS *question* off the wire,
   and appends it to a local SQLite database.
2. An **MCP server** exposes read-only tools over stdio. Claude calls
   `analyze_patterns` (and friends) and receives JSON it can summarise.

The two halves are decoupled through the database: capture is a single writer,
the server is a read-only consumer.

## Install

Requires Python 3.10+ and the `tcpdump` that ships with macOS.

```bash
git clone https://github.com/luke-nielsen/mcp-dns-insights.git
cd mcp-dns-insights
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
```

## Usage

### 1. Capture DNS traffic

Packet capture needs root, so run the daemon under `sudo`:

```bash
sudo -E mcp-dns-insights capture
```

Leave it running in the background (or install it as a launchd job — see below).
Use `-i en0` to pin a specific interface; the default `any` captures all of them.

### 2. Inspect from the terminal

```bash
mcp-dns-insights stats --hours 24      # human-readable summary
mcp-dns-insights stats --hours 24 --json
mcp-dns-insights info                  # database location & span
```

### 3. Connect it to Claude

The server speaks MCP over stdio. Register it with Claude Code:

```bash
claude mcp add dns-insights -- mcp-dns-insights serve
```

or add it to your MCP host config manually:

```json
{
  "mcpServers": {
    "dns-insights": {
      "command": "mcp-dns-insights",
      "args": ["serve"]
    }
  }
}
```

Then ask Claude: *"Analyse my DNS traffic from the last 24 hours and tell me
what's noteworthy."*

## MCP tools

| Tool                | Purpose                                                        |
| ------------------- | ------------------------------------------------------------- |
| `analyze_patterns`  | Full JSON overview: top domains, categories, temporal, notes. |
| `recent_queries`    | The most recent queries as a JSON list.                       |
| `search_domain`     | Find queries whose hostname matches a substring.              |
| `database_info`     | Database path, size, and time span.                           |

### Shape of `analyze_patterns`

```jsonc
{
  "summary": { "total_queries": 4213, "unique_hostnames": 612, "unique_domains": 241, "window": { ... } },
  "top_hostnames": [ { "name": "api.github.com", "count": 88 }, ... ],
  "top_domains":   [ { "name": "github.com", "count": 140 }, ... ],
  "query_types":   { "A": 3100, "AAAA": 980, "HTTPS": 133 },
  "clients":       [ { "name": "192.168.1.10", "count": 4100 } ],
  "resolvers":     [ { "name": "1.1.1.1", "count": 4213 } ],
  "categories":    { "analytics": 210, "advertising": 95, "cdn": 540 },
  "flagged_domains": [ { "domain": "doubleclick.net", "category": "advertising", "count": 60 } ],
  "temporal":      { "by_hour": { ... }, "busiest_hour": { ... }, "peak_queries_per_minute": 47 },
  "observations":  [ "github.com accounts for 140 queries (3% of the total).", ... ]
}
```

## Run as a launchd job (optional)

Create `~/Library/LaunchAgents/us.radiusgroup.mcp-dns-insights.plist` (capture
needs root, so a `LaunchDaemon` in `/Library/LaunchDaemons` is the better fit):

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key><string>us.radiusgroup.mcp-dns-insights</string>
  <key>ProgramArguments</key>
  <array>
    <string>/path/to/.venv/bin/mcp-dns-insights</string>
    <string>capture</string>
  </array>
  <key>RunAtLoad</key><true/>
  <key>KeepAlive</key><true/>
</dict>
</plist>
```

```bash
sudo cp us.radiusgroup.mcp-dns-insights.plist /Library/LaunchDaemons/
sudo launchctl load /Library/LaunchDaemons/us.radiusgroup.mcp-dns-insights.plist
```

## Configuration

All settings have sensible defaults and can be overridden via the environment:

| Variable                          | Default                                                    |
| --------------------------------- | --------------------------------------------------------- |
| `MCP_DNS_INSIGHTS_DB`             | `~/Library/Application Support/mcp-dns-insights/dns.db`    |
| `MCP_DNS_INSIGHTS_HOME`           | data directory (overrides the default location)           |
| `MCP_DNS_INSIGHTS_IFACE`          | `any`                                                      |
| `MCP_DNS_INSIGHTS_FILTER`         | `udp port 53 or tcp port 53`                              |
| `MCP_DNS_INSIGHTS_TCPDUMP`        | `tcpdump`                                                  |
| `MCP_DNS_INSIGHTS_FLUSH_INTERVAL` | `2.0` (seconds)                                            |
| `MCP_DNS_INSIGHTS_FLUSH_BATCH`    | `100` (queries)                                            |

## Privacy

The database contains a record of every hostname your Mac looked up — that is
sensitive. It is stored **only on your machine** and is never transmitted
anywhere except to the local MCP server you explicitly connect to Claude. The
`.gitignore` excludes `*.db` so capture data is never committed.

> Note: queries answered by DNS-over-HTTPS/TLS (e.g. some browsers' built-in
> secure DNS) are encrypted and will **not** be visible to a port-53 capture.

## Development

```bash
pip install -e ".[dev]"
pytest          # run the test suite
ruff check .    # lint
```

## License

MIT — see [LICENSE](LICENSE).