home_manager_list_options
Discover and explore all Home Manager option categories with their respective counts. This tool provides a sorted, plain-text list to help users navigate and understand available configuration options efficiently.
Instructions
List all Home Manager option categories.
Enumerates all top-level categories with their option counts.
Returns: Plain text list of categories sorted alphabetically with option counts
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||
Implementation Reference
- mcp_nixos/server.py:792-866 (handler)Main handler function for the 'home_manager_list_options' MCP tool. Parses Home Manager HTML documentation to extract and categorize top-level options, returning a formatted list of categories with counts.async def home_manager_list_options() -> str: """List all Home Manager option categories. Enumerates all top-level categories with their option counts. Returns: Plain text list of categories sorted alphabetically with option counts """ try: # Get more options to see all categories (default 100 is too few) options = parse_html_options(HOME_MANAGER_URL, limit=5000) categories: dict[str, int] = {} for opt in options: name = opt["name"] # Process option names if name and not name.startswith("."): if "." in name: cat = name.split(".")[0] else: cat = name # Option without dot is its own category # Valid categories should: # - Be more than 1 character # - Be a valid identifier (allows underscores) # - Not be common value words # - Match typical nix option category patterns if ( len(cat) > 1 and cat.isidentifier() and (cat.islower() or cat.startswith("_")) ): # This ensures valid identifier # Additional filtering for known valid categories valid_categories = { "accounts", "dconf", "editorconfig", "fonts", "gtk", "home", "i18n", "launchd", "lib", "manual", "news", "nix", "nixgl", "nixpkgs", "pam", "programs", "qt", "services", "specialisation", "systemd", "targets", "wayland", "xdg", "xresources", "xsession", } # Only include if it's in the known valid list or looks like a typical category if cat in valid_categories or (len(cat) >= 3 and not any(char.isdigit() for char in cat)): categories[cat] = categories.get(cat, 0) + 1 results = [] results.append(f"Home Manager option categories ({len(categories)} total):\n") # Sort by count descending, then alphabetically sorted_cats = sorted(categories.items(), key=lambda x: (-x[1], x[0])) for cat, count in sorted_cats: results.append(f"• {cat} ({count} options)") return "\n".join(results) except Exception as e: return error(str(e))
- mcp_nixos/server.py:247-336 (helper)Helper function to parse HTML documentation pages for NixOS/Home Manager/nix-darwin options. Extracts option names, types, and descriptions from <dt>/<dd> pairs. Used by home_manager_list_options to fetch all options for categorization.def parse_html_options(url: str, query: str = "", prefix: str = "", limit: int = 100) -> list[dict[str, str]]: """Parse options from HTML documentation.""" try: resp = requests.get(url, timeout=30) # Increase timeout for large docs resp.raise_for_status() # Use resp.content to let BeautifulSoup handle encoding detection # This prevents encoding errors like "unknown encoding: windows-1252" soup = BeautifulSoup(resp.content, "html.parser") options = [] # Get all dt elements dts = soup.find_all("dt") for dt in dts: # Get option name name = "" if "home-manager" in url: # Home Manager uses anchor IDs like "opt-programs.git.enable" anchor = dt.find("a", id=True) if anchor: anchor_id = anchor.get("id", "") # Remove "opt-" prefix and convert underscores if anchor_id.startswith("opt-"): name = anchor_id[4:] # Remove "opt-" prefix # Convert _name_ placeholders back to <name> name = name.replace("_name_", "<name>") else: # Fallback to text content name_elem = dt.find(string=True, recursive=False) if name_elem: name = name_elem.strip() else: name = dt.get_text(strip=True) else: # Darwin and fallback - use text content name = dt.get_text(strip=True) # Skip if it doesn't look like an option (must contain a dot) # But allow single-word options in some cases if "." not in name and len(name.split()) > 1: continue # Filter by query or prefix if query and query.lower() not in name.lower(): continue if prefix and not (name.startswith(prefix + ".") or name == prefix): continue # Find the corresponding dd element dd = dt.find_next_sibling("dd") if dd: # Extract description (first p tag or direct text) desc_elem = dd.find("p") if desc_elem: description = desc_elem.get_text(strip=True) else: # Get first text node, handle None case text = dd.get_text(strip=True) description = text.split("\n")[0] if text else "" # Extract type info - look for various patterns type_info = "" # Pattern 1: <span class="term">Type: ...</span> type_elem = dd.find("span", class_="term") if type_elem and "Type:" in type_elem.get_text(): type_info = type_elem.get_text(strip=True).replace("Type:", "").strip() # Pattern 2: Look for "Type:" in text elif "Type:" in dd.get_text(): text = dd.get_text() type_start = text.find("Type:") + 5 type_end = text.find("\n", type_start) if type_end == -1: type_end = len(text) type_info = text[type_start:type_end].strip() options.append( { "name": name, "description": description[:200] if len(description) > 200 else description, "type": type_info, } ) if len(options) >= limit: break return options except Exception as exc: raise DocumentParseError(f"Failed to fetch docs: {str(exc)}") from exc