Engineers ask their AI assistant which IPs are free in a subnet, and it answers from NetBox — right now, not from a stale copy in a wiki. Nobody installs anything. The API token never leaves the server.
Infrastructure teams keep two kinds of knowledge, and they rot in different ways.
Structured data — subnets, addresses, VLANs — lives in NetBox. Accurate, queryable, and reachable only through a web UI or a REST API. Mid-incident, an engineer opens a browser tab and squints.
Narrative knowledge — runbooks, incident write-ups, the reason a VLAN exists — lives in a wiki. Engineers now read this through AI assistants, which is fast and pleasant.
So the assistant can reason about why the network looks like it does, but cannot see what it currently is.
Copy the IP addresses into the documentation, so the assistant can read them.
Give the assistant a way to ask the source of truth directly, at the moment the question is asked.
A copied live value goes stale silently. That is the whole argument. Six months later the assistant confidently quotes an address that was reassigned in March, and it has no way to know it is wrong. A tool call either returns today's answer or fails loudly.
Real tools, real shapes. Pick a question.
Addresses shown are from an illustrative lab range, not any production network.
The default way to ship MCP is stdio. For a team, that is a security posture, not a convenience choice.
With stdio, the assistant spawns the process locally. Every engineer installs Python, clones a repo, and pastes an API token into a config file on their laptop. Now the token exists on N laptops. Rotating it means chasing N people. Revoking access for someone who left means hoping they deleted a dotfile.
Serving MCP over HTTP inverts every one of those properties:
EnvironmentFile readable only by the service user. It never reaches a client.The trade-off, stated plainly. The endpoint is now a network service and must be treated as one. It listens on an internal network or a private mesh — never the public internet — and it carries no authentication of its own. Network reachability is the access control. If that assumption does not hold for you, put it behind a reverse proxy with auth.
The single HTTP helper in this server issues GET and nothing else. There is no code path that writes to NetBox, so an assistant cannot be talked into deleting a prefix, however the prompt is phrased.
Pair that with a read-only NetBox token and the property survives a bug. Two independent mechanisms, because one of them will eventually be a mistake.
An unguarded MCP tool hands the assistant a Python traceback, and the assistant hands the operator a shrug. Every tool here is wrapped so transport failures come back as something a human can act on:
NetBox rejected the token (401/403). Check NETBOX_TOKEN.
NetBox is unreachable at http://netbox.internal:8090 (Connection refused).
NetBox did not answer within 20s.
This matters more than it sounds. An incident is exactly when your tooling is most likely to be degraded, and exactly when a confusing error costs the most.
An incident is exactly when your tooling is most likely to be degraded — so every failure here comes back as something an operator can act on, not a traceback. Pick a scenario and watch the path run.
The docstring is the interface — the assistant reads it to choose. Each is written for someone who has never seen NetBox, with a concrete example.
| Tool | What it answers |
|---|---|
| list_prefixes | Every documented subnet, with its VLAN and description. |
| prefix_report | For one subnet: how many IPs are documented, and the first free ones. |
| find_ip | Search by DNS name, description or address fragment. |
| subnet_contents | Every documented IP inside a subnet, in address order. |
| list_vlans | Known VLANs, ordered by VLAN ID. |
Three rules keep the assistant using it correctly: GET only (the read-only guarantee is the whole security model); the docstring is the API (write it for someone who does not know your schema); and return text a human could read, not JSON — the assistant will paraphrase it anyway, and readable output makes a hallucinated field obvious.
Python 3.10+, a reachable NetBox, and a read-only API token.
git clone https://github.com/msemino/netbox-mcp
cd netbox-mcp
python3 -m venv venv && ./venv/bin/pip install -r requirements.txt
cp .env.example .env # set NETBOX_URL and a read-only NETBOX_TOKEN
chmod 600 .env
set -a && . ./.env && set +a && ./venv/bin/python src/server.py
Then point an assistant at it:
claude mcp add --transport http netbox http://netbox-mcp.internal:8097/mcp
A systemd unit with a hardened sandbox ships in deploy/.