thedotmack/claude-mem · critical · Error

mcp-server.cjs contains a Bun-only ${bunRequireMatch[0]} cal

Error message

mcp-server.cjs contains a Bun-only ${bunRequireMatch[0]} call. This means a transitive import in src/servers/mcp-server.ts pulled in code from worker-service.ts (or another module that touches DatabaseManager/ChromaSync). The MCP server runs under Node and cannot load bun:* modules. Audit recent imports in src/servers/mcp-server.ts and src/services/worker-spawner.ts — the spawner module is intentionally lightweight and MUST NOT import anything that touches SQLite or other Bun-only modules. See PR #1645 for context.

What it means

A bundle-hygiene guard over the built MCP server (hooks/<name>.mcp-server.cjs). After esbuild bundles src/servers/mcp-server.ts, the build scans the output for require('bun:*') calls. The MCP server runs under Node (e.g. Claude Desktop), which has no bun:* modules, so any such require proves a transitive import dragged in worker-service.ts or another Bun-only module (DatabaseManager/ChromaSync). The fix is to sever the offending import chain, not to stub bun at runtime.

Source

Thrown at scripts/build-hooks.js:525

      define: {
        '__DEFAULT_PACKAGE_VERSION__': `"${version}"`
      },
      banner: {
        js: '#!/usr/bin/env node'
      }
    });

    stripHardcodedDirname(`${hooksDir}/${MCP_SERVER.name}.cjs`);

    fs.chmodSync(`${hooksDir}/${MCP_SERVER.name}.cjs`, 0o755);
    const mcpServerStats = fs.statSync(`${hooksDir}/${MCP_SERVER.name}.cjs`);
    console.log(`✓ mcp-server built (${(mcpServerStats.size / 1024).toFixed(2)} KB)`);

    const mcpBundleContent = fs.readFileSync(`${hooksDir}/${MCP_SERVER.name}.cjs`, 'utf-8');
    const bunRequireRegex = /require\(\s*["']bun:[a-z][a-z0-9_-]*["']\s*\)/;
    const bunRequireMatch = mcpBundleContent.match(bunRequireRegex);
    if (bunRequireMatch) {
      throw new Error(
        `mcp-server.cjs contains a Bun-only ${bunRequireMatch[0]} call. This means a transitive import in src/servers/mcp-server.ts pulled in code from worker-service.ts (or another module that touches DatabaseManager/ChromaSync). The MCP server runs under Node and cannot load bun:* modules. Audit recent imports in src/servers/mcp-server.ts and src/services/worker-spawner.ts — the spawner module is intentionally lightweight and MUST NOT import anything that touches SQLite or other Bun-only modules. See PR #1645 for context.`
      );
    }
    const zodRequireRegex = /require\(\s*["']zod(?:\/[^"']*)?["']\s*\)/;
    const zodRequireMatch = mcpBundleContent.match(zodRequireRegex);
    if (zodRequireMatch) {
      throw new Error(
        `mcp-server.cjs contains external ${zodRequireMatch[0]}. Claude Desktop can launch this bundle without plugin node_modules available, so Zod must be bundled into the MCP server.`
      );
    }

    const MCP_SERVER_MAX_BYTES = 600 * 1024;
    if (mcpServerStats.size > MCP_SERVER_MAX_BYTES) {
      console.warn(
        `⚠️  mcp-server.cjs is ${(mcpServerStats.size / 1024).toFixed(2)} KB (advisory budget ${(MCP_SERVER_MAX_BYTES / 1024).toFixed(0)} KB). If this jumped unexpectedly, a transitive import may have pulled worker-service.ts or another heavy module into the MCP bundle (see #1645).`
      );
    }

View on GitHub (pinned to d768ba3643)

Solutions

  1. Audit imports in src/servers/mcp-server.ts and src/services/worker-spawner.ts for anything that transitively touches SQLite/Chroma/DatabaseManager.
  2. Move the shared logic into a node-safe module with no Bun imports, and have both worker-service.ts and mcp-server.ts depend on that, OR pass data across the worker/MCP boundary via IPC/HTTP instead of importing.
  3. Keep worker-spawner.ts lightweight — it must not import anything that touches Bun-only modules (the error message states this contract explicitly).
  4. Rebuild; the guard passes when no require('bun:*') remains in the bundle.

Example fix

// before (src/services/worker-spawner.ts) — pulls a Bun-only helper
import { queryObservations } from './worker-service.js';

// after — keep the spawner node-safe; MCP server gets data via the worker HTTP API instead
import { spawn } from 'child_process';
// no DB imports here; mcp-server.ts calls worker /api/* endpoints
Defensive patterns

Strategy: validation

Validate before calling

// After building, scan the bundle yourself to catch bun:* leakage early:
const out = fs.readFileSync('hooks/mcp-server.cjs','utf8');
if (/require\(\s*["']bun:[a-z][a-z0-9_-]*["']\s*\)/.test(out)) {
  throw new Error('Bun-only require leaked into mcp-server bundle');
}

Type guard

// Node-safe module marker: a module that must not import bun:*
// Add a lint rule forbidding bun:* imports in src/servers/** and src/services/worker-spawner.ts.

Try / catch

// Build-time only. Fix the import graph — do not patch the bundle.
// Move shared logic into a node-safe module or call the worker over HTTP from the MCP server.

Prevention

When it happens

Trigger: Adding an import in src/servers/mcp-server.ts (or src/services/worker-spawner.ts) that transitively reaches a module importing 'bun:sqlite'/'bun:test'/etc. The esbuild bundle then carries the require('bun:...') and this guard fires. Cross-ref PR #1645 which introduced the isolation.

Common situations: Refactor that makes the MCP server import a shared service that itself imports DatabaseManager. Adding a utility to worker-spawner.ts that pulls in a Bun-only helper. A new feature that needs DB access from the MCP server path.

Related errors


AI-assisted analysis of thedotmack/claude-mem@d768ba3643 (2026-08-12). Data as JSON: /api/errors/aee61afdab69ca6d. Report an issue: GitHub.