Dynamic Tools
GR-MCP uses dynamic tool registration to minimize context usage. Runtime and block development tools are only registered when needed, keeping the tool list small during flowgraph design.
The Problem
Many MCP clients include tool descriptions in the system prompt. With 70+ tools, this can consume significant context:
70 tools × ~100 tokens/tool = ~7,000 tokensFor LLM applications, this is wasteful when you only need flowgraph design tools.
The Solution
GR-MCP splits tools into three groups:
Always Available (~38 tools)
Platform tools for flowgraph design:
get_blocks,make_block,remove_blockconnect_blocks,disconnect_blocksvalidate_flowgraph,generate_code- etc.
Plus mode control tools (always registered):
get_runtime_mode,enable_runtime_mode,disable_runtime_modeget_block_dev_mode,enable_block_dev_mode,disable_block_dev_modeget_client_capabilities,list_client_roots
Block Dev Tools (~18 tools)
Loaded via enable_block_dev_mode():
- Generation:
generate_sync_block,generate_basic_block,generate_interp_block,generate_decim_block - Validation:
validate_block_code,parse_block_prompt - Testing:
test_block_in_docker - Protocol:
parse_protocol_spec,generate_decoder_chain - Signal:
analyze_iq_file - Export:
generate_grc_yaml,generate_oot_skeleton,export_block_to_oot
Runtime Tools (~40 tools)
Loaded via enable_runtime_mode():
- Container lifecycle:
launch_flowgraph,stop_flowgraph - XML-RPC:
connect,set_variable,get_variable - ControlPort:
connect_controlport,get_knobs,get_performance_counters - Coverage:
collect_coverage,generate_coverage_report - OOT:
install_oot_module,detect_oot_modules
Usage Pattern
-
Flowgraph Design (~38 always-on tools)
make_block(block_type="osmosdr_source")connect_blocks(...)validate_flowgraph()generate_code(output_dir="/tmp") -
Enable Block Dev (adds ~18 tools)
enable_block_dev_mode()# Generate blocks, analyze protocols, export to OOTgenerate_sync_block(name="my_gain", description="Multiply by gain",input_signature=[{"dtype": "float", "vlen": 1}],output_signature=[{"dtype": "float", "vlen": 1}],)generate_grc_yaml(source_code="...", block_name="my_gain") -
Enable Runtime (adds ~40 more tools)
enable_runtime_mode()# Now runtime tools are availablelaunch_flowgraph(flowgraph_path="/tmp/fm.py")connect_to_container(name="gr-fm")set_variable(name="freq", value=101.1e6) -
Scale Down (disable either group independently)
disable_runtime_mode() # Removes runtime toolsdisable_block_dev_mode() # Removes block dev tools
Implementation
Mode Control Tools
@self._mcp.tooldef enable_runtime_mode() -> RuntimeModeStatus: """Enable runtime mode, registering all runtime control tools.""" if self._runtime_enabled: return RuntimeModeStatus(enabled=True, ...)
self._register_runtime_tools() self._runtime_enabled = True return RuntimeModeStatus(enabled=True, tools_registered=[...])Dynamic Registration
def _register_runtime_tools(self): """Dynamically register all runtime tools.""" p = self._provider
# Connection management self._add_tool("connect", p.connect) self._add_tool("disconnect", p.disconnect) self._add_tool("get_status", p.get_status)
# Variable control self._add_tool("list_variables", p.list_variables) self._add_tool("get_variable", p.get_variable) self._add_tool("set_variable", p.set_variable)
# ... more tools ...
# Docker-dependent tools (only if Docker available) if p._has_docker: self._add_tool("launch_flowgraph", p.launch_flowgraph) # ...
def _add_tool(self, name: str, func: Callable): """Add a tool and track it for later removal.""" self._mcp.add_tool(func) self._runtime_tools[name] = funcDynamic Unregistration
def _unregister_runtime_tools(self): """Remove all dynamically registered runtime tools.""" for name in list(self._runtime_tools.keys()): self._mcp.remove_tool(name) self._runtime_tools.clear()Conditional Registration
Some tools depend on external services:
# Docker-dependent toolsif p._has_docker: self._add_tool("launch_flowgraph", p.launch_flowgraph) self._add_tool("capture_screenshot", p.capture_screenshot)
# OOT tools require Docker for building if p._has_oot: self._add_tool("install_oot_module", p.install_oot_module)Checking Status
status = get_runtime_mode()# Returns: RuntimeModeStatus(# enabled=False,# tools_registered=[],# docker_available=True,# oot_available=True# )
enable_runtime_mode()status = get_runtime_mode()# Returns: RuntimeModeStatus(# enabled=True,# tools_registered=[# "connect", "disconnect", "get_status",# "list_variables", "get_variable", "set_variable",# "start", "stop", "lock", "unlock",# "launch_flowgraph", "list_containers",# ...# ],# docker_available=True,# oot_available=True# )Benefits
- Reduced Context — ~38 tools instead of ~90+ during design
- Faster Responses — Less prompt processing for simple queries
- Clear Separation — Design, generation, and runtime are explicit phases
- Independent Modes — Enable block dev and runtime separately or together
- Graceful Degradation — Missing Docker doesn’t break design or generation tools
- Discovery —
get_runtime_mode()andget_block_dev_mode()show what’s available