Skip to content

mcpb pack fails with a cryptic ELOOP error when the extension directory contains a symlink cycle #292

Description

@devYRPauli

Summary

When packing an extension whose directory tree contains a symbolic-link cycle (a self-referential link, or two links pointing at each other's parents), mcpb pack aborts with a raw OS error instead of a clear diagnostic, and no archive is produced. Some legitimate project layouts (accidental self-links, certain monorepo / package-manager symlink arrangements) can trigger this.

Steps to reproduce

mkdir -p demo-ext
cd demo-ext
# minimal manifest so pack gets past validation
cat > manifest.json <<'EOF'
{ "manifest_version": "0.1", "name": "demo", "version": "1.0.0",
  "server": { "type": "node", "entry_point": "index.js" } }
EOF
touch index.js
ln -s . selfloop        # self-referential symlink -> cycle
cd ..
mcpb pack demo-ext

Actual behavior

The pack fails with a message like:

ERROR: Archive error: ELOOP: too many symbolic links encountered, stat '.../demo-ext/selfloop/selfloop/.../'

pack() returns false and no .mcpb archive is written. The error surfaces the underlying syscall rather than telling the user a symlink cycle was encountered.

Expected behavior

Either:

  • skip symlinks (or symlinked directories) during the directory walk and continue packing, ideally with a warning, or
  • fail with a clear, actionable message that names the offending path and says a symlink cycle was detected.

Root cause

In src/node/files.ts, both getAllFiles and getAllFilesWithCount walk the directory recursively using statSync(filePath) (which follows symlinks) and recurse into anything stat.isDirectory() reports, with no tracking of already-visited real paths and no depth bound:

const stat = statSync(filePath);
if (stat.isDirectory()) {
  getAllFiles(filePath, baseDir, fileList, additionalPatterns);   // unbounded on a cycle
}

Because statSync follows the link, a cyclic symlink is reported as a directory and the walk descends into it until the OS raises ELOOP. The pack.ts outer try/catch then turns that into the generic "Archive error" above.

Suggested fix (for maintainer decision)

Use lstatSync to detect symlinks and either:

  • skip them (simplest; matches "we only archive real files"), or
  • follow them but guard against cycles by tracking visited real paths via fs.realpathSync and breaking when a path repeats,

surfacing a clear "symlink cycle detected, skipping " warning instead of letting ELOOP bubble up.

I'm raising this as an issue rather than a PR because the desired behavior (silently skip symlinks vs. warn-and-skip vs. hard-fail with a clear message) is a product decision I'd rather leave to the maintainers. Happy to send a PR once the preferred behavior is decided.

Environment

  • Repo: modelcontextprotocol/mcpb
  • Reproduced against current main (src/node/files.ts getAllFiles / getAllFilesWithCount).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions