Post

NixOS-03 Building flake.nix for pyghidra-mcp

NixOS-03 Building flake.nix for pyghidra-mcp

After configuring basic desktop, terminal and coding environment, Im trying to do binary tools migration. So here comes ghidra.

It is easy to install ghidra in NixOS, just using home-manager. (Also need to set _JAVA_AWT_WM_NONREPARENTING = 1 to make it working in niri wm).

While im learning how to manage ghidra extension by nix config, I found pyghidra-mcp by chance, which seems to be an excellent tool for AI-workflow. This is my first time trying to biuld a flake.nix for a repo.

0x01 Start from template

Forking the reposity and add a template flake.nix:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
  description = "pyghidra-mcp packaged with Nix";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
  };

  outputs = { self, nixpkgs }:
    let
      system = "x86_64-linux";
      pkgs = import nixpkgs {
        inherit system;
      };
    in
    {
      packages.${system}.hello = pkgs.hello;
    };
}

Then test by running nix build .#hello, which will generated a result dir if succeed, with bin/hello executable.

0x02 Test python package dependencies

Since the repo has pyproject.toml, our next step is write some python project building rule according to it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
name = "pyghidra-mcp"
version = "0.2.2"
description = "Python Command-Line Ghidra MCP"
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
    "click>=8.2.1",
    "click-option-group>=0.5.9",
    "mcp[cli]>=1.26.0",
    "pyghidra>=2.2.1",
    "chromadb>=1.3.5",
    "ghidrecomp>=0.5.8",
]
...
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
...

We need to add python version and the most important attribute buildPythonApplication(including building backend - hatchling, and some dependencies).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
{
  description = "pyghidra-mcp packaged with Nix";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
  };

  outputs = { self, nixpkgs }:
    let
      system = "x86_64-linux";
      pkgs = import nixpkgs {
        inherit system;
      };
      # add python
      python = pkgs.python313;
      py = python.pkgs;
    in
    {
      packages.${system}.default = py.buildPythonApplication {
        pname = "pyghidra-mcp";
        version = "0.2.2";

        src = ./.;

        pyproject = true;

        build-system = with py; [
          hatchling
        ];

        # python package dependencies
        dependencies = with py; [
          click
          click-option-group
          mcp 
          pyghidra
          chromadb
          # ghidrecomp
        ];
      };
    };
}

However, we cannot garatuee all python package denepdencies are in nixpkgs. So we should test them:

1
2
3
pyghidra-mcp on  main [!?] is 📦 v0.2.2 via 🐍 v3.13.12
❯ nix eval github:nixos/nixpkgs/nixos-unstable#python313Packages.click
«derivation /nix/store/f4qvwmhh1zj66qrk6k3x5bj7gqdfh0yr-python3.13-click-8.3.1.drv»

Choose the correct nix pkgs source First I run:

1
2
3
pyghidra-mcp on  main [!?] is 📦 v0.2.2 via 🐍 v3.13.12
❯ nix eval nixpkgs#python313Packages.pyghidra
error: flake 'flake:nixpkgs' does not provide attribute 'packages.x86_64-linux.python313Packages.pyghidra', 'legacyPackages.x86_64-linux.python313Packages.pyghidra' or 'python313Packages.pyghidra' Did you mean pyhidra?

This is because nixpkgs refers to the value in my system, which is nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";. However, pyghidra is only added in github:nixos/nixpkgs/nixos-unstable, not in nixos-25.11.

So I should use:

1
2
3
nixos-config on  main [!]
❯ nix eval github:nixos/nixpkgs/nixos-unstable#python313Packages.pyghidra
«derivation /nix/store/yk2f34pcnbr14d9bfbpn16i9y4lfw4hx-python3.13-pyghidra-3.1.0.drv»

Perfect! And we notice that the pyghidra package is only included in python313Packages and python314Packages, not in 3.12 or older version.

Btw, if pyghidra is not included in nixpkgs, we can build it by defining

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
pyghidra = py.buildPythonPackage rec {
  pname = "pyghidra";
  version = "2.2.1";

  src = py.fetchPypi {
    inherit pname version;
    hash = "sha256-0ajGRHpt8QFqOCZmKnXrFPKtcFjJqPSwkyLHOnpPytQ=";   # We can fill the hash with a random value first. When we build it with `nix build -L`, there should be hash not match error, and tell us the right hash.
  };

  pyproject = true;

  build-system = with py; [
    setuptools
    wheel
  ];

  dependencies = with py; [
    jpype1
  ];

  pythonImportsCheck = [
    "pyghidra"
  ];
};

However, ghidrecomp is a custom python package written by the author, not included in nixpkgs. Here we need to build it in our flake.nix:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
{
  description = "pyghidra-mcp packaged with Nix";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    pyghidra-mcp-src = {
      url = "github:clearbluejar/pyghidra-mcp";
      flake = false;
    };
  };
  outputs = { self, nixpkgs }:
    let
      system = "x86_64-linux";
      pkgs = import nixpkgs {
        inherit system;
      };
      python = pkgs.python313;
      py = python.pkgs;


      ghidrecomp = py.buildPythonPackage rec {
        pname = "ghidrecomp";
        version = "0.5.9";
        pyproject = true;

        src = pkgs.fetchPypi {
          inherit pname version;
          hash = "sha256-ocluLUidyqMRkO7kXWum8l3VZ/paro/1tQ9JgOood6I=";
        };

        build-system = with py; [
          setuptools
          wheel
        ];

        dependencies = with py; [
          pyghidra
          jpype1
          click
          lxml
          networkx
          pydot
          pyyaml
          requests
        ];
        doCheck = true;

        pythonImportsCheck = [
          "ghidrecomp"
        ];
      };
    in
    {
      ...
    };
}

The last thing: in pyproject.toml, it has mcp[cli], which means mcp + extra dependencies using by its cli. So the ultimate dependencies settings are:

1
2
3
4
5
6
7
8
dependencies = with py; [
  click
  click-option-group
  mcp 
  pyghidra
  chromadb
  ghidrecomp
]++ py.mcp.optional-dependencies.cli;

0x03 Add pyghidra-mcp-cli

We have successfully built pyghidra-mcp. It is easy to add a sencond output.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
{
  description = "pyghidra-mcp packaged with Nix";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
  };

  outputs = { self, nixpkgs }:
    let
      system = "x86_64-linux";
      pkgs = import nixpkgs {
        inherit system;
      };
      python = pkgs.python313;
      py = python.pkgs;

      ghidrecomp = py.buildPythonPackage rec {
        pname = "ghidrecomp";
        version = "0.5.9";
        pyproject = true;

        src = pkgs.fetchPypi {
          inherit pname version;
          hash = "sha256-ocluLUic2qMREO7kXWum8l3VZ/parj/WtQ9JgOood6I=";
        };

        build-system = with py; [
          setuptools
          wheel
        ];

        dependencies = with py; [
          pyghidra
          jpype1
          click
          lxml
          networkx
          pydot
          pyyaml
          requests
        ];
        doCheck = true;
        pythonImportsCheck = [
          "ghidrecomp"
        ];
      };

      pyghidra-mcp = py.buildPythonApplication {
        pname = "pyghidra-mcp";
        version = "0.2.2";

        src = ./.;

        pyproject = true;

        build-system = with py; [
          hatchling
        ];

        # python package dependencies
        dependencies = with py; [
          click
          click-option-group
          mcp 
          pyghidra    # needs ghidra >= 12.0
          chromadb
          ghidrecomp
        ]++ py.mcp.optional-dependencies.cli;
      };

      pyghidra-mcp-cli = py.buildPythonApplication {
        pname = "pyghidra-mcp-cli";
        version = "0.2.2";

        src = ./cli;
        pyproject = true;

        build-system = with py; [
          hatchling
        ];

        dependencies = with py; [
          click
          aiohttp
          pyghidra-mcp
        ];
      };
    in
    {
      # add both binary
      packages.${system} = {
        default = pkgs.symlinkJoin {
          name = "pyghidra-mcp-with-cli";

          paths = [
              pyghidra-mcp
              pyghidra-mcp-cli
            ];
        };
        pyghidra-mcp = pyghidra-mcp;
        pyghidra-mcp-cli = pyghidra-mcp-cli;
      };
    };
}

0x04 Add multi-system

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
{
  description = "pyghidra-mcp packaged with Nix";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
  };

  outputs = { self, nixpkgs }:
    let
      systems = [
        "x86_64-linux"
        "aarch64-linux"
        "x86_64-darwin"
        "aarch64-darwin"
      ];

      forAllSystems = nixpkgs.lib.genAttrs systems;

      pkgsFor = system: import nixpkgs {
        inherit system;
      };
    in
    {
      
      packages = forAllSystems (system:
      let
        pkgs = pkgsFor system;
        python = pkgs.python313;
        py = python.pkgs;

        # build ghidrecomp
        ghidrecomp = py.buildPythonPackage rec {
          pname = "ghidrecomp";
          version = "0.5.9";
          pyproject = true;

          src = pkgs.fetchPypi {
            inherit pname version;
            hash = "sha256-ocluLUic2qMREO7kXWum8l3VZ/parj/WtQ9JgOood6I=";
          };

          build-system = with py; [
            setuptools
            wheel
          ];

          dependencies = with py; [
            pyghidra
            jpype1
            click
            lxml
            networkx
            pydot
            pyyaml
            requests
          ];
          doCheck = true;
          pythonImportsCheck = [
            "ghidrecomp"
          ];
        };

        # pyghidra-mcp
        pyghidra-mcp = py.buildPythonApplication {
          pname = "pyghidra-mcp";
          version = "0.2.2";

          src = ./.;

          pyproject = true;

          build-system = with py; [
            hatchling
          ];

          # python package dependencies
          dependencies = with py; [
            click
            click-option-group
            mcp 
            pyghidra    # needs ghidra >= 12.0
            chromadb
            ghidrecomp
          ]++ py.mcp.optional-dependencies.cli;
        };

        # pyghidra-mcp-cli
        pyghidra-mcp-cli = py.buildPythonApplication {
          pname = "pyghidra-mcp-cli";
          version = "0.2.2";

          src = ./cli;
          pyproject = true;

          build-system = with py; [
            hatchling
          ];

          dependencies = with py; [
            click
            aiohttp
            pyghidra-mcp
          ];
        };
      in
      {
        default = pkgs.symlinkJoin {
          name = "pyghidra-mcp-with-cli";
          paths = [
            pyghidra-mcp
            pyghidra-mcp-cli
          ];
        };

        inherit pyghidra-mcp pyghidra-mcp-cli;
      });
      # for nix run
      apps = forAllSystems (system:
        {
          default = {
            type = "app";
            program = "${self.packages.${system}.default}/bin/pyghidra-mcp";
          };

          pyghidra-mcp = {
            type = "app";
            program = "${self.packages.${system}.pyghidra-mcp}/bin/pyghidra-mcp";
          };

          pyghidra-mcp-cli = {
            type = "app";
            program = "${self.packages.${system}.pyghidra-mcp-cli}/bin/pyghidra-mcp-cli";
          };
        });
    };
}
This post is licensed under CC BY 4.0 by the author.