Guide · Development Toolchain 2026

MCP Server Nx — project graph, affected builds, task caching for monorepos

Nx adds a project dependency graph on top of pnpm or npm workspaces so that task orchestration — build, test, lint, typecheck — runs only for the packages that are actually affected by a git change. For MCP server monorepos where a shared mcp-core library underpins ten server packages, this means a change in one server package runs tests only for that package, while a change in mcp-core automatically triggers tests in all ten dependents. Three Nx behaviours that matter specifically for MCP projects: dependsOn in nx.json enforces that shared packages build before their dependents; the Nx cache captures the output of the esbuild step so unchanged packages restore dist/ from cache without re-bundling; and nx affected uses the base branch (not HEAD~1) for differential targeting in CI, which means your base branch configuration is critical.

TL;DR

Add nx as a devDependency at the workspace root. Create nx.json with a targetDefaults block that sets build to depend on ^build (build dependencies first) and configures cache outputs pointing to dist/. Run npx nx affected -t build test in CI to skip unchanged packages. Run npx nx graph to visualise the project dependency tree.

When Nx makes sense vs pnpm -r alone

For a monorepo with 2–3 packages, pnpm -r build and pnpm -r test are sufficient. Nx adds value when:

Nx is heavier than Turborepo for simple cases. If your monorepo only needs build ordering and basic caching, Turborepo's simpler config may be the better fit. Nx earns its weight when you need its generator system, project-level configuration overrides, and the Nx Cloud remote cache.

Adding Nx to an existing pnpm workspace

# Add Nx to an existing pnpm workspace (non-destructive)
pnpm add -D -w nx@latest

# Or use the automated migration (adds nx.json and infers project graph)
npx nx@latest init
// nx.json — root Nx configuration
{
  "$schema": "./node_modules/nx/schemas/nx-schema.json",
  "defaultBase": "main",        // base branch for nx affected comparisons

  "targetDefaults": {
    "build": {
      "dependsOn": ["^build"],  // build deps before self
      "inputs": ["production"], // only rebuild if production source changes
      "outputs": ["{projectRoot}/dist"],
      "cache": true
    },
    "test": {
      "dependsOn": ["build"],   // test after build
      "inputs": ["default", "^production"],
      "cache": true
    },
    "typecheck": {
      "dependsOn": ["^build"],  // shared packages must build (for .d.ts) before typecheck
      "cache": true
    },
    "dev": {
      "cache": false            // never cache the dev watch mode
    }
  },

  "namedInputs": {
    "default": ["{projectRoot}/**/*", "sharedGlobals"],
    "production": [
      "default",
      "!{projectRoot}/src/**/*.spec.ts",
      "!{projectRoot}/vitest.config.*"
    ],
    "sharedGlobals": ["{workspaceRoot}/tsconfig.base.json"]
  }
}

The ^build in dependsOn means "run the build target of all dependencies first". This is the critical setting for MCP monorepos: it ensures packages/mcp-core is built (producing dist/ and .d.ts files) before any server package that imports from it starts building.

project.json for individual MCP server packages

Each package can have a project.json file that overrides workspace-level target defaults or adds package-specific targets. For MCP servers, the main additions are the dev target (tsx watch) and a deploy target.

// servers/mcp-github/project.json
{
  "name": "@myorg/mcp-github",
  "$schema": "../../node_modules/nx/schemas/project-schema.json",
  "sourceRoot": "servers/mcp-github/src",
  "projectType": "application",
  "targets": {
    "build": {
      "executor": "nx:run-commands",
      "options": {
        "command": "node build.mjs",
        "cwd": "{projectRoot}"
      },
      "outputs": ["{projectRoot}/dist"],
      "cache": true
    },
    "dev": {
      "executor": "nx:run-commands",
      "options": {
        "command": "tsx watch src/index.ts",
        "cwd": "{projectRoot}"
      },
      "cache": false
    },
    "test": {
      "executor": "nx:run-commands",
      "options": {
        "command": "vitest run",
        "cwd": "{projectRoot}"
      }
    },
    "typecheck": {
      "executor": "nx:run-commands",
      "options": {
        "command": "tsc --noEmit",
        "cwd": "{projectRoot}"
      }
    }
  }
}

nx affected in CI: building and testing only what changed

The most valuable Nx feature for MCP monorepos is nx affected. In a PR workflow, it compares the current branch against the base branch (main) and runs tasks only for packages that have changed or that depend on changed packages.

# CI: build and test only affected packages
npx nx affected -t build test --base=origin/main --head=HEAD

# Equivalent shorthand when defaultBase is set in nx.json
npx nx affected -t build test

# Run affected tasks in parallel (up to 3 concurrent)
npx nx affected -t build test --parallel=3

# Show which projects are affected without running anything
npx nx affected --print-affected --base=origin/main

# Run all projects regardless of change (for release CI)
npx nx run-many -t build test --all
# .github/workflows/ci.yml — Nx-aware CI
name: CI
on:
  pull_request:
    branches: [main]
  push:
    branches: [main]

jobs:
  affected:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0           # full history needed for nx affected base comparison

      - uses: pnpm/action-setup@v4
        with:
          version: 9

      - uses: actions/setup-node@v4
        with:
          node-version-file: ".nvmrc"
          cache: "pnpm"

      - run: pnpm install --frozen-lockfile

      - name: Build and test affected packages
        run: npx nx affected -t build test typecheck --parallel=3 --base=origin/main

The fetch-depth: 0 in the checkout step is required — Nx needs the full git history to compute the diff between the PR branch and the base branch. Shallow clones (the default) cause Nx to fall back to running all projects.

Visualising the project graph

nx graph opens a browser-based interactive visualisation of the dependency graph between all packages in the workspace. For MCP monorepos, this is useful for spotting unexpected cross-package dependencies (a server package importing directly from another server's internals) and for understanding which packages will be affected by a change in mcp-core.

# Open the project graph in a browser
npx nx graph

# Output the graph as JSON for CI analysis
npx nx graph --file=project-graph.json

# Show only the graph for a specific project and its dependencies
npx nx graph --focus=@myorg/mcp-github

# Show which projects are affected by changing packages/mcp-core
npx nx affected:graph --base=main

Common failure modes

SymptomCauseFix
nx affected runs all projects (no filtering)Shallow git clone — Nx can't find the base commitAdd fetch-depth: 0 to the checkout step in CI
Build succeeds but dist/ is missing in dependent packageoutputs in nx.json not pointing to the correct directorySet "outputs": ["{projectRoot}/dist"] in the build target
Shared package build not running before server buildMissing "dependsOn": ["^build"] in nx.json targetDefaultsAdd dependsOn to the build target default
Cache hit replays stale dist/ after changing a depNamed inputs not including the dependency's output in the hashAdd "^production" to the test target's inputs
nx graph shows no edges between packagesPackages not listed in pnpm-workspace.yaml or Nx can't infer the graphRun npx nx init to re-infer, or add explicit implicitDependencies in project.json