nixhub_package_versions
Retrieve version history and nixpkgs commit hashes for a specific NixOS package to ensure reproducible builds. Query by package name and limit results for precise version tracking.
Instructions
Get version history and nixpkgs commit hashes for a specific package from NixHub.io.
Use this tool when users need specific package versions or commit hashes for reproducible builds.
Args: package_name: Name of the package to query (e.g., "firefox", "python") limit: Maximum number of versions to return (default: 10, max: 50)
Returns: Plain text with package info and version history including commit hashes
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| package_name | Yes |
Implementation Reference
- mcp_nixos/server.py:1601-1694 (handler)Main handler function for the nixhub_package_versions tool. Fetches package version history from NixHub.io API, validates inputs, handles errors, formats output with commit hashes, attribute paths, and usage instructions.async def nixhub_package_versions(package_name: str, limit: int = 10) -> str: """Get version history and nixpkgs commit hashes for a specific package from NixHub.io. Use this tool when users need specific package versions or commit hashes for reproducible builds. Args: package_name: Name of the package to query (e.g., "firefox", "python") limit: Maximum number of versions to return (default: 10, max: 50) Returns: Plain text with package info and version history including commit hashes """ # Validate inputs if not package_name or not package_name.strip(): return error("Package name is required") # Sanitize package name - only allow alphanumeric, hyphens, underscores, dots if not re.match(r"^[a-zA-Z0-9\-_.]+$", package_name): return error("Invalid package name. Only letters, numbers, hyphens, underscores, and dots are allowed") if not 1 <= limit <= 50: return error("Limit must be between 1 and 50") try: # Construct NixHub API URL with the _data parameter url = f"https://www.nixhub.io/packages/{package_name}?_data=routes%2F_nixhub.packages.%24pkg._index" # Make request with timeout and proper headers headers = {"Accept": "application/json", "User-Agent": "mcp-nixos/1.0.0"} # Identify ourselves resp = requests.get(url, headers=headers, timeout=15) # Handle different HTTP status codes if resp.status_code == 404: return error(f"Package '{package_name}' not found in NixHub", "NOT_FOUND") if resp.status_code >= 500: # NixHub returns 500 for non-existent packages with unusual names # Check if the package name looks suspicious if len(package_name) > 30 or package_name.count("-") > 5: return error(f"Package '{package_name}' not found in NixHub", "NOT_FOUND") return error("NixHub service temporarily unavailable", "SERVICE_ERROR") resp.raise_for_status() # Parse JSON response data = resp.json() # Validate response structure if not isinstance(data, dict): return error("Invalid response format from NixHub") # Extract package info # Use the requested package name, not what API returns (e.g., user asks for python3, API returns python) name = package_name summary = data.get("summary", "") releases = data.get("releases", []) if not releases: return f"Package: {name}\nNo version history available in NixHub" # Build results results = [] results.append(f"Package: {name}") if summary: results.append(f"Description: {summary}") results.append(f"Total versions: {len(releases)}") results.append("") # Limit results shown_releases = releases[:limit] results.append(f"Version history (showing {len(shown_releases)} of {len(releases)}):\n") for release in shown_releases: results.extend(_format_nixhub_release(release, name)) results.append("") # Add usage hint if shown_releases and any(r.get("platforms", [{}])[0].get("commit_hash") for r in shown_releases): results.append("To use a specific version in your Nix configuration:") results.append("1. Pin nixpkgs to the commit hash") results.append("2. Use the attribute path to install the package") return "\n".join(results).strip() except requests.Timeout: return error("Request to NixHub timed out", "TIMEOUT") except requests.RequestException as e: return error(f"Network error accessing NixHub: {str(e)}", "NETWORK_ERROR") except ValueError as e: return error(f"Failed to parse NixHub response: {str(e)}", "PARSE_ERROR") except Exception as e: return error(f"Unexpected error: {str(e)}")
- mcp_nixos/server.py:1536-1580 (helper)Helper function used by nixhub_package_versions to format individual release information, including version, date, platforms, unique commit hashes (deduplicated and validated), and attribute paths.def _format_nixhub_release(release: dict[str, Any], package_name: str | None = None) -> list[str]: """Format a single NixHub release for display.""" results = [] version = release.get("version", "unknown") last_updated = release.get("last_updated", "") platforms_summary = release.get("platforms_summary", "") platforms = release.get("platforms", []) results.append(f"• Version {version}") if last_updated: # Format date nicely try: from datetime import datetime dt = datetime.fromisoformat(last_updated.replace("Z", "+00:00")) formatted_date = dt.strftime("%Y-%m-%d %H:%M UTC") results.append(f" Last updated: {formatted_date}") except Exception: results.append(f" Last updated: {last_updated}") if platforms_summary: results.append(f" Platforms: {platforms_summary}") # Show commit hashes and attribute paths for each platform (avoid duplicates) if platforms: seen_commits = set() for platform in platforms: commit_hash = platform.get("commit_hash", "") attr_path = platform.get("attribute_path", "") if commit_hash and commit_hash not in seen_commits: seen_commits.add(commit_hash) # Validate commit hash format (40 hex chars) if re.match(r"^[a-fA-F0-9]{40}$", commit_hash): results.append(f" Nixpkgs commit: {commit_hash}") else: results.append(f" Nixpkgs commit: {commit_hash} (warning: invalid format)") # Show attribute path if different from package name if attr_path and package_name and attr_path != package_name: results.append(f" Attribute: {attr_path}") return results
- mcp_nixos/server.py:1601-1601 (registration)FastMCP tool registration decorator that registers nixhub_package_versions as an MCP tool.async def nixhub_package_versions(package_name: str, limit: int = 10) -> str: