Skip to content

Filesystem server canonicalizes configured Windows mapped-drive paths to UNC at startup, then rejects identical-target Y:\ inputs #4129

@PapaBearDoes

Description

@PapaBearDoes

Summary

On Windows, when the Filesystem MCP server is configured with paths on a mapped drive (e.g. Y:\k8s where Y: is mapped to \\192.168.xxx.xxx\nas), the configured paths are canonicalized to their UNC form at server startup. However, incoming tool-call paths using the original Y:\ form are not canonicalized to the same UNC form before the allow-list comparison, so they are rejected — even though they refer to the exact same directory on disk that was configured.

The net effect is that the configured allow-list (as shown in the UI) and the runtime allow-list (as reported by list_allowed_directories) disagree, and the user has no way to know which form to send without trial-and-error.

Environment

  • OS: Windows (mapped drive Y:\\192.168.xxx.xxx\nas over SMB)
  • Client: Claude Desktop (version: <fill in from Help → About>)
  • Server: @modelcontextprotocol/server-filesystem shipped as the Filesystem extension in Claude Desktop
  • Drive mapping: Y: is a persistent SMB mapping to a NAS share, available to the user account before Claude Desktop launches (dir Y:\ works in cmd at the time of repro)

Configuration

Claude Desktop Version:

1.6608.2 (ebf1a1) 2026-05-08T23:17:27.000Z

Filesystem Extension/Connector Version:

0.2.2

The Filesystem extension's Allowed Directories UI (screenshot attached) is configured with:

  • Y:\git
  • Y:\wiki
  • Y:\k8s

These entries are exactly as typed; the UI preserves the Y:\ form on save and on subsequent views.

Steps to Reproduce

  1. On Windows, map a drive letter (e.g. Y:) to an SMB share (e.g. \\server\share) so that dir Y:\ works.
  2. Configure the Filesystem extension's Allowed Directories with one or more entries using the drive-letter form (e.g. Y:\k8s).
  3. Start Claude Desktop and confirm the Filesystem extension is enabled.
  4. From a tool call, invoke list_allowed_directories.
  5. From a tool call, invoke list_directory with path = "Y:\k8s" (or any subpath).

Expected Behavior

  • list_allowed_directories returns the same form that was configured (Y:\k8s, etc.), or the input path is canonicalized to the same form as the stored allow-list before comparison.
  • list_directory with path = "Y:\k8s" succeeds, since it refers to a configured directory.

Actual Behavior

list_allowed_directories returns the UNC form, not the configured form:

Allowed directories:
\\192.168.xxx.xxx\nas\git
\\192.168.xxx.xxx\nas\wiki
\\192.168.xxx.xxx\nas\k8s

list_directory with path = "Y:\k8s" is rejected:

Access denied - path outside allowed directories:
Y:\k8s not in \\192.168.xxx.xxx\nas\git, \\192.168.xxx.xxx\nas\wiki, \\192.168.xxx.xxx\nas\k8s

The same call with path = "\\\\192.168.xxx.xxx\\nas\\k8s" succeeds. Both forms refer to the identical directory on disk.

Suspected Cause

Per the source summary at https://deepwiki.com/modelcontextprotocol/servers/2.2-filesystem-tools-reference, the configured directory list goes through expandHome() → absolute resolution → fs.realpath()normalizePath() at startup (src/filesystem/index.ts ~lines 47–54). On Windows, fs.realpath() on a mapped drive returns the UNC target, which is why the stored allow-list is in UNC form.

The validatePath() function on incoming requests also resolves symlinks for security, but in this case the comparison evidently does not produce the same canonical form for the input as for the stored entries. Either:

  1. validatePath() is not applying fs.realpath() to the input before the prefix check, or
  2. It is applying it, but on a path that doesn't yet exist on disk (e.g. for a write_file to a new file under an allowed directory), and falling back to a non-canonicalized form, or
  3. The comparison is a string startsWith against a canonicalized allow-list using a non-canonicalized input.

Why This Matters

From a user's perspective, the configured paths in the UI are the source of truth. There's no surfaced indication that the runtime has rewritten them, and no documented guidance to use UNC form for Windows users who have mapped drives. Workflows that have been stable for weeks can break silently if the server's startup-time canonicalization behavior changes.

It also means tool callers (LLMs, IDEs, scripts) that read list_allowed_directories to learn what's allowed will see UNC, send UNC, and work — but humans reading the same configuration in the UI will see Y:\ and reasonably expect that form to work.

Suggested Fix

In validatePath() (or wherever the prefix comparison happens), ensure the input path goes through the same canonicalization pipeline used at server startup before the allow-list check — fs.realpath() for paths that exist on disk, or for write operations, walk up to the nearest existing parent and canonicalize that, then re-append the trailing components. This preserves the existing security model (symlinks resolved, traversal attacks blocked) while making the allow-list check symmetric with the allow-list construction.

Alternatively (or additionally), preserve the original configured path strings alongside the canonicalized form, return the original form from list_allowed_directories, and check incoming paths against the canonical form after applying fs.realpath() to the input.

Workaround

For any Windows user with a mapped drive in their allow-list: send paths in UNC form (\\server\share\...) instead of drive-letter form (Y:\...) until this is fixed. This is awkward but works.

Attached

Image - Screenshot: Allowed Directories settings showing the configured `Y:\` entries (UI source of truth, contradicting the runtime UNC behavior).

(Bug report written with assistance from Claude)

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