gr-mcp/src/gnuradio_mcp/providers/mcp_runtime.py
Ryan Malloy 2084c41228 runtime: Phase 1 Docker + XML-RPC control
Add RuntimeProvider with 17 MCP tools for controlling running flowgraphs:
- Container lifecycle: launch, list, stop, remove
- Connection: connect by URL or container name
- Variable control: list, get, set via XML-RPC introspection
- Flowgraph execution: start, stop, lock, unlock
- Visual feedback: screenshot capture, container logs

Docker is optional - 10 tools work without it for external flowgraphs.

Includes:
- DockerMiddleware wrapping docker.DockerClient
- XmlRpcMiddleware wrapping xmlrpc.client.ServerProxy
- Dockerfile with Xvfb + ImageMagick + VNC for headless QT
- 29 new unit tests (71 total)
2026-01-27 09:48:44 -07:00

67 lines
2.2 KiB
Python

from __future__ import annotations
import logging
from fastmcp import FastMCP
from gnuradio_mcp.middlewares.docker import DockerMiddleware
from gnuradio_mcp.providers.runtime import RuntimeProvider
logger = logging.getLogger(__name__)
class McpRuntimeProvider:
"""Registers runtime control tools with FastMCP.
Docker is optional: if unavailable, container lifecycle and visual
feedback tools are skipped, but XML-RPC connection/control tools
are still registered (for connecting to externally-managed flowgraphs).
"""
def __init__(self, mcp_instance: FastMCP, runtime_provider: RuntimeProvider):
self._mcp = mcp_instance
self._provider = runtime_provider
self.__init_tools()
def __init_tools(self):
p = self._provider
# Connection management (always available)
self._mcp.tool(p.connect)
self._mcp.tool(p.disconnect)
self._mcp.tool(p.get_status)
# Variable control (always available)
self._mcp.tool(p.list_variables)
self._mcp.tool(p.get_variable)
self._mcp.tool(p.set_variable)
# Flowgraph execution (always available)
self._mcp.tool(p.start)
self._mcp.tool(p.stop)
self._mcp.tool(p.lock)
self._mcp.tool(p.unlock)
# Docker-dependent tools
if p._has_docker:
self._mcp.tool(p.launch_flowgraph)
self._mcp.tool(p.list_containers)
self._mcp.tool(p.stop_flowgraph)
self._mcp.tool(p.remove_flowgraph)
self._mcp.tool(p.connect_to_container)
self._mcp.tool(p.capture_screenshot)
self._mcp.tool(p.get_container_logs)
logger.info("Registered 17 runtime tools (Docker available)")
else:
logger.info(
"Registered 10 runtime tools (Docker unavailable, "
"container tools skipped)"
)
@classmethod
def create(cls, mcp_instance: FastMCP) -> McpRuntimeProvider:
"""Factory: create RuntimeProvider with optional Docker support."""
docker_mw = DockerMiddleware.create()
provider = RuntimeProvider(docker_mw=docker_mw)
return cls(mcp_instance, provider)