Skip to main content
Glama

nixhub_package_versions

Retrieve version history and commit hashes for NixOS packages to ensure reproducible builds and track specific package versions.

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

TableJSON Schema
NameRequiredDescriptionDefault
package_nameYes
limitNo

Implementation Reference

  • The main handler function for the 'nixhub_package_versions' tool. Fetches package version history from NixHub.io API, validates inputs, handles errors, and formats the response with commit hashes and attribute paths. Includes input schema in docstring and registration via @mcp.tool() decorator.
    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)}")
  • Helper function used by nixhub_package_versions to format each release/version entry, including date formatting, platforms summary, deduplicated commit hashes with validation, and conditional attribute path display.
    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
  • Utility function used throughout the codebase, including nixhub_package_versions, to format consistent error messages.
    def error(msg: str, code: str = "ERROR") -> str:
        """Format error as plain text."""
        # Ensure msg is always a string, even if empty
        msg = str(msg) if msg is not None else ""
        return f"Error ({code}): {msg}"

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/utensils/mcp-nixos'

If you have feedback or need assistance with the MCP directory API, please join our Discord server