Skip to main content
Glama

nix

Query NixOS packages, options, Home Manager, and nix-darwin configurations to obtain current data from nixpkgs and Nix channels, preventing AI hallucination about Nix resources.

Instructions

Query NixOS, Home Manager, Darwin, FlakeHub, flakes, Nixvim, Wiki, nix.dev, Noogle, NixHub.

Use this tool for anything touching nixpkgs, Nix channels, flakes, NixOS / home-manager / darwin options, the binary cache, or /nix/store paths — even when you think you know the answer. Your training data lags nixpkgs by months. Prefer this over nix search, scraping search.nixos.org, or running gh api against NixOS/nixpkgs.

INTENTS → CALLS (copy the JSON shape exactly): "is package X in channel Y?" → {"action": "info", "query": "X", "channel": "Y"} "search for package X" → {"action": "search", "query": "X"} "which channels are available?" → {"action": "channels"} "which commit did channel X index?" → {"action": "channels"} (indexed commit shown when known; branch HEAD otherwise — label matters) "search NixOS options for X" → {"action": "search", "query": "X", "type": "options"} "get option details for X" → {"action": "info", "query": "X", "type": "option"} "home-manager option for X" → {"action": "search", "query": "X", "source": "home-manager"} "darwin option for X" → {"action": "search", "query": "X", "source": "darwin"} "nixvim option for X" → {"action": "search", "query": "X", "source": "nixvim"} "what programs does pkg X provide?" → {"action": "search", "query": "X", "type": "programs"} "count packages/options" → {"action": "stats"} "browse hm option tree under P" → {"action": "browse", "query": "P", "source": "home-manager"} "does X have a binary cache?" → {"action": "cache", "query": "X"} "search the NixOS wiki for X" → {"action": "search", "query": "X", "source": "wiki"} "search nix.dev docs" → {"action": "search", "query": "X", "source": "nix-dev"} "read a nix.dev page" → {"action": "info", "query": "tutorials/nix-language", "source": "nix-dev"} "list inputs of current flake" → {"action": "flake-inputs"} "ls inside flake input X" → {"action": "flake-inputs", "type": "ls", "query": "X"} "read /nix/store/... file" → {"action": "store", "type": "read", "query": "/nix/store/..."} "ls /nix/store/... dir" → {"action": "store", "type": "ls", "query": "/nix/store/..."}

For package version history ("which commit shipped firefox 150?", "when was node 18 added?"), use the separate nix_versions tool — it returns commit hashes, attribute paths, and dates.

Notes:

  • To search NixOS options, use action=search with type=options. Do NOT use action=browse for source=nixos — browse is for walking a pre-indexed option tree and only works with home-manager, darwin, nixvim, or noogle.

  • For source=nix-dev, action=info returns the page markdown. The query may be a bare docname like "tutorials/nix-language", the URL printed by nix-dev search ("https://nix.dev/tutorials/nix-language"), or a rendered ".html" URL.

  • action=info for packages matches on the exact attribute path first, then the exact pname. If multiple packages share a pname (e.g. firefox / firefox-esr / firefox-mobile), the canonical attribute wins and the response flags the disambiguation explicitly.

  • Omit parameters you don't need; do not pass empty strings for optional args.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
actionYesOne of: search, info, stats, browse, channels, flake-inputs, cache, store. Use 'search' for keyword lookup, 'info' for details about a specific name, 'browse' to walk an option hierarchy by prefix (home-manager/darwin/nixvim/noogle only). 'store' reads files or lists directories at an explicit /nix/store/ path.
queryNoSearch term for 'search', exact name for 'info', prefix path for 'browse'. For flake-inputs: input_name or input:path. For store: absolute /nix/store/ path. Leave empty for 'stats'/'channels'.
sourceNoData source for search/info/stats/browse/cache. One of: nixos (default), home-manager, darwin, flakes, flakehub, nixvim, wiki, nix-dev, noogle, nixhub. For action=flake-inputs, this may instead be a path to a flake directory; omit/default to use the current project. Ignored by action=store.nixos
typeNoSub-type of query. For source=nixos with action=search, one of: packages, options, programs, flakes. For source=nixos with action=info, one of: package, option. For flake-inputs, one of: list, ls, read. For store, one of: ls, read. Ignored by most other sources.packages
channelNoNixOS channel: unstable (default), stable, or a release like 25.05.unstable
limitNoMax results. 1-100 (or 1-2000 for flake-inputs/store read).
versionNoOnly used by action=cache. Package version (default: latest).latest
systemNoOnly used by action=cache. System arch e.g. x86_64-linux. Empty for all.

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Implementation Reference

  • Registration of the 'nix' tool in the pi extension system via defineTool(). The TypeScript side registers tool name 'nix' with parameters and an execute handler that calls the Python server.
    const nixTool = defineTool({
    	name: "nix",
    	label: "NixOS",
    	description: nixToolDescription,
    	promptSnippet:
    		"Prefer the nix tool over web search for NixOS packages, options, flakes, wiki, nix.dev, Home Manager, nix-darwin, Nixvim, Noogle, flake inputs, and binary cache status.",
    	promptGuidelines: [
    		"Prefer the nix tool over web search for NixOS-related package, option, flake, wiki, nix.dev, and cache questions.",
    		'To search NixOS options: {"action": "search", "query": "<keyword>", "type": "options"}.',
    		'To inspect a NixOS option: {"action": "info", "query": "<option.path>", "type": "option"}.',
    		"action=browse is for walking option prefixes in home-manager, darwin, nixvim, or noogle only — never with source=nixos.",
    	],
    	parameters: nixToolParams,
    	async execute(_toolCallId: string, params: NixToolParams, signal: AbortSignal | undefined) {
    		// Legacy alias: older model sessions may still emit action=options.
    		// server.py also normalizes this, but translating here keeps details.args honest.
    		const action = params.action === "options" ? "browse" : params.action;
    
    		// Only forward keys the caller actually set. Passing empty-string defaults would
    		// (a) echo noisy args back in details.args, contradicting the "omit optional
    		// parameters" guidance in the tool description, and (b) train small models to
    		// copy those empties into future calls. The Python server already supplies its
    		// own sensible defaults when a key is omitted.
    		const normalized: Record<string, string | number> = { action };
    		if (params.query !== undefined) normalized.query = params.query;
    		if (params.source !== undefined) normalized.source = params.source;
    		if (params.type !== undefined) normalized.type = params.type;
    		if (params.channel !== undefined) normalized.channel = params.channel;
    		if (params.limit !== undefined) normalized.limit = params.limit;
    		if (params.version !== undefined) normalized.version = params.version;
    		if (params.system !== undefined) normalized.system = params.system;
    
    		const text = await runNixosTool("nix", normalized, signal);
    		return {
    			content: [{ type: "text", text }],
    			details: { tool: "nix", args: normalized },
    		};
    	},
    });
  • Main 'nix' tool handler function. This is the core async function decorated with @mcp.tool() that dispatches to various source functions based on action (search, info, stats, browse, channels, flake-inputs, cache, store) and source (nixos, home-manager, darwin, flakes, flakehub, nixvim, wiki, nix-dev, noogle, nixhub).
    @mcp.tool()
    async def nix(
        action: Annotated[
            str,
            "One of: search, info, stats, browse, channels, flake-inputs, cache, store. "
            "Use 'search' for keyword lookup, 'info' for details about a specific name, "
            "'browse' to walk an option hierarchy by prefix (home-manager/darwin/nixvim/noogle only). "
            "'store' reads files or lists directories at an explicit /nix/store/ path.",
        ],
        query: Annotated[
            str,
            "Search term for 'search', exact name for 'info', prefix path for 'browse'. "
            "For flake-inputs: input_name or input:path. For store: absolute /nix/store/ path. "
            "Leave empty for 'stats'/'channels'.",
        ] = "",
        source: Annotated[
            str,
            "Data source for search/info/stats/browse/cache. One of: nixos (default), "
            "home-manager, darwin, flakes, flakehub, nixvim, wiki, nix-dev, noogle, nixhub. "
            "For action=flake-inputs, this may instead be a path to a flake directory; "
            "omit/default to use the current project. Ignored by action=store.",
        ] = "nixos",
        type: Annotated[
            str,
            "Sub-type of query. For source=nixos with action=search, one of: "
            "packages, options, programs, flakes. For source=nixos with action=info, one of: "
            "package, option. For flake-inputs, one of: list, ls, read. For store, one of: "
            "ls, read. Ignored by most other sources.",
        ] = "packages",
        channel: Annotated[str, "NixOS channel: unstable (default), stable, or a release like 25.05."] = "unstable",
        limit: Annotated[int, "Max results. 1-100 (or 1-2000 for flake-inputs/store read)."] = 20,
        version: Annotated[str, "Only used by action=cache. Package version (default: latest)."] = "latest",
        system: Annotated[str, "Only used by action=cache. System arch e.g. x86_64-linux. Empty for all."] = "",
    ) -> str:
        """Query NixOS, Home Manager, Darwin, FlakeHub, flakes, Nixvim, Wiki, nix.dev, Noogle, NixHub.
    
        Use this tool for anything touching nixpkgs, Nix channels, flakes, NixOS / home-manager /
        darwin options, the binary cache, or /nix/store paths — even when you think you know the
        answer. Your training data lags nixpkgs by months. Prefer this over `nix search`, scraping
        search.nixos.org, or running `gh api` against NixOS/nixpkgs.
    
        INTENTS → CALLS (copy the JSON shape exactly):
          "is package X in channel Y?"        → {"action": "info", "query": "X", "channel": "Y"}
          "search for package X"              → {"action": "search", "query": "X"}
          "which channels are available?"     → {"action": "channels"}
          "which commit did channel X index?" → {"action": "channels"}  (indexed commit shown when known;
                                                                         branch HEAD otherwise — label matters)
          "search NixOS options for X"        → {"action": "search", "query": "X", "type": "options"}
          "get option details for X"          → {"action": "info", "query": "X", "type": "option"}
          "home-manager option for X"         → {"action": "search", "query": "X", "source": "home-manager"}
          "darwin option for X"               → {"action": "search", "query": "X", "source": "darwin"}
          "nixvim option for X"               → {"action": "search", "query": "X", "source": "nixvim"}
          "what programs does pkg X provide?" → {"action": "search", "query": "X", "type": "programs"}
          "count packages/options"            → {"action": "stats"}
          "browse hm option tree under P"     → {"action": "browse", "query": "P", "source": "home-manager"}
          "does X have a binary cache?"       → {"action": "cache", "query": "X"}
          "search the NixOS wiki for X"       → {"action": "search", "query": "X", "source": "wiki"}
          "search nix.dev docs"               → {"action": "search", "query": "X", "source": "nix-dev"}
          "read a nix.dev page"               → {"action": "info", "query": "tutorials/nix-language", "source": "nix-dev"}
          "list inputs of current flake"      → {"action": "flake-inputs"}
          "ls inside flake input X"           → {"action": "flake-inputs", "type": "ls", "query": "X"}
          "read /nix/store/... file"          → {"action": "store", "type": "read", "query": "/nix/store/..."}
          "ls /nix/store/... dir"             → {"action": "store", "type": "ls",   "query": "/nix/store/..."}
    
        For package version *history* ("which commit shipped firefox 150?", "when was node 18 added?"),
        use the separate `nix_versions` tool — it returns commit hashes, attribute paths, and dates.
    
        Notes:
          - To search NixOS *options*, use action=search with type=options. Do NOT use action=browse
            for source=nixos — browse is for walking a pre-indexed option tree and only works with
            home-manager, darwin, nixvim, or noogle.
          - For source=nix-dev, action=info returns the page markdown. The query may be a bare
            docname like "tutorials/nix-language", the URL printed by nix-dev search
            ("https://nix.dev/tutorials/nix-language"), or a rendered ".html" URL.
          - action=info for packages matches on the exact attribute path first, then the exact pname.
            If multiple packages share a pname (e.g. firefox / firefox-esr / firefox-mobile), the
            canonical attribute wins and the response flags the disambiguation explicitly.
          - Omit parameters you don't need; do not pass empty strings for optional args.
        """
        # Limit validation: flake-inputs/store read allow up to 2000, others limited to 100
        if action == "flake-inputs" and type == "read":
            if not 1 <= limit <= MAX_LINE_LIMIT:
                return error(f"Limit must be 1-{MAX_LINE_LIMIT} for flake-inputs read")
        elif action == "store" and type == "read":
            if not 1 <= limit <= MAX_LINE_LIMIT:
                return error(f"Limit must be 1-{MAX_LINE_LIMIT} for store read")
        elif not 1 <= limit <= 100:
            return error("Limit must be 1-100")
    
        # Accept `browse` as canonical, keep `options` as a legacy alias.
        # The action=options name was confusing small models (GitHub #125).
        if action == "options":
            action = "browse"
    
        if action == "search":
            if not query:
                return error('Query required for search. Example: {"action": "search", "query": "firefox"}')
            if source == "nixos":
                if type not in ["packages", "options", "programs", "flakes"]:
                    return error(
                        "For source=nixos, type must be one of: packages, options, programs, flakes. "
                        'Example: {"action": "search", "query": "nginx", "type": "options"}'
                    )
                return await asyncio.to_thread(_search_nixos, query, type, limit, channel)
            elif source == "home-manager":
                return await asyncio.to_thread(_search_home_manager, query, limit)
            elif source == "darwin":
                return await asyncio.to_thread(_search_darwin, query, limit)
            elif source == "flakes":
                return await asyncio.to_thread(_search_flakes, query, limit)
            elif source == "flakehub":
                return await asyncio.to_thread(_search_flakehub, query, limit)
            elif source == "nixvim":
                return await asyncio.to_thread(_search_nixvim, query, limit)
            elif source == "wiki":
                return await asyncio.to_thread(_search_wiki, query, limit)
            elif source == "nix-dev":
                return await asyncio.to_thread(_search_nixdev, query, limit)
            elif source == "noogle":
                return await asyncio.to_thread(_search_noogle, query, limit)
            elif source == "nixhub":
                return await _search_nixhub(query, limit)
            else:
                return error(
                    f"Unknown source: {source!r}. Must be one of: "
                    "nixos, home-manager, darwin, flakes, flakehub, nixvim, wiki, nix-dev, noogle, nixhub."
                )
    
        elif action == "info":
            if not query:
                return error('Name required for info. Example: {"action": "info", "query": "firefox"}')
            if source == "flakes":
                example = json.dumps({"action": "search", "source": "flakes", "query": query})
                return error(
                    f"action=info is not supported for source=flakes. Use action=search instead. Example: {example}."
                )
            if source == "nixos":
                if type not in ["package", "packages", "option", "options"]:
                    return error(
                        "For source=nixos, type must be 'package' or 'option'. "
                        'Example: {"action": "info", "query": "services.nginx.enable", "type": "option"}'
                    )
                info_type = "package" if type in ["package", "packages"] else "option"
                return await asyncio.to_thread(_info_nixos, query, info_type, channel)
            elif source == "home-manager":
                return await asyncio.to_thread(_info_home_manager, query)
            elif source == "darwin":
                return await asyncio.to_thread(_info_darwin, query)
            elif source == "flakehub":
                return await asyncio.to_thread(_info_flakehub, query)
            elif source == "nixvim":
                return await asyncio.to_thread(_info_nixvim, query)
            elif source == "wiki":
                return await asyncio.to_thread(_info_wiki, query)
            elif source == "nix-dev":
                return await asyncio.to_thread(_info_nixdev, query)
            elif source == "noogle":
                return await asyncio.to_thread(_info_noogle, query)
            elif source == "nixhub":
                return await _info_nixhub(query)
            else:
                return error(
                    f"Unknown source: {source!r}. For action=info, must be one of: "
                    "nixos, home-manager, darwin, flakehub, nixvim, wiki, nix-dev, noogle, nixhub."
                )
    
        elif action == "stats":
            if source == "nixos":
                return await asyncio.to_thread(_stats_nixos, channel)
            elif source == "home-manager":
                return await asyncio.to_thread(_stats_home_manager)
            elif source == "darwin":
                return await asyncio.to_thread(_stats_darwin)
            elif source == "flakes":
                return await asyncio.to_thread(_stats_flakes)
            elif source == "flakehub":
                return await asyncio.to_thread(_stats_flakehub)
            elif source == "nixvim":
                return await asyncio.to_thread(_stats_nixvim)
            elif source == "noogle":
                return await asyncio.to_thread(_stats_noogle)
            elif source in ["wiki", "nix-dev", "nixhub"]:
                return error(f"Stats not available for source={source}.")
            else:
                return error(
                    f"Unknown source: {source!r}. For action=stats, must be one of: "
                    "nixos, home-manager, darwin, flakes, flakehub, nixvim, noogle."
                )
    
        elif action == "browse":
            if source == "nixos":
                return error(
                    "action=browse is not for NixOS. To search NixOS options, use: "
                    '{"action": "search", "query": "nginx", "type": "options"}. '
                    "To get a specific option's details, use: "
                    '{"action": "info", "query": "services.nginx.enable", "type": "option"}.'
                )
            if source not in ["home-manager", "darwin", "nixvim", "noogle"]:
                return error(
                    "action=browse only supports source in: home-manager, darwin, nixvim, noogle. "
                    'Example: {"action": "browse", "query": "programs", "source": "home-manager"}'
                )
            if source == "nixvim":
                return await asyncio.to_thread(_browse_nixvim_options, query)
            if source == "noogle":
                return await asyncio.to_thread(_browse_noogle_options, query)
            return await asyncio.to_thread(_browse_options, source, query)
    
        elif action == "channels":
            return await asyncio.to_thread(_list_channels)
    
        elif action == "flake-inputs":
            # Determine flake directory: use source if it's not a known source name
            flake_dir = source if source not in KNOWN_SOURCES else "."
    
            # Validate type parameter for flake-inputs
            # Note: "packages" is accepted as alias for "list" (default type parameter)
            if type not in ["list", "ls", "read", "packages"]:
                return error("Type must be one of: list, ls, read for flake-inputs")
    
            # Handle limit for read operation
            read_limit = limit
            if type == "read":
                if limit == 20:  # Default was used, apply DEFAULT_LINE_LIMIT
                    read_limit = DEFAULT_LINE_LIMIT
                # Ensure read_limit doesn't exceed MAX_LINE_LIMIT
                read_limit = min(read_limit, MAX_LINE_LIMIT)
    
            # Route to appropriate function
            if type == "list" or type == "packages":
                return await _flake_inputs_list(flake_dir)
            elif type == "ls":
                if not query:
                    return error("Query required for ls (input name or input:path)")
                return await _flake_inputs_ls(flake_dir, query)
            elif type == "read":
                if not query:
                    return error("Query required for read (input:path format)")
                return await _flake_inputs_read(flake_dir, query, read_limit)
            else:
                return error("Type must be one of: list, ls, read for flake-inputs")
    
        elif action == "cache":
            if not query:
                return error("Package name required for cache action")
            return await _check_binary_cache(query, version, system)
    
        elif action == "store":
            if type not in ["ls", "read"]:
                return error(
                    "Type must be one of: ls, read for store. "
                    'Example: {"action": "store", "type": "ls", "query": "/nix/store/<hash>-<name>"}'
                )
            if not query:
                return error(
                    "Query required for store (absolute /nix/store/ path). "
                    'Example: {"action": "store", "type": "ls", "query": "/nix/store/<hash>-<name>"}'
                )
            if type == "ls":
                # Match _store_read's default-promotion so a bare call returns a
                # useful window of entries for large /nix/store directories
                # instead of only the first 20.
                ls_limit = limit if limit != 20 else DEFAULT_LINE_LIMIT
                ls_limit = min(ls_limit, MAX_LINE_LIMIT)
                return await _store_ls(query, ls_limit)
    
            # type == "read": default limit behavior mirrors flake-inputs read.
            read_limit = limit
            if limit == 20:  # Default was used, apply DEFAULT_LINE_LIMIT
                read_limit = DEFAULT_LINE_LIMIT
            read_limit = min(read_limit, MAX_LINE_LIMIT)
            return await _store_read(query, read_limit)
    
        else:
            return error(
                f"Unknown action: {action!r}. Must be one of: "
                "search, info, stats, browse, channels, flake-inputs, cache, store. "
                'Example: {"action": "search", "query": "firefox"}'
            )
  • Companion 'nix_versions' tool handler for package version history from NixHub.
    @mcp.tool()
    async def nix_versions(
        package: Annotated[str, "Package name"],
        version: Annotated[str, "Specific version to find"] = "",
        limit: Annotated[int, "1-50"] = 10,
    ) -> str:
        """Get package version history from NixHub.io."""
        if not package or not package.strip():
            return error("Package name required")
        if not re.match(r"^[a-zA-Z0-9\-_.]+$", package):
            return error("Invalid package name")
        if not 1 <= limit <= 50:
            return error("Limit must be 1-50")
    
        # Fetch package data via thread pool to avoid blocking event loop
        err, data = await asyncio.to_thread(_fetch_nixhub_pkg, package)
        if err:
            return err
    
        try:
            # v1/pkg returns an array of version records
            if not isinstance(data, list) or not data:
                return error(f"Package '{package}' not found", "NOT_FOUND")
    
            releases: list[dict[str, Any]] = data
    
            # If specific version requested, find it
            if version:
                for release in releases:
                    if release.get("version") == version:
                        version_lines = [f"Found {package} version {version}\n"]
                        # Get commit hash from the release
                        commit = release.get("commit_hash", "")
                        if commit and re.match(r"^[a-fA-F0-9]{40}$", commit):
                            version_lines.append(f"Nixpkgs commit: {commit}")
                            # Get attribute path from systems data
                            systems_dict = release.get("systems", {})
                            if isinstance(systems_dict, dict):
                                for sys_info in systems_dict.values():
                                    if isinstance(sys_info, dict):
                                        attr_paths = sys_info.get("attr_paths", [])
                                        if attr_paths:
                                            version_lines.append(f"  Attribute: {attr_paths[0]}")
                                            break
                        return "\n".join(version_lines)
    
                # Version not found
                versions_list: list[str] = [str(r.get("version", "")) for r in releases[:limit]]
                return f"Version {version} not found for {package}\nAvailable: {', '.join(versions_list)}"
    
            # Build package header with rich metadata from first (latest) release
            results: list[str] = [f"Package: {package}"]
            latest = releases[0]
    
            # Add package-level metadata from latest release
            license_info: str = latest.get("license", "")
            if license_info:
                results.append(f"License: {license_info}")
    
            homepage: str = latest.get("homepage", "")
            if homepage:
                results.append(f"Homepage: {homepage}")
    
            # Get programs from systems data
            programs: list[str] = []
            systems_dict = latest.get("systems", {})
            if isinstance(systems_dict, dict):
                for sys_info in systems_dict.values():
                    if isinstance(sys_info, dict):
                        sys_programs = sys_info.get("programs", [])
                        if sys_programs:
                            programs = sys_programs
                            break
            if programs:
                progs = programs[:10]
                prog_str = ", ".join(progs)
                if len(programs) > 10:
                    prog_str += f" ... ({len(programs)} total)"
                results.append(f"Programs: {prog_str}")
    
            results.append(f"Total versions: {len(releases)}")
            results.append("")
    
            # Return version history
            shown: list[dict[str, Any]] = releases[:limit]
            results.append(f"Recent versions ({len(shown)} of {len(releases)}):\n")
            for release in shown:
                results.extend(_format_release(release, package))
                results.append("")
            return "\n".join(results).strip()
    
        except Exception as e:
            return error(str(e))
  • Typed schema for the 'nix' tool defined via FastMCP's Annotated type annotations on function parameters (action, query, source, type, channel, limit, version, system).
    # =============================================================================
    
    
    @mcp.tool()
  • TypeScript schema (nixToolParams) for the 'nix' tool using Type.Object with typed string/integer fields.
    const nixToolParams = Type.Object({
    	action: Type.String({
    		description:
    			"One of: search, info, stats, browse, channels, flake-inputs, cache, store. " +
    			"search = keyword lookup; info = details for a specific name; " +
    			"browse = walk an option hierarchy by prefix (home-manager/darwin/nixvim/noogle only); " +
    			"store = read files or list directories at an explicit /nix/store/ path.",
    	}),
    	query: Type.Optional(
    		Type.String({
    			description:
    				"Search term for 'search', exact name for 'info', prefix path for 'browse'. " +
    				"For flake-inputs: input_name or input:path. For store: absolute /nix/store/ path. " +
    				"Omit for 'stats'/'channels'.",
    		}),
    	),
    	source: Type.Optional(
    		Type.String({
    			description:
    				"Data source for search/info/stats/browse/cache. One of: nixos (default), " +
    				"home-manager, darwin, flakes, flakehub, nixvim, wiki, nix-dev, noogle, nixhub. " +
    				"For action=flake-inputs, this may instead be a path to a flake directory; " +
    				"omit/default to use the current project. Ignored by action=store.",
    		}),
    	),
    	type: Type.Optional(
    		Type.String({
    			description:
    				"Sub-type of query. For source=nixos with action=search, one of: " +
    				"packages, options, programs, flakes. For source=nixos with action=info, one of: " +
    				"package, option. For flake-inputs, one of: list, ls, read. For store, one of: " +
    				"ls, read. Ignored by most other sources.",
    		}),
    	),
    	channel: Type.Optional(
    		Type.String({ description: "NixOS channel: unstable (default), stable, or a release like 25.05." }),
    	),
    	limit: Type.Optional(
    		Type.Integer({ description: "Max results. 1-100 (or 1-2000 for flake-inputs/store read). Default 20." }),
    	),
    	version: Type.Optional(
    		Type.String({ description: "Only used by action=cache. Package version (default: latest)." }),
    	),
    	system: Type.Optional(
    		Type.String({
    			description: "Only used by action=cache. System arch e.g. x86_64-linux. Empty for all.",
    		}),
    	),
    });
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden. It explains the tool's query nature and specific behaviors like package disambiguation in `info` and the effect of omitting parameters. However, it could explicitly state the tool is read-only and does not perform mutations or require authentication.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is lengthy but well-organized with sections, bullet points, and a clear intent table. It is front-loaded with the overall purpose. While every sentence adds value, the volume could be reduced slightly for quicker parsing, but it remains appropriate for the tool's complexity.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (8 parameters, multiple actions and sources), the description covers all necessary context. It explains when to use different actions, how to construct queries, and mentions the sibling tool for missing functionality. An output schema is present, so return value details are not needed.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, and the description adds significant value beyond the schema by providing intent-to-parameter mappings, clarifying the meaning of `query` for different actions, and explaining parameter interactions (e.g., `source` for `browse`). This guidance is crucial for correct usage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly defines the tool as a query interface for the Nix ecosystem, listing specific sources and intents. It distinguishes itself from the sibling tool `nix_versions` by explicitly noting that version history should use the sibling tool.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides explicit when-to-use guidance, stating 'Use this tool for anything touching nixpkgs... Prefer this over nix search...' and includes a dedicated section for when to use the sibling tool. It also includes notes on when not to use specific actions, such as avoiding `browse` for `source=nixos`.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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