Control netplex. from Claude with MCP
netplex. includes a Model Context Protocol (MCP) server. MCP is the way Claude Desktop, Claude Code and claude.ai use tools. Point an AI agent that supports MCP at it, and it can build a lab and check its own work. It can read the topology, connect devices, power them on and run commands. Then it can pull real numbers from the links and ports to prove traffic is really moving.
Every tool below is real and shipped in tools/netplex-mcp/server.py - not illustrative pseudocode. Here is the full list, grouped by what the tools do:
78 tools as of the current release, generated straight from the server's own tool list.
Build & wire a lab:
| Tool | What it does |
|---|---|
list_labs | List all labs accessible to the current user. |
create_lab | Create a new empty lab. |
delete_lab | Permanently delete a whole lab (every node, link and saved config in it). |
get_lab | Get a lab's full declarative definition as a .npx document (JSON text) - the same content Export -> .npx downloads. |
get_topology | Get the full topology of a lab (nodes, links, status). |
describe_topology | Generate a human-readable description of a lab's topology. |
add_node | Add a node to a lab. |
add_link | Connect two nodes with a link. |
move_node | Move a node on the canvas (layout). |
delete_node | Remove a node from a lab (also removes its links). |
delete_link | Remove a link from a lab. |
update_node | Change a node's settings - the Inspector's General/Resources tabs. |
update_link | Change a link's settings - the link Inspector. |
import_containerlab | Import a ContainerLab topology (YAML) as a new netplex. |
import_lab | Import a .npx JSON document as a brand-new lab. |
export_lab | Export a lab in a given format. |
clone_lab | Duplicate a lab's definition (topology + configs + canvas layout) as a new, stopped lab - runtime state (running nodes) is not copied. |
plan_lab | Preview what applying a .npx document to a lab would change - NO write happens. |
apply_lab | Redeem a plan_hash from plan_lab and actually write the change to the LIVE lab - new nodes are created (and started, if the lab is running), |
get_task | Poll a long-running task (from apply_lab/clone_lab) for its current progress, final result, or error. |
Power & control:
| Tool | What it does |
|---|---|
start_lab | Start all nodes in a lab. |
stop_lab | Stop all nodes in a lab. |
start_node | Power on a single node (the rest of the lab is untouched). |
stop_node | Power off a single node (the rest of the lab keeps running). |
restart_node | Restart a single running node. |
get_node_status | Get the current runtime status of a node. |
wait_for_node_ready | Wait, server-side, until a node's console shows a real prompt (or a container reports running), so you never have to guess whether a box is up. |
get_node_capabilities | What this node can do: its runtime capabilities (config capture/apply, disk snapshot, logs, ...) PLUS the honest three-way config story and its |
list_hosts | List every host in this cluster (name, health, cpu/mem, running-node count) - the same fleet `GET /api/v1/cluster/nodes` the Cluster/Compute Fleet |
place_node | Pin a node to a specific cluster host, or move it back to the local controller (host="orchestrator" / "local" / ""). |
Configure & run:
| Tool | What it does |
|---|---|
get_node_config | Read a node's saved startup config, or its live running config. |
set_node_config | Save a node's startup config; optionally push it to the running device. |
clear_node_config | Remove a node's saved startup config (the node boots factory-default next time). |
get_running_config | Read a node's LIVE running config, distinct from its saved startup config. |
save_running_config | Capture a node's current running config and persist it as its saved startup config. |
run_command | Run a command on a running node and return the output. |
run_device_command | Execute a vendor-specific CLI command on a running node and return output. |
connect_ssh | Run a command on a node over SSH to its management IP if it has one and SSH answers there (faster, no console lock contention) - otherwise falls |
get_console_log | Get the last N lines of a node's serial console log. |
get_node_logs | Read a node's own runtime log stream - Docker/LXC/microVM stdout+stderr, or a QEMU node's serial console log. |
Look & feel:
| Tool | What it does |
|---|---|
get_lab_appearance | Read a lab's appearance (canvas theme/background/pattern + console font/scheme), RESOLVED - the per-lab override (if any) merged on top of the |
set_lab_appearance | Set this lab's own canvas and/or console appearance override (any subset of keys - only the ones you pass are changed, the rest of the lab's |
set_node_appearance | Set one node's visual override (shell/scale/glyph/border/stripe/layout/label/ colour) - the same per-node overrides the canvas Node-appearance |
set_link_style | Set a link's visual style override (colour/width/dashed/...) - the same `ui_state.link_overrides[link_id]` bucket the Link-appearance popover writes. |
list_icons | List built-in vendor icons PLUS (when lab_id is given) that lab's own uploaded assets - each with the exact string to pass as `glyph=` to |
arrange_lab | Auto-arrange node positions - server-side, same mechanism Studio's own "Auto-layout" toolbar button/G-shortcut uses (`POST .../topology/layout`), |
annotate_lab | Add/update/remove/list canvas annotations (shapes/lines/text/images drawn on the lab, the same `ui_state.annotations` bucket the canvas annotation |
hide_show | Hide or show canvas elements by plane or tag - the same `ui_state.hidden_planes`/`hidden_tags` lists the canvas plane/tag legend checkboxes write. |
Images & your own code:
| Tool | What it does |
|---|---|
list_images | List images available in the local library. |
get_catalog | Browse the community appliance catalog to find images to use in labs. |
download_image | Install a FREE appliance from the catalog so it can be used in labs. |
push_code | Build a real image from your OWN project code and register it in the netplex Library, ready for run_my_image - the "push your code" pipeline. |
build_status | Check the status of your most recent push_code build for an image name. |
get_build_log | Get the last N lines of your most recent push_code build's log for an image name. |
list_my_images | List your own push_code images (owner=me) as they appear in the Library. |
run_my_image | Add a node to a lab using ONE OF YOUR OWN push_code images, start it if the lab is already running, and report its /data folder - the persistent, |
Diagnose & prove it works:
| Tool | What it does |
|---|---|
get_link_stats | Get real-time statistics for a link: operstate, carrier, byte/packet counters. |
get_interface_counters | Get all interfaces and their packet/byte counters for a running node. |
diagnose_link | Run diagnostic checks on a link: combines stats + interface info + highlights asymmetries. |
start_capture | Start packet capture on a link. |
stop_capture | Stop packet capture on a link. |
get_capture_summary | Get a summary of captured packets on a link. |
list_captures | List every packet capture recorded for this lab - capture_id, which link, when it ran. |
download_capture | Get a tshark-style summary of one capture - protocol counts, not raw pcap bytes, so you can read "12 ARP requests, 0 replies" instead of parsing |
get_lab_events | Read this lab's own event log - node crashed, link down, config inject failed, and so on. |
assert_protocol_state | Check a real protocol-state claim about a running node and get real evidence, not a guess. |
test_under_network | Clone a git repo and run its test command against a peer service under simulated network conditions - latency, jitter, packet loss. |
Fault-finding, no AI required:
| Tool | What it does |
|---|---|
lint_lab | Run netplex's deterministic (no-AI) lint checks against a lab and report any findings - duplicate MACs/IPs, unwired ports, down links, stopped |
get_lab_health | One-call lab health summary: node/link/host state plus lint error/warning counts - the tool an AI should start with before deciding whether to dig |
Read-only operator tools:
| Tool | What it does |
|---|---|
show | Run a READ-ONLY vendor CLI command on a node - the safe subset an AI/ automation token can use WITHOUT the `nodes:exec` scope and WITHOUT ever |
get_mac_table | Read a node's MAC address table (which port/VLAN learned which MAC), via `show`. |
get_arp_table | Read a node's ARP/neighbor table (IP-to-MAC mappings), via `show`. |
ping | Ping a target FROM a node, over its console. |
traceroute | Traceroute to a target FROM a node, over its console. |
get_interface_state | Read a node's interface admin/oper state summary, via `show`. |
Approvals:
| Tool | What it does |
|---|---|
check_approval | Check the status of one pending approval by id (given to you in the "Waiting for a human to approve" message a gated tool call returned). |
list_pending_approvals | List this AI client's own pending "ask first" approvals - changes this session tried to make that a human has not yet allowed or denied. |
Hardware tools:
| Tool | What it does |
|---|---|
list_tool_instances | List the hardware-tool instances (traffic generator, RFC 2544 tester, scanner, ...) the current user owns - in labs and in the Tools workbench - |
Configure Claude Desktop - add to claude_desktop_config.json:
{
"mcpServers": {
"netplex": {
"command": "python3",
"args": ["/path/to/netplex/tools/netplex-mcp/server.py"],
"env": {
"NETPLEX_URL": "http://localhost:8088",
"NETPLEX_USER": "admin",
"NETPLEX_PASS": "netplex"
}
}
}
}
Restart Claude Desktop and the tools above become available to the model automatically - ask it to "list my labs" or "create a lab called staging with two Alpine hosts" and it calls the real REST API underneath, exactly like the Studio UI does.
Run it standalone (stdio transport, the MCP default):
NETPLEX_URL=http://localhost:8088 NETPLEX_USER=admin NETPLEX_PASS=netplex \ python3 tools/netplex-mcp/server.py
Or over SSE for a web-facing client: python3 server.py --transport sse --port 9999.
Because every tool call is just the REST API underneath, anything the MCP server can do, the CLI or SDK can also do directly - see "CLI quickstart" and "The Python SDK" for the non-AI equivalents of the same operations.
Build and debug a lab with your own AI agent
Building a topology is the easy half. The harder, more useful thing an agent can do is tell you why a link isn't passing traffic - not "the API call succeeded" but "port A is transmitting and port B is receiving nothing." That's what the diagnose tools are for, and they read the numbers straight from Linux (the counters it keeps for every network port), not from a config file that could be wrong.
A typical debug turn looks like this:
$ agent "why can't leaf01 reach leaf02?" → diagnose_link(lab_id="l-9f2", link_id="lnk-14") { "link_id": "lnk-14", "stats": { "interface_a": { "name": "eth1", "operstate": "up", "carrier": 1, "tx_bytes": 48210, "rx_bytes": 0 }, "interface_b": { "name": "eth1", "operstate": "up", "carrier": 1, "tx_bytes": 0, "rx_bytes": 0 } }, "asymmetries": [ "A→B: A transmitting but B not receiving" ] }
That's a real response shape from diagnose_link - it composites the link's raw sysfs counters from both ends into one call and flags the asymmetry itself, so the agent doesn't have to hand-roll the comparison. Under the hood it's calling the same GET /labs/{lab_id}/links/{link_id}/stats route get_link_stats exposes directly, plus each node's live interface list via get_interface_counters - both are also callable on their own when you want raw numbers instead of a diagnosis.
If the counters alone aren't enough, the agent can go one level deeper and look at the actual frames: start_capture(lab_id, link_id), reproduce the issue, stop_capture(lab_id, link_id), then get_capture_summary(lab_id, link_id) for a protocol/source/destination breakdown of what actually crossed the wire - the same capture pipeline the Studio UI's Captures tab uses, just callable from the agent's own loop. All six diagnose/capture tools are read-only-annotated, so they work even when the MCP server is locked down with NETPLEX_MCP_READONLY=1 for an operator who should be able to look but not touch.
Since September 2026, wait_for_node_ready waits until a node's console shows its real vendor prompt (or the container reports healthy) instead of an agent hand-rolling a poll loop - and ping/assert_protocol_state answer "can node A reach node B" directly. The full, current tool list is larger than the categories above show; a complete refresh of this page's tool catalogue is tracked as a follow-up.