Ryu MCP server
by maconair0
README.md
# Ryu MCP server
An MCP server for the [Ryu](https://github.com/faucetsdn/ryu) OpenFlow controller.
It lets an AI agent read an OpenFlow network — switches, links, hosts, flows,
port counters — and request flow changes, while keeping every change behind a
human approval gate. It was built as the **client-access edge** of a multi-domain
setup, where Ryu maps enterprise VLAN traffic onto the uplinks that hand off to
the packet core.
## Why the write tools do not write
The write tools validate a flow, put it on an approval queue on disk, and return
an `approval_id`. Nothing reaches Ryu until an operator approves it and the drain
step installs it:
```bash
python run_ryu_mcp.py --list-pending
python run_ryu_mcp.py --approve <id> # or --reject <id>
python run_ryu_mcp.py --drain # install approved flows
```
No tool takes an argument that skips this. A flag the model can set is a flag
the model will set. The single bypass, `RYU_MCP_WRITE_MODE=direct`, is chosen by
whoever starts the server — for a host system that already puts a person in
front of every change and would otherwise ask twice.
## Tools
| read tool | answers |
|---|---|
| `ryu_health_check` | is Ryu reachable, and are `ofctl_rest` and `rest_topology` loaded |
| `ryu_get_topology` | switches with their ports, links between them, discovered hosts |
| `ryu_list_switches` | connected datapaths |
| `ryu_get_switch` | one switch's description and port states |
| `ryu_get_flows` | installed flows with packet counters |
| `ryu_get_port_stats` | per-port packet, error and drop counters |
| write tool | queues |
|---|---|
| `ryu_add_flow` | one flow (`/stats/flowentry/add`) |
| `ryu_delete_flow` | removal of one exact flow (`/stats/flowentry/delete_strict`) |
| `ryu_map_vlan` | a client port onto a VLAN on an uplink, both directions |
Every tool returns JSON with `ok`. A refusal or an unreachable Ryu comes back as
data, not as an exception.
## Install
```bash
git clone https://github.com/maconair0/ryu-mcp-server.git
cd ryu-mcp-server
python -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt
```
Python 3.10+. Ryu itself does not need to be installed alongside this server;
it is reached over REST.
## Configuration
| variable | default | notes |
|---|---|---|
| `RYU_BASE_URL` | — | e.g. `http://127.0.0.1:8080`; overrides host/port |
| `RYU_HOST` / `RYU_PORT` | `127.0.0.1` / `8080` | Ryu's `--wsapi-port` |
| `RYU_TIMEOUT` | `20` | seconds |
| `RYU_MCP_WRITE_MODE` | `queue` | `queue` or `direct` |
| `RYU_STATE_DIR` | `ryu_mcp/state/` | approval queue and audit log |
| `RYU_MCP_HOST` / `RYU_MCP_PORT` | `127.0.0.1` / `3005` | this server's endpoint |
## Running
```bash
python run_ryu_mcp.py --check # is Ryu there?
python run_ryu_mcp.py --ryu-url http://127.0.0.1:8080 # serve over SSE
python run_ryu_mcp.py --transport stdio
```
Ryu must run with the REST apps this server uses:
```bash
ryu-manager --observe-links ryu.app.ofctl_rest ryu.app.rest_topology
```
`--observe-links` is what makes `rest_topology` report links; without it the
link list is always empty.
## A lab to test against
`lab/` builds one container with Ryu and Mininet. Ryu does not install on
Python 3.12, and Mininet needs root and Open vSwitch, so a container is the
clean way to run both:
```bash
docker build -t ryu-lab lab/
docker run -d --name ryu-lab --privileged \
-p 8080:8080 -p 6653:6653 -v /lib/modules:/lib/modules:ro ryu-lab
```
It starts two edge switches, each with two enterprise hosts and an uplink:
```
h1 (10.10.0.1) ─┐ ┌─ h3 (10.10.0.2)
s1 ── port 3 → core1 s2 ── port 3 → core2
h2 (10.20.0.1) ─┘ (0x101) (0x102) └─ h4 (10.20.0.2)
```
The edge switches are not linked to each other: traffic between the sites has to
cross the core, which is another controller's domain. `core1` and `core2` stand
in for that handover. No forwarding app runs, so nothing passes until flows are
installed — which is what makes a provisioning request observable.
Mapping a site's client port onto the core uplink:
```
ryu_map_vlan(dpid="0000000000000101", client_port=1, uplink_port=3, vlan_id=10)
```
## Notes
- **A switch has two names.** `rest_topology` reports dpids as 16-digit hex
(`0000000000000101`); `ofctl_rest` takes them as decimal (`257`). Every tool
accepts either.
- **A VLAN match needs the present bit.** Under OpenFlow 1.3 a tagged VID is
matched as `vid | 0x1000`. A bare `10` matches nothing and installs without
complaint. `ryu_map_vlan` sets it.
- **Ryu ignores match fields it does not know.** A typo like `in_prt` is dropped
and the flow matches more than intended, so match fields and action types are
validated before anything is queued.
- **Ryu's own flows are hidden.** The table-miss and LLDP punt flows it installs
are not anybody's configuration; `include_controller_defaults=true` shows them.
## Tests
```bash
python -m unittest discover -s tests -t .
```
No Ryu or network needed; the REST API is dummy. Includes tests that a queued or
rejected write leaves Ryu untouched.
## Licence
Apache 2.0. See [LICENSE](LICENSE).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues