Skip to content

MCP brain_* tools fail with ERR_PACKAGE_PATH_NOT_EXPORTED — bin/mcp-server.js require()s ESM-only @ruvector/pi-brain #372

@ronmikailov

Description

@ronmikailov

Summary

All 11 mcp__ruvector__brain_* tools in the ruvector MCP server fail at runtime with:

ERR_PACKAGE_PATH_NOT_EXPORTED
No "exports" main defined in .../node_modules/@ruvector/pi-brain/package.json

The CLI (ruvector brain status, etc.) works — only the MCP tools are broken.

Environment

  • ruvector 0.2.22 (npm, global)
  • @ruvector/pi-brain 0.1.1 (npm, global; pulled in as a sibling install)
  • Node.js v20.19.6
  • Linux (WSL2)

Reproduction

npm i -g ruvector @ruvector/pi-brain
# Start the MCP server in any MCP client (Claude Code, etc.) and call:
#   mcp__ruvector__brain_status
# Result:
#   { "success": false, "error": "No \"exports\" main defined in .../@ruvector/pi-brain/package.json" }

Or reproduce directly without MCP:

cd $(npm root -g)/ruvector
node -e "try { require('@ruvector/pi-brain'); } catch (e) { console.log(e.code, e.message); }"
# ERR_PACKAGE_PATH_NOT_EXPORTED No "exports" main defined in .../@ruvector/pi-brain/package.json

Root cause

bin/mcp-server.js is CommonJS and uses synchronous require() in 11 brain handlers:

// bin/mcp-server.js, lines 3242, 3259, 3276, 3293, 3310, 3327, 3344, 3361, 3378, 3395, 3412
const piBrain = require('@ruvector/pi-brain');
const PiBrainClient = piBrain.PiBrainClient || piBrain.default;

But @ruvector/pi-brain@0.1.1 is ESM-only with no require condition in its export map:

// @ruvector/pi-brain/package.json
{
  "type": "module",
  "main": "dist/index.js",
  "exports": {
    ".": { "import": "./dist/index.js", "types": "./dist/index.d.ts" }
  }
}

Since there is no "require" condition, Node refuses the CJS require() and throws ERR_PACKAGE_PATH_NOT_EXPORTED. The handler's catch block only converts MODULE_NOT_FOUND to the friendly "install @ruvector/pi-brain" hint, so this error falls through to the raw e.message path and the tool returns { success: false, error: "No \"exports\" main defined ..." }.

Affected tools (all 11 brain_* MCP tools)

brain_search, brain_share, brain_get, brain_vote, brain_list, brain_delete, brain_status, brain_drift, brain_partition, brain_transfer, brain_sync

Why the CLI is unaffected

ruvector brain <subcmd> calls pi.ruv.io over HTTPS without loading @ruvector/pi-brain, so the ESM interop path is never hit.

Suggested fix

The MCP handler is already async (server.setRequestHandler(CallToolRequestSchema, async (request) => {...})), so a one-line change in each handler is sufficient:

- const piBrain = require('@ruvector/pi-brain');
+ const piBrain = await import('@ruvector/pi-brain');
  const PiBrainClient = piBrain.PiBrainClient || piBrain.default;

This is the same edit × 11 call sites. Verified locally:

cd $(npm root -g)/ruvector
node -e "(async () => { const p = await import('@ruvector/pi-brain'); console.log(Object.keys(p)); })()"
# [ 'PiBrainClient' ]

Alternative: publish @ruvector/pi-brain with a dual CJS/ESM build and add a "require" condition to the exports map. The dynamic-import fix in the consumer is the smaller diff and lets pi-brain stay ESM-only.

Workaround (for anyone hitting this now)

Patch the installed MCP server in place:

sed -i "s |require('@ruvector/pi-brain')|await import('@ruvector/pi-brain')|g" \
  $(npm root -g)/ruvector/bin/mcp-server.js
# Restart the MCP server / reconnect from the client

Confirmed this unblocks all 11 tools locally. Any npm i -g ruvector upgrade will wipe the patch until this is fixed upstream.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions