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).
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 packaborts 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
Actual behavior
The pack fails with a message like:
pack()returnsfalseand no.mcpbarchive is written. The error surfaces the underlying syscall rather than telling the user a symlink cycle was encountered.Expected behavior
Either:
Root cause
In
src/node/files.ts, bothgetAllFilesandgetAllFilesWithCountwalk the directory recursively usingstatSync(filePath)(which follows symlinks) and recurse into anythingstat.isDirectory()reports, with no tracking of already-visited real paths and no depth bound:Because
statSyncfollows the link, a cyclic symlink is reported as a directory and the walk descends into it until the OS raisesELOOP. Thepack.tsouter try/catch then turns that into the generic "Archive error" above.Suggested fix (for maintainer decision)
Use
lstatSyncto detect symlinks and either:fs.realpathSyncand breaking when a path repeats,surfacing a clear "symlink cycle detected, skipping " warning instead of letting
ELOOPbubble 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
modelcontextprotocol/mcpbmain(src/node/files.tsgetAllFiles/getAllFilesWithCount).