metamcp: package v2.4.22 as a NixOS-deployable service

Single-derivation pnpm/Turbo monorepo build producing:
  - metamcp           full orchestrator (waits for PG, runs drizzle
                      migrations, launches backend :12009 + frontend :12008)
  - metamcp-backend   bare backend launcher
  - metamcp-frontend  Next.js standalone server.js launcher

Notes:
  - Upstream pins packageManager: pnpm@9.0.0; rewrite to match the nixpkgs
    pnpm (Turbo requires the field but won't fight a matching version).
  - Frontend uses next.config 'output: standalone'; we copy .next/static
    and public/ into the standalone tree since Next doesn't.
  - HOSTNAME defaults to 0.0.0.0 (override with METAMCP_HOSTNAME) — Next
    standalone otherwise inherits the system hostname and is unreachable
    on 127.0.0.1.
This commit is contained in:
Oleks
2026-05-14 23:55:22 +03:00
parent cf54f1c94f
commit 7d58ed23a4
2 changed files with 152 additions and 0 deletions
+1
View File
@@ -57,6 +57,7 @@
{
hello-world = pkgs.callPackage ./packages/hello-world.nix { };
geesefs = pkgs.callPackage ./packages/geesefs.nix { };
metamcp = pkgs.callPackage ./packages/metamcp.nix { };
xonsh = pkgs.callPackage ./packages/xonsh.nix {
xonsh-unwrapped = import ./packages/xonsh-unwrapped.nix {
inherit (pkgs) lib python3Packages fetchFromGitHub;
+151
View File
@@ -0,0 +1,151 @@
{
lib,
stdenv,
fetchFromGitHub,
makeWrapper,
nodejs_20,
pnpm_10,
fetchPnpmDeps,
pnpmConfigHook,
postgresql,
}:
# MetaMCP — MCP aggregator/orchestrator.
#
# Upstream is a pnpm monorepo (Turbo) with two apps:
# - apps/backend : Express/tRPC API, built with tsup -> dist/index.js
# port 12009 (internal), runs drizzle-kit migrations at boot
# - apps/frontend : Next.js 15, port 12008 (public), launched via `next start`
#
# Both require runtime node_modules (Next.js non-standalone; drizzle-kit is
# a devDep invoked at runtime by docker-entrypoint.sh). We ship the whole
# built tree under $out/lib/metamcp and provide launcher scripts.
#
# The upstream Dockerfile patches Next.js' proxy timeout (30s -> 600s) by
# sed-editing two files inside node_modules/.pnpm/next@.../...; we replicate
# that in postBuild so the behaviour matches the official image.
#
# Build is single-derivation: pnpm fetch -> pnpm build -> install. First
# build prints the right pnpmDeps hash; paste it back here and rebuild.
let
pname = "metamcp";
version = "2.4.22";
src = fetchFromGitHub {
owner = "metatool-ai";
repo = "metamcp";
rev = "v${version}";
hash = lib.fakeHash; # nix build will print the right one
};
in
stdenv.mkDerivation (finalAttrs: {
inherit pname version src;
pnpmDeps = fetchPnpmDeps {
inherit pname version src;
fetcherVersion = 3;
hash = lib.fakeHash; # nix build will print the right one
};
nativeBuildInputs = [
nodejs_20
pnpm_10
pnpmConfigHook
makeWrapper
];
# pnpmConfigHook places node_modules; build the workspace with Turbo.
buildPhase = ''
runHook preBuild
# Match the upstream Dockerfile sed-patch on Next.js proxy timeout.
# Files live under the pnpm virtual store; glob to tolerate minor
# next/react version bumps on later tags.
for f in \
node_modules/.pnpm/next@*/node_modules/next/dist/server/lib/router-utils/proxy-request.js \
node_modules/.pnpm/next@*/node_modules/next/dist/esm/server/lib/router-utils/proxy-request.js; do
if [ -f "$f" ]; then
sed -i -e "s/30000/600000/" "$f"
fi
done
pnpm build
runHook postBuild
'';
installPhase = ''
runHook preInstall
mkdir -p $out/lib/metamcp $out/bin
# Ship the whole built workspace. This is bulky but reliable:
# `next start` needs .next + node_modules + package.json side-by-side,
# and the backend's runtime invokes `pnpm exec drizzle-kit migrate`.
cp -r \
apps \
packages \
node_modules \
package.json \
pnpm-workspace.yaml \
pnpm-lock.yaml \
turbo.json \
docker-entrypoint.sh \
$out/lib/metamcp/
# Sanitise the entrypoint: drop the hard-coded `cd /app/...` paths,
# set our launch root via $METAMCP_ROOT, keep the orchestration logic.
substituteInPlace $out/lib/metamcp/docker-entrypoint.sh \
--replace-fail "/app" "$out/lib/metamcp"
# Launcher: identical sequence to docker-entrypoint.sh, with PATH
# carrying pg_isready (for the readiness wait) and node.
cat > $out/bin/metamcp <<EOF
#!${stdenv.shell}
export METAMCP_ROOT=$out/lib/metamcp
export PATH=${
lib.makeBinPath [
nodejs_20
pnpm_10
postgresql # for pg_isready
]
}:\$PATH
cd \$METAMCP_ROOT
exec ${stdenv.shell} \$METAMCP_ROOT/docker-entrypoint.sh "\$@"
EOF
chmod +x $out/bin/metamcp
# Direct sub-launchers (handy for systemd "two-unit" deployments if
# you ever want to skip the bundled orchestrator).
makeWrapper ${nodejs_20}/bin/node $out/bin/metamcp-backend \
--add-flags "$out/lib/metamcp/apps/backend/dist/index.js" \
--set NODE_ENV production
cat > $out/bin/metamcp-frontend <<EOF
#!${stdenv.shell}
export PATH=${lib.makeBinPath [ nodejs_20 pnpm_10 ]}:\$PATH
cd $out/lib/metamcp/apps/frontend
exec pnpm start "\$@"
EOF
chmod +x $out/bin/metamcp-frontend
runHook postInstall
'';
# The pnpm/turbo build writes into $HOME; give it a writable one.
preBuild = ''
export HOME=$TMPDIR
'';
meta = {
description = "MetaMCP MCP aggregator, orchestrator, middleware, gateway";
homepage = "https://github.com/metatool-ai/metamcp";
license = lib.licenses.mit;
mainProgram = "metamcp";
platforms = [
"x86_64-linux"
"aarch64-linux"
];
};
})