Architecture
GR-MCP follows a Middleware + Provider pattern that abstracts GNU Radio’s internal objects into clean, serializable models suitable for the MCP protocol.
High-Level Overview
┌─────────────────────────────────────────────────────────────────┐│ MCP Client ││ (Claude, Cursor, etc.) │└─────────────────────────────────────────────────────────────────┘ │ │ MCP Protocol (stdio/SSE) ▼┌─────────────────────────────────────────────────────────────────┐│ FastMCP App ││ (server.py) │└─────────────────────────────────────────────────────────────────┘ │ ┌───────────────┴───────────────┐ ▼ ▼ ▼┌───────────────────────┐ ┌───────────────────────┐ ┌───────────────────────┐│ McpPlatformProvider │ │ McpBlockDevProvider │ │ McpRuntimeProvider ││ (~30 tools, always) │ │ (~18 tools, dynamic) │ │ (~40 tools, dynamic) ││ │ │ │ │ ││ • get_blocks │ │ enable_block_dev_mode │ │ enable_runtime_mode ││ • make_block │ │ generate_sync_block │ │ launch_flowgraph ││ • connect_blocks │ │ validate_block_code │ │ connect_to_container ││ • validate_flowgraph │ │ generate_grc_yaml │ │ set_variable ││ • generate_code │ │ export_block_to_oot │ │ install_oot_module ││ • ... │ │ ... │ │ ... │└───────────────────────┘ └───────────────────────┘ └───────────────────────┘ │ │ │ ▼ ▼ ▼┌───────────────────────┐ ┌───────────────────────┐ ┌───────────────────────┐│ PlatformProvider │ │ BlockDevProvider │ │ RuntimeProvider ││ (Business Logic) │ │ (Business Logic) │ │ (Business Logic) │└───────────────────────┘ └───────────────────────┘ └───────────────────────┘ │ │ │ ▼ ▼ ▼┌───────────────────────┐ ┌───────────────────────┐ ┌───────────────────────┐│ Middlewares │ │ Middlewares │ │ Middlewares ││ │ │ │ │ ││ • PlatformMiddleware │ │ • BlockGenerator │ │ • DockerMiddleware ││ • FlowGraphMiddleware │ │ • ProtocolAnalyzer │ │ • XmlRpcMiddleware ││ • BlockMiddleware │ │ • OOTExporter │ │ • ThriftMiddleware ││ │ │ │ │ • OOTInstaller │└───────────────────────┘ └───────────────────────┘ └───────────────────────┘ │ │ │ ▼ ▼ ▼┌───────────────────────┐ ┌───────────────────────┐ ┌───────────────────────┐│ GNU Radio Objects │ │ Code Generation │ │ External Services ││ │ │ │ │ ││ • Platform │ │ • AST validation │ │ • Docker daemon ││ • FlowGraph │ │ • Prompt templates │ │ • XML-RPC servers ││ • Block │ │ • GRC YAML templates │ │ • Thrift servers ││ • Connections │ │ • OOT scaffolding │ │ • Container processes │└───────────────────────┘ └───────────────────────┘ └───────────────────────┘Layer Responsibilities
MCP Layer (FastMCP)
- Handles MCP protocol communication
- Registers tools and resources
- Manages the server lifecycle
Provider Layer
Business logic that doesn’t know about MCP:
- PlatformProvider: All flowgraph operations (create, edit, validate, save)
- BlockDevProvider: Block generation, protocol analysis, OOT export
- RuntimeProvider: All runtime operations (launch, connect, control)
Middleware Layer
Wraps external systems with validation and normalization:
- PlatformMiddleware: Wraps GNU Radio’s
Platformclass - FlowGraphMiddleware: Wraps
FlowGraphwith block/connection management - BlockMiddleware: Wraps
Blockwith parameter/port access - BlockGeneratorMiddleware: Code generation and validation for custom blocks
- ProtocolAnalyzerMiddleware: Protocol parsing, decoder chain generation, IQ analysis
- OOTExporterMiddleware: OOT skeleton creation, block export, GRC YAML generation
- DockerMiddleware: Wraps Docker SDK operations
- XmlRpcMiddleware: Wraps XML-RPC client
- ThriftMiddleware: Wraps ControlPort Thrift client
- OOTInstallerMiddleware: Manages OOT module builds
Models Layer
Pydantic models for serialization:
# Examplesclass BlockModel(BaseModel): name: str key: str state: str
class ParamModel(BaseModel): key: str value: str name: str dtype: str
class PortModel(BaseModel): parent: str key: str name: str dtype: str direction: strData Flow
Flowgraph Operations
Tool Call: make_block(block_type="osmosdr_source") │ ▼McpPlatformProvider.make_block() │ ▼PlatformProvider.make_block(block_name="osmosdr_source") │ ▼FlowGraphMiddleware.add_block("osmosdr_source") │ Creates GNU Radio Block object │ Generates unique name ▼BlockMiddleware(block) │ ▼Return: "osmosdr_source_0" (str)Runtime Operations
Tool Call: set_variable(name="freq", value=101.1e6) │ ▼McpRuntimeProvider._runtime_tools["set_variable"] │ ▼RuntimeProvider.set_variable(name="freq", value=101.1e6) │ ▼XmlRpcMiddleware.set_variable("freq", 101.1e6) │ XML-RPC call to running flowgraph ▼Return: TrueDynamic Tool Registration
Both runtime and block dev tools are registered dynamically to minimize context usage:
# At startup: ~38 always-on tools (platform + mode control)get_runtime_mode() # Check runtime statusenable_runtime_mode() # Register runtime toolsdisable_runtime_mode() # Unregister runtime toolsget_block_dev_mode() # Check block dev statusenable_block_dev_mode() # Register block dev toolsdisable_block_dev_mode() # Unregister block dev toolsget_client_capabilities() # Debug client infolist_client_roots() # Debug client roots
# After enable_block_dev_mode(): ~18 additional toolsgenerate_sync_block()validate_block_code()generate_grc_yaml()export_block_to_oot()# ...etc
# After enable_runtime_mode(): ~40 additional toolslaunch_flowgraph()connect_to_container()set_variable()# ...etcKey Design Decisions
Why Middlewares?
GNU Radio objects have complex internal state and non-serializable references. Middlewares:
- Provide a clean interface with Pydantic models
- Handle validation and error formatting
- Manage resource lifecycle (connections, processes)
Why Providers?
Providers separate business logic from MCP registration:
- Testable without MCP infrastructure
- Reusable in non-MCP contexts
- Clear dependency injection
Why Dynamic Registration?
Many MCP clients include all tool descriptions in the system prompt. With 70+ tools, this wastes significant context. Dynamic registration:
- Starts with minimal tools for flowgraph design
- Expands when runtime control is needed
- Can contract back when done
File Organization
src/gnuradio_mcp/├── server.py # FastMCP app entry point + Platform init├── models.py # All Pydantic models├── utils.py # Helper functions├── oot_catalog.py # OOT module catalog├── middlewares/│ ├── platform.py # PlatformMiddleware│ ├── flowgraph.py # FlowGraphMiddleware│ ├── block.py # BlockMiddleware│ ├── block_generator.py # BlockGeneratorMiddleware│ ├── protocol_analyzer.py # ProtocolAnalyzerMiddleware│ ├── oot_exporter.py # OOTExporterMiddleware│ ├── docker.py # DockerMiddleware│ ├── xmlrpc.py # XmlRpcMiddleware│ ├── thrift.py # ThriftMiddleware│ └── oot.py # OOTInstallerMiddleware├── prompts/ # LLM prompt templates for block generation└── providers/ ├── base.py # PlatformProvider ├── mcp.py # McpPlatformProvider ├── block_dev.py # BlockDevProvider ├── mcp_block_dev.py # McpBlockDevProvider ├── runtime.py # RuntimeProvider └── mcp_runtime.py # McpRuntimeProvider