mcp-eveng

# mcp-eveng
[](https://github.com/madmickstar/mcp_eveng/actions/workflows/ci.yml)
[](LICENSE)
[](pyproject.toml)
A [Model Context Protocol](https://modelcontextprotocol.io) server that lets LLM
clients (Claude Desktop, Claude Code, or any other MCP host) drive an
[EVENG](https://www.eve-ng.net/) network emulator instance: create and edit labs,
add/wire nodes and networks, start/stop/wipe devices, and browse templates,
folders and users — all through the EVENG REST API.
## Table of contents
- [Features](#features)
- [Installation](#installation)
- [Upgrading](#upgrading)
- [Capture relay](#capture-relay)
- [Run App](#run-app)
- [Configuration](#configuration)
- [EVENG connection](#eveng-connection-always-used-regardless-of-transport)
- [MCP network settings](#mcp-network-settings-only-used-with---sse-or---http)
- [`MCP_LOG_LEVEL`](#mcp_log_level-options-and-where-logs-go)
- [`MCP_ALLOWED_HOSTS`](#mcp_allowed_hosts-dns-rebinding-protection)
- [`MCP_STATEFUL`](#mcp_stateful-session-persistence-across-restarts)
- [`MCP_API_KEY` and `MCP_TLS_*`](#mcp_api_key-and-mcp_tls_-optional-extra-security)
- [EVE-NG Pro vs Community MCP tools](#eve-ng-pro-vs-community-mcp-tools)
- [Available MCP tools](#available-mcp-tools)
- [Controlling which MCP tools are exposed](#controlling-which-mcp-tools-are-exposed)
- [Project layout](#project-layout)
- [Troubleshooting](#troubleshooting)
- [Manual curl commands](#manual-curl-commands)
- [A note on sessions and relogin](#a-note-on-sessions-and-relogin)
- [Known issues](#known-issues)
- [Development](#development)
- [Why `mcp` is pinned below `2.0`](#why-mcp-is-pinned-below-20)
- [License](#license)
- [Tested versions](#tested-versions)
## Features
- All three MCP transports: `stdio`, `--sse`, `--http`
- Full coverage of the EVE-NG REST API
- HTTP/HTTPS and API key support
- 47 tools to manage your EVE-NG labs
- Bulk edits across many nodes at once
- Stream Wireshark captures to a local Wireshark
- Adjust link quality settings
- Supports both Community and PRO editions
## Installation
This project is **not published on PyPI** — install directly from a git
clone:
```bash
git clone https://github.com/madmickstar/mcp_eveng.git
cd mcp_eveng
pip install -e .
```
- **[Linux / macOS install & running guide](docs/install-linux.md)**
- **[Windows install & running guide](docs/install-windows.md)**
## Upgrading
**[Upgrading guide](docs/upgrading.md)** — updating an existing
`mcp-eveng` and/or `mcp-relay` install.
## Capture relay
Stream Wireshark capture to a local Wireshark without a personal
SSH+sudo account on the EVE-NG host. Limited to EVE-NG PRO only.
**[Capture relay guide](docs/capture-relay.md)**
## Run App
```bash
python -m mcp_eveng # stdio mode (default)
python -m mcp_eveng --sse # sse mode
python -m mcp_eveng --http # streamable-http mode
```
`--sse` and `--http` rely on variables configured in your `.env` file —
see `.env.example`.
## Configuration
Copy `.env.example` to `.env` and fill in your EVENG server details:
```bash
cp .env.example .env
```
### EVENG connection (always used, regardless of transport)
| Variable | Default | Description |
| --- | --- | --- |
| `EVENG_HOST` | `127.0.0.1` | EVENG server IP or hostname — **no scheme or port**, those are separate variables below |
| `EVENG_PORT` | `443` | EVENG server port |
| `EVENG_PROTOCOL` | `https` | `http` or `https` |
| `EVENG_USERNAME` | `admin` | Login username |
| `EVENG_PASSWORD` | `eve` | Login password |
| `EVENG_HTML5` | `-1` | EVENG `html5` login flag (`-1` auto, `0` Pro/HTML5-only, `1` native console) |
| `EVENG_VERIFY_SSL` | `false` | Verify TLS certs. Default is `false`, since EVE-NG (especially Pro) commonly uses a self-signed HTTPS cert; set to `true` if your server has a valid cert |
| `EVENG_TIMEOUT_SECONDS` | `30` | HTTP request timeout |
Since `EVENG_HOST` is an IP/hostname only, always set `EVENG_PORT` and
`EVENG_PROTOCOL` explicitly to match your deployment rather than relying on
the https/443 defaults.
### MCP network settings (only used with `--sse` or `--http`)
| Variable | Default | Description |
| --- | --- | --- |
| `MCP_HOST` | `127.0.0.1` | Bind host |
| `MCP_PORT` | `8000` | Bind port |
| `MCP_HTTP_PATH` | `/mcp` | Mount path for the Streamable HTTP app (`--http`) |
| `MCP_SSE_PATH` | `/sse` | Mount path for the legacy SSE app (`--sse`) |
| `MCP_LOG_LEVEL` | `INFO` | `DEBUG`, `INFO`, `WARNING`, `ERROR`, or `CRITICAL` |
| `MCP_ALLOWED_HOSTS` | `localhost:*` | Comma-separated Host-header allowlist. **Required** when `MCP_HOST` is not a loopback address |
| `MCP_STATEFUL` | `true` | `false` disables streamable-http session persistence |
| `MCP_API_KEY` | unset | If set, every request needs `Authorization: Bearer <key>` or gets a 401 |
| `MCP_TLS_CERT_PATH` | unset | TLS certificate file. Serves HTTPS instead of plain HTTP when set together with `MCP_TLS_KEY_PATH` |
| `MCP_TLS_KEY_PATH` | unset | TLS certificate's private key file. Required together with `MCP_TLS_CERT_PATH` |
| `MCP_TLS_KEY_PASSWORD` | unset | Only needed if the private key above is itself password-protected |
| `MCP_LOG_FILE_ENABLED` | `false` | Also write logs to a rotating file under `MCP_LOG_DIR`, in addition to stderr |
| `MCP_LOG_DIR` | `log` | Directory for rotating log files (created automatically). Only used when `MCP_LOG_FILE_ENABLED` is true |
| `MCP_LOG_MAX_MB` | `10` | Roll over to a new log file once the current one reaches this many megabytes |
| `MCP_LOG_BACKUP_COUNT` | `5` | Number of rolled-over log files to keep before the oldest is deleted |
All variables can also be set as real environment variables, which take
precedence over `.env`.
`MCP_TOOLS_CONFIG_PATH` (default `tools.env`) and the four `MCP_LOG_FILE_*`
variables apply to **every** transport, including stdio — they aren't
scoped to `--sse`/`--http` like the rest of this table, since tool
registration and logging setup both happen before transport is even
dispatched on. See "Controlling which MCP tools are exposed" below and
"`MCP_LOG_LEVEL`: options and where logs go" just below that.
#### `MCP_LOG_LEVEL`: options and where logs go
Options are the standard Python logging levels — `DEBUG`, `INFO`, `WARNING`,
`ERROR`, `CRITICAL` (case-insensitive; an invalid value fails fast at
startup). Logs always go to **stderr**, never stdout, in every transport —
not just stdio — because stdout is reserved for the stdio JSON-RPC stream
and nothing else should ever print to it. Every log line's own leading
timestamp is ISO-8601 with milliseconds: `Z` for UTC, or a `+HH:MM`/
`-HH:MM` offset for the server process's local timezone otherwise — e.g.
`2026-09-11T19:31:00.001Z INFO mcp_eveng: Starting mcp-eveng with
transport=stdio`.
**Every tool call is logged** at `INFO` as key=value fields — `status`
(`call`, `finished`, or `error`), the tool name, the connecting client's
address, and (on the initial `call` line) the JSON arguments the client
passed, with sensitive-looking values redacted (see below), or (on the
matching `finished`/`error` follow-up line) how long the call took and,
for `error`, the error message. This is on unconditionally; there's no
separate toggle for it, since it's the main thing worth logging in the
first place. Example:
```
2026-09-11T19:31:00.001Z INFO mcp_eveng.tool_calls: status=call tool=get_lab client=192.168.1.50:54321 arguments={"path": "/my-lab.unl"}
2026-09-11T19:31:00.043Z INFO mcp_eveng.tool_calls: status=finished tool=get_lab client=192.168.1.50:54321 duration_ms=42.1
```
There's no separate `timestamp=` field in the message body — the line's
own leading timestamp above already covers it. `client` is
`host:port` of the connecting
client for `--sse`/`--http` (taken from the underlying HTTP connection),
or `stdio` when running over stdio — a local subprocess pipe genuinely has
no network client address to report.
**Sensitive argument values are redacted before logging** — any argument
whose name looks like a password, secret, token, API key, or credential
(matched case-insensitively, e.g. `password`, `rdp_password`, `api_key`)
is logged as `***REDACTED***` instead of its real value; the argument name
itself still appears, so you can see *that* a password was being changed,
just not what to. See `_SENSITIVE_ARGUMENT_SUFFIXES` in `tool_logging.py`
if you ever need to extend the list for a new argument.
**Optional rotating log file:** set `MCP_LOG_FILE_ENABLED="true"` to also
write everything to a rotating file (`mcp-eveng.log` inside `MCP_LOG_DIR`,
default `log/` — created automatically), on top of stderr, which is always
used regardless of this setting. `MCP_LOG_MAX_MB` (default `10`) controls
when it rolls over to `mcp-eveng.log.1`, `.2`, etc.; `MCP_LOG_BACKUP_COUNT`
(default `5`) controls how many rolled-over files are kept before the
oldest is deleted. If you'd rather redirect stderr yourself instead
(e.g. `mcp-eveng --http 2>> mcp-eveng.log`), that still works too — the two
approaches aren't mutually exclusive, though most people will want one or
the other, not both.
#### `MCP_ALLOWED_HOSTS`: DNS-rebinding protection
The `mcp` SDK validates the HTTP `Host` header on `--sse`/`--http` requests to
guard against DNS-rebinding attacks ([`TransportSecuritySettings`](https://github.com/modelcontextprotocol/python-sdk)).
The default, `localhost:*`, matches the default loopback bind host
(`MCP_HOST=127.0.0.1`) for local use out of the box. When `MCP_HOST` is
anything else (e.g. `0.0.0.0` to bind all interfaces), update
`MCP_ALLOWED_HOSTS` to match — since it now has a non-empty default,
`mcp-eveng` no longer refuses to start if you forget; it starts, but then
rejects every request at runtime with a Host-header mismatch, which is a
more confusing failure to debug than a startup error. See
[Troubleshooting](#troubleshooting) if requests are being rejected
unexpectedly.
Format is a comma-separated list of `host:port` or `host:*` (any port)
entries, matching the SDK's native `allowed_hosts` syntax:
```bash
MCP_ALLOWED_HOSTS="localhost:*,192.168.10.100:*"
```
#### `MCP_STATEFUL`: session persistence across restarts
Streamable HTTP is stateful by default (`stateless_http=False` in the SDK):
each client gets a session id tied to server-side state. If you restart the
server, clients that already negotiated a session can be left holding a
session id the server no longer recognizes. Set `MCP_STATEFUL=false` to run
with `stateless_http=True` instead, which drops session persistence so a
restart never confuses connected clients — useful for `--http` deployments
that get redeployed/restarted regularly. This is a real SDK feature
(`FastMCP(..., stateless_http=...)`), not a workaround.
#### `MCP_API_KEY` and `MCP_TLS_*`: optional extra security
Neither is required — `MCP_ALLOWED_HOSTS` above is the only thing this
server enforces by default. Both are opt-in for anyone who wants more than
that, e.g. a `--http` deployment reachable beyond localhost.
`MCP_API_KEY`, if set, requires every `--sse`/`--http` request to present
it via `Authorization: Bearer <key>`, or the request gets a `401` before
it ever reaches the MCP handler.
`MCP_TLS_CERT_PATH`/`MCP_TLS_KEY_PATH` (both required together, or leave
both unset) serve `--sse`/`--http` over HTTPS instead of plain HTTP.
`MCP_TLS_KEY_PASSWORD` is only needed if the private key file itself is
encrypted. Requires this server's own certificate to be one the *client*
trusts.
If `MCP_TLS_CERT_PATH` points at a certificate file inclusive of
certificate chain, the server certificate must come first, with the CA
certificate below it — this is a universal PEM chain-file convention
(the same order Apache/nginx/every OpenSSL-based server expects), not
specific to this project. See `docs/tools-reference.md` for the full
detail on both settings, including request/response examples.
For running the server and configuring it in Claude Desktop / Claude Code
(both stdio and streamable-http), see the
**[Linux/macOS](docs/install-linux.md)** or
**[Windows](docs/install-windows.md)** guide — the exact commands and
JSON differ enough between platforms (path syntax, shell env-var syntax,
and how each OS handles `PATH` for GUI-launched subprocesses) that they're
kept there rather than duplicated here.
## EVE-NG Pro vs Community MCP tools
EVE-NG's REST API has no explicit "edition" field, but the version string
`get_status` returns carries a `-PRO` suffix on PRO servers (confirmed
live: `6.5.0-27-PRO`); plain Community builds don't have it (confirmed
live: `6.2.0-4`). This is the only reliable signal for which edition a
server is running, and it's what every edition-aware behavior below
derives from (`edition.is_pro_edition`). An unrecognized or missing
version string is treated as Community, the more conservative assumption.
Five tools genuinely behave differently by edition — confirmed against
EVE-NG's own official [features-compare page](https://www.eve-ng.net/index.php/features-compare/),
live testing, or both:
- `connect_interface` — Pro and Community versions both support this MCP tool. Community version requires nodes to be stopped; Pro does not.
- `export_node` — Pro only.
- `share_lab` — Pro only.
- `set_link_quality` / `get_link_quality` — Pro only.
- `list_captures` / `get_capture` — Pro only.
## Available MCP tools
Tool names have no prefix (`get_status`, not `eveng_get_status`) — be aware
this means a name could collide with another MCP server's tool if you ever
connect more than one server with overlapping names to the same client.
**Comm Eve**/**Pro Eve**: which EVE-NG edition(s) support the tool — see
"EVE-NG Pro vs Community MCP tools" above for how edition is detected
and why these six specifically differ.
| Area | Tool | Description | Comm Eve | Pro Eve |
| --- | --- | --- | --- | --- |
| System | `get_status` | Reports EVE-NG server status and version. | ✅ | ✅ |
| | `list_node_templates` | Lists available node templates. | ✅ | ✅ |
| | `get_node_template` | Gets details for a single node template, including its images. | ✅ | ✅ |
| | `list_network_types` | Lists valid network types (bridge, cloud/pnetX, etc.). | ✅ | ✅ |
| | `list_user_roles` | Lists available user roles. Disabled by default. | ✅ | ✅ |
| Server introspection | `list_tools` | Lists the tools published by the MCP server. | ✅ | ✅ |
| Folders | `list_folder` | Lists the contents of a folder. | ✅ | ✅ |
| | `add_folder` | Creates a new folder. | ✅ | ✅ |
| | `move_folder` | Moves or renames a folder. | ✅ | ✅ |
| | `delete_folder` | Deletes a folder. Requires user confirmation before it does anything. | ✅ | ✅ |
| Users | `list_users` | Lists user accounts. Disabled by default. | ✅ | ✅ |
| | `get_user` | Gets details for a single user. Disabled by default. | ✅ | ✅ |
| | `add_user` | Creates a new user account. Disabled by default. | ✅ | ✅ |
| | `edit_user` | Edits an existing user account. Disabled by default. | ✅ | ✅ |
| | `delete_user` | Deletes a user account. Requires user confirmation before it does anything. Disabled by default. | ✅ | ✅ |
| Labs | `get_lab` | Gets metadata for a lab. | ✅ | ✅ |
| | `open_lab` | Looks up a lab and reports its lock status. | ✅ | ✅ |
| | `create_lab` | Creates a new lab. | ✅ | ✅ |
| | `edit_lab` | Edits a lab's metadata. | ✅ | ✅ |
| | `share_lab` | Shares a lab with one or more users. | | ✅ |
| | `move_lab` | Moves a lab to a different folder. | ✅ | ✅ |
| | `delete_lab` | Deletes a lab. Requires user confirmation before it does anything. Disabled by default. | ✅ | ✅ |
| | `get_lab_topology` | Gets a lab's node/network topology. | ✅ | ✅ |
| | `get_lab_links` | Gets a lab's link (interface) mappings. | ✅ | ✅ |
| | `list_lab_pictures` | Lists background pictures placed in a lab. | ✅ | ✅ |
| | `list_labs` | Recursively lists every lab under a folder. | ✅ | ✅ |
| Networks | `list_lab_networks` | Lists networks in a lab. | ✅ | ✅ |
| | `add_lab_network` | Adds a network to a lab. | ✅ | ✅ |
| | `edit_lab_network` | Edits an existing network. | ✅ | ✅ |
| | `delete_lab_network` | Deletes a network. Requires user confirmation before it does anything. | ✅ | ✅ |
| Nodes | `list_lab_nodes` | Lists nodes in a lab. | ✅ | ✅ |
| | `add_lab_node` | Adds a node to a lab. | ✅ | ✅ |
| | `edit_lab_node` | Edits an existing node. | ✅ | ✅ |
| | `change_node_delay` | Changes a node's startup delay, one node or in bulk. | ✅ | ✅ |
| | `edit_lab_nodes_by_template` | Bulk-edits interfaces/cpu/memory/icon/image across nodes sharing a template. | ✅ | ✅ |
| | `delete_lab_node` | Deletes a node. Requires user confirmation before it does anything. | ✅ | ✅ |
| | `get_node_interfaces` | Gets a node's interfaces and what they're wired to. | ✅ | ✅ |
| | `connect_interface` | Wires a node's interface to another node or to a network. | ✅ | ✅ |
| | `start_node` | Starts a node, or every node in a lab. | ✅ | ✅ |
| | `stop_node` | Stops a node, or every node in a lab. | ✅ | ✅ |
| | `wipe_node` | Wipes a node's saved configuration. Wiping *every* node needs `node_id="all"` plus a `confirm=true` follow-up call -- an omitted `node_id` is rejected rather than defaulting to "wipe everything". | ✅ | ✅ |
| | `export_node` | Exports a node's running configuration. | | ✅ |
| | `set_link_quality` | Sets per-connection delay/jitter/packet-loss/bandwidth. | | ✅ |
| | `get_link_quality` | Gets current delay/jitter/packet-loss/bandwidth on both sides of a connection. | | ✅ |
| Live console access | `telnet_node` | Sends CLI commands to a running node's console over telnet. | ✅ | ✅ |
| Capture relay | `list_captures` | Lists running Wireshark capture containers. Disabled by default. | | ✅ |
| | `get_capture` | Mints a one-time URL to stream a capture to a local Wireshark. Disabled by default. | | ✅ |
"Disabled by default" tools: see "Controlling which MCP tools are exposed"
below for how to turn them on. "Requires user confirmation" tools: see
`docs/tools-reference.md` for the search → select → confirm flow they
each go through before anything is deleted.
More detailed information about each tool — confirmed EVE-NG quirks,
design reasoning, and non-obvious behavior — can be found in
**[docs/tools-reference.md](docs/tools-reference.md)**.
## Controlling which MCP tools are exposed
Every tool can be individually enabled or disabled, via a dedicated
dotenv-syntax config file — kept separate from the main `.env` so tool
visibility is easy to review and diff independently of connection
settings. Copy **`tools.env.pro.example`** (PRO edition) or
**`tools.env.comm.example`** (Community edition) to `tools.env` (or point
`MCP_TOOLS_CONFIG_PATH` at wherever you keep it) and set any tool to
`enabled` or `disabled`:
```
get_status=enabled
list_users=disabled
```
See `docs/tools-reference.md` for the full detail — how the two example
files differ, what's enabled by default, and how `list_tools` reflects
`tools.env` exactly.
## Project layout
```
mcp-eveng/
├── src/mcp_eveng/
│ ├── client.py # async EVENG REST API client (incl. list_all_labs recursion helper)
│ ├── config.py # pydantic-settings, reads .env
│ ├── confirmation.py # shared search/select/confirm state machine for deletes
│ ├── dependencies.py # shared client singleton
│ ├── edition.py # PRO vs Community detection, shared by all edition-gated tools
│ ├── exceptions.py
│ ├── search.py # case-insensitive record search (used by delete tools)
│ ├── telnet.py # raw asyncio telnet client (IAC handling) for telnet_node
│ ├── tool_config.py # per-tool enable/disable config loader (tools.env)
│ ├── vendor.py # best-effort vendor extraction + image-availability check
│ ├── server.py # FastMCP assembly + transport security/statefulness/API key/TLS
│ ├── __main__.py # CLI: --sse / --http flags
│ ├── tools/ # one module per API area
│ └── capture_relay/ # standalone mcp-relay service (own entrypoint, own config,
│ # shares this same venv and .env -- see docs/capture-relay.md)
├── systemd/
│ ├── mcp-eveng.service # ready-to-use unit for the main MCP server
│ └── mcp-relay.service # ready-to-use unit for the standalone capture-relay service
├── scripts/
│ └── eve-capture.bat # Windows capture:// protocol handler companion
├── tests/
│ ├── conftest.py
│ ├── test_*.py
│ ├── tools/
│ └── capture_relay/
├── docs/
│ ├── install-linux.md # Linux/macOS install, running, Claude Desktop JSON
│ ├── install-windows.md # Windows install, running, Claude Desktop JSON
│ ├── capture-relay.md # full capture-relay setup guide
│ ├── upgrading.md # updating an existing install
│ ├── manual-curl-commands.md # testing the server directly over HTTP
│ └── tools-reference.md # detailed per-tool design notes (see "Available MCP tools")
├── assets/
│ └── banner.png
├── .env.example # shared config for both mcp-eveng and mcp-relay -- copy to .env
├── tools.env.pro.example # per-tool enable/disable config, PRO -- copy to tools.env
├── tools.env.comm.example # same, Community edition (disables 2 PRO-only tools)
└── .github/workflows/ # CI + PyPI publish
```
## Troubleshooting
**A tool call fails with `500 Internal Server Error` and no useful
message** — `mcp-eveng` itself now raises a more actionable error for
this (any 5xx response from EVE-NG with no JSON body, which is what an
unhandled server-side exception typically looks like). This is often
caused by a stale lock file left behind on the EVE-NG server by an
earlier interrupted request. On the EVE-NG server, check for one with:
```bash
find /opt/unetlab/labs/ -name '*.lock'
```
and remove any found with:
```bash
find /opt/unetlab/labs/ -name '*.lock' -exec rm {} \;
```
then retry. If that doesn't resolve it, check the EVE-NG server's own
logs for the underlying exception.
**`IncompleteFieldDefinitionWarning: Field 'lifespan' has an incomplete
definition...`** — this comes from inside the `mcp` SDK itself, not from
`mcp-eveng`. The SDK's internal `FastMCP` `Settings` model has a
self-referential `lifespan` field type that it never calls
`model_rebuild()` on, so `pydantic-settings` warns about it on every
`FastMCP` construction. It has no functional effect (nothing reads that
field from the environment) and `mcp-eveng` suppresses it by default — if
you still see it, you're likely on an `mcp` version where the warning text
changed slightly; it's safe to ignore either way.
**Streaming capture via curl** — if a Windows client connecting to this
server (`curl.exe`, or anything else using Windows' native Schannel TLS
stack) fails with `schannel: next InitializeSecurityContext failed:
SEC_E_INTERNAL_ERROR`, check your certificate's key algorithm —
confirmed by directly comparing a cert that triggered this against one
that didn't: the failing one used ECDSA with the P-521 curve
(`secp521r1`). Windows Schannel has a documented incompatibility with
P-521 certificates specifically (P-256/P-384 ECDSA and RSA are
unaffected — this isn't "avoid ECDSA," just that one specific curve).
Regenerate the certificate with RSA (2048-bit or larger) or ECDSA
P-256/P-384 instead.
## Manual curl commands
Test the server directly over HTTP without an MCP client — useful for
quick troubleshooting. **[Manual curl commands guide](docs/manual-curl-commands.md)**.
## A note on sessions and relogin
EVE-NG only allows one active session per user account — see
[docs/tools-reference.md](docs/tools-reference.md#sessions-and-relogin)
for what that means in practice and how `EvengClient` handles it.
## Known issues
`stop_node` (and anything that requires stopping a node first —
`edit_lab_node`, `change_node_delay`, `edit_lab_nodes_by_template`,
`connect_interface` on Community edition) can fail persistently on
certain nodes with `"Request not valid (60027)."`, with no way found so
far to make that specific node stoppable again through the API.
## Development
```bash
pip install -e ".[dev]"
# run tests with coverage
pytest
# lint / type-check
ruff check .
mypy src
```
### Why `mcp` is pinned below `2.0`
The official MCP Python SDK shipped a `2.0.0` release on 2026-07-28 alongside
the `2026-07-28` protocol revision. It is a deliberate breaking rework
(`FastMCP` renamed to `MCPServer`, new import paths, stateless transports)
and the SDK maintainers themselves recommend the `1.x` line for production
use while `2.x` stabilizes. This project pins `mcp[cli]>=1.23.0,<2.0.0`
intentionally — see `pyproject.toml`. Revisit this pin (and re-verify all
three transports, `transport_security`, and `stateless_http`) when migrating
to `2.x`.
## License
MIT — see [LICENSE](LICENSE).
## Tested versions
The EVE-NG server versions this project has actually been exercised
against live, confirmed via each server's own `get_status` response:
- **PRO**: `6.5.0-27-PRO`
- **Community**: `6.2.0-4`
Other versions of either edition likely work too — nothing in this
project depends on a specific point release beyond the documented
edition differences (see "EVE-NG Pro vs Community MCP tools") — but these are
the two actually confirmed.
TDQS
Scored across 36 tools
Most tools have clearly distinct purposes, with operations well-organized around labs, nodes, networks, and folders. A few tools like `edit_lab_node` and `edit_lab_nodes_by_template` have overlapping scope but are differentiated by bulk vs. single-node editing.
Tool names follow a consistent verb_noun pattern (e.g., `add_lab_node`, `delete_lab_network`, `start_node`). Minor inconsistencies exist like `change_node_delay` vs. `edit_lab_node`, but overall the pattern is predictable and readable.
With 36 tools, the surface is large but well-justified by the complexity of EVE-NG lab management (folders, labs, nodes, networks, wiring, lifecycle). Each tool covers a distinct operation, and the count feels appropriate for the domain.
The tool set covers the full lifecycle for EVE-NG: CRUD for folders, labs, nodes, and networks; node wiring; power operations; export; and status. No obvious gaps for common lab management tasks, including advanced features like bulk editing and sharing.