Content
# CML MCP Server

A Model Context Protocol (MCP) server that enables AI agents to dynamically deploy and manage Cisco Modeling Labs (CML) network topologies through conversational interfaces.
## What This Does
This MCP server provides AI agents with the ability to:
- **Deploy complete network topologies conversationally** - Describe what you want, and the agent builds it
- **Dynamically modify existing labs** - Add nodes, connect devices, reconfigure on the fly
- **Manage lab lifecycle** - Start, stop, and remove nodes and entire labs
- **Query lab state** - Inspect topologies, find nodes by tags, check configurations
### Example Conversations
**Deploy a complete topology:**
> "Can you deploy a topology named ai-test-network with two WAN routers connected to each other, an HQ site connected to both with a ubuntu image connected, and a datacenter with a FTD firewall connected to both routers and a splunk instance connected to the datacenter firewall?"
**Expand existing topology:**
> "Can you add a Splunk instance to my lab-name topology?"
**Manage labs:**
> "Start all nodes tagged 'production' in my lab"
> "Stop the entire test-lab"
> "Remove the temporary test nodes from my topology"
The agent understands natural language, translates it into the appropriate CML operations, and executes them using these tools.
## Quick Start
### Prerequisites
- Docker installed
- Access to a Cisco Modeling Labs (CML) server
- CML credentials (username/password or API token)
### Run the Container
```bash
# Pull the latest image
docker pull ghcr.io/presidio-federal/cml-mcp:latest
# Run with environment variables
docker run -d \
-p 3010:3010 \
-e CML_HOST=your-cml-server.example.com \
-e CML_USERNAME=admin \
-e CML_PASSWORD=your-password \
-e DISABLE_JWT_AUTH=true \
-e CML_VERIFY_SSL=false \
--name cml-mcp-server \
ghcr.io/presidio-federal/cml-mcp:latest
```
### Required Environment Variables
| Variable | Description | Example |
|----------|-------------|---------|
| `CML_HOST` | CML server hostname or IP (without https://) | `cml.example.com` or `10.10.20.50` |
| `CML_USERNAME` | CML username | `admin` |
| `CML_PASSWORD` | CML password | `yourpassword` |
**Alternative authentication:**
- Use `CML_TOKEN` instead of username/password
### Optional Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| `CML_VERIFY_SSL` | Verify SSL certificates | `false` |
| `DISABLE_JWT_AUTH` | Disable JWT authentication | `false` |
| `PORT` | Server port | `3010` |
| `HOST` | Server host | `0.0.0.0` |
### Using Docker Compose
Create `docker-compose.yml`:
```yaml
version: '3.8'
services:
cml-mcp:
image: ghcr.io/presidio-federal/cml-mcp:latest
ports:
- "3010:3010"
environment:
- CML_HOST=your-cml-server.example.com
- CML_USERNAME=admin
- CML_PASSWORD=your-password
- DISABLE_JWT_AUTH=true
- CML_VERIFY_SSL=false
restart: unless-stopped
```
Then run:
```bash
docker-compose up -d
```
### Using Environment File
For passwords with special characters, use an `.env` file:
```bash
# .env
CML_HOST=your-cml-server.example.com
CML_USERNAME=admin
CML_PASSWORD=P@ssw0rd!$pecial#chars
DISABLE_JWT_AUTH=true
CML_VERIFY_SSL=false
```
Run with:
```bash
docker run -d -p 3010:3010 --env-file .env --name cml-mcp-server ghcr.io/presidio-federal/cml-mcp:latest
```
## Testing with FastMCP
FastMCP provides an interactive way to test the MCP server and its tools.
### Install FastMCP
```bash
pip install fastmcp
```
### Connect to Your Running Server
```bash
# Connect to the container
fastmcp client http://localhost:3010
```
### Example Session
```bash
$ fastmcp client http://localhost:3010
Connected to: CML Python MCP Server v1.0.0
Available tools:
- cml_list_labs
- cml_get_lab_details
- cml_create_topology
- cml_start_stop_lab
- ... (and more)
# List all labs
> cml_list_labs
# Get details about a lab
> cml_get_lab_details --lab_id="My Lab"
# Create a new topology
> cml_create_topology --lab_title="Test" --node_configs='[{"label":"R1","node_definition":"csr1000v"}]'
# Start entire lab
> cml_start_stop_lab --lab_id="Test" --operation="start"
```
## Available Tools (17 Total)
### Lab Management
- **cml_list_labs** - List all labs with metadata
- **cml_get_lab_details** - Get complete lab topology (nodes, links, interfaces)
- **cml_start_stop_lab** - Start or stop entire lab
- **cml_create_topology** - Create complete topology with nodes and links
### Node Operations
- **cml_add_node_to_topology** - Add nodes to existing lab
- **cml_remove_node_from_topology** - Remove nodes from lab
- **cml_get_node_info** - Get detailed node information
- **cml_apply_configs** - Apply configurations to nodes
### Tag-Based Management
- **cml_manage_node_tags** - Add or remove tags from nodes
- **cml_find_nodes_by_tags** - Find nodes with specific tags
- **cml_start_nodes_by_tag** - Start nodes matching tag(s)
- **cml_stop_nodes_by_tag** - Stop nodes matching tag(s)
### Topology Building
- **cml_connect_nodes** - Create links between nodes
- **cml_get_next_available_interface** - Find available interfaces
- **cml_get_available_coordinates** - Calculate optimal node positions
### Discovery
- **cml_list_node_definitions** - List available device types
- **cml_list_image_definitions** - List available software images
## Common Workflows
### Deploy a New Topology
```json
{
"tool": "cml_create_topology",
"lab_title": "Hub-and-Spoke",
"node_configs": [
{
"label": "Hub-Router",
"node_definition": "csr1000v",
"x": 200,
"y": 100,
"config": "hostname Hub",
"tags": ["core", "hub"]
},
{
"label": "Spoke-1",
"node_definition": "iosv",
"x": 100,
"y": 300,
"tags": ["spoke"]
}
],
"link_configs": [
{"node_a": "Hub-Router", "node_b": "Spoke-1"}
]
}
```
### Add Node to Existing Lab
```json
{
"tool": "cml_add_node_to_topology",
"lab_id": "My Lab",
"nodes": [
{
"label": "New-Router",
"node_definition": "csr1000v",
"x": 500,
"y": 200,
"tags": ["new"]
}
]
}
```
### Tag-Based Operations
```bash
# Tag nodes
cml_manage_node_tags --lab_id="My Lab" --node_ids='["uuid1","uuid2"]' --operation="add" --tags='["production"]'
# Start tagged nodes
cml_start_nodes_by_tag --lab_id="My Lab" --tags="production"
# Stop tagged nodes
cml_stop_nodes_by_tag --lab_id="My Lab" --tags="test"
```
## Building and Contributing
### Clone the Repository
```bash
git clone https://github.com/Presidio-Federal/cml-mcp.git
cd cml-mcp
```
### Build Locally
```bash
# Build the container
docker build -t cml-mcp:local .
# Run your local build
docker run -d -p 3010:3010 \
-e CML_HOST=your-cml-server.com \
-e CML_USERNAME=admin \
-e CML_PASSWORD=password \
-e DISABLE_JWT_AUTH=true \
--name cml-mcp-test \
cml-mcp:local
```
### Development Setup
```bash
# Create virtual environment
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install dependencies
pip install -r requirements.txt
# Create .env file
cat > .env << EOF
CML_HOST=your-cml-server.example.com
CML_USERNAME=admin
CML_PASSWORD=your-password
DISABLE_JWT_AUTH=true
CML_VERIFY_SSL=false
EOF
# Run locally
python -m server
# Test with FastMCP in another terminal
fastmcp client http://localhost:3010
```
### Adding New Tools
1. Create tool file in `tools/` directory (e.g., `my_tool.py`)
2. Implement class with `execute()` method
3. Add import to `tools/__init__.py`
4. Register tool in `server.py`
5. Test with FastMCP
6. Submit pull request
**Tool Template:**
```python
"""
My Tool Description
"""
import logging
from typing import Dict, Any
from virl2_client import ClientLibrary
logger = logging.getLogger(__name__)
class MyTool:
"""Tool that does something useful."""
def execute(self, client: ClientLibrary, param1: str) -> Dict[str, Any]:
"""
Execute the tool.
Args:
client: Authenticated CML client
param1: Description
Returns:
Dictionary with results
"""
try:
# Implementation here
return {
"ok": True,
"result": "success"
}
except Exception as e:
logger.error(f"Error: {e}")
return {
"ok": False,
"error": str(e)
}
# Create singleton
my_tool = MyTool()
```
## Troubleshooting
### Container won't start
```bash
# Check logs
docker logs cml-mcp-server
# Verify environment variables
docker exec cml-mcp-server env | grep CML
```
### Can't connect to CML
```bash
# Test from container
docker exec cml-mcp-server ping your-cml-server
# Verify CML is accessible
curl -k https://your-cml-server/
```
### Authentication fails
- Verify CML credentials are correct
- Check if CML account is active
- For special characters in password, use `.env` file or single quotes
- Try using `CML_TOKEN` instead of username/password
### SSL certificate errors
- Set `CML_VERIFY_SSL=false` for self-signed certificates
- For production, install valid SSL certificates on CML server
## Architecture
**Technology Stack:**
- **Framework:** FastMCP (Python MCP server framework)
- **CML SDK:** virl2_client (official Cisco Modeling Labs Python SDK)
- **Transport:** HTTP Streaming
- **Container:** Multi-stage Docker build with Python 3.11
**Why Python SDK:**
- Persistent connections with automatic retry
- Intelligent operation batching
- Built-in caching and connection pooling
- Cisco-maintained compatibility
## CI/CD
The project uses GitHub Actions for automated builds:
- **Trigger:** Push to `main` branch or version tag
- **Process:** Build → Tag → Push to GitHub Container Registry
- **Tags:** Version (from `__init__.py`), timestamp, commit SHA, latest
## License
This project is maintained by Presidio Federal and is available for public use.
## Support
For issues or questions:
- Open an issue on [GitHub](https://github.com/Presidio-Federal/cml-mcp/issues)
- Check CML and virl2_client documentation
- Verify CML connectivity and credentials
Connection Info
You Might Also Like
everything-claude-code
Complete Claude Code configuration collection - agents, skills, hooks,...
markitdown
MarkItDown-MCP is a lightweight server for converting URIs to Markdown.
cc-switch
All-in-One Assistant for Claude Code, Codex & Gemini CLI across platforms.
servers
Model Context Protocol Servers
servers
Model Context Protocol Servers
Time
A Model Context Protocol server for time and timezone conversions.