- Starlight docs: 28 pages covering getting started, guides, tool reference, concepts (architecture, dynamic tools, runtime comms) - LoRa examples: channel scanner, quality analyzer, multi-SF receiver with both .grc and .py forms, plus ADSB+LoRa combo test - .gitignore: exclude generated artifacts (*_patched_*.py, *.wav, docs build cache, tests/scratch/) - Add .mcp.json for local MCP server config - Sync uv.lock with date-based version
356 lines
10 KiB
Python
356 lines
10 KiB
Python
#!/usr/bin/env python3
|
|
"""Generate MDX documentation from GR-MCP tool docstrings.
|
|
|
|
Usage:
|
|
cd docs
|
|
python scripts/generate-api-docs.py
|
|
|
|
This script introspects the PlatformProvider and RuntimeProvider classes
|
|
to extract tool signatures and docstrings, then generates MDX files for
|
|
the Starlight documentation site.
|
|
"""
|
|
|
|
import inspect
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import get_type_hints
|
|
|
|
# Add project root to path
|
|
PROJECT_ROOT = Path(__file__).parent.parent.parent
|
|
sys.path.insert(0, str(PROJECT_ROOT))
|
|
|
|
# Import providers
|
|
from gnuradio_mcp.providers.base import PlatformProvider
|
|
from gnuradio_mcp.providers.runtime import RuntimeProvider
|
|
|
|
OUTPUT_DIR = Path(__file__).parent.parent / "src/content/docs/reference/tools"
|
|
|
|
# Tool categorization
|
|
TOOL_CATEGORIES = {
|
|
"flowgraph": {
|
|
"title": "Flowgraph Tools",
|
|
"description": "Tools for managing flowgraph structure: blocks, connections, save/load.",
|
|
"tools": [
|
|
"get_blocks",
|
|
"make_block",
|
|
"remove_block",
|
|
"save_flowgraph",
|
|
"load_flowgraph",
|
|
"get_flowgraph_options",
|
|
"set_flowgraph_options",
|
|
"export_flowgraph_data",
|
|
"import_flowgraph_data",
|
|
],
|
|
},
|
|
"blocks": {
|
|
"title": "Block Tools",
|
|
"description": "Tools for block parameter and port management.",
|
|
"tools": [
|
|
"get_block_params",
|
|
"set_block_params",
|
|
"get_block_sources",
|
|
"get_block_sinks",
|
|
"bypass_block",
|
|
"unbypass_block",
|
|
],
|
|
},
|
|
"connections": {
|
|
"title": "Connection Tools",
|
|
"description": "Tools for wiring blocks together.",
|
|
"tools": [
|
|
"get_connections",
|
|
"connect_blocks",
|
|
"disconnect_blocks",
|
|
],
|
|
},
|
|
"validation": {
|
|
"title": "Validation Tools",
|
|
"description": "Tools for checking flowgraph validity.",
|
|
"tools": [
|
|
"validate_block",
|
|
"validate_flowgraph",
|
|
"get_all_errors",
|
|
],
|
|
},
|
|
"platform": {
|
|
"title": "Platform Tools",
|
|
"description": "Tools for discovering available blocks and managing OOT paths.",
|
|
"tools": [
|
|
"get_all_available_blocks",
|
|
"search_blocks",
|
|
"get_block_categories",
|
|
"load_oot_blocks",
|
|
"add_block_path",
|
|
"get_block_paths",
|
|
],
|
|
},
|
|
"codegen": {
|
|
"title": "Code Generation",
|
|
"description": "Tools for generating Python code and evaluating expressions.",
|
|
"tools": [
|
|
"generate_code",
|
|
"evaluate_expression",
|
|
"create_embedded_python_block",
|
|
],
|
|
},
|
|
"runtime-mode": {
|
|
"title": "Runtime Mode",
|
|
"description": "Tools for enabling/disabling runtime features and checking client capabilities.",
|
|
"tools": [
|
|
"get_runtime_mode",
|
|
"enable_runtime_mode",
|
|
"disable_runtime_mode",
|
|
"get_client_capabilities",
|
|
"list_client_roots",
|
|
],
|
|
},
|
|
"docker": {
|
|
"title": "Docker Tools",
|
|
"description": "Tools for container lifecycle management.",
|
|
"tools": [
|
|
"launch_flowgraph",
|
|
"list_containers",
|
|
"stop_flowgraph",
|
|
"remove_flowgraph",
|
|
"capture_screenshot",
|
|
"get_container_logs",
|
|
],
|
|
},
|
|
"xmlrpc": {
|
|
"title": "XML-RPC Tools",
|
|
"description": "Tools for XML-RPC connection and variable control.",
|
|
"tools": [
|
|
"connect",
|
|
"connect_to_container",
|
|
"disconnect",
|
|
"get_status",
|
|
"list_variables",
|
|
"get_variable",
|
|
"set_variable",
|
|
"start",
|
|
"stop",
|
|
"lock",
|
|
"unlock",
|
|
],
|
|
},
|
|
"controlport": {
|
|
"title": "ControlPort Tools",
|
|
"description": "Tools for ControlPort/Thrift connection and monitoring.",
|
|
"tools": [
|
|
"connect_controlport",
|
|
"connect_to_container_controlport",
|
|
"disconnect_controlport",
|
|
"get_knobs",
|
|
"set_knobs",
|
|
"get_knob_properties",
|
|
"get_performance_counters",
|
|
"post_message",
|
|
],
|
|
},
|
|
"coverage": {
|
|
"title": "Coverage Tools",
|
|
"description": "Tools for collecting Python code coverage from containers.",
|
|
"tools": [
|
|
"collect_coverage",
|
|
"generate_coverage_report",
|
|
"combine_coverage",
|
|
"delete_coverage",
|
|
],
|
|
},
|
|
"oot": {
|
|
"title": "OOT Tools",
|
|
"description": "Tools for OOT module detection and installation.",
|
|
"tools": [
|
|
"detect_oot_modules",
|
|
"install_oot_module",
|
|
"list_oot_images",
|
|
"remove_oot_image",
|
|
"build_multi_oot_image",
|
|
"list_combo_images",
|
|
"remove_combo_image",
|
|
],
|
|
},
|
|
}
|
|
|
|
|
|
def get_method_info(method):
|
|
"""Extract signature and docstring from a method."""
|
|
sig = inspect.signature(method)
|
|
doc = inspect.getdoc(method) or "No description available."
|
|
|
|
# Parse docstring sections
|
|
lines = doc.split("\n")
|
|
description = []
|
|
args = []
|
|
returns = ""
|
|
example = []
|
|
|
|
section = "description"
|
|
for line in lines:
|
|
stripped = line.strip()
|
|
if stripped.startswith("Args:"):
|
|
section = "args"
|
|
continue
|
|
elif stripped.startswith("Returns:"):
|
|
section = "returns"
|
|
continue
|
|
elif stripped.startswith("Example:") or stripped.startswith("Examples:"):
|
|
section = "example"
|
|
continue
|
|
|
|
if section == "description":
|
|
description.append(line)
|
|
elif section == "args":
|
|
args.append(line)
|
|
elif section == "returns":
|
|
returns += line + "\n"
|
|
elif section == "example":
|
|
example.append(line)
|
|
|
|
# Get parameter info from signature
|
|
params = []
|
|
for name, param in sig.parameters.items():
|
|
if name == "self":
|
|
continue
|
|
param_type = ""
|
|
if param.annotation != inspect.Parameter.empty:
|
|
param_type = str(param.annotation).replace("typing.", "")
|
|
default = ""
|
|
if param.default != inspect.Parameter.empty:
|
|
default = repr(param.default)
|
|
params.append({
|
|
"name": name,
|
|
"type": param_type,
|
|
"default": default,
|
|
})
|
|
|
|
# Get return type
|
|
return_type = ""
|
|
try:
|
|
hints = get_type_hints(method)
|
|
if "return" in hints:
|
|
return_type = str(hints["return"]).replace("typing.", "")
|
|
except Exception:
|
|
pass
|
|
|
|
return {
|
|
"name": method.__name__,
|
|
"description": "\n".join(description).strip(),
|
|
"params": params,
|
|
"returns": returns.strip(),
|
|
"return_type": return_type,
|
|
"args_doc": "\n".join(args).strip(),
|
|
"example": "\n".join(example).strip(),
|
|
}
|
|
|
|
|
|
def get_all_methods():
|
|
"""Get all tool methods from both providers."""
|
|
methods = {}
|
|
|
|
# Get PlatformProvider methods
|
|
for name, method in inspect.getmembers(PlatformProvider, predicate=inspect.isfunction):
|
|
if not name.startswith("_"):
|
|
methods[name] = get_method_info(method)
|
|
|
|
# Get RuntimeProvider methods
|
|
for name, method in inspect.getmembers(RuntimeProvider, predicate=inspect.isfunction):
|
|
if not name.startswith("_"):
|
|
methods[name] = get_method_info(method)
|
|
|
|
return methods
|
|
|
|
|
|
def generate_mdx(category_key: str, category: dict, methods: dict) -> str:
|
|
"""Generate MDX content for a category."""
|
|
lines = [
|
|
"---",
|
|
f'title: {category["title"]}',
|
|
f'description: {category["description"]}',
|
|
"---",
|
|
"",
|
|
f'{category["description"]}',
|
|
"",
|
|
]
|
|
|
|
for tool_name in category["tools"]:
|
|
if tool_name not in methods:
|
|
lines.append(f"## `{tool_name}`")
|
|
lines.append("")
|
|
lines.append("*Documentation pending.*")
|
|
lines.append("")
|
|
continue
|
|
|
|
info = methods[tool_name]
|
|
|
|
lines.append(f"## `{tool_name}`")
|
|
lines.append("")
|
|
lines.append(info["description"])
|
|
lines.append("")
|
|
|
|
# Parameters table
|
|
if info["params"]:
|
|
lines.append("### Parameters")
|
|
lines.append("")
|
|
lines.append("| Name | Type | Default | Description |")
|
|
lines.append("|------|------|---------|-------------|")
|
|
for param in info["params"]:
|
|
default = param["default"] if param["default"] else "-"
|
|
ptype = param["type"] if param["type"] else "-"
|
|
# Extract description from args_doc if available
|
|
desc = "-"
|
|
if info["args_doc"]:
|
|
for arg_line in info["args_doc"].split("\n"):
|
|
if arg_line.strip().startswith(f"{param['name']}:"):
|
|
desc = arg_line.split(":", 1)[1].strip()
|
|
break
|
|
lines.append(f"| `{param['name']}` | `{ptype}` | `{default}` | {desc} |")
|
|
lines.append("")
|
|
|
|
# Returns
|
|
if info["returns"] or info["return_type"]:
|
|
lines.append("### Returns")
|
|
lines.append("")
|
|
if info["return_type"]:
|
|
lines.append(f"**Type:** `{info['return_type']}`")
|
|
lines.append("")
|
|
if info["returns"]:
|
|
lines.append(info["returns"])
|
|
lines.append("")
|
|
|
|
# Example
|
|
if info["example"]:
|
|
lines.append("### Example")
|
|
lines.append("")
|
|
lines.append("```python")
|
|
lines.append(info["example"])
|
|
lines.append("```")
|
|
lines.append("")
|
|
|
|
lines.append("---")
|
|
lines.append("")
|
|
|
|
return "\n".join(lines)
|
|
|
|
|
|
def main():
|
|
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
|
|
|
print("Extracting methods from providers...")
|
|
methods = get_all_methods()
|
|
print(f"Found {len(methods)} methods")
|
|
|
|
for category_key, category in TOOL_CATEGORIES.items():
|
|
print(f"Generating {category_key}.mdx...")
|
|
content = generate_mdx(category_key, category, methods)
|
|
output_path = OUTPUT_DIR / f"{category_key}.mdx"
|
|
output_path.write_text(content)
|
|
print(f" Wrote {output_path}")
|
|
|
|
print("\nDone! Generated MDX files in:")
|
|
print(f" {OUTPUT_DIR}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|