Skip to main content
Glama
taitson

flexlm-mcp

by taitson
README.md
# flexlm-mcp

An [MCP](https://modelcontextprotocol.io) server that lets an AI agent read a **FlexLM / FlexNet** license server and tell you where you are wasting seats.

It wraps `lmutil lmstat`, parses the 1990s-style text into structured data, and runs a handful of opinionated checks: features that are saturated, **linger measured in days** (which quietly turns a floating pool into named licenses), the same user holding duplicate seats, and idle bundles sitting next to saturated ones. Every finding says what is happening, why it costs money, and what to do next.

Read-only. It never runs `lmremove`, `lmreread` or `lmdown`.

Built against Siemens (`ugslmd`) output; the grammar is FlexNet's, so Autodesk, Ansys, PTC, MathWorks and other FlexLM-licensed vendors parse the same way. Where a vendor prints something unusual, the line is returned in `unparsed_lines` instead of being guessed at.

## Why

A 13-seat bundle of engineering CAD licenses was permanently at 13/13. Engineers waited every morning. The obvious answer was "buy more seats."

One `lmstat -a` said otherwise:

```
alice WS-0101 ... start Wed 8/5 15:52  (linger: 7727858 / 8751540)
```

The two linger numbers are *remaining / total*, in seconds. `8751540 s` is **101 days**. Anyone who opened the application once kept a seat for three months, application closed or not. The pool was not full — it was *frozen*. Another seat was held twice by one person because their laptop appeared under two hostname spellings (`WS-0106` and `WS-106`). A sibling 20-seat bundle was 75% idle.

None of that shows on a utilization dashboard. It shows in the checkout lines, and nobody reads checkout lines — so this tool does.

## Tools

| Tool | What it answers |
|---|---|
| `flexlm_status` | Is the server up? Daemons, seats issued vs in use, saturated features, bundle occupancy. |
| `flexlm_list_features` | Features with issued / in use / free / utilization. Filter by substring (e.g. a bundle code). |
| `flexlm_feature_checkouts` | Who holds one feature, since when, linger total and remaining, days held. |
| `flexlm_user_sessions` | Everything one user holds across the server. |
| `flexlm_find_waste` | Saturation, long linger, duplicate seats, idle bundles — with severity and next action. |
| `flexlm_parse_lmstat` | Same analysis on pasted `lmstat -a` text. No server access needed. |

## Install

```bash
pip install flexlm-mcp        # or: pip install git+https://github.com/taitson/flexlm-mcp
```

You need `lmutil` (ships with every vendor's license server tools; for Siemens it is under `<NX install>\UGFLEXLM\lmutil.exe`).

Environment:

| Variable | Meaning |
|---|---|
| `LMUTIL_PATH` | Full path to `lmutil`. If unset, `lmutil` on `PATH`, then the usual Siemens folder. |
| `FLEXLM_DEFAULT_SERVER` | Default `port@host` when a tool call does not pass one. |
| `FLEXLM_TIMEOUT_S` | Seconds to wait for `lmstat` (default 60). |

### Claude Desktop

```json
{
  "mcpServers": {
    "flexlm": {
      "command": "flexlm-mcp",
      "env": {
        "LMUTIL_PATH": "C:\\Siemens\\NX2306\\UGFLEXLM\\lmutil.exe",
        "FLEXLM_DEFAULT_SERVER": "28000@licsrv01"
      }
    }
  }
}
```

### Claude Code

```bash
claude mcp add flexlm -e LMUTIL_PATH="C:\Siemens\NX2306\UGFLEXLM\lmutil.exe" -e FLEXLM_DEFAULT_SERVER=28000@licsrv01 -- flexlm-mcp
```

Then ask: *"Where are we wasting license capacity?"* or *"Who is holding NX93100 and for how long?"*

## Without an agent

The same analysis from the command line:

```bash
python -m flexlm_mcp waste 28000@licsrv01
python -m flexlm_mcp features 28000@licsrv01 --filter NX93100
python -m flexlm_mcp parse saved_lmstat.txt
```

Example output:

```
[HIGH  ] long_linger  CAD5000
         CAD5000: 10 of 10 seats have linger of up to 101 days
         Linger keeps a seat reserved after the application exits. At 101 days, anyone who opened
         the application once holds a seat for months whether they use it or not — the floating pool
         behaves like named licenses. With the feature saturated, this is capacity you already paid
         for and cannot use.
         → Ask the license administrator to set LINGER for this feature to minutes (e.g. LINGER
           <feature> 900 in the options file) or the vendor default, then restart the vendor daemon.

[HIGH  ] duplicate_seat  CAD5000
         CAD5000: frank holds 2 seats (WS-0106, WS-106)
         Same machine under two hostname spellings (leading zero, case or domain suffix). FlexLM
         counts them as two users.
         → Normalize the hostname on the machine and let the stale seat linger out, or have the
           administrator run lmremove for the stale handle.

[MEDIUM] idle_bundle  CAD3000
         CAD3000: 16/20 seats free while CAD5000 is saturated
```

## What the checks mean

**Saturated** — ≥ 90% of issued seats in use. Not a finding by itself; it is the reason to look at the next three.

**Long linger** — `LINGER` is a FlexLM option that keeps a seat reserved after the application exits, so a user can reconnect without losing it. Minutes are sensible. Days mean the seat count in `lmstat` has nothing to do with how many people are working. The fix is configuration, not purchase: `LINGER <feature> <seconds>` in the vendor options file, then `lmreread`.

**Duplicate seat** — one login holding two seats of one feature. Usually one machine under two hostname spellings (`WS-0106` vs `WS-106`, case, a domain suffix). The tool normalizes hostnames to spot it.

**Idle bundle** — vendors sell capability bundles (`NX93100`, `NX91110`…) and `lmstat` names features `BUNDLE_feature`. When one bundle is saturated and a sibling is mostly free, the answer may be moving users or re-allocating seats, not buying.

**Server down** — includes the classic VPN trap: `lmgrd` answers with its *hostname*, not its IP. A machine without corporate DNS connects to the port and then fails with `HOST_NOT_FOUND`. The finding says to check DNS first.

## Privacy

`lmstat` output contains user names and hostnames. The tool returns them to your agent because that is what "who is holding this seat" means; it stores nothing. Do not commit real captures to a repository — the test fixture here is synthetic.

## Development

```bash
pip install -e .
python -m unittest discover -s tests -v
```

The parser is pure functions over text (`flexlm_mcp/parser.py`); the checks are arithmetic over the parsed report (`flexlm_mcp/analysis.py`); running `lmutil` is isolated in `runner.py`. Add a vendor quirk by extending the fixture and the parser together.

## License

MIT.