#!/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()