---
title: "I Gave My AI Agent the Keys to My Network"
date: 2026-03-03
description: "How I built an MCP server that lets my AI agent configure my firewall, manage VLANs, control smart home devices, and troubleshoot connectivity — all through natural language."
tags: ["ai-hacks-on-tap","mcp","home-network","home-automation","agent-experience"]
readingTime: "14 min read"
url: https://alexmoening.com/dev-thoughts/i-gave-my-ai-agent-the-keys-to-my-network.html
markdownUrl: https://alexmoening.com/dev-thoughts/i-gave-my-ai-agent-the-keys-to-my-network.md
---

# I Gave My AI Agent the Keys to My Network

[← Back to /dev/thoughts](/dev-thoughts/)

<p class="lead">I built a single MCP server that gives my AI agent control over my entire home network — firewall rules, VLAN assignments, smart plugs, LED strips, and 235 Home Assistant entities. A task that used to mean juggling three SSH sessions and copy-pasting UUIDs now takes one sentence and thirty seconds.</p>

### The Moment That Got Me

I was migrating my home network to a proper VLAN-segmented setup — the kind of project where you're SSH'd into three different things simultaneously, cross-referencing MAC addresses in one terminal while editing firewall rules in another. It's the kind of work that makes you feel like a sysadmin in 2003, except now the stakes include your housemate's ability to stream Netflix.

Halfway through, I realized my TV was on the wrong VLAN. To fix that manually, I'd need to:

<table class="data-table">
    <thead>
        <tr>
            <th>Step</th>
            <th>Action</th>
            <th>System</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>1</td>
            <td>Find the network configuration IDs</td>
            <td>SSH → Firewall</td>
        </tr>
        <tr>
            <td>2</td>
            <td>Log into the switch controller API with cookie-based auth</td>
            <td>REST → UniFi</td>
        </tr>
        <tr>
            <td>3</td>
            <td>Look up the device ID for the correct switch</td>
            <td>REST → UniFi</td>
        </tr>
        <tr>
            <td>4</td>
            <td>Find which port the TV is on</td>
            <td>REST → UniFi</td>
        </tr>
        <tr>
            <td>5</td>
            <td>Build a JSON payload that merges the new VLAN assignment with existing port overrides (without clobbering other ports)</td>
            <td>Manual JSON</td>
        </tr>
        <tr>
            <td>6</td>
            <td>Push the config and force-provision the switch</td>
            <td>REST → UniFi</td>
        </tr>
        <tr>
            <td>7</td>
            <td>Kick the device to get a new DHCP lease on the correct subnet</td>
            <td>SSH → Firewall</td>
        </tr>
    </tbody>
</table>

Instead, I said: *"Move the Sony TV on Switch B port 6 to the IoT VLAN."*

The agent found the switch, identified the port, looked up the correct network ID, built the override payload, applied it, and provisioned the switch. Thirty seconds. No context switching. No copy-pasting UUIDs between terminals.

That's when I knew this was worth writing about.

### What I Actually Built

The setup is a single MCP (Model Context Protocol) server running as a Docker container on my NAS. It speaks to five different systems through their native protocols, and exposes everything as 45 tools through one HTTP endpoint. My AI coding agent connects to it and can control the entire home infrastructure through natural language.

<div class="flow-diagram flow-vertical" role="img" aria-label="Architecture: AI Agent connects via HTTP to MCP Server on NAS, which connects to Firewall via SSH, Switch Controller via REST, Home Assistant via REST, LED Controllers via HTTP, and Smart Plugs via local protocol">
    <div class="flow-step">
        <span class="step-icon">🤖</span>
        <span class="step-text">AI Agent (Claude Code on Mac)</span>
    </div>
    <span class="flow-arrow" aria-hidden="true">↓ HTTP</span>
    <div class="flow-step">
        <span class="step-icon">🔧</span>
        <span class="step-text">MCP Server (Docker on NAS) — 45 tools, 1 endpoint</span>
    </div>
    <span class="flow-arrow" aria-hidden="true">↓</span>
    <div class="flow-step" style="display: flex; gap: 0.5rem; flex-wrap: wrap; justify-content: center;">
        <span class="step-text" style="flex: none;">🛡️ Firewall (SSH)</span>
        <span class="step-text" style="flex: none;">🔀 Switches (REST)</span>
        <span class="step-text" style="flex: none;">🏠 Home Assistant (REST)</span>
        <span class="step-text" style="flex: none;">💡 LEDs (HTTP)</span>
        <span class="step-text" style="flex: none;">🔌 Plugs (Local)</span>
    </div>
</div>

<table class="data-table">
    <thead>
        <tr>
            <th>Module</th>
            <th>Protocol</th>
            <th>Auth</th>
            <th>What It Manages</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>Firewall (pfSense)</td>
            <td>SSH</td>
            <td>ed25519 key</td>
            <td>Firewall rules, DHCP leases, ARP tables, static maps</td>
        </tr>
        <tr>
            <td>Switch Controller (UniFi)</td>
            <td>REST API</td>
            <td>Cookie auth</td>
            <td>Switch ports, VLANs, WiFi SSIDs, client tracking</td>
        </tr>
        <tr>
            <td>Home Assistant</td>
            <td>REST API</td>
            <td>Bearer token</td>
            <td>235 entities, automations, scenes, services</td>
        </tr>
        <tr>
            <td>LED Controllers (WLED x3)</td>
            <td>HTTP JSON</td>
            <td>None (LAN only)</td>
            <td>Colors, effects, brightness, presets</td>
        </tr>
        <tr>
            <td>Smart Plugs (Kasa x3)</td>
            <td>Local protocol</td>
            <td>None (LAN only)</td>
            <td>On/off, energy monitoring, WiFi provisioning</td>
        </tr>
    </tbody>
</table>

The key design decision: **one server, not five**. Every home infrastructure tool I looked at wanted to be its own microservice. But I don't need Kubernetes in my living room. I need one container that starts on boot and talks to everything.

Each backend system gets a module — a thin client wrapper plus MCP tool registration. The module pattern is dead simple:

<table class="data-table">
    <thead>
        <tr>
            <th>File</th>
            <th>Role</th>
            <th>Example</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>client.py</code></td>
            <td>Protocol wrapper</td>
            <td>aiohttp session with Bearer token auth</td>
        </tr>
        <tr>
            <td><code>tools.py</code></td>
            <td>MCP tool registration</td>
            <td><code>register_tools(mcp, client)</code> → 7 tools</td>
        </tr>
        <tr>
            <td><code>config.py</code></td>
            <td>Settings model</td>
            <td>Host, port, token with env var overrides</td>
        </tr>
        <tr>
            <td><code>server.py</code></td>
            <td>Conditional import</td>
            <td><code>if "homeassistant" in modules_enabled:</code></td>
        </tr>
    </tbody>
</table>

New module? Write a client class, register some tools, add a settings model with environment variable overrides. The server picks it up on next deploy. I've been averaging about 45 minutes per module including deployment and testing.

### Why MCP and Not Just SSH Scripts?

Fair question. I've been writing shell scripts to manage networks for two decades. The difference isn't automation — it's *context*.

When I ask the agent to "troubleshoot why the HomePod isn't responding in Apple Home," it doesn't just run one command. It chains reasoning across multiple systems:

<table class="data-table steps">
    <thead>
        <tr>
            <th>Step</th>
            <th>Action</th>
            <th>System</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>1</td>
            <td>Query all connected clients and their VLANs</td>
            <td>Switch Controller</td>
        </tr>
        <tr>
            <td>2</td>
            <td>Check the ARP table to see what's actually on the network</td>
            <td>Firewall</td>
        </tr>
        <tr>
            <td>3</td>
            <td>Pull configuration to see what IPs are stored</td>
            <td>Home Assistant</td>
        </tr>
        <tr>
            <td>4</td>
            <td>Compare stored IPs against actual IPs</td>
            <td>Agent reasoning</td>
        </tr>
        <tr>
            <td>5</td>
            <td>Find five integrations with stale IPs from months ago</td>
            <td>Agent reasoning</td>
        </tr>
        <tr>
            <td>6</td>
            <td>Update config entries with correct addresses</td>
            <td>Home Assistant</td>
        </tr>
        <tr>
            <td>7</td>
            <td>Remove seven dead integrations that no longer exist</td>
            <td>Home Assistant</td>
        </tr>
        <tr>
            <td>8</td>
            <td>Restart the service</td>
            <td>Home Assistant</td>
        </tr>
    </tbody>
</table>

That's eight steps across three different systems, using three different authentication methods, and making decisions along the way. A shell script could do steps 1 and 2. The rest requires reasoning — looking at the data from step 1, cross-referencing it with step 3, and deciding what to do. That's where the agent shines.

The MCP protocol gives the agent *structured access* to each system. Instead of parsing `arp -a` output with regex, it gets clean JSON with MAC addresses, IPs, and interface names as distinct fields. Instead of screen-scraping a web UI, it calls `ha_list_entities(domain="light")` and gets back 23 lights with their current state, brightness, and last-changed timestamp.

### The Five Modules

Here's what each module actually does, and why the tool count matters less than you'd think.

<table class="data-table">
    <thead>
        <tr>
            <th>Module</th>
            <th>Tools</th>
            <th>Capabilities</th>
            <th>Example Command</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><strong>Firewall</strong></td>
            <td>15</td>
            <td>ARP tables, DHCP leases, static IPs, firewall rules, interface status, gateway health</td>
            <td>"Add a static DHCP reservation for this device"</td>
        </tr>
        <tr>
            <td><strong>Switch Controller</strong></td>
            <td>9</td>
            <td>VLAN assignments, client tracking, WiFi SSID management, port configuration</td>
            <td>"Disable the legacy WiFi network"</td>
        </tr>
        <tr>
            <td><strong>Home Assistant</strong></td>
            <td>7</td>
            <td>Entity control, automation management, service calls across dozens of device types</td>
            <td>"Disable the bathroom motion sensor automation"</td>
        </tr>
        <tr>
            <td><strong>LED Controllers</strong></td>
            <td>8</td>
            <td>On/off, brightness, RGB color, effects, presets across 3 WLED strips</td>
            <td>"Set the picture frame to warm orange at half brightness"</td>
        </tr>
        <tr>
            <td><strong>Smart Plugs</strong></td>
            <td>6</td>
            <td>On/off, real-time wattage, energy monitoring, WiFi provisioning</td>
            <td>"Migrate this plug from the old WiFi to the new one"</td>
        </tr>
    </tbody>
</table>

### The Architecture Choice: Why One Container?

<table class="data-table">
    <thead>
        <tr>
            <th>Advantage</th>
            <th>One Container (What I Built)</th>
            <th>Five Containers (What I Didn't)</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>Cross-module queries</td>
            <td>Free — tools share one server process</td>
            <td>Three MCP connections, three auth flows, triple the context</td>
        </tr>
        <tr>
            <td>Deployment</td>
            <td>One <code>rsync</code>, one rebuild, under a minute</td>
            <td>Docker Compose orchestration, dependency ordering</td>
        </tr>
        <tr>
            <td>Network access</td>
            <td>One container with host networking, one position on the Core VLAN</td>
            <td>Per-container network config, firewall rules per container</td>
        </tr>
        <tr>
            <td>Mental model</td>
            <td>One URL, all 45 tools, self-describing names</td>
            <td>"Which server handles VLAN changes again?"</td>
        </tr>
    </tbody>
</table>

### What Surprised Me

<table class="data-table">
    <thead>
        <tr>
            <th>Surprise</th>
            <th>What Happened</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><strong>The agent is better at firewall rule syntax than I am</strong></td>
            <td>pfSense stores config in XML with PHP-encoded arrays. Getting the nesting right for source, destination, protocol, interface, direction — the agent nails it every time because the tool validates inputs before writing.</td>
        </tr>
        <tr>
            <td><strong>Smart home troubleshooting is where this really pays off</strong></td>
            <td>Home automation involves a web of dependencies: LED controller on the right VLAN, HA integration with the right IP, HomeKit bridge with the right entity IDs, automation with the right device references. When any of these breaks, the failure mode is "no response" with zero diagnostics. The agent checks all layers in sequence.</td>
        </tr>
        <tr>
            <td><strong>Natural language makes VLAN work feel normal</strong></td>
            <td>Before: dreading VLAN changes because each required juggling UUIDs across systems. Now: "put this device on the IoT network" is a sentence. Cognitive load dropped from "senior network engineer" to "homeowner."</td>
        </tr>
        <tr>
            <td><strong>Error handling matters more than features</strong></td>
            <td>Every tool returns <code>{"success": false, "error": "..."}</code> on failure. When a Smart Plug command failed because the firewall blocked the protocol port, the agent told me which port to open and on which interface — not just "connection timed out."</td>
        </tr>
    </tbody>
</table>

### The Security Model

I want to be clear about the trust boundaries here, because "I gave my AI the keys to my network" sounds irresponsible out of context.

<table class="data-table">
    <thead>
        <tr>
            <th>Boundary</th>
            <th>Implementation</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><strong>Network isolation</strong></td>
            <td>The MCP server runs on the local network only. Not exposed to the internet. Behind the same firewall that protects everything else. The only client is my AI coding tool on the same LAN.</td>
        </tr>
        <tr>
            <td><strong>Secrets management</strong></td>
            <td>API keys, passwords, and tokens live in an environment file on the NAS. Source code has settings models with defaults and env var overrides — no credentials. The <code>.env</code> is excluded from version control.</td>
        </tr>
        <tr>
            <td><strong>Human-in-the-loop</strong></td>
            <td>Write operations require confirmation. The agent proposes changes (add this firewall rule, move this port to that VLAN) and I approve or deny each one. MCP provides the guardrail.</td>
        </tr>
        <tr>
            <td><strong>Scoped auth</strong></td>
            <td>The Home Assistant token expires in a year. The switch controller uses a dedicated service account. The firewall uses SSH key auth. Nothing uses shared or admin credentials.</td>
        </tr>
    </tbody>
</table>

### What's Next: From Tools to Watchdog

The 45 tools I have now are reactive — I ask the agent to do something, and it does it. But the architecture opens the door to something more interesting: **agentic loops that proactively monitor the network**.

Imagine an agent that runs on a schedule — say, every few hours — and quietly checks things:

<table class="data-table">
    <thead>
        <tr>
            <th>Check</th>
            <th>What It Does</th>
            <th>Why It Matters</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><strong>Misconfiguration detection</strong></td>
            <td>Compare the ARP table against static DHCP maps. Flag devices that are on the wrong VLAN or have conflicting assignments.</td>
            <td>After a network migration, stale configs hide everywhere. An agent that cross-references would have caught the HomePod issue before I noticed it.</td>
        </tr>
        <tr>
            <td><strong>Anomalous device detection</strong></td>
            <td>Diff the connected client list against a known-good baseline. Alert on new MAC addresses that haven't been seen before.</td>
            <td>Rogue devices on a VLAN-segmented network are a real concern. A periodic scan turns the agent into a lightweight IDS.</td>
        </tr>
        <tr>
            <td><strong>Service health monitoring</strong></td>
            <td>Poll Home Assistant entities for stale <code>last_changed</code> timestamps. Flag sensors or automations that have gone silent.</td>
            <td>Motion sensors that stop reporting, automations that haven't triggered in weeks — these are quiet failures that compound until something visible breaks.</td>
        </tr>
        <tr>
            <td><strong>Firewall rule drift</strong></td>
            <td>Snapshot the firewall rule set periodically. Diff against the previous snapshot. Flag unexpected changes.</td>
            <td>Rule sets accumulate cruft. An agent that reviews them periodically can flag redundant, conflicting, or overly permissive rules before they become a problem.</td>
        </tr>
        <tr>
            <td><strong>Energy anomalies</strong></td>
            <td>Track smart plug wattage over time. Alert when a device draws significantly more or less power than its baseline.</td>
            <td>A plug drawing 0W that should be drawing 60W means something died. A plug drawing 200W that normally draws 60W might be a problem worth investigating.</td>
        </tr>
    </tbody>
</table>

The tools are already there. The agent already knows how to query each system. The missing piece is the loop — a scheduler that triggers the agent, a memory store that tracks baselines, and a notification channel for findings. That's where I'm headed next.

I'm also planning a few more modules — the Hue bridge for direct light control, Proxmox for VM management, and compound tools that span multiple systems. "Migrate this device to the IoT VLAN" as a single high-level operation that touches the switch, firewall, and DHCP in sequence.

But the bigger vision is an agent that doesn't just respond to my commands — one that occasionally takes a peek at the network, notices things I wouldn't have noticed, and surfaces them before they become outages. Not a full SIEM. Not a monitoring stack. Just an agent with the right tools, a schedule, and enough context to connect the dots.

### Should You Build This?

<table class="data-table">
    <thead>
        <tr>
            <th>Your Setup</th>
            <th>Verdict</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>Segmented home network with VLANs, managed switches, smart devices</td>
            <td><strong>Yes.</strong> The investment is a few hundred lines of Python per module. The payoff is immediate.</td>
        </tr>
        <tr>
            <td>Flat home network with a consumer router</td>
            <td><strong>Probably not.</strong> The complexity isn't there yet, and consumer routers don't expose APIs worth wrapping.</td>
        </tr>
        <tr>
            <td>The prosumer gap: pfSense/OPNsense, managed switches, a few smart home systems</td>
            <td><strong>The sweet spot.</strong> Complex enough to need tooling, simple enough that one container handles it. This is where natural language control goes from "neat demo" to "I can't go back."</td>
        </tr>
    </tbody>
</table>

The MCP protocol is the right abstraction for this. It gives the agent structured tool access without requiring a full web application. The tools are stateless — each call is independent — so there's no session management or state machine to debug. It's just functions that talk to APIs.

### The Punchline

Two weeks ago, I was configuring VLANs by hand. SSH into the firewall, cross-reference with the switch controller, check the DHCP lease, update the Home Assistant config, hope I didn't fat-finger a UUID.

Now I say things like "why isn't the bathroom motion sensor automation working?" and the agent checks the automation config, finds the device reference, verifies the sensor is online, and tells me the device ID changed after a firmware update — then offers to fix it.

The network still requires the same technical knowledge to *design*. You still need to understand VLANs, firewall rules, DHCP scoping, and inter-VLAN routing. But the day-to-day *operation* — the config changes, the troubleshooting, the "something broke and I don't know which layer" investigations — that's where having 45 tools behind natural language changes the game.

My home network is more complex than it's ever been. And it's never been easier to manage.

---

## Navigation

- [Home](/)
- [About](/about.html)
- [Projects](/projects.html)
- [Contact](/contact.html)
- [/dev/thoughts](/dev-thoughts/)

*Copyright 2026 Alex Moening. Opinions expressed are my own.*
