🚀 Model Context Protocol (MCP) Server for Cisco Modeling Labs (CML)
The Model Context Protocol (MCP) Server for Cisco Modeling Labs (CML) is a server implementation that allows LLM apps like Claude Desktop, Claude Code, and Cursor to interact with CML. It provides a set of tools for creating lab topologies, querying status, controlling labs and nodes, managing users and groups, and running commands on devices.
✨ Features
- Create Lab Topologies: A tool for creating new labs and defining network topologies.
- Query Status: Tools to retrieve status information for labs, nodes, and the CML server itself.
- Control Labs and Nodes: Tools to start and stop labs or individual nodes as needed.
- Manage CML Users and Groups: Tools to list, create, and delete local users and groups.
- Run Commands on Devices: Using PyATS, MCP clients can execute commands on virtual devices within CML labs.
📦 Installation
Requirements
- Python 3.12+
- Cisco Modeling Labs (CML) 2.9 or later
- PyATS (optional; used for device CLI command execution)
- The uv Python package/project manager
Windows Requirements
If you do not want to run CLI commands on devices running in CML, you only need to install the base cml-mcp package. However, if you want full support, Windows users also require either Windows Subsystem for Linux (WSL) with Python and uv installed within WSL or a Docker environment running on the Windows machine.
🚀 Quick Start
You have several options to run this server, depending on your needs and platform:
Option 1: Standard I/O (stdio) Transport
This is the traditional way to run the server, where it communicates directly with the MCP client via standard input/output streams.
Using uvx (Easiest - No CLI Support)
The easiest way is to use uvx, which downloads the server from PyPi and runs it in a standalone environment. This works for Linux, Mac, and Windows users but does not provide CLI command support. Edit your client's config and add something like the following. This example is for Claude Desktop:
{
"mcpServers": {
"Cisco Modeling Labs (CML)": {
"command": "uvx",
"args": [
"cml-mcp"
],
"env": {
"CML_URL": "<URL_OF_CML_SERVER>",
"CML_USERNAME": "<USERNAME_ON_CML_SERVER>",
"CML_PASSWORD": "<PASSWORD_ON_CML_SERVER>",
"CML_VERIFY_SSL": "false",
"DEBUG": "false"
}
}
}
}
In order to execute CLI commands on devices running within CML, Linux and Mac users will need to change the "args" to cml-mcp[pyats]. For example:
{
"mcpServers": {
"Cisco Modeling Labs (CML)": {
"command": "uvx",
"args": [
"cml-mcp[pyats]"
],
"env": {
"CML_URL": "<URL_OF_CML_SERVER>",
"CML_USERNAME": "<USERNAME_ON_CML_SERVER>",
"CML_PASSWORD": "<PASSWORD_ON_CML_SERVER>",
"CML_VERIFY_SSL": "false",
"PYATS_USERNAME": "<DEVICE_USERNAME>",
"PYATS_PASSWORD": "<DEVICE_PASSWORD>",
"PYATS_AUTH_PASS": "<DEVICE_ENABLE_PASSWORD>",
"DEBUG": "false"
}
}
}
}
The additional PYATS environment variables are needed to let the MCP server know how to login to those running devices.
Windows users that want CLI command support and are using Windows Subsystem for Linux (WSL) should configure:
{
"mcpServers": {
"Cisco Modeling Labs (CML)": {
"command": "wsl",
"args": [
"uvx",
"cml-mcp[pyats]"
],
"env": {
"CML_URL": "<URL_OF_CML_SERVER>",
"CML_USERNAME": "<USERNAME_ON_CML_SERVER>",
"CML_PASSWORD": "<PASSWORD_ON_CML_SERVER>",
"PYATS_USERNAME": "<DEVICE_USERNAME>",
"PYATS_PASSWORD": "<DEVICE_PASSWORD>",
"PYATS_AUTH_PASS": "<DEVICE_ENABLE_PASSWORD>",
"CML_VERIFY_SSL": "false",
"DEBUG": "false",
"WSLENV": "CML_URL/u:CML_USERNAME/u:CML_PASSWORD/u:CML_VERIFY_SSL/u:PYATS_USERNAME/u:PYATS_PASSWORD/u:PYATS_AUTH_PASS/u:DEBUG/u"
}
}
}
}
Windows (and really Mac and Linux users, too) that want CLI command support and are using Docker should configure:
{
"mcpServers": {
"Cisco Modeling Labs (CML)": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"--pull",
"always",
"-e",
"CML_URL",
"-e",
"CML_USERNAME",
"-e",
"CML_PASSWORD",
"-e",
"PYATS_USERNAME",
"-e",
"PYATS_PASSWORD",
"-e",
"PYATS_AUTH_PASS",
"-e",
"CML_VERIFY_SSL",
"-e",
"DEBUG",
"xorrkaz/cml-mcp:latest"
],
"env": {
"CML_URL": "<URL_OF_CML_SERVER>",
"CML_USERNAME": "<USERNAME_ON_CML_SERVER>",
"CML_PASSWORD": "<PASSWORD_ON_CML_SERVER>",
"CML_VERIFY_SSL": "false",
"PYATS_USERNAME": "<DEVICE_USERNAME>",
"PYATS_PASSWORD": "<DEVICE_PASSWORD>",
"PYATS_AUTH_PASS": "<DEVICE_ENABLE_PASSWORD>",
"DEBUG": "false"
}
}
}
}
Using FastMCP CLI
An alternative is to use FastMCP CLI to install the server into your favorite client. FastMCP CLI supports Claude Desktop, Claude Code, Cursor, and manual JSON generation. To use FastMCP, do the following:
- Clone this repository:
git clone https://github.com/xorrkaz/cml-mcp.git
- Change directory to the cloned repository.
- Run
uv sync to install all the correct dependencies, including FastMCP 2.0. Note: on Linux and Mac, run uv sync --all-extras to get CLI command support.
- Create a
.env file with the following variables set:CML_URL=<URL_OF_CML_SERVER>
CML_USERNAME=<USERNAME_ON_CML_SERVER>
CML_PASSWORD=<PASSWORD_ON_CML_SERVER>
CML_VERIFY_SSL=false
DEBUG=false
PYATS_USERNAME=<DEVICE_USERNAME>
PYATS_PASSWORD=<DEVICE_PASSWORD>
PYATS_AUTH_PASS=<DEVICE_ENABLE_PASSWORD>
- Run the FastMCP CLI command to install the server. For example:
fastmcp install claude-desktop src/cml_mcp/server.py:server_mcp --project `realpath .` --env-file .env
Option 2: HTTP Transport (Streaming)
The server now supports HTTP streaming transport, which is useful for running the MCP server as a standalone service that can be accessed by multiple clients or when you want to run it in a containerized or remote environment. This mode uses HTTP Server-Sent Events (SSE) for real-time communication.
Running the HTTP Server
To run the server in HTTP mode, set the CML_MCP_TRANSPORT environment variable to http. You can also configure the bind address and port.
First, install the package:
uv venv
source .venv/bin/activate
uv pip install cml-mcp
Or for development, clone the repository and sync dependencies:
git clone https://github.com/xorrkaz/cml-mcp.git
cd cml-mcp
uv sync
Then run the server using uvicorn:
export CML_URL=<URL_OF_CML_SERVER>
export CML_MCP_TRANSPORT=http
export CML_MCP_BIND=0.0.0.0
export CML_MCP_PORT=9000
uvicorn cml_mcp.server:app --host 0.0.0.0 --port 9000
Or create a .env file with these settings:
CML_URL=<URL_OF_CML_SERVER>
CML_MCP_TRANSPORT=http
CML_MCP_BIND=0.0.0.0
CML_MCP_PORT=9000
CML_VERIFY_SSL=false
DEBUG=false
CML_ALLOWED_URLS=https://cml1.example.com,https://cml2.example.com
CML_URL_PATTERN=^https://cml\.example\.com
Then run:
cml-mcp
The server will start and listen for HTTP connections at http://0.0.0.0:9000.
Authentication in HTTP Mode
When using HTTP transport, authentication is handled differently than stdio mode:
- CML Credentials: Instead of being set via environment variables, CML credentials are provided via the
X-Authorization HTTP header using Basic authentication format.
- PyATS Credentials: For CLI command execution, PyATS credentials can be provided via the
X-PyATS-Authorization header (Basic auth) and the enable password via the X-PyATS-Enable header.
- Multiple CML Hosts: When running in HTTP mode, clients can connect to different CML servers by providing the
X-CML-URL header. For security, you must configure allowed URLs via the CML_ALLOWED_URLS environment variable (comma-separated list) or CML_URL_PATTERN (regex pattern).
Example headers:
X-Authorization: Basic <base64_encoded_cml_username:cml_password>
X-PyATS-Authorization: Basic <base64_encoded_device_username:device_password>
X-PyATS-Enable: Basic <base64_encoded_enable_password>
X-CML-URL: https://cml-server.example.com
Configuring MCP Clients for HTTP
To use the HTTP server with an MCP client, you'll need to use the mcp-remote tool to connect to the HTTP endpoint. Most MCP clients like Claude Desktop don't natively support HTTP streaming, so mcp-remote acts as a bridge between the client (which expects stdio) and the HTTP server. This bridge requires Node.js to be installed on your client machine. Node.js includes the npx utility that allows you to run Javascript/Typescript applications in a dedicated environment.
Add the following configuration to your MCP client config (e.g., Claude Desktop's claude_desktop_config.json):
{
"mcpServers": {
"Cisco Modeling Labs (CML)": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"http://<server_host>:9000/mcp",
"--header",
"X-Authorization: Basic <base64_encoded_cml_credentials>",
"--header",
"X-PyATS-Authorization: Basic <base64_encoded_device_credentials>"
]
}
}
}
Replace the placeholders with your actual values:
<server_host>: The hostname or IP address where your HTTP server is running.
<base64_encoded_cml_credentials>: Base64-encoded username:password for CML.
<base64_encoded_device_credentials>: Base64-encoded username:password for device access.
Example:
{
"mcpServers": {
"Cisco Modeling Labs (CML)": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://192.168.10.210:8443/mcp",
"--header",
"X-Authorization: Basic <base64_encoded_cml_credentials>",
"--header",
"X-PyATS-Authorization: Basic <base64_encoded_device_credentials>"
]
}
}
}
Note: When using HTTPS with a self-signed certificate, you'll need to disable TLS certificate validation by adding an env section:
{
"mcpServers": {
"Cisco Modeling Labs (CML)": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://192.168.10.210:8443/mcp",
"--header",
"X-Authorization: Basic <base64_encoded_cml_credentials>",
"--header",
"X-PyATS-Authorization: Basic <base64_encoded_device_credentials>"
],
"env": {
"NODE_TLS_REJECT_UNAUTHORIZED": "0"
}
}
}
}
To encode your credentials in Base64:
Linux/Mac:
echo -n "username:password" | base64
echo -n "device_username:device_password" | base64
echo -n "enable_password" | base64
Windows (use WSL):
wsl bash -c 'echo -n "username:password" | base64'
wsl bash -c 'echo -n "device_username:device_password" | base64'
wsl bash -c 'echo -n "enable_password" | base64'
Alternatively, you can use online Base64 encoders or PowerShell:
[Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes("username:password"))
If you need to specify an enable password, add another header:
"--header",
"X-PyATS-Enable: Basic <base64_encoded_enable_password>"
Docker with HTTP Transport
You can also run the server in HTTP mode using Docker:
docker run -d \
--rm \
--name cml-mcp \
-p 9000:9000 \
-e CML_URL=<URL_OF_CML_SERVER> \
-e CML_MCP_TRANSPORT=http \
xorrkaz/cml-mcp:latest
This exposes the HTTP server on port 9000, allowing external MCP clients to connect.
💻 Usage Examples
Basic Usage
The tools should show up automatically in your MCP client, and you can chat with the LLM to get it to invoke tools as needed. For example, the following sequence of prompts nicely shows off some of the server's capabilities:
- Create a new CML lab called "Joe's MCP Lab".
- Add two IOL nodes, a unmanaged switch, and an external connector to this lab.
- Connect the two IOL nodes to the unmanaged switch and the unmanaged switch to the external connector.
- Configure the routers so that their connected interfaces have IPs in the 192.0.2.0/24 subnet. Configure OSPF on them. Then start the lab and validate OSPF is working correctly.
- Add a box annotation around the two IOL nodes that indicates they speak OSPF. Make it a green box.
And here is an obligatory demo GIF to show it working in Claude Desktop:
Advanced Usage
System Prompt
If your LLM tool supports a system prompt, or you want to provide some richer initial context, here's a good example courtesy of Hank Preston:
You are a network lab assistant specializing in supporting Cisco Modeling Labs (CML). You provide a natural language interface for many common lab activities such as:
- Creating new lab
- Adding nodes to a lab
- Creating interfaces between nodes
- Configuring nodes
- Creating annotations
You have access to tools to access the CML server.
📄 License
The MCP server portion of this project is licensed under the BSD 2-Clause "Simplified" License. However, it leverages the pydantic schema typing code from CML itself, which is covered under a proprietary Cisco license.