diff --git a/packages/default.nix b/packages/default.nix index 97b6635..1cb10e1 100644 --- a/packages/default.nix +++ b/packages/default.nix @@ -32,5 +32,6 @@ yarr = pkgs.callPackage ./yarr/package.nix { }; pdf-mcp = pkgs.callPackage ./pdf-mcp/package.nix { }; mcp-searxng = pkgs.callPackage ./mcp-searxng/package.nix { }; + python-ssh-mcp = pkgs.callPackage ./python-ssh-mcp/package.nix { }; # example-mcp-server = pkgs.callPackage ./example-mcp-server/package.nix { }; } \ No newline at end of file diff --git a/packages/python-ssh-mcp/package.nix b/packages/python-ssh-mcp/package.nix new file mode 100644 index 0000000..e6d215b --- /dev/null +++ b/packages/python-ssh-mcp/package.nix @@ -0,0 +1,86 @@ +{ + lib, + python313Packages, + fetchFromGitHub, + fetchPypi, +}: + +# Upstream pins `requires-python = ">=3.11,<3.14"`, so this must NOT be built +# with the default `python3Packages` (which is 3.14.x on nixos-unstable and +# would be rejected by the build backend's requires-python check). Pin the +# 3.13 package set explicitly. +# +# `fastmcp[tasks]` is a HARD runtime dependency: `src/ssh_mcp/tools/exec_tools.py` +# does `from fastmcp.server.tasks import TaskConfig` at import time, and the +# decorator validates at import. nixpkgs' `fastmcp` installs only the +# client+server extras (optional-dependencies are passthru-only in nix and +# install nothing), so the `tasks` extra's backend -- pydocket -- has to be +# listed explicitly or the server crashes at startup. +let + # Internal dependency: upstream pins `unidiff>=0.7.5,<0.8.0`, but nixpkgs only + # carries 1.0.0. The 0.7.x API surface used by the ssh_edit tool + # (`PatchSet.from_string` and hunk iteration) is unchanged in 1.0.0, but + # buildPythonApplication's runtime-dep check enforces the specifier, so the + # pinned version still has to be provided. 0.7.5 is sdist-only on PyPI + # (no wheel), so this is a source build. + unidiff = python313Packages.buildPythonPackage rec { + pname = "unidiff"; + version = "0.7.5"; + + pyproject = true; + build-system = [ python313Packages.setuptools ]; + + src = fetchPypi { + inherit pname version; + hash = "sha256-Ll8BYgUiSJRrnwlwpA6eEkI2v4bIK3CCEUOm/B3qJXQ="; + }; + + doCheck = false; + pythonImportsCheck = [ "unidiff" ]; + + meta = with lib; { + description = "Unified diff parsing and application"; + homepage = "https://github.com/matiasb/python-unidiff"; + license = licenses.mit; + }; + }; +in +python313Packages.buildPythonApplication rec { + pname = "python-ssh-mcp"; + version = "1.5.2"; + + pyproject = true; + + src = fetchFromGitHub { + owner = "Nightreaver"; + repo = "python-ssh-mcp"; + rev = "73c00c9b0d26add59798bc7b39dff88ac547b15a"; # v1.5.2 + hash = "sha256-OfwOiBan5sqGEs/zzHHC4vJdcALOLWaIr8uENCbQ4FU="; + }; + + build-system = [ python313Packages.hatchling ]; + + dependencies = with python313Packages; [ + fastmcp + pydocket # the fastmcp[tasks] extra + asyncssh + pydantic + pydantic-settings + unidiff + ]; + + # Upstream ships no importable test suite in the sdist workflow used here + # (its 1649 tests need the dockerized sshd fixtures); skip and rely on the + # import check + the smoke test below. + doCheck = false; + pythonImportsCheck = [ "ssh_mcp" ]; + + meta = with lib; { + description = "SSH MCP server for multi-host systemd/docker/apt administration (read-only by default, tiered)"; + homepage = "https://github.com/Nightreaver/python-ssh-mcp"; + license = licenses.gpl3Only; + mainProgram = "ssh-mcp"; + platforms = platforms.all; + maintainers = [ ]; + }; +}