Skip to main content

batfish-q-mcp

An MCP (Model Context Protocol) server that exposes Batfish as tools for AI assistants. Point it at a snapshot of your network configs and ask questions in plain language, without writing Batfish queries yourself.

With it, an assistant can:

  • run any of 60 curated Batfish questions (node properties, routing, ACLs, reachability, parse issues, and more)
  • run assertions, such as "no forwarding loops", singly or as a YAML suite
  • fork a snapshot with failed nodes or interfaces and diff the result against the original
  • return layer 3 and routing topology as nodes and edges

This project is an offshoot of Bat-Q, a Streamlit GUI over Batfish. See Acknowledgements.

Example prompts

Once the server is connected to your MCP client and a snapshot is loaded, try:

  • "List the snapshots and tell me whether the configs parsed cleanly."
  • "Are there any forwarding loops in this snapshot?"
  • "Show me the layer 3 topology."
  • "What happens to reachability from r2 to r3 if r1 fails?"
  • "Which nodes have BGP configured, and what are their peers?"

Behind the scenes the assistant calls the tools listed below.

Quick start

1. Start Batfish

The server needs a running Batfish instance. The quickest way is Docker:

docker run --rm -p 9996:9996 -p 9997:9997 batfish/allinone

The server connects to 127.0.0.1 by default. If Batfish runs elsewhere, set BATFISH_SERVER (for example BATFISH_SERVER=<batfish-host>).

2. Install batfish-q-mcp

Requires Python >=3.10.

git clone https://github.com/martimy/batfish-q-mcp.git
cd batfish-q-mcp

# Recommended: use a virtual environment
python3 -m venv .batmcp
source .batmcp/bin/activate
pip install -e .

Alternatives:

pip install -r requirements.txt
pip install -e ".[dev]"   # includes test dependencies

This installs a console script, batfish-q-mcp, which starts the MCP server over stdio.

3. Connect your MCP client

Claude Desktop, Cursor, and other stdio clients

{
  "mcpServers": {
    "batfish-q-mcp": {
      "command": "batfish-q-mcp",
      "env": { "BATFISH_SERVER": "127.0.0.1" }
    }
  }
}

If you installed into a virtual environment, use the absolute path to the script instead (find it with which batfish-q-mcp while the environment is active):

{
  "mcpServers": {
    "batfish-q-mcp": {
      "command": "/path/to/batfish-q-mcp/.batmcp/bin/batfish-q-mcp",
      "env": { "BATFISH_SERVER": "127.0.0.1" }
    }
  }
}

Restart the client so it picks up the new server.

OpenCode

OpenCode uses opencode.json (schema https://opencode.ai/config.json) with mcp.servers, type: "local", and command given as an array:

{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "servers": {
      "batfish-q-mcp": {
        "type": "local",
        "command": ["/path/to/batfish-q-mcp/.batmcp/bin/batfish-q-mcp"],
        "environment": {
          "BATFISH_SERVER": "127.0.0.1"
        }
      }
    }
  }
}

Put the file in your project root (or ~/.config/opencode/), then start or restart OpenCode from that directory so it reloads MCP servers.

Note: OpenCode Code Mode isolates tools. There are no direct batfish_* tools; call them through the execute tool, for example:

await tools["batfish-q-mcp"].batfish_list_questions({});

4. Load a snapshot and ask

Package your configs as a snapshot zip (see Providing a snapshot), put it in your workspace, and tell the assistant the filename. The assistant uploads it and runs the questions you ask.

A fresh server process has no Batfish network set yet. Pass network (for example "NETWORK") on the first call that needs snapshots; it then stays set for the life of the process. Calling batfish_list_snapshots without network fails with Network must be set.

Running the server manually (optional)

To check that the server starts outside a client:

export BATFISH_SERVER=127.0.0.1
batfish-q-mcp
# or
python -m batfish_mcp.server

It speaks stdio JSON-RPC (FastMCP) and does not print logs to stdout.

Providing a snapshot

  • Format: one Batfish zip containing configs, plus optional batfish/ and hosts/ folders. For example, abc_net.zip contains abc_net/configs/R1.cfg, R2.cfg, R3.cfg, FW.cfg and hosts/server_*.json.

  • Where: drop the zip in your workspace and tell the assistant the filename.

  • Upload: the assistant base64-encodes the zip and calls batfish_upload_snapshot(name, zip_base64, network?, overwrite?).

    • name is sanitized (safe_snapshot_name). Collisions are made unique (name_1, ...) unless overwrite=true.
    • zip_base64 must be valid base64 with correct padding (= or ==). Validation is enforced server-side.
  • Already on the Batfish server: give the assistant the snapshot and network names; no upload is needed.

  • Shell access: you can also upload directly with pybatfish:

    PYTHONPATH=src python3 - <<'PY'
    import os
    from pybatfish.client.session import Session
    bf = Session(host=os.getenv("BATFISH_SERVER", "127.0.0.1"))
    bf.set_network("NETWORK")
    bf.init_snapshot("abc_net.zip", name="abc_net", overwrite=True)
    print(bf.list_snapshots())
    PY
    

Tools

The server provides 9 tools. batfish_run_question gives access to the full 60-question catalog.

Tool Description
batfish_list_questions(category?) The 60-question curated catalog, with input types and TYPE_HINTS descriptions. No Batfish server needed.
batfish_list_snapshots(network?) Snapshots of a network.
batfish_upload_snapshot(name, zip_base64, network?, overwrite?) Upload a snapshot zip (base64). The name is sanitized (safe_snapshot_name); collisions are made unique (name_1, ...).
batfish_run_question(fun, options?, snapshot?, reference_snapshot?, network?) Run any catalog question. Giving reference_snapshot and snapshot runs a differential question. Returns JSON-safe {fun, snapshot, reference_snapshot, status, rows, removed_columns}.
batfish_list_assertions() Live pybatfish assertion names plus the 16-entry registry (specs) with parameter descriptions.
batfish_run_assertion(name, params?, question?, snapshot?, network?) Validate and run one assertion (answer-based ones fetch the answer first). Returns {type, status, message, snapshot, ran_at, runtime, fun?, answer_rows?}.
batfish_run_assertion_suite(suite_yaml, snapshot?, network?) Validate (all-or-nothing) and run a YAML suite; examples/assertions.yaml shows the schema. Returns results[], errors[], summary.
batfish_fork_snapshot(base_name, name?, failed_nodes?, failed_interfaces?, overwrite?, network?) Fork a snapshot, optionally failing nodes or interfaces, then diff the fork against the base with a differential question.
batfish_get_topology(kind, snapshot?, network?) kind="layer3" (from layer3Edges, with interface titles) or "routing" (from bgpEdges). Returns {kind, snapshot, node_count, edge_count, nodes, edges}.

All tools share one Batfish session (batfish_mcp.session, using BATFISH_SERVER, default 127.0.0.1), serialized with a lock.

Tool call examples

For reference, this is roughly what the assistant calls when you ask the example prompts. Upload abc_net.zip, then:

batfish_upload_snapshot(name="abc_net.zip", zip_base64="UEsDBB==", network="NETWORK")
batfish_run_question(fun="nodeProperties", snapshot="abc_net")
batfish_run_assertion(name="assert_no_forwarding_loops", snapshot="abc_net")
batfish_fork_snapshot(base_name="abc_net", failed_nodes=["r1"])
batfish_run_question(fun="differentialReachability",
    options={"pathConstraints": {"startLocation": "r2", "endLocation": "r3"},
             "headers": {"dstIps": "192.168.3.1"}},
    snapshot="abc_net_Fail", reference_snapshot="abc_net")

Topology:

batfish_get_topology(kind="layer3", snapshot="abc_net", network="NETWORK")
batfish_get_topology(kind="routing", snapshot="abc_net", network="NETWORK")

In OpenCode, wrap these in execute, for example:

await tools["batfish-q-mcp"].batfish_run_question({
  fun: "initIssues", snapshot: "abc_net", network: "NETWORK"
});

See HOW-TO.md for a longer walkthrough.

Troubleshooting

  • Network must be set: pass network="NETWORK" on the first call.
  • zip_base64 is not valid base64: Incorrect padding: re-encode with base64 -w 0 abc_net.zip (no newlines) and keep the trailing = or ==. Do not trim by hand.
  • Snapshot already exists: pass overwrite: true, or accept the name_1 suffix.
  • No parse issues but wrong IPs? Run batfish_run_question(fun="initIssues") and fileParseStatus first.
  • Reachability to 240.254.254.1 fails but 8.8.8.8 works: 240.0.0.0/4 is reserved, and an ISP egress filter such as Block outgoing traffic using reserved addresses drops it. Use a public IP to test internet egress.

Layout

src/batfish_mcp/
  server.py           # FastMCP tools over the headless core
  session.py          # thread-safe singleton Session
  catalog.py          # curated question catalog (60 questions)
  assertion_specs.py  # assertion registry (params as {name, type, optional})
  assertion_io.py     # assertion YAML load/validate/dump
  assertions.py       # assertion runner (session- vs answer-based, PASS/FAIL/ERROR)
  queries.py          # question runner (incl. differential snapshots)
  utils.py            # snapshot-name sanitizing, template helpers
  presenter.py        # pure DataFrame/trace serialization helpers
  hints.py            # TYPE_HINTS: tool-parameter descriptions
  topology.py         # L3 + routing graph construction from answers
tests/
  fakes.py            # FakeBatfish + assertion fake (no server needed)
  test_queries.py
  test_assertions.py
  test_server.py      # MCP tools + wire round-trip
examples/assertions.yaml
HOW-TO.md             # mini tutorial

Tests

pip install -e ".[dev]"
pytest          # no Batfish server required (fakes + one in-memory MCP round-trip)

tests/test_assertions.py also checks that assertion_specs.py stays in sync with the installed pybatfish (scripted-assertion signatures).

Acknowledgements

batfish-q-mcp started as an offshoot of Bat-Q, a Streamlit GUI over Batfish. The reusable headless core (question catalog, assertion registry and runner, answer serialization, snapshot tooling) came from there; the GUI layer was dropped and replaced with the MCP transport. Both projects are Apache-2.0.

License

Apache-2.0 (see LICENSE).

Metadata

Release files for batfish-q-mcp 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for batfish-q-mcp 0.1.0
File Size Uploaded Provenance Actions
batfish_q_mcp-0.1.0.tar.gz 41.2 kB Without provenance Details for batfish_q_mcp-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for batfish-q-mcp 0.1.0
File Interpreter ABI Platform Actions
batfish_q_mcp-0.1.0-py3-none-any.whl Python 3 none any Details for batfish_q_mcp-0.1.0-py3-none-any.whl

Total release size: 79.1 kB

Release files / batfish_q_mcp-0.1.0.tar.gz

Download URL batfish_q_mcp-0.1.0.tar.gz
Size 41.2 kB
Tags Source
SHA-256 checksum
How to use checksums
76ed82c6c2f40155ef8bf0c34007172e55c56a9e15014a55d0d59e78135144fb
BLAKE2b-256 checksum
How to use checksums
4944877b77b9b40c33becdd99e0f762cddd2af9eb2be44ae5edebe00828841e0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.3

Release files / batfish_q_mcp-0.1.0-py3-none-any.whl

Download URL batfish_q_mcp-0.1.0-py3-none-any.whl
Size 37.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
062b3b5a9d9a97ff0411eca3f3243d58da0a29c7b041a2c3aaf726c88f5038a8
BLAKE2b-256 checksum
How to use checksums
c633a60e6fcc00c073896625b63328a6c1715cfe9496d2e1fafaee0d9215cfae
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.3

Security for batfish-q-mcp 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page