Skip to content

fix(plugin): provision externalised MCP runtime deps on first launch#3

Merged
cdeust merged 1 commit into
mainfrom
fix/mcp-runtime-deps-provisioning
Jun 1, 2026
Merged

fix(plugin): provision externalised MCP runtime deps on first launch#3
cdeust merged 1 commit into
mainfrom
fix/mcp-runtime-deps-provisioning

Conversation

@cdeust
Copy link
Copy Markdown
Owner

@cdeust cdeust commented Jun 1, 2026

Follow-up to #2. That PR fixed the startup deadlock; this one fixes the other half of "won't run after a clean install": the server's runtime dependencies aren't present.

Problem

claude plugin install clones repository files but runs no install step, so a fresh install has no node_modules. The committed esbuild bundle (mcp-server/index.js) intentionally externalises three runtime deps that must resolve from disk at launch:

Dep Why external Kind
ajv / ajv-formats ajv's runtime-compiled validators require() their helpers (ajv/dist/runtime/*) via specifiers esbuild can't statically inline hard (static require → needed at load)
better-sqlite3 native addon; the platform-specific binary can't be bundled or committed cross-platform optional (dynamic import(), guarded by tryCreateEvidenceRepository)

So a clean claude plugin install fails to launch the server with Cannot find module 'ajv'. (ajv-formats was also a phantom dependency — required by the bundle but declared in no package.json.)

Fix

Provision the externals on first launch — the same ensure-then-exec pattern automatised-pipeline uses for its Rust binary (bin/ensure-binary.sh):

  • mcp-server/package.json declares the externals: ajv + ajv-formats as dependencies, better-sqlite3 as an optionalDependency (a failed native build is non-fatal — the server still starts, just without the evidence-DB cache).
  • bin/ensure-deps.sh installs them into mcp-server/node_modules on first launch (idempotent; no-ops once present), then exec node mcp-server/index.js. --no-package-lock keeps the install dir free of a second lockfile (the workspace pins via pnpm-lock.yaml).
  • .mcp.json launches via the script instead of node directly.

Declared ranges match the workspace (benchmark ajv ^8.17.1, core better-sqlite3 ^11.7.0) and resolve to the bundled set (ajv 8.18.0, ajv-formats 3.0.1, better-sqlite3 11.10.0).

Verification (clean clone, no node_modules)

  • First launch: installs 44 pkgs (~19s) → server answers MCP initialize.
  • Second launch: no reinstall → answers initialize immediately.
  • ajv / ajv-formats / better-sqlite3 all resolve from mcp-server/node_modules.
  • No package-lock.json litter.
  • bash -n clean; script committed 100755.

Note

As with automatised-pipeline, the first launch pays the install cost (~20s) before the server is ready; subsequent launches are instant. If Claude Code's MCP connect times out on that first attempt, a reconnect succeeds.

🤖 Generated with Claude Code

`claude plugin install` clones repository files but runs no install step, so a
fresh install had no node_modules — and the committed esbuild bundle
(mcp-server/index.js) intentionally externalises three runtime deps that must
resolve from disk at launch:

  - ajv / ajv-formats — ajv's runtime-compiled validators require() their
    helpers (ajv/dist/runtime/*) via specifiers esbuild cannot statically
    inline. Static require => hard requirement at server load. (ajv-formats was
    also a phantom dep — required by the bundle, declared nowhere.)
  - better-sqlite3 — native addon; the platform-specific binary cannot be
    bundled or committed cross-platform. Loaded via dynamic import() and guarded
    by tryCreateEvidenceRepository => optional (absence only disables the
    evidence-DB cache, it does not block startup).

Result: a clean `claude plugin install` could not launch the server
(`Cannot find module 'ajv'`).

Fix — provision the deps on first launch, mirroring automatised-pipeline's
bin/ensure-binary.sh ensure-then-exec pattern:
  - mcp-server/package.json declares the externals: ajv + ajv-formats as
    dependencies, better-sqlite3 as an optionalDependency (so a failed native
    build is non-fatal).
  - bin/ensure-deps.sh installs them into mcp-server/node_modules on first
    launch (idempotent; no-ops once present) and execs node.
  - .mcp.json launches via the script instead of node directly.

Versions match the workspace declarations (benchmark ajv ^8.17.1, core
better-sqlite3 ^11.7.0) and resolve to the bundled set (ajv 8.18.0,
ajv-formats 3.0.1, better-sqlite3 11.10.0).

Verification (clean clone, no node_modules):
- first launch: installs 44 pkgs (~19s) then answers MCP `initialize`
- second launch: no reinstall, answers `initialize` immediately
- ajv / ajv-formats / better-sqlite3 all resolve from mcp-server/node_modules
- --no-package-lock keeps the install dir free of a second lockfile

Note: as with automatised-pipeline, the FIRST launch pays the install cost
(~20s) before the server is ready; subsequent launches are instant.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cdeust cdeust merged commit f945ac0 into main Jun 1, 2026
2 checks passed
@cdeust cdeust deleted the fix/mcp-runtime-deps-provisioning branch June 1, 2026 08:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant