clab-mcp
by mithun2k5
README.md
# clab-mcp
Two MCP servers that let Claude build, deploy and verify [containerlab](https://containerlab.dev)
network topologies on macOS — from a plain-English sentence.
> build me a 2-spine 4-leaf EVPN fabric with VNI 10010
Claude turns that into a complete topology: loopbacks, `/31` fabric addressing,
OSPF areas, BGP EVPN peerings to every route reflector, per-leaf bridge and
VXLAN devices, host attachment — then deploys it and walks the verification
chain from OSPF adjacency down to a packet capture of the VXLAN header.
**Full setup and usage runbook: [SETUP.md](SETUP.md)**
---
## How it works
containerlab is a Linux program — netlink, network namespaces, veth pairs. On
macOS it runs inside a Linux VM. These servers run on macOS and reach into that
VM with `orb -m <vm>`.
```
macOS
├── Claude Desktop
│ ├── clab-builder ── writes topology + config files ──┐
│ └── clab-lab ── orb -m clab ──┐ │
└── OrbStack │ │
└── Linux VM "clab" <─────────┘ │
├── containerlab + docker │
└── frr containers <──────── files synced ──────┘
```
The split is deliberate:
- **`clab-builder`** only writes files. No OrbStack, no containerlab, no
network access. It cannot break a running lab.
- **`clab-lab`** is the only thing that runs containerlab.
## Requirements
- macOS (Apple Silicon or Intel)
- Claude Desktop
- Python 3.10+
- OrbStack with a Linux machine (default name `clab`) running Docker and
containerlab
Setting up the VM itself is out of scope — [SETUP.md](SETUP.md) Stage 0 verifies
it rather than installing it.
## Install
```bash
git clone https://github.com/<your-username>/clab-mcp.git ~/clab-mcp
cd ~/clab-mcp
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
bash register.sh
```
`register.sh` runs preflight checks, backs up your Claude Desktop config with a
timestamp, then **merges** two entries into `mcpServers` — existing servers and
other top-level keys are untouched. Re-running only updates the two entries.
Then quit Claude Desktop with **`Cmd + Q`** (closing the window leaves the old
processes running) and reopen it.
Verify by asking Claude to `run clab_env`.
## Configuration
| Var | Default | Server | Meaning |
|---|---|---|---|
| `CLAB_VM` | `clab` | clab-lab | OrbStack machine name |
| `CLAB_VM_DIR` | `~/labs` | clab-lab | topology location inside the VM |
| `CLAB_MAC_DIR` | `~/containerlab-mac` | both | lab directory on macOS |
| `CLAB_SUDO` | `sudo` | clab-lab | set empty if the VM user is root |
| `CLAB_TIMEOUT` | `900` | clab-lab | seconds allowed for deploy/destroy |
| `CLAB_FRR_IMAGE` | `quay.io/frrouting/frr:10.4.1` | clab-builder | router image |
| `CLAB_HOST_IMAGE` | `ghcr.io/srl-labs/network-multitool` | clab-builder | host image |
Pass any of them to `register.sh` and they are written into the config entries:
```bash
CLAB_VM=mylab CLAB_MAC_DIR=~/my-labs bash register.sh
```
## Tools
### clab-builder — generates topologies
| Tool | Purpose |
|---|---|
| `build_fabric` | short YAML spec in, complete fabric out — **the main one** |
| `list_images` | image catalog with arm64 / login status |
| `create_lab`, `add_node`, `connect`, `set_node_config` | build a topology node by node |
| `render_lab` | write the in-memory lab to disk |
| `show_lab` | preview generated YAML without writing |
| `validate_lab` | correctness checks |
| `list_built_labs`, `delete_lab` | housekeeping |
### clab-lab — deploys and inspects
| Tool | Purpose |
|---|---|
| `clab_env` | arch, containerlab version, docker version, lab dir |
| `sync_lab_dir` | mirror the macOS lab dir into the VM |
| `list_topologies`, `list_labs`, `inspect_lab`, `read_topology` | discovery |
| `deploy`, `destroy` | lifecycle |
| `graph` | export Mermaid or draw.io |
| `node_exec`, `vtysh`, `node_logs` | node access |
| `capture` | tcpdump in the node's netns |
## The fabric spec
Every key except `lab` has a default:
```yaml
lab: evpn-vxlan
spines: 2
leaves: 4
underlay: ospf # ospf (iBGP + route reflector) | ebgp (per-device ASN)
overlay: evpn # evpn | none
asn: 65000
route_reflectors: [spine1, spine2]
spine_loopbacks: 10.255.0.0/24
leaf_loopbacks: 10.255.1.0/24
p2p: 10.0.0.0/24
fabric_mtu: 9100
vnis:
- {vni: 10010, vlan: 10}
hosts:
- {name: pc1, leaf: leaf1, vni: 10010, ip: 192.168.10.11/24}
- {name: pc2, leaf: leaf2, vni: 10010, ip: 192.168.10.22/24}
```
Change `spines:` and `leaves:` and the addressing, interface numbering, OSPF,
BGP peerings and per-leaf VXLAN devices are all recomputed.
## What validate_lab catches
The failures that leave a fabric looking healthy while passing no traffic:
- an interface used by two links, or an endpoint naming a node that does not exist
- a `/31` pair whose two ends are not actually on the same subnet
- a BGP neighbour address matching no configured loopback or link address
- a VXLAN device sourced from an IP that is not that leaf's own loopback
- a VTEP whose BGP is missing `advertise-all-vni`
- an access port joined to a bridge that is not a real link
- the loopback address set *after* the VXLAN device is created
- `bgpd`/`ospfd` disabled in `daemons` while the config uses them
- eBGP without `no bgp ebgp-requires-policy` — FRR 7.4+ silently drops everything
- an eBGP EVPN spine missing `retain route-target all`, or rewriting the VTEP next-hop
## Coverage and limits
- `underlay: ospf` + `overlay: evpn` is the validated path — deployed and
verified end to end including a packet capture of the VXLAN header.
- `underlay: ebgp` generates the standard eBGP-CLOS-with-EVPN pattern and passes
every static check, but has not been deployed on a live fabric.
- **L2VNI only.** No L3VNI, no tenant VRF, no anycast gateway — hosts sharing a
VNI must share a subnet.
- **FRR only.** SR Linux nodes can be added with `add_node`, but `build_fabric`
does not generate SR Linux configs.
- Images used are arm64-native and login-free: FRRouting and network-multitool.
## License
MIT — see [LICENSE](LICENSE).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues