Skip to content

Running in Docker

GR-MCP can run flowgraphs in Docker containers, providing isolation, headless GUI rendering (via Xvfb), and real-time control through XML-RPC or ControlPort.

Prerequisites

  • Docker daemon running (sudo systemctl start docker)
  • User in the docker group (sudo usermod -aG docker $USER)
  • Base runtime image built (see Installation)

Enable Runtime Mode

Runtime tools are not loaded by default to minimize context usage. Enable them first:

enable_runtime_mode()
# Returns RuntimeModeStatus with:
# enabled: True
# tools_registered: ["launch_flowgraph", "connect_to_container", ...]
# docker_available: True
# oot_available: True

Launch a Flowgraph

  1. Generate the Python script

    Docker containers run .py files, not .grc files. Generate code first:

    generate_code(output_dir="/tmp")
    # Creates /tmp/fm_receiver.py
  2. Launch in a container

    launch_flowgraph(
    flowgraph_path="/tmp/fm_receiver.py",
    name="fm-radio",
    xmlrpc_port=8080, # Enable XML-RPC on this port
    enable_vnc=True # Optional: VNC server on port 5900
    )

    Returns a ContainerModel with:

    • name: Container name
    • status: “running”
    • xmlrpc_port: Mapped port number
    • vnc_port: VNC port (if enabled)
  3. Connect to the running flowgraph

    connect_to_container(name="fm-radio")

    This auto-discovers the XML-RPC port from container labels.

Real-Time Variable Control

Once connected, you can read and modify flowgraph variables:

# List available variables
list_variables()
# Returns: [VariableModel(name="freq", value=101100000.0), ...]
# Read a variable
get_variable(name="freq")
# Returns: 101100000.0
# Tune to a different station
set_variable(name="freq", value=98.5e6)

Thread-Safe Updates

For multiple parameter changes, lock the flowgraph first:

lock() # Pause processing
set_variable(name="freq", value=102.7e6)
set_variable(name="gain", value=35)
unlock() # Resume processing

Visual Feedback

Screenshots

Capture the QT GUI display:

screenshot = capture_screenshot(name="fm-radio")
# Returns ScreenshotModel with:
# path: "/tmp/gr-mcp-screenshots/fm-radio-2024-01-15-14-30-00.png"
# width: 1024
# height: 768

VNC Access

If launched with enable_vnc=True, connect with any VNC client:

Terminal window
# Using TigerVNC
vncviewer localhost:5900
# Using Remmina
remmina -c vnc://localhost:5900

Container Logs

Check for errors or debug output:

get_container_logs(name="fm-radio", tail=50)
# Returns last 50 lines of stdout/stderr

Container Lifecycle

# List all GR-MCP containers
list_containers()
# Stop a running flowgraph (graceful shutdown)
stop_flowgraph(name="fm-radio")
# Remove the container
remove_flowgraph(name="fm-radio")
# Force remove if stuck
remove_flowgraph(name="fm-radio", force=True)

Flowgraph Execution Control

Control the flowgraph’s execution state via XML-RPC:

stop() # Pause execution (flowgraph.stop())
start() # Resume execution (flowgraph.start())

Using SDR Hardware

Pass device paths to access hardware inside the container:

launch_flowgraph(
flowgraph_path="/tmp/fm_receiver.py",
name="fm-hardware",
device_paths=["/dev/bus/usb"] # RTL-SDR access
)
device_paths=["/dev/bus/usb"]

Auto-Image Selection

For flowgraphs using OOT modules, let GR-MCP auto-detect and build the required image:

launch_flowgraph(
flowgraph_path="lora_rx.py",
auto_image=True # Detects gr-lora_sdr, builds/uses appropriate image
)

This:

  1. Analyzes the flowgraph for OOT imports
  2. Checks if required modules are installed
  3. Builds missing modules from the OOT catalog
  4. Creates a combo image if multiple OOT modules are needed

Complete Example

#!/usr/bin/env python3
"""Launch and control an FM receiver via Docker."""
import asyncio
import time
from fastmcp import Client
async def main():
async with Client("gr-mcp") as client:
# Enable runtime tools
await client.call_tool("enable_runtime_mode", {})
# Launch container
result = await client.call_tool("launch_flowgraph", {
"flowgraph_path": "/tmp/fm_receiver.py",
"name": "fm-demo",
"xmlrpc_port": 8080,
"enable_vnc": True
})
print(f"Container: {result.data.name}")
# Wait for startup
time.sleep(3)
# Connect
await client.call_tool("connect_to_container", {"name": "fm-demo"})
# Scan FM band
for freq in [88.1e6, 95.5e6, 101.1e6, 107.9e6]:
await client.call_tool("set_variable", {
"name": "freq",
"value": freq
})
print(f"Tuned to {freq/1e6:.1f} MHz")
time.sleep(2)
# Capture final state
await client.call_tool("capture_screenshot", {"name": "fm-demo"})
# Cleanup
await client.call_tool("stop_flowgraph", {"name": "fm-demo"})
await client.call_tool("remove_flowgraph", {"name": "fm-demo"})
if __name__ == "__main__":
asyncio.run(main())

Next Steps