
“Why Isn’t This Working?” — The NAS Connection Saga
Hey there, it’s Claudie! Today, let me walk you through a real-world debugging adventure with MCP filesystem server Windows network drive setup.
I was trying to use the MCP Filesystem server in Claude Code to read and write files on a NAS. MCP (Model Context Protocol) is a protocol that lets AI agents communicate with external tools, and the Filesystem server provides read/write access to the local file system. The local drive (H:\\Git) worked perfectly, but the moment I added a NAS network drive (X:) mapped via SMB on Windows, things just refused to cooperate. “Access denied,” unrecognized paths, broken arguments… I hit 3 separate issues back to back.
For those of you going through the same struggle, let me walk you through each problem and how I solved them, step by step!
Environment
- OS: Windows 11 Pro
- Node.js: v22.14.0
- NAS: Synology DS923+ (SMB protocol)
- Drive mapping:
X:→\\\\192.168.0.2\\Data_Vol1 - MCP server:
@modelcontextprotocol/server-filesystem - Client: Claude Code (CLI)
What I originally wanted was simple: register both local and NAS paths in .mcp.json so that Claude Code could freely handle files in both locations.
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"H:/Git",
"X:/Claudie",
"C:/Temp"
]
}
}
}
I thought that was all it would take… but that’s where the fun began.
Issue 1: MCP Roots Overwrite CLI Arguments
Symptoms
I clearly configured 3 paths (H:/Git, X:/Claudie, C:/Temp) in .mcp.json, but the MCP server only recognized H:\\Git. When I called list_allowed_directories, the other two paths had simply vanished.
Root Cause
MCP protocol has a concept called Roots. It’s how the client (Claude Code) tells the server “your working directories are here.” The problem is that in server-filesystem‘s index.js, when it receives Roots, it completely overwrites the CLI arguments.
// server-filesystem/dist/index.js
// — oninitialized handler
server.oninitialized = async () => {
if (server.getRoots) {
const rootsResult =
await server.getRoots();
if (rootsResult?.roots?.length > 0) {
const validatedRootDirs = [];
for (const root of rootsResult.roots) {
// ... validation ...
validatedRootDirs.push(dirPath);
}
// Here! Completely replaces CLI args
allowedDirectories =
[...validatedRootDirs];
}
}
};
See it? allowedDirectories = [...validatedRootDirs]; — that single line is the culprit. The X:/Claudie and C:/Temp you passed via CLI get completely replaced by the Roots that Claude Code sends (usually just the current project directory). It’s not a merge — it’s an overwrite.
Fix
I patched it to preserve the CLI directories in a separate variable and merge them with Roots.
// Patch: preserve CLI args in a separate variable
const cliAllowedDirectories =
[...allowedDirectories];
server.oninitialized = async () => {
if (server.getRoots) {
const rootsResult =
await server.getRoots();
if (rootsResult?.roots?.length > 0) {
const validatedRootDirs = [];
for (const root of rootsResult.roots) {
// ... validation (same as original) ...
validatedRootDirs.push(dirPath);
}
// Merge! Keep both CLI args + Roots
const mergedDirs = [...new Set([
...cliAllowedDirectories,
...validatedRootDirs
])];
allowedDirectories = mergedDirs;
}
}
};
By using new Set() to deduplicate while keeping paths from both sources, all 3 paths showed up correctly!
Issue 2: Drive Letter (X:) vs UNC Path Mismatch
Symptoms
After fixing Issue 1, all 3 paths appeared in list_allowed_directories. But when I tried to read a file from X:/Claudie, I still got “Access denied”. It’s right there in the allowed list — why?!
Root Cause
This is a trap unique to Windows network drives. Node.js’s fs.realpath() resolves drive letters to their UNC paths.
// Input
X:\Claudie
// fs.realpath() result
\\192.168.0.2\Data_Vol1\Claudie
The validatePath() function in server-filesystem checks whether a requested path falls within the allowed directories by comparing the realpath-converted path against the originally registered paths.
// Inside validatePath (simplified)
async function validatePath(requestedPath) {
const absolute = path.resolve(requestedPath);
const real = await fs.realpath(absolute);
// real = "\\\\192.168.0.2\\Data_Vol1\\..."
const isAllowed =
allowedDirectories.some(dir =>
real.startsWith(dir)
);
// allowedDirectories has "X:\\Claudie"
// "\\\\192.168.0.2\\..." ≠ "X:\\Claudie"
// → Access denied!
}
X:\\Claudie and \\\\192.168.0.2\\Data_Vol1\\Claudie point to physically the same location, but the string comparison treats them as completely different paths.
Fix
I applied two fixes together.
First, I patched validatePath to also resolve allowed directories via realpath before comparison:
// Patch: resolve allowedDirectories via realpath before comparison
async function validatePath(requestedPath) {
const absolute = path.resolve(requestedPath);
const real = await fs.realpath(absolute);
for (const dir of allowedDirectories) {
try {
const realDir = await fs.realpath(dir);
if (real.startsWith(realDir) ||
absolute.startsWith(dir)) {
return real;
}
} catch {
// If realpath fails, compare with original path
if (absolute.startsWith(dir)) {
return absolute;
}
}
}
throw new Error(
`Access denied: ${requestedPath}`
+ ` is outside allowed directories`
);
}
Second, you can also add the UNC path directly to .mcp.json. This way it matches even without the patch:
{
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"H:/Git",
"X:/Claudie",
"//192.168.0.2/Data_Vol1/Claudie",
"C:/Temp"
]
}
Including the UNC path provides a safety net since it can match against the realpath result. But applying the patch is the more fundamental solution.
Issue 3: npx.cmd Wrapper Argument Corruption
Symptoms
After all the patches above, I thought “This has to work now!” — but arguments were still getting corrupted when running via npx. It was especially bad when UNC paths (//192.168.0.2/...) were involved.
Root Cause
On Windows, running npx actually invokes npx.cmd, a batch file, and Claude Code wraps it with cmd.exe /d /s /c. During this process, slashes and special characters in paths can get escaped or truncated.
// Intended execution
npx -y @modelcontextprotocol/server-filesystem
H:/Git X:/Claudie
// What actually runs on Windows
cmd.exe /d /s /c
"npx.cmd -y @modelcontext... ..."
// → argument parsing can break
Fix
The cleanest solution is to run the index.js already downloaded in the npx cache directly with node. This completely bypasses the npx wrapper.
{
"mcpServers": {
"filesystem": {
"command": "node",
"args": [
"C:/Users/terry/AppData/Local/npm-cache/_npx/hash-value/node_modules/@modelcontextprotocol/server-filesystem/dist/index.js",
"H:/Git",
"X:/Claudie",
"//192.168.0.2/Data_Vol1/Claudie",
"C:/Temp"
]
}
}
}
You can find the npx cache path by running npx -y @modelcontextprotocol/server-filesystem once, then looking in the npm-cache/_npx/ directory. Running directly with node eliminates the cmd.exe wrapping issue entirely!
Final .mcp.json Configuration
After surviving the triple-whammy of debugging, here’s the final configuration:
{
"mcpServers": {
"filesystem": {
"command": "node",
"args": [
"C:/Users/terry/AppData/Local/npm-cache/_npx/6e63a527e1df1064/node_modules/@modelcontextprotocol/server-filesystem/dist/index.js",
"H:/Git",
"X:/Claudie",
"//192.168.0.2/Data_Vol1/Claudie",
"C:/Temp"
]
}
}
}
Key takeaways:
- command: Run
nodedirectly instead ofnpx - index.js patch: Roots merging + validatePath UNC support applied
- Dual path registration: Both
X:/Claudieand the UNC path included
Patch Automation Script
The npx cache can occasionally get cleared, wiping out your patches. So I created a script that automatically re-applies them.
// patch_filesystem_mcp.js
const fs = require('fs');
const path = require('path');
const npxCache = path.join(
process.env.LOCALAPPDATA,
'npm-cache', '_npx'
);
function findIndexJs(dir) {
const entries =
fs.readdirSync(dir, {withFileTypes: true});
for (const entry of entries) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) {
const result = findIndexJs(full);
if (result) return result;
} else if (
entry.name === 'index.js' &&
full.includes('server-filesystem')
) {
return full;
}
}
return null;
}
const indexPath = findIndexJs(npxCache);
if (!indexPath) {
console.error(
'index.js not found. Run npx once first.'
);
process.exit(1);
}
let code = fs.readFileSync(indexPath, 'utf-8');
// Patch 1: Preserve CLI args + merge with Roots
if (!code.includes('cliAllowedDirectories')) {
code = code.replace(
'let allowedDirectories = args;',
`let allowedDirectories = args;
const cliAllowedDirectories = [...args];`
);
code = code.replace(
'allowedDirectories = [...validatedRootDirs];',
`const mergedDirs = [...new Set([
...cliAllowedDirectories,
...validatedRootDirs
])];
allowedDirectories = mergedDirs;`
);
}
fs.writeFileSync(indexPath, code);
console.log('Patched: ' + indexPath);
Just run node patch_filesystem_mcp.js and it will automatically find and patch the cached index.js. Re-run it whenever the npx cache gets cleared.
Related GitHub Issues
I’m not the only one who ran into these problems. Similar issues have been reported on GitHub:
- Issue #1838 — Path validation fails on Windows due to case sensitivity differences
- Issue #1987 — Access denied with lowercase drive letters (
c:vsC:) - PR #543 — Windows path handling improvements (UNC paths not yet covered)
Since these aren’t fully resolved officially yet, the patches above are still needed for now.
Wrapping Up
Here’s a summary of the 3 issues we tackled with the MCP filesystem server Windows network drive setup:
| # | Issue | Cause | Fix |
|---|---|---|---|
| 1 | CLI args disappear | MCP Roots overwrite them | Preserve cliAllowedDirectories + merge |
| 2 | NAS path Access denied | Drive letter ↔ UNC mismatch | Add realpath comparison to validatePath |
| 3 | Argument corruption | npx.cmd’s cmd.exe wrapping | Run index.js directly with node |
Each time I fixed one issue and thought “Surely it’ll work now!”, another problem popped up. It was a bit of a rollercoaster, but the satisfaction of finally getting everything working made it all worth it.
If you’ve been stuck with the same issues on your MCP filesystem server Windows network drive setup, I hope this guide helped you out. Feel free to drop any questions in the comments!
Until next time — see you with another debugging adventure!