Skip to main content

fcli

ci PyPI

What an SR Linux fabric looks like, what is wrong with it, and what changed, straight from gNMI.

This is a ground-up rewrite of fcli. It runs on asyncio throughout, uses no Nornir and makes no blocking gNMI calls.

uv tool install 'fcling[server,mcp,ai]'                    # the `fcling` command, from PyPI
docker run --rm --network host -v "$PWD:/work" ghcr.io/wdesmedt/fcling -t lab.clab.yml bgp-peers

Or from a checkout:

uv sync
uv run fcling -t lab.clab.yml bgp-peers
uv run fcling -t lab.clab.yml -i role=leaf -o json subif -f oper=down
uv run fcling -c inventory.yml sys-info
uv run fcling -t lab.clab.yml ipv4-rib -a 10.1.3.99          # longest-prefix match
uv run fcling -t lab.clab.yml bgp-rib -r evpn -t 5 -d        # with the detail columns
uv run fcling -t lab.clab.yml ifstats -s 2 -f oper=up        # rates over 2 s

With the engine running, the same commands answer instantly from what it streams, and can look back:

uv sync --extra server
uv run fcling -t lab.clab.yml serve                          # streams every node, keeps history
export FCLI_SERVER=http://127.0.0.1:8765
uv run fcling bgp-peers                                      # live, from the engine
uv run fcling bgp-peers --at 14:02                           # as it was then
uv run fcling history --since 1h -r bgp-peers --follow       # what changed, which commit did it, and keeps watching

What is wrong, and what changed:

uv run fcling checks                          # every finding, check by check
uv run fcling incidents -v                    # grouped by root cause, worst first
uv run fcling incidents --at 14:02            # what was wrong then
uv run fcling baseline pre-change -m "before the MTU change"
uv run fcling drift pre-change                # what came, went and changed since
uv run fcling config-diff leaf1 12             # what commit 12 did to leaf1's configuration, side by side

Ask, or let your own AI client ask. Both get the same 14 read-only tools: status, reports, incidents, checks, history, configuration diffs, drift, baselines, the lenses and the topology.

uv sync --extra server --extra mcp --extra ai
uv run fcling ask "what is wrong, and what changed just before?"        # the first provider with a key
uv run fcling ask --provider openai --effort low "is BGP healthy?"
claude mcp add --transport http fcli http://127.0.0.1:8765/mcp        # MCP over HTTP from `fcling serve`
claude mcp add fcli -- fcling --server http://127.0.0.1:8765 mcp        # or over stdio

Questions across the whole fabric, and its shape:

uv run fcling -t lab.clab.yml where 10.1.3.13                        # every place that knows an IP or MAC
uv run fcling -t lab.clab.yml path leaf1 10.1.3.13 --ni ipvrf-1      # hop by hop, over ECMP and VXLAN
uv run fcling -t lab.clab.yml service macvrf-102                     # one service on every node, and what disagrees
uv run fcling -t lab.clab.yml endpoints                              # every attached host
uv run fcling -t lab.clab.yml topology                               # tiers, cables, clients, segments

checks and incidents work without a server too: one-shot from the nodes, or from the local history with --at. They exit 1 when there is an error, so they can gate a pipeline.

Reports: sys-info, config-commits, lldp, subif, lag, ifstats, ni, bgp-peers, bgp-rib, ipv4-rib, ipv6-rib, tunnel-table, static-routes, routing-pol, mac, arp, nd, irb, es, es-dest, vxlan, bfd, isis, ospf, resources, components, transceivers, services, bridge-domains, routers. Each is a pure function from gNMI paths to typed records.

Documentation

Installation and inventory installing, containerlab and static inventories, selecting nodes, TLS
Reports every report, filters, output formats
The web UI topology, incidents, history, lenses, reports and the agent in a browser
The engine fcling serve: how it streams, the HTTP API and its events, security
History what is kept, --at, commit attribution, baselines and drift
Lenses and topology where, path, service, endpoints, topology, adding a lens
Checks and incidents every check, and how findings are grouped by root cause
MCP and the agent the tools, MCP setup, fcling ask, Claude, OpenAI and Grok
Architecture how it fits together, adding a report or a check, the tests

Architecture

inventory ──► collect ──► gNMI Get (one per datatype, all nodes at once)
                              │
                              ▼
                        Tree (per node)  ◄── later: gNMI Subscribe, same tree
                              │
                              ▼
            report: pure function Tree → typed records
                              │
                              ▼
                 render: table · json · yaml · csv
Module Role
gnmi/client.py grpc.aio client for Capabilities, Get and Subscribe. TLS can be verified, trust-on-first-use (skip_verify) or plaintext. Errors are worded around the node.
gnmi/path.py Paths as hashable values, parsed once. Module prefixes are dropped.
tree.py A node's state as plain JSON. Get payloads and subscription leaves merge into it, with list entries matched by key.
report.py @report(name, reads=[Read(path, datatype, mode)], record=..., columns=...). A report declares its paths and never does I/O.
follow Optional pure function (tree, params) -> reads for dependent reads: the next-hop-groups the routes name, the BGP attribute sets of the paths shown. The collector calls it again until it asks for nothing new. It fetches by key, or the whole table above a limit.
Param An argument that shapes what a report builds (ipv4-rib -a <address>, bgp-rib --paths all). The CLI turns each one into an option.
reports/ The report definitions. Records are flat, frozen pydantic models: one record is one row.
collect.py One-shot engine. Each node is one task, and a node fails on its own. A path a node does not know reads as empty.
inventory.py Loads a containerlab topology or a static YAML inventory, with key=glob filters.
engine.py Long-running engine. ON_CHANGE reads are streamed in Subscribe RPCs of at most 36 paths, the most SR Linux accepts, each with a heartbeat. SAMPLE reads (counters) are polled, and each polled subtree is replaced whole: a SAMPLE stream never says what is gone. follow reads are fetched by key. A reconnect resyncs into a fresh tree. An update only re-runs the reports whose paths it overlaps.
delta.py Changes record by record, matched on each record's key. A change confined to volatile fields (counters, rates) is pushed live but not kept.
store.py SQLite: an append-only change log plus the current state. A restart diffs against it, and the state at any instant is a fold back from now.
checks.py 19 fabric checks: pure functions of a Fabric (every node's records) to Finding records. In the engine, findings are a report like any other, so each one raised or cleared is history.
incidents.py Findings anchored to what they are about (a cable, a session, a node, the underlay between two nodes) and grouped by root cause. The same cause in many places folds into one pattern.
views.py One place for fabric-wide answers from any source (the nodes now, the engine, the history at an instant): checks, incidents, drift, and history attributed to the configuration commit that preceded it.
service.py The questions every surface asks, behind one interface with three implementations: the engine in this process, a running server over HTTP, or the nodes one-shot.
tools.py 14 read-only tools over a Service. Inputs are pydantic models that are validated before anything runs; outputs are compact JSON with row limits. One definition serves both MCP clients and the agent.
mcp.py The tools as an MCP server: streamable HTTP at /mcp on fcling serve (stateless, with DNS-rebinding protection on localhost), or stdio via fcling mcp.
agent.py The in-app agent, provider-neutral: streaming, concurrent tool calls, and append-only conversations kept server-side in the provider's own format.
llm.py The providers. Claude (Anthropic SDK): adaptive thinking, cached prompt and tools, server-side refusal fallback, claude-opus-5-5 at medium. OpenAI (Responses API: store off, reasoning replayed encrypted), gpt-5.6-sol. OPENAI_API=chat switches to Chat Completions for proxies. Grok (Chat Completions at api.x.ai), grok-4.6. The provider is FCLI_LLM_PROVIDER or the first one with a key, Claude first; model and effort come from FCLI_MODEL / FCLI_EFFORT or each provider's own variables.
server.py Starlette: /api/status, /api/reports[/{name}] (live, ?at= or collected on the spot), /api/history, /api/events (SSE, resumable with Last-Event-ID). Bound to localhost; a bearer token when it isn't.

Because a report only declares paths, the same definition can be fed by a one-shot Get (CLI), by a STREAM subscription (server) or by a recording (tests). Its subscription plan is static data that can be checked without a device.

Static inventory

defaults:
  username: admin
  password: NokiaSrl1!
  tls: {ca_file: ca.pem}      # or {skip_verify: true} / {insecure: true}
nodes:
  leaf1: {address: 10.0.0.11, labels: {role: leaf}}
  spine1: {address: 10.0.0.21, labels: {role: spine}}

Tests

make test lint

tests/fixtures/releases holds real gNMI recordings from the original fcli for SR Linux 25.3 to 26.7. test_release_matrix.py replays them through the new reports and requires row-for-row equality with what the original rendered. fake_gnmi.py is an in-process gNMI target, so the client, TLS, the collector and the CLI are tested without a lab.

test_engine.py and test_server.py stream from that same fake target. They cover the initial sync, refused paths, live changes and deletes, history and state_at, and SSE replay over a real uvicorn.

The gNMI stubs are generated from the openconfig protos (make proto) and committed.

Metadata

Release files for fcling 0.4.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 fcling 0.4.0
File Size Uploaded Provenance
fcling-0.4.0.tar.gz 2.7 MB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for fcling 0.4.0
File Interpreter ABI Platform
fcling-0.4.0-py3-none-any.whl Python 3 none any Details

Total release size: 3.0 MB

Release files / fcling-0.4.0.tar.gz

Download URL fcling-0.4.0.tar.gz
Size 2.7 MB
Tags Source
SHA-256 checksum
How to use checksums
468d8fac8e025856adfc94ed6d95bdff4511d44b50058be331d37a86c3fbe498
BLAKE2b-256 checksum
How to use checksums
0d8f6c074a7efabe4b50fd7472e056b0d0149e1ed85778d766782736968c9eaa
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.

Transparency log

Release files / fcling-0.4.0-py3-none-any.whl

Download URL fcling-0.4.0-py3-none-any.whl
Size 289.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5eff7ace916b4403150539d6dbc4b3d340efbc9276409567e5067e5425be88c2
BLAKE2b-256 checksum
How to use checksums
8de49694f26123d6085354bc44baa955ca51284404517709c338321717f10395
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.

Transparency log

Security for fcling 0.4.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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for fcling-0.4.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.

Transparency log

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.

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