Skip to content

[server-filesystem] Single missing allowed-directory causes silent process exit due to Promise.all rejection #4238

@minduch

Description

@minduch

Summary

@modelcontextprotocol/server-filesystem v0.2.0 (secure-filesystem-server) crashes
on startup if any allowed directory in argv does not exist. The crash is caused
by Promise.all(dirs.map(fs.stat)) rejecting on the first ENOENT, which terminates
the entire startup with no fallback and no actionable stderr captured by hosts that
don't surface child-process stderr (e.g. Claude Desktop on Windows MSIX, see linked
report).

Reproduction

node dist/index.js "C:\exists" "C:\does-not-exist"

Output:
Error accessing directory C:\does-not-exist: Error: ENOENT: no such file or directory,
stat 'C:\does-not-exist'
at async Object.stat (node:internal/fs/promises:1038:18)
at async file:///.../dist/index.js:43:23
at async Promise.all (index 1)
at async file:///.../dist/index.js:41:1

Process exits 1. No partial startup, no warning, no continuation with valid dirs.

Impact

When run under a host that does not pipe child-process stderr to the user (Claude
Desktop on the MSIX/Microsoft Store build of Windows is one such host), the user sees
only "Server transport closed unexpectedly" in the host log with no further detail.
Diagnosing the missing-directory cause requires running node dist/index.js manually
outside the host — a step most users won't think to take.

In my case the single offending entry was C:\NetPhantom 8 QS (a directory I had
removed). The other 8 allowed directories were valid, but the entire server died.

Expected behavior

Either:

  1. Skip missing directories with a clear warning to stderr and start with the
    valid ones. This matches how most "allowlist" tools behave and is the friendliest
    to users whose allowed_directories list drifts.
  2. Fail hard with a clear stderr message naming the offending directory, then
    process.exit(1). This preserves strict-validation semantics but at least makes
    the error survive process.on('uncaughtException')/host stderr capture.

Either way, replacing Promise.all with Promise.allSettled and inspecting each
result allows the server to report all missing/invalid directories in one pass
instead of dying on the first.

Suggested patch

Around dist/index.js:41-43 (the startup validation block), something like:

const results = await Promise.allSettled(
  allowedDirectories.map(async (dir) => {
    const stat = await fs.stat(dir);
    if (!stat.isDirectory()) {
      throw new Error(`Not a directory: ${dir}`);
    }
    return dir;
  }),
);

const valid: string[] = [];
const errors: string[] = [];
results.forEach((r, i) => {
  if (r.status === 'fulfilled') valid.push(r.value);
  else errors.push(`${allowedDirectories[i]}: ${(r.reason as Error).message}`);
});

if (errors.length > 0) {
  console.error(`[server-filesystem] Skipping ${errors.length} invalid director(y/ies):`);
  errors.forEach((e) => console.error(`  - ${e}`));
}

if (valid.length === 0) {
  console.error('[server-filesystem] No valid allowed directories; exiting.');
  process.exit(1);
}

// proceed with `valid`

Environment

  • @modelcontextprotocol/server-filesystem v0.2.0
  • Node.js: bundled with Claude Desktop (Windows MSIX build) + tested with system Node
  • OS: Windows 11 Pro Canary build, MSIX-installed Claude Desktop v1.8555.2.0
  • Server name reported in initialize: secure-filesystem-server v0.2.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    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