AI-Powered Workflows

Give Claude Live Access to Your Network with a Custom MCP Server

A laptop terminal connected by glowing lines to three network routers, representing an AI assistant querying live lab devices

Something is off with OSPF on your network. Normally that means SSHing into two or three boxes, running the same five show commands on each, and squinting at timer values until something doesn’t match.

I asked Claude to do that part. Not by pasting output into a chat window, but by giving it live, read-only SSH access to my home lab through a custom MCP server I built with FastMCP and Netmiko. I typed one sentence, “something’s off with OSPF on my network, take a look and tell me what you find,” and got a correct diagnosis from real device output in about 30 seconds.

Here’s how it works, what it can and can’t do, and how to build your own version against your own lab.

The Problem With Copy-Paste AI Troubleshooting

Pasting show output into a browser chat works, but it has three problems that get worse the more you rely on it:

  • You are the data-collection layer. Every command, every device, every paste is manual.
  • The model only sees what you thought to paste. If the real issue is on a device or in a command you didn’t think to check, the model never sees it.
  • There’s no guardrail. If you paste show running-config into a general-purpose chat window, every secret in that config goes with it.

MCP (Model Context Protocol) fixes the first two by letting Claude call tools directly, on your hardware, on demand. It does not automatically fix the third. That’s something you have to build in, and it’s a big part of what this post covers.

MCP itself isn’t a Claude-only thing anymore. Anthropic donated the spec to the Linux Foundation in December 2025, and OpenAI and Google have both adopted it since. Building an MCP server is a transferable skill, not a one-vendor party trick.

The Workflow

Why build from scratch

There are existing community MCP servers for network devices and for Cisco Modeling Labs. I looked at a few. I built my own anyway, for three reasons: I know exactly what every line of it does, it’s scoped to exactly my lab instead of a generic device fleet, and building one is the actual skill I wanted to walk through on camera. If you’d rather start from an existing server, that’s a legitimate choice. This post is for the “I want to understand and control every tool Claude can call” crowd.

The inventory file

Devices live in a YAML file, not hardcoded in Python:

defaults:
  username: admin
  password: cisco123
  device_type: cisco_ios

devices:
  core-rtr-01:
    host: 192.168.1.250
    description: Core router
  edge-rtr-01:
    host: 10.0.0.2
    description: Edge / WAN router
  access-sw-01:
    host: 192.168.1.251
    description: Access switch

The defaults block applies to every device. Each device entry only needs a host and a description. Adding a fourth device is one more block under devices:.

The server: 5 tools, about 150 lines

server.py is a FastMCP server exposing 5 read-only tools:

Tool What it does
list_devices() Returns the inventory: hostnames, IPs, descriptions
get_device_status(device) Bundles show ip interface brief, show ip ospf neighbor, show ip route, and show cdp neighbors into one SSH session
run_show_command(device, command) Runs an arbitrary show command, with restrictions (more on this below)
find_in_config(device, pattern) Greps show running-config for a regex pattern
compare_running_to_startup(device) Diffs running vs. startup config to catch unsaved drift

Each tool is a plain Python function with a @mcp.tool decorator. FastMCP reads the type hints and docstring and turns that into the JSON schema Claude uses to decide when and how to call it. You’re not writing JSON-RPC by hand. You’re writing Python functions with good docstrings, and the framework handles the protocol.

One thing that will bite you if you don’t know it going in: never use print() in an MCP server that talks over stdio. The protocol uses stdout for JSON-RPC messages, and a stray print() statement corrupts the channel and breaks the server in ways that are miserable to debug. Use Python’s logging module instead, pointed at a file or stderr.

Before wiring this into Claude at all, run it through the MCP Inspector:

npx @modelcontextprotocol/inspector uv run python server.py

This opens a browser UI where you can call each tool directly and see the raw response. Confirm all 5 tools show up and list_devices returns your 3 devices before you go anywhere near a chat client.

Two layers of guardrails

This is the part that matters more than the demo. The server is read-only by construction, but “read-only” still has to account for the fact that a Cisco running-config contains secrets.

Layer 1: a command blocklist. run_show_command only accepts strings starting with show , and it blocks any command containing run, running, startup, secret, password, username, community, key, or crypto (case-insensitive). show running-config never executes through this tool, full stop.

Layer 2: output redaction. Two tools, find_in_config and compare_running_to_startup, do read from the running-config because that’s the only place to find drift or grep for a pattern. Before that text ever reaches Claude, a list of regex patterns strips anything secret-shaped:

Pattern Before After
enable secret enable secret 5 $1$abc... enable secret <REDACTED>
line password password 7 0822455D0A16 password <REDACTED>
SNMP community snmp-server community public RO snmp-server community <REDACTED> RO
OSPF MD5 key message-digest-key 1 md5 7 0822... message-digest-key 1 md5 <REDACTED>

Claude can still tell you that a community string exists, that it’s read-only, and whether an ACL restricts it, all without ever seeing the actual string. The structure survives. The secret doesn’t.

Wiring it into Claude Desktop

Custom local MCP servers go through Settings -> Developer -> Edit Config in Claude Desktop. Not the Connectors tab. Connectors is for remote, OAuth-based servers like Google Drive or Slack. A local Python script you wrote yourself is a different thing entirely, and the two are easy to mix up the first time.

The config entry needs the absolute path to the Python interpreter inside your virtual environment, not your system Python:

{
  "mcpServers": {
    "network-assistant": {
      "command": "C:\\Users\\you\\mcp-network-assistant\\.venv\\Scripts\\python.exe",
      "args": [
        "C:\\Users\\you\\mcp-network-assistant\\server.py"
      ]
    }
  }
}

On Windows, that means double backslashes. A single backslash breaks the config silently, with no error message pointing you anywhere useful. After saving, fully quit Claude Desktop from the tray icon and relaunch it. It only reads this file on startup.

I also set up a project with a short system prompt telling Claude that a network-assistant server is connected to my lab, what the three devices are, and to go investigate directly instead of asking me for show output. The tool docstrings tell Claude when a tool is relevant. The project prompt removes any doubt about whether a question is even about this lab in the first place.

The headline query

With everything wired up, I asked one question: “Something’s off with OSPF on my network. Take a look and tell me what you find.”

Claude called list_devices, then get_device_status against both routers, noticed the OSPF neighbor table was empty on both sides, then pulled the OSPF configuration from each device with find_in_config to compare timer values. Total time: about 30 seconds.

The answer: core-rtr-01 was running OSPF’s default Hello/Dead timers (10 seconds / 40 seconds), while edge-rtr-01 had been explicitly configured with non-default timers (Hello 30 / Dead 120). OSPF requires matching Hello and Dead intervals on both ends of an adjacency, so the neighbor relationship never formed. The fix Claude proposed: remove the explicit timer override on the edge router with no ip ospf hello-interval 30, returning it to the matching defaults.

That’s the same diagnosis I’d reach manually by running show ip ospf interface on both ends and comparing timer lines, except Claude gathered and compared the evidence itself, across two devices, in the time it takes to read this paragraph.

Adapting For Your Network

The starter kit ships with real, working credentials for a 3-device Cisco IOL lab (admin / cisco123), built specifically so you can import the included CML topology and have it work without editing anything. If you’re pointing this at your own gear, here’s what to expect:

  • More devices is just more entries in inventory.yaml. The code doesn’t change.
  • Other vendors will mostly work for run_show_command and find_in_config, since those are generic. get_device_status and the redaction patterns assume Cisco IOS-style show output and command syntax, so Fortinet, Aruba, or Meraki gear will need those adjusted.
  • Credentials belong in inventory.yaml. If you’re running this against anything that isn’t a disposable lab, treat that file like any other file with stored credentials: restrict its permissions, and don’t reuse lab-only passwords anywhere that matters.
  • The blocklist is broad on purpose. It blocks commands containing certain words, which occasionally catches a legitimate show command that happens to include one of them. Tighten or loosen BLOCKED_TOKENS to fit how your team actually phrases commands.

Honest Limitations

A few things worth knowing before you build this for real.

The guardrails are the point, and they’re easy to remove. REDACT_PATTERNS is a single Python list near the top of server.py. Set it to [] and the redaction layer disappears entirely, the secrets that were being masked come through in plain text. I built it this way deliberately, so anyone studying the code can see exactly what one list of regexes is protecting. But it also means the protection is only as good as that list, and only as good as nobody quietly deleting it. If you adapt this for your own network, audit REDACT_PATTERNS and BLOCKED_TOKENS yourself rather than trusting that mine cover everything you care about.

Claude doesn’t know your protocols better because it’s connected to your lab. MCP gives Claude live evidence, real show output from real devices, but it doesn’t give Claude protocol expertise it doesn’t already have. I’ve seen other AI tools confidently state things about routing protocols that are flatly wrong. Connecting Claude to live data makes its answers grounded in your actual network state, which is a real improvement. It does not make Claude an authority on whether the proposed fix is correct. Verify the fix the same way you would if a junior engineer suggested it: read the command, understand what it does, and test it in the lab before it goes anywhere near production.

Composing multiple MCP servers is powerful, but finding the right ones takes real trial and error. Part of the appeal of MCP is that you’re not limited to one server. Claude can have several connected at once and decide which tools to call for a given question. In practice, I went looking for a second server to pair with this one. I ran into the same wall a few times. A server that sounded like a great fit on paper turned out to be scoped to something adjacent to what I actually needed. Or its retrieval quality for the specific thing I was asking about wasn’t there yet. The ecosystem is moving fast, but “this MCP server exists for that vendor” and “this MCP server actually answers the question I have” are not the same thing yet. Budget time for that evaluation if you’re planning to stack servers.

Claude Code took noticeably longer than Claude Desktop for the same query. I ran the identical headline OSPF query through Claude Code using the same .mcp.json config, and it took about 2.5 minutes versus roughly 30 seconds in Claude Desktop. The likely reason isn’t that Claude Code is “slower” at reasoning. I have a number of other plugins and MCP servers configured in Claude Code, and it has to work out which of those tools are relevant before it gets to the network-assistant ones. Claude Desktop, in this test, only had the one MCP server pointed at it. If you’re running Claude Code with a heavily loaded plugin setup, expect tool selection overhead that a clean Claude Desktop install won’t have.

Get The Artifact

Everything in this post, the server code, the inventory file, the CML lab topology, and example queries, is on GitHub:

github.com/GTalksTech/netops-toolkit/tree/main/scripts/netmiko/mcp-network-assistant

Clone it, import the topology into CML, run uv sync, and inventory.yaml will work against the lab unmodified. The README covers the full setup for both Claude Desktop and Claude Code.