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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions