Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -399,3 +399,109 @@ Show all EVPN RT=2 routes for MAC address that starts with "1A:DC":
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
```

# MCP Server

`nornir-srl` includes an MCP (Model Context Protocol) server that exposes all `fcli` commands as tools, enabling LLM clients like Claude to query SRLinux network resources directly.

## Starting the MCP server

The MCP server is available via the `fcli-mcp` command. It requires either a Nornir config file (`--cfg`) or a containerlab topology file (`--topo-file`):

```bash
# Using a containerlab topology file (stdio transport - default)
fcli-mcp --topo-file lab.clab.yml

# Using a Nornir config file
fcli-mcp --cfg nornir_config.yaml

# Using SSE transport on a custom port
fcli-mcp --topo-file lab.clab.yml --transport sse --port 8080
```

### CLI options

| Option | Description | Default |
|--------|-------------|---------|
| `--cfg`, `-c` | Nornir YAML config file | _(required, mutually exclusive with `--topo-file`)_ |
| `--topo-file`, `-t` | Containerlab topology file | _(required, mutually exclusive with `--cfg`)_ |
| `--cert-file` | TLS certificate file (used with `--topo-file`) | `None` |
| `--transport` | MCP transport: `stdio`, `sse`, or `streamable-http` | `stdio` |
| `--host` | Bind address for network transports | `127.0.0.1` |
| `--port`, `-p` | Listen port for network transports | `8000` |
| `--auth-token` | Bearer token for client authentication | `None` |
| `--log-level`, `-l` | Logging level | `ERROR` |

### Authentication

Bearer-token authentication can be enabled for network transports (`sse` and `streamable-http`) via `--auth-token` or the `FCLI_MCP_AUTH_TOKEN` environment variable:

```bash
# Via CLI flag
fcli-mcp --topo-file lab.clab.yml --transport sse --auth-token my-secret-token

# Via environment variable
FCLI_MCP_AUTH_TOKEN=my-secret-token fcli-mcp --topo-file lab.clab.yml --transport sse
```

When enabled, clients must include the token in the `Authorization` header:

```
Authorization: Bearer my-secret-token
```

> **Note:** Authentication is only applicable to network transports (`sse` and `streamable-http`). It is ignored when using `stdio` transport.

## Adding to Claude

### Claude Code (claude mcp add)

To add the MCP server to [Claude Code](https://docs.anthropic.com/en/docs/claude-code), use the `claude mcp add` command with the `stdio` transport (default):

```bash
# With a containerlab topology file
claude mcp add fcli -- fcli-mcp --topo-file /path/to/lab.clab.yml

# With a Nornir config file
claude mcp add fcli -- fcli-mcp --cfg /path/to/nornir_config.yaml
```

### Claude Desktop

To add the MCP server to [Claude Desktop](https://claude.ai/download), add the following to your Claude Desktop configuration file (`claude_desktop_config.json`):

```json
{
"mcpServers": {
"fcli": {
"command": "fcli-mcp",
"args": ["--topo-file", "/path/to/lab.clab.yml"]
}
}
}
```

## Available MCP tools

The MCP server exposes the following 16 tools:

| Tool | Description |
|------|-------------|
| `bgp_peers` | BGP peers and their status |
| `sys_info` | System information |
| `subinterfaces` | Sub-interfaces |
| `lag` | LAG interfaces |
| `ipv4_rib` | IPv4 RIB entries |
| `ipv6_rib` | IPv6 RIB entries |
| `bgp_rib` | BGP RIB |
| `mac_table` | MAC table |
| `network_instances` | Network instances and interfaces |
| `lldp_neighbors` | LLDP neighbors |
| `irb_interfaces` | IRB interfaces |
| `ethernet_segments` | Ethernet segments |
| `es_destinations` | ES destinations |
| `vxlan_tunnels` | VXLAN tunnels |
| `arp_table` | ARP table |
| `ipv6_neighbors` | IPv6 neighbor discovery table |

All tools accept optional `inv_filter` and `field_filter` parameters as comma-separated `key=value` pairs (e.g. `"site=dc1,role=leaf"`).

28 changes: 18 additions & 10 deletions nornir_srl/connections/routing.py
Original file line number Diff line number Diff line change
Expand Up @@ -445,20 +445,25 @@ def get_rib(
"type": nh.get("type"),
"subinterface": nh.get("subinterface"),
}
if "resolving-tunnel" in nh:
indirect = nh.get("indirect", {})
resolving_tunnel = indirect.get(
"resolving-tunnel", nh.get("resolving-tunnel")
)
resolving_route = indirect.get(
"resolving-route", nh.get("resolving-route")
)
if resolving_tunnel:
tmp_map[nh["index"]].update(
{
"tunnel": (nh.get("resolving-tunnel")).get("tunnel-type")
"tunnel": resolving_tunnel.get("tunnel-type")
+ ":"
+ (nh.get("resolving-tunnel")).get("ip-prefix")
+ resolving_tunnel.get("ip-prefix")
}
)
if "resolving-route" in nh:
if resolving_route:
tmp_map[nh["index"]].update(
{
"resolving-route": (nh.get("resolving-route")).get(
"ip-prefix"
)
"resolving-route": resolving_route.get("ip-prefix")
}
)

Expand Down Expand Up @@ -528,7 +533,10 @@ def get_rib(
else:
nh_ni = ni["name"]
route["_next-hop"] = [
nh.get("ip-address")
nh.get("resolving-route") + " (indirect)"
if nh.get("type") == "indirect"
and nh.get("resolving-route")
else nh.get("ip-address")
for nh in nhgroup_mapping[nh_ni].get(
route["next-hop-group"], {}
)
Expand All @@ -554,8 +562,8 @@ def get_rib(
if nh.get("tunnel")
]
if len(route["_nh_itf"]) == 0:
resolving_routes = [
nh.get("resolving-route", {})
route["_nh_itf"] = [
nh.get("resolving-route")
for nh in nhgroup_mapping[nh_ni].get(
route["next-hop-group"], {}
)
Expand Down
Loading
Loading