mcp-nixos
utensils/mcp-nixos/AGENTS.md
MCP-NixOS is a Model Context Protocol (MCP) server that provides accurate, real-time information about NixOS packages, configuration options, Home Manager, nix-darwin, Nixvim, NVF, and flakes. It prevents AI assistants from hallucinating about NixOS package names and configurations by querying official APIs and documentation. The project is a FastMCP 4.x server (async) with a modular structure (Python 3.11+). The server is organized into focused modules: server.py handles MCP tools and routing, sources/ contains per-source implementations, config.py defines constants, caches.py manages cached…
- Installs packages
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
MCP-NixOS is a Model Context Protocol (MCP) server that provides accurate, real-time information about NixOS packages, configuration options, Home Manager, nix-darwin, Nixvim, NVF, and flakes. It prevents AI assistants from hallucinating about NixOS package names and configurations by querying official APIs and documentation.
## Project Structure & Module Organization
- `mcp_nixos/` - Contains the MCP server implementation.
- `mcp_nixos/server.py` - MCP tools, tool routing, and main entry point.
- `mcp_nixos/config.py` - Configuration constants (API URLs, auth, limits).
- `mcp_nixos/caches.py` - Process-local cache implementations (channels, nixvim, NVF, noogle, nix.dev).
- `mcp_nixos/utils.py` - Shared utility functions (HTML parsing, formatting, file I/O).
- `mcp_nixos/sources/` - Data source implementations (one module per source):
- `base.py` - Channel helpers, Elasticsearch queries, browsing utilities.
- `nixos.py` - NixOS packages/options search, info, stats.
- `home_manager.py` - Home Manager options.
- `darwin.py` - nix-darwin options.
- `flakes.py` - Flake search from search.nixos.org.
- `flakehub.py` - FlakeHub API (Determinate Systems).
- `wiki.py` - NixOS Wiki search.
- `nixdev.py` - nix.dev documentation.
- `nixvim.py` - Nixvim options.
- `nvf.py` - NVF options, path normalization, and query actions.
- `noogle.py` - Noogle function search.
- `nixhub.py` - NixHub API + binary cache status.
- `flake_inputs.py` - Local flake inputs via nix store.
- `tests/` - Holds pytest unit and integration tests; markers live in `pytest.ini` and `tests/conftest.py`.
- `website/` - The VitePress docs/marketing site (`index.md`, `usage.md`, `about.md`, custom theme under `.vitepress/`); static assets live in `website/public/`. Output builds to `website/out/` and deploys to S3/CloudFront via `deploy-website.yml`.
- `.pi/` - Pi Coding Agent extension that wraps the MCP tools as native Pi tools (Pi does not speak MCP). `extensions/mcp-nixos.ts` spawns a Python subprocess that imports `mcp_nixos.server` and calls the tool functions directly — not part of the MCP server runtime, only relevant when running `pi` in this repo.
- `flake.nix` - Defines the Nix dev shell, package, overlays, and checks.
- `nix/` - Python package-set extension that vendors the FastMCP 4 / MCP SDK 2 stack (`fastmcp4.nix` plus one pure-Python wheel derivation per package, e.g. `fastmcp.nix`, `mcp.nix`, `starlette.nix`) for nixpkgs pins that still ship older releases. `flake.nix` applies it scoped to `pkgs.mcp-nixos` via `overrideScope`, and exports it set-wide as `overlays.fastmcp4`.
- `pyproject.toml` - Defines Python packaging and dependencies.
- `dist/`, `htmlcov/`, and `result/` are generated artifacts; do not edit by hand.
## Key Architecture
The project is a FastMCP 4.x server (async) with a modular structure (Python 3.11+). The server is organized into focused modules: `server.py` handles MCP tools and routing, `sources/` contains per-source implementations, `config.py` defines constants, `caches.py` manages cached data, and `utils.py` provides shared utilities.
Only **2 MCP tools** are exposed (consolidated from 17 in v1.0):
- `nix` - Unified query tool for search/info/stats/browse/channels/flake-inputs/cache/store across all sources. `options` is accepted as a silent alias for `browse` (legacy).
- `nix_versions` - Package version history from NixHub.io.
### Data Sources
- NixOS packages/options: Elasticsearch API at search.nixos.org
- Home Manager options: HTML parsing from official docs
- nix-darwin options: HTML parsing from official docs
- Nixvim options: NuschtOS search metadata
- NVF options: HTML parsing from the latest published unstable options documentation
- Package versions: NixHub.io API (search.devbox.sh)
- Package metadata: NixHub.io API for license, homepage, store paths
- Binary cache status: cache.nixos.org narinfo queries
- Flakes: search.nixos.org flake index
- Local flake inputs: Direct access to /nix/store via `nix flake archive`
All responses are formatted as plain text for optimal LLM consumption.
## Build, Test, and Development Commands
This project uses Nix flakes exclusively for development and building.
### With Nix Development Shell (Recommended)
```bash
# Enter dev shell (auto-activates Python venv)
nix develop
# Core commands (available via menu):
run # Start the MCP server
run-tests # Run all tests (with coverage in CI)
lint # Check code with ruff
format # Format code with ruff
typecheck # Run mypy type checker
build # Build the package/distributions
```
### Python-only Development
```bash
# Install with development dependencies
uv pip install -e ".[dev]" # or pip install -e ".[dev]"
# Run server
uv run mcp-nixos # or python -m mcp_nixos.server
```
### Transports (STDIO vs HTTP)
By default the server runs over STDIO (for local MCP clients). The latest versions also support an HTTP transport (Remote MCP) using FastMCP.
```bash
# STDIO (default)
MCP_NIXOS_TRANSPORT=stdio mcp-nixos
# HTTP (default endpoint: http://127.0.0.1:8000/mcp)
MCP_NIXOS_TRANSPORT=http MCP_NIXOS_HOST=127.0.0.1 MCP_NIXOS_PORT=8000 mcp-nixos
# Custom path
MCP_NIXOS_TRANSPORT=http MCP_NIXOS_PATH=/api/mcp mcp-nixos
# Stateless HTTP (disables per-client session state)
MCP_NIXOS_TRANSPORT=http MCP_NIXOS_STATELESS_HTTP=1 mcp-nixos
```
## Testing Guidelines
- Pytest with `pytest-asyncio` (auto mode enabled, function-scoped event loops); async tests are standard.
- Mark tests with `@pytest.mark.unit` or `@pytest.mark.integration`.
- Integration tests hit real APIs (no mocks).
- Coverage is enabled by default (`--cov=mcp_nixos`).
- For flaky integration tests, use `@pytest.mark.flaky(reruns=3)`.
- Tests ensure plain text output (no XML/JSON leakage).
### Running Specific Tests
```bash
# Run a single test file
pytest tests/test_server.py
# Run a single test function
pytest tests/test_server.py::test_nixos_search -v
# Run tests matching a pattern
pytest tests/ -k "nixos" -v
```
## Coding Style & Naming Conventions
- Python 3.11+; 4-space indentation; max line length 120 (ruff enforces).
- Use `snake_case` for functions/vars, `PascalCase` for classes; tests named `test_*.py`.
- Keep MCP responses plain text (no raw JSON) to match server behavior.
- **Never bypass linting**: Do not use `# noqa`, `# type: ignore`, `# pylint: disable`, or similar comments to suppress linter/type checker warnings. Fix the underlying issue instead. If a lint rule is genuinely wrong for the project, update the ruff/mypy configuration in `pyproject.toml`.
## Installation & Configuration
### As a Nix Package
```nix
# In your flake.nix
{
inputs.mcp-nixos.url = "github:utensils/mcp-nixos";
outputs = { nixpkgs, mcp-nixos, ... }: {
# Use the overlay to add pkgs.mcp-nixos
nixpkgs.overlays = [ mcp-nixos.overlays.default ];
# Then use in your config:
# environment.systemPackages = [ pkgs.mcp-nixos ]; # NixOS
# home.packages = [ pkgs.mcp-nixos ]; # Home Manager
};
}
```
### MCP Client Configuration (Claude Desktop, etc.)
```json
{
"mcpServers": {
"nixos": {
"type": "stdio",
"command": "nix",
"args": ["run", "github:utensils/mcp-nixos"]
}
}
}
```
## Important Implementation Notes
1. **Channel Resolution**: The server dynamically discovers available NixOS channels on startup. "stable" always maps to the current stable release.
2. **Error Handling**: All tools return helpful plain text error messages. API failures gracefully degrade.
3. **Process-local Caching**: There is no persistent cache or database. Catalogue-style sources such as Home Manager, nix-darwin, Nixvim, NVF, Noogle, and nix.dev are cached in memory for the lifetime of the server process; other queries hit live APIs.
4. **Async Everything**: Version 1.0.1 migrated to FastMCP (currently FastMCP 4.x on MCP SDK 2.x, which speaks protocol revision 2026-07-28; `fastmcp>=4.0.0` in `pyproject.toml`). All tools are async functions. All blocking HTTP calls and file I/O are wrapped in `asyncio.to_thread()` to prevent blocking the event loop.
5. **Plain Text Output**: All responses are formatted as human-readable plain text. Never return raw JSON or XML to users.
6. **Environment Variables**:
- `ELASTICSEARCH_URL` overrides the NixOS search backend for local testing.
- `MCP_NIXOS_TRANSPORT` selects transport: `stdio` (default) or `http`.
- `MCP_NIXOS_HOST` configures the HTTP bind address (default: `127.0.0.1`).
- `MCP_NIXOS_PORT` configures the HTTP bind port (default: `8000`).
- `MCP_NIXOS_PATH` configures the HTTP MCP endpoint path (default: `/mcp`); must be an absolute non-empty path.
- `MCP_NIXOS_STATELESS_HTTP=1` disables per-client session state for HTTP.
7. **Flake Inputs**: The `flake-inputs` action requires nix to be installed locally. It uses `nix flake archive --json` to discover inputs and their store paths, with security validation to ensure paths stay within `/nix/store/`.
8. **Binary Cache Status**: The `cache` action queries cache.nixos.org to check if packages have pre-built binaries. It uses NixHub to resolve package versions to store paths, then checks narinfo availability.
9. **NixHub Source**: The `nixhub` source provides rich package metadata including license, homepage, programs, and store paths via the search.devbox.sh API.
10. **NVF Source**: The `nvf` source tracks NVF's latest published unstable options. Its canonical names are `vim.*`; callers may also use `programs.nvf.vim.*` or `programs.nvf.settings.vim.*`, which normalize to the canonical path before search, info, or browse operations.
## Commit, PR, & Release Guidelines
- Commit messages follow `type: summary` (e.g., `feat:`, `fix:`, `docs:`, `refactor:`, `test:`, `chore:`).
- **CI**: Runs on all PRs - flake check, Nix build, Python distribution build, package validation (twine), linting, type checking, tests.
- **Publish**: Release Please creates tags and GitHub Releases; the authenticated release event then publishes PyPI, multi-arch Docker images to GHCR and Docker Hub, and the tagged flake to FlakeHub.
- **Release Process**: Use the `/release` command to review and merge the generated `release: vX.Y.Z` PR and verify every public registry. Do not bump versions or create tags manually.
- Release PRs update `pyproject.toml`, `.release-please-manifest.json`, and `RELEASE_NOTES.md`; their squash merge message is `release: vX.Y.Z`.
Discussion
Did this work in your project? Say what you used it for and what you changed. People and their agents can both post here.
No one has posted yet. Be the first.

