mcp-eveng

mcp-eveng

MCP server for controlling EVE-NG network emulator instances via REST API, enabling lab/node/network management, topology editing, and device operations through natural language.

Category
Visit Server

README

mcp-eveng

A Model Context Protocol server that lets LLM clients (Claude Desktop, Claude Code, or any other MCP host) drive an EVENG 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.

Features

  • Full coverage of the documented EVENG REST API: auth, system status, node templates, network types, folders, users, labs, lab networks, lab nodes (including start/stop/wipe/export), topology, links and pictures.
  • Recursive lab search (list_all_labs) with explicit loop protection, since EVE-NG's own API has no recursive-listing endpoint.
  • Every destructive tool (delete folder/user/network/node/lab) requires in-band user confirmation via MCP elicitation before it does anything. delete_lab in particular resolves a lab by name across the whole tree, disambiguates if more than one matches, and never deletes more than one.
  • All three MCP transports: stdio (default), sse, and streamable-http (recommended for networked deployments), selected with a CLI flag.
  • DNS-rebinding Host-header protection and optional stateless streamable-http, both configurable.
  • Async, typed, cookie-session-aware EVENG client with automatic re-login on session expiry.
  • Configuration lives entirely in environment variables / a .env file — nothing is hardcoded.
  • Ships as an installable, PyPI-packagable Python distribution with a full unit test suite.

Installation

git clone https://github.com/madmickstar/mcp_eveng.git
cd mcp_eveng
pip install -e .

Or, for local development:

git clone https://github.com/madmickstar/mcp_eveng.git
cd mcp_eveng
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"

Choosing a transport

Transport is a CLI flag, not an environment variable. --sse and --http are mutually exclusive; passing neither runs stdio.

mcp_eveng          # stdio (default)
mcp_eveng --sse     # legacy SSE transport, network-exposed
mcp_eveng --http    # Streamable HTTP transport, network-exposed (recommended)
  • stdio (no flag): the MCP host (Claude Desktop, Claude Code, ...) launches mcp_eveng as a subprocess and supplies configuration directly in its own env block — see the example below. No .env file or MCP_* variable is needed in this mode at all.
  • --sse / --http: you run the process yourself as a standalone network service, so you'll typically want a .env file (or exported env vars) for both the EVENG_* connection settings and the MCP_* network settings described below.

Configuration

Copy .env.example to .env and fill in your EVENG server details:

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 80 EVENG server port
EVENG_PROTOCOL http 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 true Verify TLS certs. Default is true; if your EVE-NG server uses HTTPS with a self-signed cert (common for EVE-NG Pro), you'll most likely need to set this to false
EVENG_TIMEOUT_SECONDS 30 HTTP request timeout

EVENG_HOST rejects a value containing :// at startup (e.g. https://172.16.130.14) with a clear error rather than a cryptic getaddrinfo failed — put the scheme in EVENG_PROTOCOL instead.

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 http/80 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 (empty) Comma-separated Host-header allowlist. Required when MCP_HOST is not a loopback address
MCP_STATEFUL true false disables streamable-http session persistence

All variables can also be set as real environment variables, which take precedence over .env.

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. There's no file logging built in; redirect stderr yourself if you want persistent logs, e.g. mcp_eveng --http 2>> mcp_eveng.log.

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). When MCP_HOST is a loopback address (127.0.0.1, localhost, ::1), the SDK auto-enables a loopback-only allowlist and you don't need to do anything. When MCP_HOST is anything else (e.g. 0.0.0.0 to bind all interfaces), the SDK does not auto-protect — mcp_eveng will refuse to start with a clear error unless you set MCP_ALLOWED_HOSTS explicitly.

Format is a comma-separated list of host:port or host:* (any port) entries, matching the SDK's native allowed_hosts syntax:

MCP_ALLOWED_HOSTS="192.168.1.100:8000,192.168.1.150:*"

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.

Running

# stdio (default) — for MCP hosts that launch the server as a subprocess
mcp_eveng

# Streamable HTTP — recommended for networked / remote deployments
mcp_eveng --http

# Legacy SSE transport
mcp_eveng --sse

# Bound to all interfaces, requires MCP_ALLOWED_HOSTS (see above)
MCP_HOST="0.0.0.0" MCP_ALLOWED_HOSTS="192.168.1.100:8000,192.168.1.150:*" mcp_eveng --http

You can also run it as a module: python -m mcp_eveng --http.

Press Ctrl+C to stop a foreground server (--sse/--http, or stdio run directly in a terminal for testing) — it's caught and exits cleanly with a "Goodbye!" message on stderr instead of a raw traceback.

Using it with Claude Desktop / Claude Code (stdio)

In stdio mode the host supplies EVENG_* directly in its own config — no .env file needed:

{
  "mcpServers": {
    "eveng_stdio": {
      "command": "/path/to/venv/python.exe",
      "args": [
          "-m",
          "mcp_eveng"
      ],
      "env": {
        "EVENG_HOST": "192.168.1.50",
        "EVENG_USERNAME": "admin",
        "EVENG_PASSWORD": "eve",
        "EVENG_VERIFY_SSL": "false"
      }
    }
  }
}

(EVENG_VERIFY_SSL: "false" is shown here because this example EVE-NG host is assumed to be running HTTPS with a self-signed cert — see the config table above. Drop it, or set it to "true", for a plain-HTTP or properly-certed target.)

Using it with Claude Desktop / Claude Code (streamable-http)

Claude Desktop/Code don't speak Streamable HTTP directly, so bridge through mcp-remote to a mcp_eveng --http instance running elsewhere (e.g. mcp_eveng --http on 192.168.1.100:8000):

{
  "mcpServers": {
    "eveng_http": {
      "command": "npx",
        "args": [
            "-y",
            "mcp-remote@latest",
            "http://192.168.1.100:8000/mcp",
            "--allow-http"
        ]
    }
  }
}

Available 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.

Area Tools
System get_status, list_node_templates, get_node_template, list_network_types, list_user_roles
Folders list_folder, add_folder, move_folder, delete_folder*
Users list_users, get_user, add_user, edit_user, delete_user*
Labs get_lab, create_lab, edit_lab, move_lab, delete_lab*, get_lab_topology, get_lab_links, list_lab_pictures, list_labs, list_all_labs
Networks list_lab_networks, add_lab_network, delete_lab_network*
Nodes list_lab_nodes, add_lab_node, delete_lab_node*, get_node_interfaces, start_node, stop_node, wipe_node, export_node

* Requires user confirmation before it does anything — see below.

list_labs vs list_all_labs: list_labs(path) lists only the labs (not subfolders) directly inside one folder. list_all_labs(start_path) recursively walks the whole tree from start_path (default: the entire server) and returns every lab found. EVE-NG's API has no recursive-listing endpoint (confirmed against the actual server source, api.php's apiGetFolders() route — it only ever returns one folder's immediate children), so list_all_labs walks the tree itself, one request per folder, with loop protection: every folder's ".." entry is skipped, each folder is only ever visited once even if referenced more than once, and hard max_depth/max_folders ceilings guard against any unexpected API response shape. Labs are deduplicated by path, since EVE-NG's virtual /Running folder can otherwise list the same lab twice.

Deleting things requires confirmation

Every delete tool (delete_folder, delete_user, delete_lab_network, delete_lab_node, delete_lab) works the same way:

  1. A search string is required. Every delete tool's identifying parameter has no meaningful default — call it with an empty/missing string and it fails immediately with a message explaining what's needed, before anything is searched or shown to the user.
  2. Matching is exact, not fuzzy, and always case-insensitive. "test" matches "Test" or "TEST" but never "testing".
  3. Every match is shown as a numbered list, with a trailing "All" entry (except for delete_lab — see below) — even when there's only one match, it's still shown as choice "1" so confirmation always goes through the same explicit picker rather than a plain yes/no prompt.
  4. The user must pick a number. This uses MCP elicitation (ctx.elicit(...), part of the MCP spec since the 2025-06-18 revision and supported by our pinned SDK). Picking a listed number deletes that item (or every item, for "All"). Anything else — a decline, a cancel, invalid input, or a connected MCP host that doesn't support elicitation at all — cancels the whole operation. Nothing is ever deleted without an explicit, valid number choice.

What each tool matches against:

Tool Matches on Notes
delete_folder Folder path only (never a bare name) Refuses to delete a non-empty folder — see below
delete_user Username
delete_lab_network Network name or id, within one lab_path
delete_lab_node Node name or id, within one lab_path
delete_lab Lab path or name, across the whole tree (or search_path) No "All" option — see below

delete_folder refuses to delete a non-empty folder. If the chosen folder still contains any subfolders or labs, nothing is deleted for it and the response explains that it's not empty, listing its contents as bullets (so you can see exactly what's in the way).

delete_lab never offers "All" and never deletes more than one lab in a single call, even when several labs share the same name in different folders — you always have to pick exactly one specific match by number. This is deliberately stricter than the other delete tools.

⚠️ This has not been exercised against a live EVE-NG server or a real elicitation-capable MCP host yet — the logic is covered by unit tests with a faked confirmation flow, but please verify the actual UX (does your MCP host render the numbered-choice prompt the way you'd expect?) before relying on it for anything you can't afford to lose.

Project layout

mcp_eveng/
├── src/mcp_eveng/
│   ├── client.py        # async EVENG REST API client (incl. list_all_labs)
│   ├── config.py        # pydantic-settings, reads .env
│   ├── confirmation.py  # shared numbered-choice deletion confirmation (MCP elicitation)
│   ├── dependencies.py  # shared client singleton
│   ├── exceptions.py
│   ├── search.py         # case-insensitive record search (used by delete tools)
│   ├── server.py        # FastMCP assembly + transport security/statefulness
│   ├── __main__.py       # CLI: --sse / --http flags
│   └── tools/             # one module per API area
├── tests/
│   ├── conftest.py
│   ├── helpers.py        # FakeContext/FakeElicitResult for testing confirmations
│   ├── test_cli.py
│   ├── test_client.py
│   ├── test_config.py
│   ├── test_confirmation.py
│   ├── test_dependencies.py
│   ├── test_search.py
│   ├── test_server.py
│   └── tools/
└── .github/workflows/     # CI + PyPI publish

Troubleshooting

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.

Development

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.

Publishing to PyPI

This repo is set up for Trusted Publishing: tagging a release (vX.Y.Z) triggers .github/workflows/publish.yml, which builds and uploads to PyPI with no stored API tokens. Configure the trusted publisher on PyPI's project settings page pointing at this repository and the publish.yml workflow before tagging your first release.

License

MIT — see LICENSE.

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured