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
dockergroup (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: TrueLaunch a Flowgraph
-
Generate the Python script
Docker containers run
.pyfiles, not.grcfiles. Generate code first:generate_code(output_dir="/tmp")# Creates /tmp/fm_receiver.py -
Launch in a container
launch_flowgraph(flowgraph_path="/tmp/fm_receiver.py",name="fm-radio",xmlrpc_port=8080, # Enable XML-RPC on this portenable_vnc=True # Optional: VNC server on port 5900)Returns a
ContainerModelwith:name: Container namestatus: “running”xmlrpc_port: Mapped port numbervnc_port: VNC port (if enabled)
-
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 variableslist_variables()# Returns: [VariableModel(name="freq", value=101100000.0), ...]
# Read a variableget_variable(name="freq")# Returns: 101100000.0
# Tune to a different stationset_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 processingVisual 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: 768VNC Access
If launched with enable_vnc=True, connect with any VNC client:
# Using TigerVNCvncviewer localhost:5900
# Using Remminaremmina -c vnc://localhost:5900Container Logs
Check for errors or debug output:
get_container_logs(name="fm-radio", tail=50)# Returns last 50 lines of stdout/stderrContainer Lifecycle
# List all GR-MCP containerslist_containers()
# Stop a running flowgraph (graceful shutdown)stop_flowgraph(name="fm-radio")
# Remove the containerremove_flowgraph(name="fm-radio")
# Force remove if stuckremove_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"]device_paths=["/dev/bus/usb"]device_paths=["/dev/bus/usb"]device_paths=["/dev/ttyUSB0"]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:
- Analyzes the flowgraph for OOT imports
- Checks if required modules are installed
- Builds missing modules from the OOT catalog
- 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 asyncioimport timefrom 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
- Runtime Control Guide — Advanced XML-RPC patterns
- ControlPort Monitoring — Performance metrics via Thrift
- Code Coverage — Collect coverage from containerized runs