The Virtual Satellite Network Emulator System (VSNES) is a Python-based tool designed to simulate satellite networks, including satellite orbits, ground stations, and communication channels. It provides tools for visualizing scenarios in Cesium, managing virtual machines (VMs) and containers for emulation, and propagating satellite orbits using TLE data. VSNES can be integrated into AI workflows by exposing a Model Context Protocol (MCP) server, enabling AI agents such as Claude to autonomously configure, launch, and monitor satellite network emulations.
A new Flask-based REST API server runs on port 5050 alongside the Cesium visualization server, exposing the full emulator lifecycle over HTTP. This replaces the need for direct CLI interaction and enables integration with external tools, dashboards, and automation pipelines.
Key endpoints:
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/health |
Health check |
POST |
/api/upload-config |
Upload a TOML configuration file |
POST |
/api/upload-tle |
Upload a TLE file |
POST |
/api/load-config |
Load config.toml into the system |
POST |
/api/init-scenario |
Initialize the scenario from loaded config |
GET |
/api/scenario |
Get current scenario description |
POST |
/api/write-czml |
Generate the CZML file for Cesium |
POST |
/api/start-vms |
Start all VMs |
POST |
/api/stop-vm/<name> |
Stop a specific VM |
POST |
/api/stop-all-vms |
Stop all VMs |
DELETE |
/api/delete-vm/<name> |
Delete a specific VM |
DELETE |
/api/delete-all-vms |
Delete all VMs |
POST |
/api/compose/up |
Start the Docker node containers (docker compose up -d); optional JSON body {"services": ["satellite-1", ...]} to start a subset |
POST |
/api/compose/down |
Stop and remove the Docker node containers (docker compose down) |
POST |
/api/simulation/start |
Start full emulation and visualization |
POST |
/api/visualization/start |
Start visualization only |
POST |
/api/simulation/stop |
Stop the running simulation |
GET |
/api/status |
Get system status and simulation progress |
POST |
/api/reset |
Stop simulation and reset all state |
GET |
/api/help |
List all available endpoints |
The API tracks system state across the lifecycle (IDLE → CONFIG_LOADED → SCENARIO_INIT → PREPARING_VMS → RUNNING_SIMULATION) and returns 409 Conflict responses for invalid transitions.
To start (launched automatically by SatelliteEmulator.py):
python SatelliteEmulator.py # starts API + interactive CLI
python SatelliteEmulator.py --web # API + Cesium web serverA Model Context Protocol (MCP) server built with fastmcp, running on port 8560. It exposes the emulator and VM management as callable tools for AI agents (e.g., Claude Desktop, Claude Code).
Available tools:
Emulator Control (via REST API)
| Tool | Description |
|---|---|
prepare_simulation |
Load config and initialize scenario |
start_simulation |
Start emulation and visualization |
stop_simulation |
Stop and reset the emulator |
get_emulator_status |
Get full system status |
compose_up |
Start the Docker node containers (optionally a list of service names) |
compose_down |
Stop and remove the Docker node containers |
VM Management (direct Libvirt/SSH)
| Tool | Description |
|---|---|
list_vms |
List all VMs (name, state, IP) via Libvirt |
manage_vm_power |
Start or stop a VM (or all VMs) via Libvirt |
execute_vm_command |
Run a shell command on a VM (or all VMs) via SSH |
Configuration Generation (AI-assisted setup)
| Tool | Description |
|---|---|
get_config_guide |
Returns a structured guide the AI uses to interview the user and collect scenario parameters |
generate_config_toml |
Generates a config.toml file from satellites, ground stations, channels, and time parameters |
generate_docker_compose |
Generates docker-compose.yml from a config file or explicit node list, optionally creating a Dockerfile |
To start (launched automatically by SatelliteEmulator.py):
python SatelliteEmulator.py --mcp # API + MCP
python SatelliteEmulator.py --all # API + Web + NTP + MCPConfigure SSH credentials and API base URL via environment variables:
export SNES_API_URL=http://localhost:5050
export VM_SSH_USER=debian
export VM_SSH_PASS=debianA custom UDP NTP server that synchronizes VM clocks to the emulator's simulated time. It reads the current simulation timestamp from simulation_time.txt (updated by the emulator during runtime) and serves it to NTP clients. Falls back to system time if the file is unavailable.
# Default (port 123, requires root)
sudo python3 ntpserver.py
# Custom port (no root required)
python3 ntpserver.py --port 12345The API server starts the NTP server automatically on port 12345.
A new browser-based control panel is served by the Cesium visualization server (Class/static/). It includes a custom design system with Orbitron and Lato fonts, icon set, and a responsive layout. The frontend communicates with the REST API to load configs, manage VMs, and control the simulation without using the CLI.
All server components write structured logs to /tmp/log/snes.log with timestamps and severity levels, in addition to console output.
- Orbit Propagation: Supports SGP4 and Two-Body models for satellite orbit propagation.
- Node Management: Handles satellites and ground stations as nodes in the network.
- Communication Channels: Simulates communication delays and properties between nodes.
- Visualization: Generates CZML files for Cesium-based 3D visualization.
- VM Management: Creates, starts, stops, and deletes virtual machines for emulation.
- Server Integration: Provides a Flask-based server for Cesium visualizations and API endpoints.
- REST API: Full HTTP API for programmatic control of the emulator lifecycle.
- MCP Server: AI-agent-friendly tool interface via the Model Context Protocol.
- Simulation NTP: Custom NTP server serving simulation time to emulated VMs.
Contains the core classes and modules for the emulator.
-
Core Classes:
Node.py: Base class for all nodes (satellites and ground stations).Orbit.py: Base class for handling satellite orbits using TLE data.Scenario.py: Manages the overall emulation scenario.
-
Extended Classes:
Satellite.py: ExtendsNodeto represent satellites.Ground_Station.py: ExtendsNodeto represent ground stations.
-
Orbit Propagation Models:
SGP4.py: Implements the SGP4 model for orbit propagation.TwoBody.py: Implements a two-body model for orbit propagation.
-
Communication and Channels:
Channel.py: Handles communication channels between nodes.channel_threshold.py: Defines thresholds and parameters for channel configurations.
-
Time Management:
Time_parameters.py: Manages time-related settings for the emulation.
-
Server and Visualization:
Server.py: Flask server for Cesium visualization (port 5500).static/: Web dashboard assets (CSS, JS, fonts, icons).templates/: HTML and CZML templates for Cesium.
Interactive CLI entry point. Talks to the REST API to load configs, manage VMs, generate CZML, and launch or stop the simulation.
REST API server (port 5050). Exposes the full emulator lifecycle over HTTP. Also starts the Cesium visualization server and the NTP server automatically on launch.
MCP server (port 8560). Exposes emulator control, VM management, and configuration generation as AI-callable tools. Enables full integration with AI agents such as Claude Desktop or Claude Code.
Simulation-aware NTP server. Serves emulator simulation time to nodes in the emulated network.
Defines all satellite and ground station containers for Docker-based deployments on the vsnes_net (172.27.12.0/24) bridge network.
Dockerfile: Debian 12 image with SSH, networking tools, and k3s pre-installed.entrypoint.sh: Container startup script.
Configuration file defining the scenario: nodes, channels, time parameters, and network settings.
Follow these steps to set up VSNES on your machine:
- Linux-based operating system (tested on Ubuntu).
- Python 3.10 or later.
- Virtualization support (QEMU/KVM).
sudo install.shThe czml library requires a small fix. Open the file:
sudo nano /usr/local/lib/python3.10/dist-packages/czml/czml.py
Note: Find the exact path with
pip3 show czml
Replace:
from pygeoif.geometry import as_shape as asShapeWith:
from pygeoif.factories import shape as asShapeSatelliteEmulator.py is the single entry point. It always starts apiServer.py internally and drops into an interactive CLI once the API is ready. Optional flags control which additional services start alongside it.
python SatelliteEmulator.py # API only
python SatelliteEmulator.py --web # API + Cesium web server (port 5580)
python SatelliteEmulator.py --ntp # API + NTP server (port 12345)
python SatelliteEmulator.py --mcp # API + MCP server (port 8560)
python SatelliteEmulator.py --all # API + Web + NTP + MCP
python SatelliteEmulator.py --web --mcp # combine any flagsServices started and their addresses:
| Service | Address | Flag |
|---|---|---|
| REST API | http://localhost:5050 |
always |
| Cesium GUI | http://localhost:5580 |
--web |
| NTP server | port 12345 |
--ntp |
| MCP server | http://localhost:8560 |
--mcp |
Available interactive commands:
| Command | Aliases | Description |
|---|---|---|
help |
Show all available actions | |
load_scenario |
load |
Load config.toml and initialize the scenario |
scenario |
Display loaded nodes and their types | |
start_vms |
vm |
Create or start containers/VMs for all nodes |
compose_up [services...] |
compose up |
Start the Docker node containers via the API (docker compose up -d); optionally name specific services, e.g. compose_up satellite-1 ibi_es |
compose_down |
compose down |
Stop and remove the Docker node containers via the API (docker compose down) |
write_czml |
Generate ScenarioCZML.czml for Cesium |
|
run_all |
run |
Run full emulation and Cesium visualization |
run_emulator |
emu, emulator, run_emu |
Run emulation only (no Cesium) |
run_cesium |
cesium |
Run Cesium visualization only |
stop |
Stop the running simulation | |
shutdown_vms |
Shut down all nodes | |
delete_vm |
delete |
Delete a specific VM or all VMs |
exit |
End the program (prompts to delete VMs) |
python SatelliteEmulator.py --web- REST API:
http://localhost:5050 - Cesium GUI:
http://localhost:5580 - API docs:
http://localhost:5050/api/help
Typical workflow:
# 1. Upload files
curl -F "file=@config.toml" http://localhost:5050/api/upload-config
curl -F "file=@sample.tle" http://localhost:5050/api/upload-tle
# 2. Start the Docker node containers (all, or a subset of services)
curl -X POST http://localhost:5050/api/compose/up
curl -X POST -H "Content-Type: application/json" \
-d '{"services": ["satellite-1", "satellite-2", "satellite-3", "ibi_es"]}' \
http://localhost:5050/api/compose/up
# 3. Load and initialize
curl -X POST http://localhost:5050/api/load-config
curl -X POST http://localhost:5050/api/init-scenario
# 4. Start simulation
curl -X POST http://localhost:5050/api/simulation/start
# 5. Monitor
curl http://localhost:5050/api/status
# 6. Stop
curl -X POST http://localhost:5050/api/simulation/stop
# 7. Tear down the containers
curl -X POST http://localhost:5050/api/compose/downThe MCP server connects an AI agent directly to VSNES, allowing it to configure, launch, and monitor satellite network emulations through natural language.
python SatelliteEmulator.py --mcp # API + MCP
python SatelliteEmulator.py --all # API + Web + NTP + MCPConfigure the server via environment variables:
export SNES_API_URL=http://localhost:5050 # REST API address
export VM_SSH_USER=debian # SSH user for VMs
export VM_SSH_PASS=debian # SSH password for VMsTo connect from Claude Desktop, add the following to your claude_desktop_config.json:
{
"mcpServers": {
"vsnes": {
"url": "http://localhost:8560/mcp"
}
}
}Typical AI-driven workflow:
- AI calls
get_config_guideto learn what parameters to collect - AI interviews the user and calls
generate_config_tomlto writeconfig.toml - AI calls
generate_docker_composeto create the container manifest - AI calls
prepare_simulation→start_simulationto launch the emulation - AI monitors progress with
get_emulator_statusand stops withstop_simulation
The configuration file (config.toml) defines the parameters for VSNES. Below is a detailed explanation of each section:
network: Specifies the subnet for the emulated network (in CIDR notation).network_ext: Specifies the subnet of the network which hosts the external domains/VMs in the Relay mode (in CIDR notation).unicast_flooding: If1, the virtual switch of brSATEMU will not logically map MAC addresses with ports. This means that all the traffic received from one port will be broadcasted to all the other ports. If0, the virtual switch will map MAC addresses and forward traffic only to the correct destination port.
TimeInterval: Time step for the simulation in minutes.Contact_speed: Speed multiplier for contact periods.Non_contact_speed: Speed multiplier for non-contact periods.start_datetime: Start time of the simulation inYYYY-MM-DD HH:MM:SSformat.end_datetime: End time of the simulation inYYYY-MM-DD HH:MM:SSformat.
TLE: Path to the TLE file containing satellite orbital data.SatelliteSistem: Defines individual satellites.propagator: Orbit propagation model (SGP4orTwoBody).Service: Satellite service type (StandardorRelay).name: Name of the satellite.group: Group name (e.g.,LEOfor Low Earth Orbit).OS: Operating system of the satellite's VM (ubuntuoralpine).username: Username for the VM.password: Password for the VM.isVM: Indicates if the satellite is a virtual machine (1for true,0for false).ip_ext: External IP address of the VM.interface: Network interface name.clone_VM: Configuration for cloning the VM.name_VM: Name of the base VM to clone.
GroundSistem: Defines individual ground stations.name: Name of the ground station.group: Group name (e.g.,GSfor Ground Station).latitude: Latitude of the ground station in degrees.longitude: Longitude of the ground station in degrees.height: Height of the ground station above sea level in meters.OS: Operating system of the ground station's VM (ubuntuoralpine).username: Username for the VM.password: Password for the VM.interface: Network interface name.clone_VM: Configuration for cloning the VM.name_VM: Name of the base VM to clone.
Channel: Defines communication links between nodes.Node1: Group name of the first node (e.g.,LEOfor satellites).Node2: Group name of the second node (e.g.,GSfor ground stations).Min_elevation_angle: Minimum elevation angle for the link in degrees.Threshold: Maximum distance for the link in meters.Data_rate: Data rate of the link in Mbit/s.Packet_loss: Percentage of packet loss for the link.Correlated_losses: Percentage of correlated packet losses (e.g., burst losses).
- Ensure that the TLE file specified in the
SpaceSegmentsection exists and contains valid TLE data.sample.tlecontains a sample TLE file. - The
clone_VMsection is optional and only required if you are cloning virtual machines for the emulation. - The
Channelssection allows you to define multiple communication links between nodes. - libvirt implements DHCP, so if you want to avoid modifying the configuration of each VM to allocate a static IP:
- Get the MAC address of the VM:
$ virsh domiflist <VM_name> - Apply the DHCP rule:
$ virsh net-edit default
<dhcp> ... <host mac="<VM_MAC>" name="VM_name" ip="<VM_static_IP>"/>
- Restart the network, in this example
default:$ virsh net-destroy defaultand$ virsh net-start default - Reboot the VM:
$ virsh shutdown <VM_name>and$ virsh start <VM_name>
- Get the MAC address of the VM:
- Be careful if a kernel-level VPN is up in the host, as it will affect the routing table and make VMs not reachable. In that case, consider disabling the VPN.
Libvirt Dashboard utilizes Prometheus to visualize metrics of VMs in Grafana.
Install Prometheus
$ sudo apt install prometheus
$ sudo systemctl status prometheus
Install Grafana
$ sudo apt-get install -y apt-transport-https software-properties-common wget
$ sudo mkdir -p /etc/apt/keyrings/
$ echo "deb [signed-by=/etc/apt/keyrings/grafana.gpg] https://apt.grafana.com stable main" | sudo tee -a /etc/apt/sources.list.d/grafana.list
$ echo "deb [signed-by=/etc/apt/keyrings/grafana.gpg] https://apt.grafana.com beta main" | sudo tee -a /etc/apt/sources.list.d/grafana.list
$ sudo apt-get update
$ sudo apt-get install grafana
$ sudo systemctl daemon-reload
$ sudo systemctl start grafana-server
$ sudo systemctl enable grafana-server
$ sudo systemctl status grafana-server
Install prometheus-libvirt-exporter (https://github.com/zhangjianweibj/prometheus-libvirt-exporter)
$ sudo apt install golang-go
$ go install github.com/goreleaser/goreleaser@latest
$ go install github.com/go-task/task/v3/cmd/task@latest
$ git clone https://github.com/zhangjianweibj/prometheus-libvirt-exporter.git
$ cd prometheus-libvirt-exporter/
$ go mod tidy
$ go mod vendor
$ go build ./...
$ go build
Configure Prometheus
$ nano /etc/prometheus/prometheus.yml
At the end of the file /etc/prometheus/prometheus.yml (within scrape_configs:), add the following:
- job_name: 'libvirt_exporter'
static_configs:
- targets: ['localhost:9000']
$ sudo systemctl restart prometheus
Add a new data source connection of type Prometheus to Grafana. Include http://localhost:9090 as the connection URL.
Download or copy the dashboard in JSON: https://grafana.com/grafana/dashboards/15682-libvirt/
Import or paste it in Grafana
Run prometheus-libvirt-exporter to start capturing metrics (Grafana will visualize them automatically)
$ cd prometheus-libvirt-exporter
$ ./prometheus-libvirt-exporter
Developed within i2-22-RDI-IoT A2 DSS Sim. Aquest projecte ha rebut finançament per part del Govern de la Generalitat de Catalunya dins del marc de l'estrategia NewSpace a Catalunya.
Developed by Fundació Privada Internet i Innovació Digital a Catalunya (i2CAT). Find more information at https://i2cat.net/tech-transfer/
Licensed under the GNU AFFERO GENERAL PUBLIC LICENSE. See https://www.gnu.org/licenses/agpl-3.0.en.html.
For licensing enquiries: techtransfer@i2cat.net
