Skip to content

filesystem: server exits silently when an allowed_directories entry does not exist on disk #4152

@dbDez

Description

@dbDez

Summary

The secure-filesystem-server package (src/filesystem/) terminates the Node process during startup if any path passed in allowed_directories is missing or inaccessible. The exit produces no stderr output, no JSON-RPC error response, and no log line indicating which path was the culprit — only the host (Claude Desktop, in my case) sees an abrupt transport close.

This is a sharp edge for end users: any folder rename, drive disconnect, or stale config entry takes the entire connector offline with no diagnostic to act on.

Environment

  • Host: Claude Desktop (Windows UWP, but the bug is in the server itself and is host-agnostic)
  • OS: Windows 11
  • Filesystem connector extension ID: ant.dir.ant.anthropic.filesystem
  • MCP server build: ships with current Claude Desktop (Node-based, secure-filesystem-server v0.2.0, per server log line "serverInfo":{"name":"secure-filesystem-server","version":"0.2.0"})

Reproduction

  1. Configure the filesystem MCP server with an allowed_directories list that contains at least one path which does not exist on disk. For example:

    {
      "allowed_directories": [
        "C:\\PKM",
        "C:\\Dev",
        "C:\\DevBackup"
      ]
    }

    where C:\PKM and C:\Dev exist but C:\DevBackup has been renamed or deleted.

  2. Launch the host so the MCP server is spawned.

  3. Observe that the server starts, accepts the initialize handshake, and then exits within 1–2 seconds. No tools/list is ever served.

Expected behaviour

One of:

  • Preferred: the server starts successfully using the valid subset of allowed_directories, emits a structured warning (stderr or JSON-RPC notifications/message) naming each invalid path, and continues serving requests scoped to the surviving paths.
  • Acceptable: the server fails fast but writes a clear stderr line identifying the offending path, e.g. Allowed directory does not exist: C:\DevBackup — startup aborted, and exits with a non-zero code that the host can surface.

Either makes the failure actionable.

Actual behaviour

The server exits with no stderr output and no JSON-RPC error. From the host's perspective the connector simply disappears mid-handshake.

Verbatim from %APPDATA%\Claude\logs\mcp-server-Filesystem.log (timestamps trimmed):

[Filesystem] [info] Initializing server...
[Filesystem] [info] Using built-in Node.js for MCP server: Filesystem
[Filesystem] [info] Server started and connected successfully
[Filesystem] [info] Message from client: {"method":"initialize", ... "id":0}
[Filesystem] [info] Server transport closed
[Filesystem] [info] Server transport closed unexpectedly, this is likely due to the process exiting early.
  If you are developing this MCP server you can add output to stderr ... and it will appear in this log.
[Filesystem] [error] Server disconnected.

The host UI surfaces three opaque toasts: "MCP Filesystem: Server disconnected", "This isn't working right now", "Could not attach to MCP server Filesystem". None mention which path is bad, which makes the issue hard to diagnose without reading server source.

Suspected root cause

During startup the server resolves every entry in allowed_directories via something equivalent to fs.realpathSync(path) to canonicalise it for later path-prefix checks. When a path is missing this throws ENOENT. The throw is not caught at the validation site, so it propagates to top-level and Node terminates with no handler logging it.

Suggested fix

Wrap per-path validation in try/catch and partition the input into "valid" and "invalid" buckets. Pseudocode:

const valid: string[] = [];
const invalid: { path: string; reason: string }[] = [];

for (const raw of allowedDirectories) {
  try {
    const resolved = fs.realpathSync(raw);
    const stat = fs.statSync(resolved);
    if (!stat.isDirectory()) {
      invalid.push({ path: raw, reason: 'not a directory' });
      continue;
    }
    valid.push(resolved);
  } catch (err: any) {
    invalid.push({ path: raw, reason: err?.code ?? err?.message ?? 'unknown' });
  }
}

for (const { path, reason } of invalid) {
  console.error(`[filesystem] Skipping allowed directory "${path}": ${reason}`);
}

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

// continue startup with `valid`

This:

  • Names every bad path on stderr, which the host displays in its server log.
  • Keeps the connector alive for the valid subset (matches user intent in the common case where only one of several drives is offline).
  • Aborts cleanly with a clear message if every path is bad.

An optional follow-up would be to emit a notifications/message MCP event after initialize so the host can render an inline warning in its UI, but the stderr line alone already unlocks 95% of the diagnostic value.

Impact

This silent-failure mode wastes substantial debugging time. In my case I'd renamed a folder hours earlier and forgotten it was in the connector list; the toasts gave no indication that a path was the problem, so I spent ~30 minutes reading mcp.log, main.log, and the Filesystem server log before finally diffing the allowed_directories JSON against the actual filesystem.

I've also filed a corresponding feedback note with Anthropic about the Claude Desktop side of the UX (better host-side dialog when a connector fails). This issue covers only the upstream server-side change.

Additional notes

  • The same code path may affect other Node-based MCP servers in this repo that take a directory list as a startup argument — worth checking, though I haven't reproduced there.
  • Happy to test a candidate fix on Windows + Claude Desktop UWP and confirm the new behaviour if a PR lands.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions