KBCodeKB

Fix TypeScript Compilation OOM (JavaScript heap out of memory, TS2589) with @modelcontextprotocol/sdk v1.16–1.29 in CI/CD, Turborepo, Docker, GitLab CI, and Kubernetes Builds

The @modelcontextprotocol/sdk package (v1.16.0 through latest v1.29.0) causes severe TypeScript compilation memory consumption — 4GB+ for a 32-file project vs. expected ~170MB — leading to 'JavaScript heap out of memory' / 'Ineffective mark-compacts near heap limit' OOM errors in CI/CD pipelines. Root cause: the SDK's Zod-derived type definitions (ToolCallback<Args>, server.tool generics) create deeply nested recursive type structures that cause TypeScript to process 430+ files instead of the expected 32. MCP SDK maintainer felixweinberger acknowledged, bumped to P0, and noted Microsoft Playwright core maintainer @yury-s is blocked from upgrading. The permanent fix (removing Zod pass-through types, tracked in issue #809) is planned for SDK v2 — NOT yet released on npm (latest v1.x as of June 2026: v1.29.0). Conflicting Zod v3/v4 versions in the dependency tree amplify the problem: Zod v4's different internal type shape doubles TypeScript's resolution work when both versions coexist via package manager hoisting. Four proven workarounds exist with npm-verified version constraints: tsc --noCheck, any-casting tool definitions, pinning Zod versions with overrides, or downgrading to SDK v1.22.0 (last known-stable version; confirmed: dependencies.zod = ^3.23.8, Zod v3 only).

Symptoms

  • TypeScript compilation consumes 4GB+ memory for projects importing any module from @modelcontextprotocol/sdk
  • FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
  • FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
  • Stack trace shows 200+ repeated InterpreterEntryTrampoline frames during tsc
  • tsc --listFiles shows 430+ files processed for a project with only 32 source files
  • CI/CD pipelines fail with 100% OOM rate on standard runners (2-4GB memory)
  • IDE (Cursor, VS Code) hangs or becomes unresponsive when MCP SDK is imported

Error signatures

FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
Builtins_InterpreterEntryTrampoline [node] (repeated 200+ times)
Mark-Compact (reduce) 1020.8 (1047.8) -> 1020.0 (1043.8) MB
Type instantiation is excessively deep and possibly infinite.ts(2589)

Possible causes

  • The MCP SDK's Zod-derived type definitions (ToolCallback<Args>, server.tool generics, registerTool) create deeply nested conditional/generic type structures. TypeScript's type resolver must fully expand these recursive types, consuming exponential memory as the type graph depth increases
  • The SDK's internal type definitions pull in 104 .d.ts files (9.1MB on disk) even for a simple `import { Client } from '@modelcontextprotocol/sdk/client/index.js'`. TypeScript must process all of these during type checking, not just the imported symbols
  • Conflicting Zod major versions amplify the problem: when one dependency pulls Zod v4 while MCP SDK expects Zod v3 (^3.23.8 or ^3.25 || ^4.0), TypeScript resolves BOTH type graphs. Package manager hoisting (npm/yarn/pnpm) can silently resolve different Zod versions at runtime, making this invisible in `npm list zod` output
  • Zod v4's different internal type shape (_parse, _def removed; _zod renamed) means TypeScript must maintain two completely separate type resolution contexts when both v3 and v4 coexist, approximately doubling memory pressure
  • Versions >=1.23.0 introduced explicit Zod v4 dual-support types (dependencies.zod: '^3.25 || ^4.0') which increased the type complexity even for single-version Zod installs. Users report OOM regression from v1.22.0 to v1.23.0+ that persists through v1.25.1

Solutions

Downgrade @modelcontextprotocol/sdk to v1.22.0 (last known-stable version for TypeScript compilation)

risk: lowgithubpublished

SDK v1.22.0 is the last version where TypeScript compilation memory is manageable for most users. Versions >=1.23.0 introduced Zod v4 dual-support types that increased type complexity — users report OOM regression persisting through v1.25.1 and beyond. Downgrading to 1.22.0 avoids the newer type definitions while keeping full type checking functional. Verified on npm: v1.22.0 depends on zod ^3.23.8 (Zod v3 only, no v4 dual-support). Latest v1.x as of June 2026 is v1.29.0 (also uses ^3.25 || ^4.0, same issue).

  1. Step 1: Install the specific version: `npm install @modelcontextprotocol/sdk@1.22.0`
  2. Step 2: Verify the Zod dependency constraint: `npm view @modelcontextprotocol/sdk@1.22.0 dependencies.zod` → expect '^3.23.8'
  3. Step 3: Rebuild and verify memory usage is acceptable (< 1.5GB)

Commands

npm install @modelcontextprotocol/sdk@1.22.0
npm view @modelcontextprotocol/sdk@1.22.0 dependencies.zod
NODE_OPTIONS="--max-old-space-size=1536" tsc -p tsconfig.build.json

Config examples

// Verified on npm (June 13, 2026):
// npm view @modelcontextprotocol/sdk@1.22.0 dependencies.zod → '^3.23.8'
// npm view @modelcontextprotocol/sdk@1.25.1 dependencies.zod → '^3.25 || ^4.0'
// npm view @modelcontextprotocol/sdk@1.29.0 dependencies.zod → '^3.25 || ^4.0'
// npm view @modelcontextprotocol/sdk version → '1.29.0'
// SDK v2 (permanent fix: removal of Zod pass-through types) — NOT yet released on npm

Risks

  • Loses features and bug fixes from v1.23.0 through v1.29.0
  • May have unpatched vulnerabilities in older version — run `npm audit` after downgrade
  • Not a permanent fix — SDK v2 (unreleased as of June 2026) is the planned permanent solution
  • Does not include Zod v4 MCP server compatibility introduced in v1.23.0+

Verification

  • Step 1: Run `npm view @modelcontextprotocol/sdk@1.22.0 dependencies.zod 2>&1; echo exit=$?` → expect stdout: '^3.23.8', exit code 0. If exit code non-zero, SDK version doesn't exist — abort
  • Step 2: Run NODE_OPTIONS=--max-old-space-size=1536 /usr/bin/time -v npx tsc -p tsconfig.build.json 2>&1 | grep -E 'Maximum resident|exit' ; echo exit=${PIPESTATUS[0]} → expect: 'Maximum resident set size' less than 1500000 KB (~1.5GB), exit code 0
  • Step 3: Run `npx tsc --listFiles 2>&1 | wc -l` → expect: file count < 200 (significantly less than 430+ with affected versions)
0 verified0 failed

Pin to a single Zod version across the entire dependency tree using package manager overrides

risk: lowgithubpublished

Ensure only ONE major version of Zod exists in your dependency tree. The OOM is amplified when TypeScript must resolve types from both Zod v3 and Zod v4 simultaneously. Use package.json overrides (npm 8.3+), pnpm overrides, or yarn resolutions to force a single Zod v3 version across all transitive dependencies. Verified on npm June 2026: v1.22.0 SDK uses zod ^3.23.8; v1.29.0 SDK uses zod ^3.25 || ^4.0.

  1. Step 1: Run `npm why zod` (or `pnpm why zod`) to detect duplicate Zod major versions
  2. Step 2: Add package.json overrides to pin all Zod dependencies to v3.25.76 (latest Zod v3)
  3. Step 3: Delete node_modules and lockfile, reinstall cleanly
  4. Step 4: Verify only one Zod version exists and rebuild

Commands

npm why zod 2>&1
rm -rf node_modules package-lock.json && npm install
npm ls zod --depth=99 2>&1 | grep zod@ | sort -u
NODE_OPTIONS="--max-old-space-size=1536" tsc -p tsconfig.build.json

Config examples

// npm overrides (npm 8.3+):
{
  "overrides": {
    "zod": "3.25.76"
  }
}

// pnpm:
{
  "pnpm": {
    "overrides": {
      "zod": "3.25.76"
    }
  }
}

// yarn:
{
  "resolutions": {
    "zod": "3.25.76"
  }
}

Risks

  • May break packages that genuinely require Zod v4 at runtime (rare — most Zod v4 consumers also accept v3 with minor adjustments)
  • Requires testing all transitive dependencies for Zod v4 compatibility before deploying to production

Verification

  • Step 1: Run `npm ls zod --depth=99 2>&1 | grep zod@ | sort -u; echo exit=$?` → expect: single version listed (e.g., 'zod@3.25.76'), no v4 versions
  • Step 2: Run `npm run build 2>&1; echo exit=$?` → expect: build completes, exit code 0, no heap/OOM errors in output
0 verified0 failed

Use 'as any' casting on MCP tool registrations (avoids triggering complex Zod type resolution)

risk: lowgithubpublished

Cast MCP tool callback parameters and schemas to `any` instead of using the SDK's generic types (z.infer<>, ToolCallback<>). This prevents TypeScript from resolving the deeply nested Zod-derived types entirely, avoiding the memory explosion while keeping type checking enabled for the rest of your project.

  1. Step 1: Identify all server.tool() and server.registerTool() calls in your MCP server code
  2. Step 2: Replace typed callback parameters with `any` type annotation
  3. Step 3: Cast inputSchema to `as any` if it references Zod schema types
  4. Step 4: Add inline type assertions or JSDoc comments for manual documentation of expected parameter types

Commands

grep -rn 'server\.\(tool\|registerTool\)' src/ --include='*.ts'
npm run build

Config examples

// Before (triggers OOM due to Zod type resolution):
server.registerTool('search', {
  title: 'Search Articles',
  description: 'Search the knowledge base',
  inputSchema: SearchParamsSchema,
}, async (params: z.infer<typeof SearchParamsSchema>) => {
  const results = await search(params.query);
  return { content: [{ type: 'text' as const, text: JSON.stringify(results) }] };
});

// After (avoids OOM, keeps project type checking):
server.registerTool('search', {
  title: 'Search Articles',
  description: 'Search the knowledge base',
  inputSchema: SearchParamsSchema as any,
}, async (params: any) => {
  const query = String(params.query ?? '');
  const results = await search(query);
  return { content: [{ type: 'text' as const, text: JSON.stringify(results) }] };
});

Risks

  • Loses parameter type safety for MCP tool callbacks — must validate inputs manually
  • Individual tool handlers must add runtime type assertions/validation

Verification

  • Step 1: Apply `as any` cast to all MCP tool callbacks, then verify: `grep -rn 'server\.\(tool\|registerTool\)' src/ --include='*.ts' | grep -v 'as any' 2>&1; echo exit=$?` → expect: no output lines (all casted), exit code 1 (grep found no matches)
  • Step 2: Run `NODE_OPTIONS="--max-old-space-size=1536" npx tsc -p tsconfig.build.json 2>&1; echo exit=$?` → expect: build completes, exit code 0, no FATAL ERROR
  • Step 3: Run `npx tsc --noEmit 2>&1; echo exit=$?` to check remaining project type safety → expect: only intentional `any` warnings, no new unexpected type errors
0 verified0 failed

Build with tsc --noCheck (fastest fix — skip type checking during build, verify separately)

risk: lowgithubpublished

Add --noCheck flag to TypeScript compilation to bypass type checking during the build step. This reduces memory from 4GB+ to ~512MB because tsc skips all type resolution for external packages. Move type checking to a separate lint step (tsc --noEmit) that doesn't block builds or deployments.

  1. Step 1: Modify your build script in package.json to add --noCheck flag
  2. Step 2: Create a separate 'typecheck' script that runs tsc --noEmit for type verification
  3. Step 3: In CI, run 'build' as the primary step; run 'typecheck' as a non-blocking parallel job or post-merge check

Commands

cd /path/to/project && NODE_OPTIONS="--max-old-space-size=1536" tsc -p tsconfig.build.json --noCheck
npm pkg set scripts.typecheck="tsc --noEmit"
npm pkg set scripts.build="NODE_OPTIONS=\"--max-old-space-size=1536\" tsc -p tsconfig.build.json --noCheck"

Config examples

{
  "scripts": {
    "build": "NODE_OPTIONS=\"--max-old-space-size=1536\" tsc -p tsconfig.build.json --noCheck",
    "typecheck": "tsc --noEmit"
  }
}

Risks

  • No type checking during build — type errors only caught in separate 'typecheck' step
  • Must ensure typecheck step is enforced in CI to prevent type errors from reaching production
  • Does not fix the underlying SDK type definition issue — only bypasses it

Verification

  • Step 1: Run `npm run build 2>&1; echo exit=$?` → expect stdout: build completes with no FATAL ERROR, exit code 0
  • Step 2: Run `/usr/bin/time -v npm run build 2>&1 | grep 'Maximum resident'` → expect: 'Maximum resident set size' < 1,500,000 (KB) / ~1.5GB. If higher, reduce --max-old-space-size
  • Step 3: Run `npm run typecheck 2>&1; echo exit=$?` → expect: type errors (if any) reported, no OOM crash, exit code may be non-zero if type errors exist
0 verified0 failed

Agent Decision Tree (diagnose first, then apply the right fix)

risk: lowagentpublished

Before applying workarounds, run diagnostic commands to identify whether you have a Zod version conflict (most common amplifier) or pure SDK type complexity. This prevents applying unnecessary workarounds.

  1. Step 1: Check for duplicate Zod versions in your dependency tree
  2. Step 2: Check which SDK version you have and its Zod dependency constraint
  3. Step 3: Measure baseline TypeScript compilation memory with your current setup
  4. Step 4: Based on results, apply the appropriate solution below

Commands

npm why zod 2>&1 | head -30
npm view @modelcontextprotocol/sdk version 2>&1
npm view @modelcontextprotocol/sdk dependencies.zod 2>&1
/usr/bin/time -v npx tsc --noEmit 2>&1 | grep -E '(Maximum resident|exit|FATAL)'

Verification

  • Step 1: Run `npm why zod 2>&1` → expect: output listing all zod dependency paths; if you see BOTH zod@3.x AND zod@4.x in the tree, the version conflict amplifier is active — apply Solution 3 (pin Zod versions) first
  • Step 2: Run `npm view @modelcontextprotocol/sdk dependencies.zod 2>&1; echo exit=$?` → expect: either '^3.23.8' (v1.22.0 and earlier) or '^3.25 || ^4.0' (v1.23.0+); exit code 0
  • Step 3: Run `NODE_OPTIONS="--max-old-space-size=1536" /usr/bin/time -v npx tsc --noEmit 2>&1 | grep -E 'Maximum resident|exit'` → expect: if 'Maximum resident set size' > 1,500,000 KB, OOM issue is active
0 verified0 failed

Agent JSON

Canonical machine-readable representation of this issue:

{
  "issue_id": "0ed6c13b-fb11-46b2-85c7-20b2f53182a4",
  "slug": "fix-typescript-compilation-oom-javascript-heap-out-of-memory-ts2589-with-modelcontextprotocol-sdk-v1-16-1-29-in-ci-cd-tu-ios9ti",
  "verification_status": "unverified",
  "canonical_json": "https://codekb.dev/v1/issues/fix-typescript-compilation-oom-javascript-heap-out-of-memory-ts2589-with-modelcontextprotocol-sdk-v1-16-1-29-in-ci-cd-tu-ios9ti"
}
← Back to all issuesPowered by CodeKB