Skip to content

Commit d2eb945

Browse files
authored
Merge pull request #2851 from olaservo/fix-wsl-path-conversion
Resolve filesystem issue where WSL paths are incorrectly converted to Windows format
2 parents 52490b8 + ed4b36b commit d2eb945

File tree

2 files changed

+238
-45
lines changed

2 files changed

+238
-45
lines changed

src/filesystem/__tests__/path-utils.test.ts

Lines changed: 203 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,25 @@ describe('Path Utilities', () => {
1010
.toBe('/home/user/some path');
1111
});
1212

13-
it('converts WSL paths to Windows format', () => {
13+
it('never converts WSL paths (they work correctly in WSL with Node.js fs)', () => {
14+
// WSL paths should NEVER be converted, regardless of platform
15+
// They are valid Linux paths that work with Node.js fs operations inside WSL
1416
expect(convertToWindowsPath('/mnt/c/NS/MyKindleContent'))
15-
.toBe('C:\\NS\\MyKindleContent');
17+
.toBe('/mnt/c/NS/MyKindleContent');
18+
expect(convertToWindowsPath('/mnt/d/Documents'))
19+
.toBe('/mnt/d/Documents');
1620
});
1721

18-
it('converts Unix-style Windows paths to Windows format', () => {
19-
expect(convertToWindowsPath('/c/NS/MyKindleContent'))
20-
.toBe('C:\\NS\\MyKindleContent');
22+
it('converts Unix-style Windows paths only on Windows platform', () => {
23+
// On Windows, /c/ style paths should be converted
24+
if (process.platform === 'win32') {
25+
expect(convertToWindowsPath('/c/NS/MyKindleContent'))
26+
.toBe('C:\\NS\\MyKindleContent');
27+
} else {
28+
// On Linux, leave them unchanged
29+
expect(convertToWindowsPath('/c/NS/MyKindleContent'))
30+
.toBe('/c/NS/MyKindleContent');
31+
}
2132
});
2233

2334
it('leaves Windows paths unchanged but ensures backslashes', () => {
@@ -34,11 +45,20 @@ describe('Path Utilities', () => {
3445
.toBe('C:\\Program Files\\Some App');
3546
});
3647

37-
it('handles uppercase and lowercase drive letters', () => {
48+
it('handles drive letter paths based on platform', () => {
49+
// WSL paths should never be converted
3850
expect(convertToWindowsPath('/mnt/d/some/path'))
39-
.toBe('D:\\some\\path');
40-
expect(convertToWindowsPath('/d/some/path'))
41-
.toBe('D:\\some\\path');
51+
.toBe('/mnt/d/some/path');
52+
53+
if (process.platform === 'win32') {
54+
// On Windows, Unix-style paths like /d/ should be converted
55+
expect(convertToWindowsPath('/d/some/path'))
56+
.toBe('D:\\some\\path');
57+
} else {
58+
// On Linux, /d/ is just a regular Unix path
59+
expect(convertToWindowsPath('/d/some/path'))
60+
.toBe('/d/some/path');
61+
}
4262
});
4363
});
4464

@@ -67,21 +87,33 @@ describe('Path Utilities', () => {
6787
.toBe('C:\\NS\\MyKindleContent');
6888
});
6989

70-
it('handles WSL paths', () => {
90+
it('always preserves WSL paths (they work correctly in WSL)', () => {
91+
// WSL paths should ALWAYS be preserved, regardless of platform
92+
// This is the fix for issue #2795
7193
expect(normalizePath('/mnt/c/NS/MyKindleContent'))
72-
.toBe('C:\\NS\\MyKindleContent');
94+
.toBe('/mnt/c/NS/MyKindleContent');
95+
expect(normalizePath('/mnt/d/Documents'))
96+
.toBe('/mnt/d/Documents');
7397
});
7498

7599
it('handles Unix-style Windows paths', () => {
76-
expect(normalizePath('/c/NS/MyKindleContent'))
77-
.toBe('C:\\NS\\MyKindleContent');
100+
// On Windows, /c/ paths should be converted
101+
if (process.platform === 'win32') {
102+
expect(normalizePath('/c/NS/MyKindleContent'))
103+
.toBe('C:\\NS\\MyKindleContent');
104+
} else if (process.platform === 'linux') {
105+
// On Linux, /c/ is just a regular Unix path
106+
expect(normalizePath('/c/NS/MyKindleContent'))
107+
.toBe('/c/NS/MyKindleContent');
108+
}
78109
});
79110

80111
it('handles paths with spaces and mixed slashes', () => {
81112
expect(normalizePath('C:/NS/My Kindle Content'))
82113
.toBe('C:\\NS\\My Kindle Content');
114+
// WSL paths should always be preserved
83115
expect(normalizePath('/mnt/c/NS/My Kindle Content'))
84-
.toBe('C:\\NS\\My Kindle Content');
116+
.toBe('/mnt/c/NS/My Kindle Content');
85117
expect(normalizePath('C:\\Program Files (x86)\\App Name'))
86118
.toBe('C:\\Program Files (x86)\\App Name');
87119
expect(normalizePath('"C:\\Program Files\\App Name"'))
@@ -91,10 +123,19 @@ describe('Path Utilities', () => {
91123
});
92124

93125
it('preserves spaces in all path formats', () => {
126+
// WSL paths should always be preserved
94127
expect(normalizePath('/mnt/c/Program Files/App Name'))
95-
.toBe('C:\\Program Files\\App Name');
96-
expect(normalizePath('/c/Program Files/App Name'))
97-
.toBe('C:\\Program Files\\App Name');
128+
.toBe('/mnt/c/Program Files/App Name');
129+
130+
if (process.platform === 'win32') {
131+
// On Windows, Unix-style paths like /c/ should be converted
132+
expect(normalizePath('/c/Program Files/App Name'))
133+
.toBe('C:\\Program Files\\App Name');
134+
} else {
135+
// On Linux, /c/ is just a regular Unix path
136+
expect(normalizePath('/c/Program Files/App Name'))
137+
.toBe('/c/Program Files/App Name');
138+
}
98139
expect(normalizePath('C:/Program Files/App Name'))
99140
.toBe('C:\\Program Files\\App Name');
100141
});
@@ -105,15 +146,16 @@ describe('Path Utilities', () => {
105146
.toBe('C:\\NS\\Sub&Folder');
106147
expect(normalizePath('C:/NS/Sub&Folder'))
107148
.toBe('C:\\NS\\Sub&Folder');
149+
// WSL paths should always be preserved
108150
expect(normalizePath('/mnt/c/NS/Sub&Folder'))
109-
.toBe('C:\\NS\\Sub&Folder');
110-
151+
.toBe('/mnt/c/NS/Sub&Folder');
152+
111153
// Test tilde in path (short names in Windows)
112154
expect(normalizePath('C:\\NS\\MYKIND~1'))
113155
.toBe('C:\\NS\\MYKIND~1');
114156
expect(normalizePath('/Users/NEMANS~1/FOLDER~2/SUBFO~1/Public/P12PST~1'))
115157
.toBe('/Users/NEMANS~1/FOLDER~2/SUBFO~1/Public/P12PST~1');
116-
158+
117159
// Test other special characters
118160
expect(normalizePath('C:\\Path with #hash'))
119161
.toBe('C:\\Path with #hash');
@@ -128,10 +170,19 @@ describe('Path Utilities', () => {
128170
it('capitalizes lowercase drive letters for Windows paths', () => {
129171
expect(normalizePath('c:/windows/system32'))
130172
.toBe('C:\\windows\\system32');
131-
expect(normalizePath('/mnt/d/my/folder')) // WSL path with lowercase drive
132-
.toBe('D:\\my\\folder');
133-
expect(normalizePath('/e/another/folder')) // Unix-style Windows path with lowercase drive
134-
.toBe('E:\\another\\folder');
173+
// WSL paths should always be preserved
174+
expect(normalizePath('/mnt/d/my/folder'))
175+
.toBe('/mnt/d/my/folder');
176+
177+
if (process.platform === 'win32') {
178+
// On Windows, Unix-style paths should be converted and capitalized
179+
expect(normalizePath('/e/another/folder'))
180+
.toBe('E:\\another\\folder');
181+
} else {
182+
// On Linux, /e/ is just a regular Unix path
183+
expect(normalizePath('/e/another/folder'))
184+
.toBe('/e/another/folder');
185+
}
135186
});
136187

137188
it('handles UNC paths correctly', () => {
@@ -172,4 +223,132 @@ describe('Path Utilities', () => {
172223
expect(expandHome('C:/test')).toBe('C:/test');
173224
});
174225
});
226+
227+
describe('WSL path handling (issue #2795 fix)', () => {
228+
// Save original platform
229+
const originalPlatform = process.platform;
230+
231+
afterEach(() => {
232+
// Restore platform after each test
233+
Object.defineProperty(process, 'platform', {
234+
value: originalPlatform,
235+
writable: true,
236+
configurable: true
237+
});
238+
});
239+
240+
it('should NEVER convert WSL paths - they work correctly in WSL with Node.js fs', () => {
241+
// The key insight: When running `wsl npx ...`, Node.js runs INSIDE WSL (process.platform === 'linux')
242+
// and /mnt/c/ paths work correctly with Node.js fs operations in that environment.
243+
// Converting them to C:\ format breaks fs operations because Windows paths don't work inside WSL.
244+
245+
// Mock Linux platform (inside WSL)
246+
Object.defineProperty(process, 'platform', {
247+
value: 'linux',
248+
writable: true,
249+
configurable: true
250+
});
251+
252+
// WSL paths should NOT be converted, even inside WSL
253+
expect(normalizePath('/mnt/c/Users/username/folder'))
254+
.toBe('/mnt/c/Users/username/folder');
255+
256+
expect(normalizePath('/mnt/d/Documents/project'))
257+
.toBe('/mnt/d/Documents/project');
258+
});
259+
260+
it('should also preserve WSL paths when running on Windows', () => {
261+
// Mock Windows platform
262+
Object.defineProperty(process, 'platform', {
263+
value: 'win32',
264+
writable: true,
265+
configurable: true
266+
});
267+
268+
// WSL paths should still be preserved (though they wouldn't be accessible from Windows Node.js)
269+
expect(normalizePath('/mnt/c/Users/username/folder'))
270+
.toBe('/mnt/c/Users/username/folder');
271+
272+
expect(normalizePath('/mnt/d/Documents/project'))
273+
.toBe('/mnt/d/Documents/project');
274+
});
275+
276+
it('should convert Unix-style Windows paths (/c/) only when running on Windows (win32)', () => {
277+
// Mock process.platform to be 'win32' (Windows)
278+
Object.defineProperty(process, 'platform', {
279+
value: 'win32',
280+
writable: true,
281+
configurable: true
282+
});
283+
284+
// Unix-style Windows paths like /c/ should be converted on Windows
285+
expect(normalizePath('/c/Users/username/folder'))
286+
.toBe('C:\\Users\\username\\folder');
287+
288+
expect(normalizePath('/d/Documents/project'))
289+
.toBe('D:\\Documents\\project');
290+
});
291+
292+
it('should NOT convert Unix-style paths (/c/) when running inside WSL (linux)', () => {
293+
// Mock process.platform to be 'linux' (WSL/Linux)
294+
Object.defineProperty(process, 'platform', {
295+
value: 'linux',
296+
writable: true,
297+
configurable: true
298+
});
299+
300+
// When on Linux, /c/ is just a regular Unix directory, not a drive letter
301+
expect(normalizePath('/c/some/path'))
302+
.toBe('/c/some/path');
303+
304+
expect(normalizePath('/d/another/path'))
305+
.toBe('/d/another/path');
306+
});
307+
308+
it('should preserve regular Unix paths on all platforms', () => {
309+
// Test on Linux
310+
Object.defineProperty(process, 'platform', {
311+
value: 'linux',
312+
writable: true,
313+
configurable: true
314+
});
315+
316+
expect(normalizePath('/home/user/documents'))
317+
.toBe('/home/user/documents');
318+
319+
expect(normalizePath('/var/log/app'))
320+
.toBe('/var/log/app');
321+
322+
// Test on Windows (though these paths wouldn't work on Windows)
323+
Object.defineProperty(process, 'platform', {
324+
value: 'win32',
325+
writable: true,
326+
configurable: true
327+
});
328+
329+
expect(normalizePath('/home/user/documents'))
330+
.toBe('/home/user/documents');
331+
332+
expect(normalizePath('/var/log/app'))
333+
.toBe('/var/log/app');
334+
});
335+
336+
it('reproduces exact scenario from issue #2795', () => {
337+
// Simulate running inside WSL: wsl npx @modelcontextprotocol/server-filesystem /mnt/c/Users/username/folder
338+
Object.defineProperty(process, 'platform', {
339+
value: 'linux',
340+
writable: true,
341+
configurable: true
342+
});
343+
344+
// This is the exact path from the issue
345+
const inputPath = '/mnt/c/Users/username/folder';
346+
const result = normalizePath(inputPath);
347+
348+
// Should NOT convert to C:\Users\username\folder
349+
expect(result).toBe('/mnt/c/Users/username/folder');
350+
expect(result).not.toContain('C:');
351+
expect(result).not.toContain('\\');
352+
});
353+
});
175354
});

src/filesystem/path-utils.ts

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@ import os from 'os';
88
*/
99
export function convertToWindowsPath(p: string): string {
1010
// Handle WSL paths (/mnt/c/...)
11+
// NEVER convert WSL paths - they are valid Linux paths that work with Node.js fs operations in WSL
12+
// Converting them to Windows format (C:\...) breaks fs operations inside WSL
1113
if (p.startsWith('/mnt/')) {
12-
const driveLetter = p.charAt(5).toUpperCase();
13-
const pathPart = p.slice(6).replace(/\//g, '\\');
14-
return `${driveLetter}:${pathPart}`;
14+
return p; // Leave WSL paths unchanged
1515
}
16-
16+
1717
// Handle Unix-style Windows paths (/c/...)
18-
if (p.match(/^\/[a-zA-Z]\//)) {
18+
// Only convert when running on Windows
19+
if (p.match(/^\/[a-zA-Z]\//) && process.platform === 'win32') {
1920
const driveLetter = p.charAt(1).toUpperCase();
2021
const pathPart = p.slice(2).replace(/\//g, '\\');
2122
return `${driveLetter}:${pathPart}`;
@@ -38,21 +39,29 @@ export function convertToWindowsPath(p: string): string {
3839
export function normalizePath(p: string): string {
3940
// Remove any surrounding quotes and whitespace
4041
p = p.trim().replace(/^["']|["']$/g, '');
41-
42-
// Check if this is a Unix path (starts with / but not a Windows or WSL path)
43-
const isUnixPath = p.startsWith('/') &&
44-
!p.match(/^\/mnt\/[a-z]\//i) &&
45-
!p.match(/^\/[a-zA-Z]\//);
46-
42+
43+
// Check if this is a Unix path that should not be converted
44+
// WSL paths (/mnt/) should ALWAYS be preserved as they work correctly in WSL with Node.js fs
45+
// Regular Unix paths should also be preserved
46+
const isUnixPath = p.startsWith('/') && (
47+
// Always preserve WSL paths (/mnt/c/, /mnt/d/, etc.)
48+
p.match(/^\/mnt\/[a-z]\//i) ||
49+
// On non-Windows platforms, treat all absolute paths as Unix paths
50+
(process.platform !== 'win32') ||
51+
// On Windows, preserve Unix paths that aren't Unix-style Windows paths (/c/, /d/, etc.)
52+
(process.platform === 'win32' && !p.match(/^\/[a-zA-Z]\//))
53+
);
54+
4755
if (isUnixPath) {
4856
// For Unix paths, just normalize without converting to Windows format
4957
// Replace double slashes with single slashes and remove trailing slashes
5058
return p.replace(/\/+/g, '/').replace(/\/+$/, '');
5159
}
52-
53-
// Convert WSL or Unix-style Windows paths to Windows format
60+
61+
// Convert Unix-style Windows paths (/c/, /d/) to Windows format if on Windows
62+
// This function will now leave /mnt/ paths unchanged
5463
p = convertToWindowsPath(p);
55-
64+
5665
// Handle double backslashes, preserving leading UNC \\
5766
if (p.startsWith('\\\\')) {
5867
// For UNC paths, first normalize any excessive leading backslashes to exactly \\
@@ -67,15 +76,15 @@ export function normalizePath(p: string): string {
6776
// For non-UNC paths, normalize all double backslashes
6877
p = p.replace(/\\\\/g, '\\');
6978
}
70-
79+
7180
// Use Node's path normalization, which handles . and .. segments
7281
let normalized = path.normalize(p);
73-
82+
7483
// Fix UNC paths after normalization (path.normalize can remove a leading backslash)
7584
if (p.startsWith('\\\\') && !normalized.startsWith('\\\\')) {
7685
normalized = '\\' + normalized;
7786
}
78-
87+
7988
// Handle Windows paths: convert slashes and ensure drive letter is capitalized
8089
if (normalized.match(/^[a-zA-Z]:/)) {
8190
let result = normalized.replace(/\//g, '\\');
@@ -85,10 +94,15 @@ export function normalizePath(p: string): string {
8594
}
8695
return result;
8796
}
88-
89-
// For all other paths (including relative paths), convert forward slashes to backslashes
90-
// This ensures relative paths like "some/relative/path" become "some\\relative\\path"
91-
return normalized.replace(/\//g, '\\');
97+
98+
// On Windows, convert forward slashes to backslashes for relative paths
99+
// On Linux/Unix, preserve forward slashes
100+
if (process.platform === 'win32') {
101+
return normalized.replace(/\//g, '\\');
102+
}
103+
104+
// On non-Windows platforms, keep the normalized path as-is
105+
return normalized;
92106
}
93107

94108
/**

0 commit comments

Comments
 (0)