mcpField
N
Netdryrun

Netdryrun

Local
netdryrun.comCloud & Infra0Updated 1d ago

About Netdryrun

Netdryrun tests network configuration changes in a virtual containerlab topology before deployment, for network engineers and teams using CI. It checks real control-plane behavior, including BGP sessions, routes, prefixes, and reachability, and produces test evidence without touching production.

Key features

  1. 01Test changes in virtual network labs
  2. 02Check BGP, routes, prefixes, and ping
  3. 03Record known-good behavior as a baseline
  4. 04Run checks in GitHub Actions
  5. 05Export Markdown, JUnit, and JSON reports

Use cases

  1. 01Validate network changes in a pull request
  2. 02Catch route leaks before production
  3. 03Compare a lab against a recorded baseline
  4. 04Let an agent verify a proposed change

MCP support

The MCP server exposes tools to deploy and test a lab, run checks against an active lab, validate configuration, and list check types. It returns a verdict and does not push configurations to production.

Configuration

Local server config is the same for every client. Copy it into your client’s MCP config file.

mcp.json · Universal config
{
"mcpServers": {
"netdryrun-netdryrun": {
"command": "uvx",
"args": [
"netdryrun"
]
}
}
}
Run commanduvx netdryrun

Local servers need a runtime on your machine (e.g. Node.js 18+). Config file locations differ by client; see your client’s docs.

Packages
pypinetdryrun0.3.2

Tools

5

Saved tool list · updated 1d ago

ParameterTypeRequiredDescription
pathstringYes
keep_labbooleanNo

Details

From the project README

Netdryrun

CI OpenSSF Scorecard

Test network changes before they touch production.

Netdryrun™ boots your topology as a real virtual network (containerlab), applies your configs, and checks how the network actually behaves: BGP sessions, prefix counts, routes, reachability. It catches mistakes like route leaks before they reach production, and leaves evidence a reviewer can trust.

Netdryrun catching a leaked default route that ping and session checks miss

$ netdryrun run examples/frr-bgp-broken
Deploying lab ndr-frr-broken (3 nodes)...
  [PASS] r1: BGP session to 10.0.12.2 is established -- state is established (6.3s)
  [PASS] r1: ping 192.168.3.1 from 192.168.1.1 expects success -- 3/3 replies (2.3s)
  ... (3 more passing checks trimmed)
  [FAIL] r1: route 0.0.0.0/0 via bgp is absent -- unexpectedly present via bgp (not selected) (0.1s)
FAILED: 5 passed, 1 failed (6 checks)

That run caught a leaked default route that was received but not selected. A ping test or a config linter would have missed it.

Status: v0.3.0, early. Works today with FRR, Nokia SR Linux, SONiC (docker-sonic-vs) and Arista EOS (cEOS, bring your own image) on Linux and WSL2. On the verdict corpus, Netdryrun judged 24/30 realistic BGP changes correctly (ping-only checks 20/30, all-sessions-up checks 15/30) and 16/18 on the harder v2 (9/18 and 7/18). Both are small benchmarks the maintainers wrote on one FRR topology, with expected verdicts committed before any run, and both ran on Netdryrun 0.2.0: they compare methods, they are not a production reliability estimate. Feedback and issues are very welcome.

Why

  • It tests behavior, not syntax. Linters and config diffs can't tell you whether BGP will come up or whether a prefix leaks. A real control plane can.
  • It's built for CI. One GitHub Action, a pass/fail exit code, a Markdown summary in the job, JUnit XML, and a JSON evidence file with every command and raw output.
  • It doesn't lie about convergence. Positive checks wait for the network to converge. Negative checks ("this route must be absent") run only after convergence and must hold for a time window, so they can't pass just because BGP hasn't come up yet.

Quick start

Try it in your browser (no install)

Open in GitHub Codespaces

The codespace comes with Docker, containerlab and Netdryrun already installed. When the terminal shows Ready, run:

bash examples/agent-demo/demo.sh

A route-leaking change is rejected, then the fix passes every check. It uses your GitHub account's free Codespaces hours (2-core machine). Details.

On your own machine

Requirements: Linux or WSL2, Docker, containerlab, Python 3.10+.

pip install netdryrun
git clone https://github.com/netdryrun/netdryrun && cd netdryrun
netdryrun run examples/frr-bgp          # 3 FRR routers, should pass
netdryrun run examples/srl-frr          # SR Linux + FRR, should pass
netdryrun run examples/frr-bgp-broken   # a bad change, should fail
On your own containerlab topology (3 commands)
netdryrun record my-lab/topology.clab.yml --deploy   # snapshot today's behavior into my-lab/netdryrun.yml
# ...edit your configs (the change you want to test)...
netdryrun run my-lab                                   # did anything break?

record writes a baseline of every established BGP session, prefix count and BGP route. Review it, commit it, and every future change is tested against it, locally or in CI.

netdryrun.yml

lab: topology.clab.yml      # your containerlab topology
timeout: 90                 # seconds a positive check may wait for convergence

checks:
  - type: bgp_session       # session reaches a state (default: established)
    node: r1
    neighbor: 10.0.12.2
  - type: bgp_prefixes      # accepted prefix count within a range
    node: r1
    neighbor: 10.0.12.2
    min: 1
    max: 10
  - type: route             # route present (or absent), optionally via a protocol
    node: r1
    prefix: 192.168.3.0/24
    protocol: bgp
  - type: ping              # reachability, optionally from a source address
    node: r1
    source: 192.168.1.1
    target: 192.168.3.1
  - type: route             # negative check: must stay absent after convergence
    node: r1
    prefix: 0.0.0.0/0
    protocol: bgp
    present: false

Failure scenarios break something, check the network still behaves, then restore it. They can take links down, or make an FRR neighbor announce routes it shouldn't, to prove a max-prefix limit or an import filter actually holds:

scenarios:
  - name: r1-r2 link fails
    link_down: {node: r1, interface: eth1}
    checks:
      - {type: ping, node: r1, source: 192.168.1.1, target: 192.168.2.1}   # rerouted via r3
      - {type: bgp_session, node: r1, neighbor: 10.0.12.2, state: down}
  - name: customer floods 200 prefixes
    inject_routes: {node: ce1, count: 200, from: 10.100.16.0/24}
    checks:
      - {type: bgp_prefixes, node: up1, neighbor: 10.0.2.1, max: 51}   # the limit stops the flood

Every check also accepts name, timeout and vrf. Ping accepts expect: failure and count. Drivers are detected from the topology; override them with nodes: {r1: {driver: frr}}. Nodes that are configured after boot (SONiC) take setup commands: nodes: {sonic1: {setup: [...]}}.

CheckFRRSR LinuxArista EOS (cEOS)SONiC (VS)
bgp_session✅✅✅✅
bgp_prefixes (accepted = received minus rejected by policy)✅✅✅✅
route present / absent✅✅✅✅
bgp_route best-path attributes: communities (standard, large, well-known), AS path (exact or regex), local preference, MED, next hop✅✅✅✅
ping success / failure✅✅✅✅

Commands

CommandWhat it does
netdryrun run [path]Deploy the lab, run the checks, destroy the lab. Add --keep to leave it up for debugging
netdryrun check [path]Run the checks against a lab that is already running
netdryrun validate [path]Validate netdryrun.yml without running anything
netdryrun record topology.clab.ymlSnapshot a known-good lab into a netdryrun.yml baseline, so you don't write checks by hand

Reports: --json FILE (full evidence), --markdown FILE, --junit FILE. Under GitHub Actions, a summary is appended to the job page automatically. Exit codes: 0 passed, 1 a check failed, 2 config or lab error, 3 inconclusive (the lab never reached a testable state, so the run says nothing about the change).

GitHub Action

name: Network dry run
on: pull_request
jobs:
  dryrun:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: netdryrun/[email protected]
        with:
          path: network/        # folder containing netdryrun.yml

The action installs containerlab, runs the checks, uploads the JSON and JUnit reports as an artifact, and fails the job if any check fails. Outputs: status, passed, failed, inconclusive-reason.

For AI agents (MCP)

AI can propose network changes. Netdryrun verifies the outcome. Agents: read AGENTS.md.

AI agents are starting to change networks. Netdryrun gives them a safety gate: an MCP server that lets an agent dry-run its change in a real lab and proceed only if every check passes.

pip install netdryrun

Add it to your MCP client (for example Claude Code):

claude mcp add netdryrun -- netdryrun mcp

Tools: dry_run (deploy, check, destroy, verdict), run_checks (against a running lab), validate, check_types. Each result carries a separate verdict field: SAFE TO PROCEED, DO NOT APPLY THIS CHANGE, or INCONCLUSIVE: the lab did not reach a testable state; ... when the lab never came up.

Demo (examples/agent-demo): the ticket says the customer's new block must reach the upstream and the peer. The tempting change passes every ticket check, session and ping, but leaks routes; Netdryrun rejects it. The two-line fix passes the same checks. (A scripted replay of the two attempts; CI runs it.)

Attempt 1 passes the ticket checks but fails four leak guards; attempt 2 passes all 13 checks

Safety model:

  • The server never touches production. It has no tool that pushes config anywhere; it only builds and tests labs.
  • Tool results contain normalized check results, not raw device output, so text inside a lab (a banner, a route description) can't inject instructions into the agent. Raw evidence goes to the JSON report file.
  • A lab is code: containerlab runs privileged containers. Only point the server at topologies you trust, on a machine you'd let run Docker.

How it compares

Netdryrun complements these tools; it doesn't replace them.

ToolWhat it does bestWhere Netdryrun fits
BatfishStatic analysis of configs, without booting anything; fast and broadNetdryrun runs the real control plane, so it sees runtime behavior: convergence, timers, what actually lands in the RIB
pyATS / GenieA full test framework with deep Cisco parsersNetdryrun is a small declarative YAML file plus one GitHub Action, with no test code to write
containerlabRuns the labNetdryrun drives containerlab; it doesn't replace it

What Netdryrun adds: positive checks wait for convergence, negative checks run only after convergence and must hold, received-but-not-selected routes count as present, and every run produces one pass/fail plus an evidence file.

Honest limits

  • Lab routers are not your production routers. A pass on FRR, SR Linux, SONiC VS or Arista cEOS is strong evidence for protocol logic and policy intent, but not proof of hardware, scale or version-specific behavior. For Arista, bring your own licensed cEOS image to test real vendor code.
  • Checks cover IPv4/IPv6 unicast BGP (sessions, prefix counts, best-path attributes), routes and ping, with link-down, node-isolation and route-injection (FRR only) failure scenarios. More checks are coming: OSPF/IS-IS adjacencies, path/traceroute.

How results are checked, and who writes the code (mostly AI agents): How it's built.

Roadmap

  • Outage replays: famous real-world outages (route leaks, mass withdrawals) rebuilt as runnable labs and check packs. Would your change have caused one?
  • More failure scenarios, more check types (OSPF/IS-IS, traceroute) and drivers
  • PR comments with diffs, and signed evidence for change tickets

License

Apache-2.0. "Netdryrun" and the Netdryrun logo are trademarks; see TRADEMARKS.md.

Comments

Markdown supported

New to MCP?

From what the protocol is to installing your first server, these answers cover most first questions.

An open protocol that defines how AI applications connect to external tools and data in a uniform way. Clients talk to the model; MCP servers provide tools, resources and prompt templates. They exchange standard messages, so one server can be reused by many clients.