@j0hanz/filesystem-mcp 1.19.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +333 -838
- package/dist/cli-help.d.ts +3 -0
- package/dist/cli-help.d.ts.map +1 -0
- package/dist/cli-help.js +169 -0
- package/dist/cli-help.js.map +1 -0
- package/dist/cli.d.ts +28 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +168 -123
- package/dist/cli.js.map +1 -0
- package/dist/core/concurrency.d.ts +70 -0
- package/dist/core/concurrency.d.ts.map +1 -0
- package/dist/core/concurrency.js +173 -0
- package/dist/core/concurrency.js.map +1 -0
- package/dist/core/config.d.ts +19 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +11 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/cursor.d.ts +23 -0
- package/dist/core/cursor.d.ts.map +1 -0
- package/dist/core/cursor.js +62 -0
- package/dist/core/cursor.js.map +1 -0
- package/dist/core/errors.d.ts +96 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +356 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/file-uri.d.ts +24 -0
- package/dist/core/file-uri.d.ts.map +1 -0
- package/dist/core/file-uri.js +52 -0
- package/dist/core/file-uri.js.map +1 -0
- package/dist/core/fmt.d.ts +38 -0
- package/dist/core/fmt.d.ts.map +1 -0
- package/dist/core/fmt.js +109 -0
- package/dist/core/fmt.js.map +1 -0
- package/dist/core/fs.d.ts +108 -0
- package/dist/core/fs.d.ts.map +1 -0
- package/dist/core/fs.js +243 -0
- package/dist/core/fs.js.map +1 -0
- package/dist/core/glob.d.ts +41 -0
- package/dist/core/glob.d.ts.map +1 -0
- package/dist/core/glob.js +399 -0
- package/dist/core/glob.js.map +1 -0
- package/dist/core/input-required.d.ts +147 -0
- package/dist/core/input-required.d.ts.map +1 -0
- package/dist/core/input-required.js +261 -0
- package/dist/core/input-required.js.map +1 -0
- package/dist/core/mime.d.ts +15 -0
- package/dist/core/mime.d.ts.map +1 -0
- package/dist/core/mime.js +188 -0
- package/dist/core/mime.js.map +1 -0
- package/dist/core/observability.d.ts +10 -0
- package/dist/core/observability.d.ts.map +1 -0
- package/dist/core/observability.js +76 -0
- package/dist/core/observability.js.map +1 -0
- package/dist/core/page-store.d.ts +41 -0
- package/dist/core/page-store.d.ts.map +1 -0
- package/dist/core/page-store.js +74 -0
- package/dist/core/page-store.js.map +1 -0
- package/dist/core/path-completer.d.ts +10 -0
- package/dist/core/path-completer.d.ts.map +1 -0
- package/dist/core/path-completer.js +227 -0
- package/dist/core/path-completer.js.map +1 -0
- package/dist/core/path-discovery.d.ts +8 -0
- package/dist/core/path-discovery.d.ts.map +1 -0
- package/dist/core/path-discovery.js +105 -0
- package/dist/core/path-discovery.js.map +1 -0
- package/dist/core/path-utils.d.ts +9 -0
- package/dist/core/path-utils.d.ts.map +1 -0
- package/dist/core/path-utils.js +121 -0
- package/dist/core/path-utils.js.map +1 -0
- package/dist/core/path.d.ts +148 -0
- package/dist/core/path.d.ts.map +1 -0
- package/dist/core/path.js +674 -0
- package/dist/core/path.js.map +1 -0
- package/dist/core/primitives.d.ts +23 -0
- package/dist/core/primitives.d.ts.map +1 -0
- package/dist/core/primitives.js +54 -0
- package/dist/core/primitives.js.map +1 -0
- package/dist/core/read.d.ts +74 -0
- package/dist/core/read.d.ts.map +1 -0
- package/dist/core/read.js +562 -0
- package/dist/core/read.js.map +1 -0
- package/dist/core/schema.d.ts +139 -0
- package/dist/core/schema.d.ts.map +1 -0
- package/dist/core/schema.js +345 -0
- package/dist/core/schema.js.map +1 -0
- package/dist/core/search.d.ts +100 -0
- package/dist/core/search.d.ts.map +1 -0
- package/dist/core/search.js +248 -0
- package/dist/core/search.js.map +1 -0
- package/dist/core/sensitive.d.ts +6 -0
- package/dist/core/sensitive.d.ts.map +1 -0
- package/dist/core/sensitive.js +142 -0
- package/dist/core/sensitive.js.map +1 -0
- package/dist/core/store.d.ts +57 -0
- package/dist/core/store.d.ts.map +1 -0
- package/dist/core/store.js +162 -0
- package/dist/core/store.js.map +1 -0
- package/dist/core/util.d.ts +26 -0
- package/dist/core/util.d.ts.map +1 -0
- package/dist/core/util.js +66 -0
- package/dist/core/util.js.map +1 -0
- package/dist/core/watcher-registry.d.ts +68 -0
- package/dist/core/watcher-registry.d.ts.map +1 -0
- package/dist/core/watcher-registry.js +312 -0
- package/dist/core/watcher-registry.js.map +1 -0
- package/dist/http-policy.d.ts +121 -0
- package/dist/http-policy.d.ts.map +1 -0
- package/dist/http-policy.js +340 -0
- package/dist/http-policy.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +77 -41
- package/dist/index.js.map +1 -0
- package/dist/instructions.d.ts +12 -0
- package/dist/instructions.d.ts.map +1 -0
- package/dist/instructions.js +71 -0
- package/dist/instructions.js.map +1 -0
- package/dist/prompts.d.ts +8 -6
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +74 -168
- package/dist/prompts.js.map +1 -0
- package/dist/resources.d.ts +73 -9
- package/dist/resources.d.ts.map +1 -0
- package/dist/resources.js +392 -163
- package/dist/resources.js.map +1 -0
- package/dist/server.d.ts +62 -1
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +139 -1
- package/dist/server.js.map +1 -0
- package/dist/tools/batch.d.ts +89 -0
- package/dist/tools/batch.d.ts.map +1 -0
- package/dist/tools/batch.js +157 -0
- package/dist/tools/batch.js.map +1 -0
- package/dist/tools/create.d.ts +2 -0
- package/dist/tools/create.d.ts.map +1 -0
- package/dist/tools/create.js +142 -0
- package/dist/tools/create.js.map +1 -0
- package/dist/tools/define.d.ts +110 -0
- package/dist/tools/define.d.ts.map +1 -0
- package/dist/tools/define.js +394 -0
- package/dist/tools/define.js.map +1 -0
- package/dist/tools/delete-file.d.ts +2 -4
- package/dist/tools/delete-file.d.ts.map +1 -0
- package/dist/tools/delete-file.js +330 -87
- package/dist/tools/delete-file.js.map +1 -0
- package/dist/tools/diff.d.ts +2 -0
- package/dist/tools/diff.d.ts.map +1 -0
- package/dist/tools/diff.js +94 -0
- package/dist/tools/diff.js.map +1 -0
- package/dist/tools/edit.d.ts +2 -0
- package/dist/tools/edit.d.ts.map +1 -0
- package/dist/tools/edit.js +442 -0
- package/dist/tools/edit.js.map +1 -0
- package/dist/tools/index.d.ts +26 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +52 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/list.d.ts +2 -0
- package/dist/tools/list.d.ts.map +1 -0
- package/dist/tools/list.js +291 -0
- package/dist/tools/list.js.map +1 -0
- package/dist/tools/move.d.ts +2 -0
- package/dist/tools/move.d.ts.map +1 -0
- package/dist/tools/move.js +321 -0
- package/dist/tools/move.js.map +1 -0
- package/dist/tools/patch.d.ts +2 -0
- package/dist/tools/patch.d.ts.map +1 -0
- package/dist/tools/patch.js +155 -0
- package/dist/tools/patch.js.map +1 -0
- package/dist/tools/progress.d.ts +52 -0
- package/dist/tools/progress.d.ts.map +1 -0
- package/dist/tools/progress.js +172 -0
- package/dist/tools/progress.js.map +1 -0
- package/dist/tools/read.d.ts +2 -4
- package/dist/tools/read.d.ts.map +1 -0
- package/dist/tools/read.js +398 -152
- package/dist/tools/read.js.map +1 -0
- package/dist/tools/replace-in-files.d.ts +2 -4
- package/dist/tools/replace-in-files.d.ts.map +1 -0
- package/dist/tools/replace-in-files.js +435 -264
- package/dist/tools/replace-in-files.js.map +1 -0
- package/dist/tools/roots.d.ts +2 -4
- package/dist/tools/roots.d.ts.map +1 -0
- package/dist/tools/roots.js +29 -52
- package/dist/tools/roots.js.map +1 -0
- package/dist/tools/search-content.d.ts +2 -4
- package/dist/tools/search-content.d.ts.map +1 -0
- package/dist/tools/search-content.js +262 -228
- package/dist/tools/search-content.js.map +1 -0
- package/dist/tools/search-files.d.ts +2 -4
- package/dist/tools/search-files.d.ts.map +1 -0
- package/dist/tools/search-files.js +162 -161
- package/dist/tools/search-files.js.map +1 -0
- package/dist/tools/stat.d.ts +2 -4
- package/dist/tools/stat.d.ts.map +1 -0
- package/dist/tools/stat.js +202 -62
- package/dist/tools/stat.js.map +1 -0
- package/dist/transport/http.d.ts +5 -0
- package/dist/transport/http.d.ts.map +1 -0
- package/dist/transport/http.js +276 -0
- package/dist/transport/http.js.map +1 -0
- package/dist/transport/shared.d.ts +45 -0
- package/dist/transport/shared.d.ts.map +1 -0
- package/dist/transport/shared.js +65 -0
- package/dist/transport/shared.js.map +1 -0
- package/dist/transport/stdio.d.ts +32 -0
- package/dist/transport/stdio.d.ts.map +1 -0
- package/dist/transport/stdio.js +279 -0
- package/dist/transport/stdio.js.map +1 -0
- package/dist/transport.d.ts +5 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +4 -0
- package/dist/transport.js.map +1 -0
- package/package.json +37 -46
- package/dist/assets/logo.svg +0 -35
- package/dist/completions.d.ts +0 -2
- package/dist/completions.js +0 -572
- package/dist/config.d.ts +0 -119
- package/dist/config.js +0 -31
- package/dist/lib/abort.d.ts +0 -7
- package/dist/lib/abort.js +0 -81
- package/dist/lib/constants.d.ts +0 -42
- package/dist/lib/constants.js +0 -313
- package/dist/lib/errors.d.ts +0 -28
- package/dist/lib/errors.js +0 -301
- package/dist/lib/file-operations/core.d.ts +0 -53
- package/dist/lib/file-operations/core.js +0 -144
- package/dist/lib/file-operations/metadata.d.ts +0 -77
- package/dist/lib/file-operations/metadata.js +0 -878
- package/dist/lib/file-operations/search.d.ts +0 -46
- package/dist/lib/file-operations/search.js +0 -1194
- package/dist/lib/file-operations/traversal.d.ts +0 -40
- package/dist/lib/file-operations/traversal.js +0 -310
- package/dist/lib/fs-helpers.d.ts +0 -46
- package/dist/lib/fs-helpers.js +0 -606
- package/dist/lib/globs.d.ts +0 -2
- package/dist/lib/globs.js +0 -19
- package/dist/lib/logger.d.ts +0 -27
- package/dist/lib/logger.js +0 -91
- package/dist/lib/observability.d.ts +0 -33
- package/dist/lib/observability.js +0 -373
- package/dist/lib/paths.d.ts +0 -35
- package/dist/lib/paths.js +0 -634
- package/dist/lib/resource-store.d.ts +0 -28
- package/dist/lib/resource-store.js +0 -195
- package/dist/lib/utils.d.ts +0 -7
- package/dist/lib/utils.js +0 -37
- package/dist/lib/zod-codecs.d.ts +0 -2
- package/dist/lib/zod-codecs.js +0 -18
- package/dist/pkg-info.d.ts +0 -7
- package/dist/pkg-info.js +0 -9
- package/dist/resources/generated-instructions.d.ts +0 -1
- package/dist/resources/generated-instructions.js +0 -105
- package/dist/resources/tool-catalog.d.ts +0 -2
- package/dist/resources/tool-catalog.js +0 -78
- package/dist/resources/tool-info.d.ts +0 -9
- package/dist/resources/tool-info.js +0 -206
- package/dist/resources/workflows.d.ts +0 -1
- package/dist/resources/workflows.js +0 -72
- package/dist/schemas.d.ts +0 -505
- package/dist/schemas.js +0 -676
- package/dist/server/bootstrap.d.ts +0 -6
- package/dist/server/bootstrap.js +0 -563
- package/dist/server/event-store.d.ts +0 -18
- package/dist/server/event-store.js +0 -71
- package/dist/server/roots-manager.d.ts +0 -28
- package/dist/server/roots-manager.js +0 -227
- package/dist/server/task-store.d.ts +0 -10
- package/dist/server/task-store.js +0 -91
- package/dist/tools/apply-patch.d.ts +0 -4
- package/dist/tools/apply-patch.js +0 -235
- package/dist/tools/calculate-hash.d.ts +0 -4
- package/dist/tools/calculate-hash.js +0 -187
- package/dist/tools/contract.d.ts +0 -50
- package/dist/tools/contract.js +0 -1
- package/dist/tools/create-directory.d.ts +0 -4
- package/dist/tools/create-directory.js +0 -68
- package/dist/tools/diff-files.d.ts +0 -4
- package/dist/tools/diff-files.js +0 -136
- package/dist/tools/edit-file.d.ts +0 -4
- package/dist/tools/edit-file.js +0 -256
- package/dist/tools/icons.d.ts +0 -15
- package/dist/tools/icons.js +0 -24
- package/dist/tools/list-directory.d.ts +0 -4
- package/dist/tools/list-directory.js +0 -218
- package/dist/tools/move-file.d.ts +0 -4
- package/dist/tools/move-file.js +0 -178
- package/dist/tools/read-multiple.d.ts +0 -4
- package/dist/tools/read-multiple.js +0 -207
- package/dist/tools/shared.d.ts +0 -172
- package/dist/tools/shared.js +0 -599
- package/dist/tools/stat-many.d.ts +0 -4
- package/dist/tools/stat-many.js +0 -104
- package/dist/tools/task-support.d.ts +0 -29
- package/dist/tools/task-support.js +0 -472
- package/dist/tools/tree.d.ts +0 -4
- package/dist/tools/tree.js +0 -102
- package/dist/tools/write-file.d.ts +0 -4
- package/dist/tools/write-file.js +0 -60
- package/dist/tools.d.ts +0 -5
- package/dist/tools.js +0 -54
package/dist/lib/paths.js
DELETED
|
@@ -1,634 +0,0 @@
|
|
|
1
|
-
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
-
import { realpath, stat } from 'node:fs/promises';
|
|
3
|
-
import { homedir, platform } from 'node:os';
|
|
4
|
-
import { dirname, isAbsolute, join, normalize, parse, posix, relative, resolve, sep, win32, } from 'node:path';
|
|
5
|
-
import { fileURLToPath } from 'node:url';
|
|
6
|
-
import { assertNotAborted, withAbort } from './abort.js';
|
|
7
|
-
import { SENSITIVE_FILE_ALLOWLIST, SENSITIVE_FILE_DENYLIST, } from './constants.js';
|
|
8
|
-
import { ErrorCode, isAbortError, isNodeError, McpError } from './errors.js';
|
|
9
|
-
import { Logger } from './logger.js';
|
|
10
|
-
const WINDOWS_PATH_SEPARATOR = '\\';
|
|
11
|
-
const POSIX_PATH_SEPARATOR = '/';
|
|
12
|
-
export function toPosixPath(value) {
|
|
13
|
-
return value.includes(WINDOWS_PATH_SEPARATOR)
|
|
14
|
-
? value.replace(/\\/gu, POSIX_PATH_SEPARATOR)
|
|
15
|
-
: value;
|
|
16
|
-
}
|
|
17
|
-
const IS_WINDOWS = platform() === 'win32';
|
|
18
|
-
const WINDOWS_ABSOLUTE_RE = /^[a-z]:\//iu;
|
|
19
|
-
const HOME_PREFIX_LENGTH = 2;
|
|
20
|
-
const CHAR_CODE_SPACE = 32;
|
|
21
|
-
const CHAR_CODE_DOT = 46;
|
|
22
|
-
function normalizePathForMatch(input) {
|
|
23
|
-
return toPosixPath(normalize(input));
|
|
24
|
-
}
|
|
25
|
-
function normalizeForMatch(input) {
|
|
26
|
-
const normalized = normalizePathForMatch(input);
|
|
27
|
-
// Always lowercase for case-insensitive denylist matching on all platforms.
|
|
28
|
-
// Prevents bypassing `.env` block with `.ENV` on case-sensitive filesystems.
|
|
29
|
-
return normalized.toLowerCase();
|
|
30
|
-
}
|
|
31
|
-
function compilePatternGlobs(normalizedPattern) {
|
|
32
|
-
const globs = new Set([normalizedPattern]);
|
|
33
|
-
const isWindowsAbsolute = WINDOWS_ABSOLUTE_RE.test(normalizedPattern);
|
|
34
|
-
if (!normalizedPattern.startsWith('**/') && !isWindowsAbsolute) {
|
|
35
|
-
const withoutRoot = normalizedPattern.replace(/^\/+/u, '');
|
|
36
|
-
if (withoutRoot.length > 0) {
|
|
37
|
-
globs.add(`**/${withoutRoot}`);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
return [...globs];
|
|
41
|
-
}
|
|
42
|
-
function compilePatterns(patterns) {
|
|
43
|
-
const unique = new Set();
|
|
44
|
-
for (const pattern of patterns) {
|
|
45
|
-
const trimmed = pattern.trim();
|
|
46
|
-
if (trimmed.length > 0) {
|
|
47
|
-
unique.add(trimmed);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
const compiled = [];
|
|
51
|
-
for (const pattern of unique) {
|
|
52
|
-
const normalized = normalizeForMatch(pattern);
|
|
53
|
-
const matchesPath = normalized.includes('/');
|
|
54
|
-
compiled.push({
|
|
55
|
-
globs: matchesPath ? compilePatternGlobs(normalized) : [normalized],
|
|
56
|
-
matchesPath,
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
return compiled;
|
|
60
|
-
}
|
|
61
|
-
function toPatternSet(patterns) {
|
|
62
|
-
const pathGlobs = new Set();
|
|
63
|
-
const nameGlobs = new Set();
|
|
64
|
-
for (const pattern of patterns) {
|
|
65
|
-
const target = pattern.matchesPath ? pathGlobs : nameGlobs;
|
|
66
|
-
for (const glob of pattern.globs) {
|
|
67
|
-
target.add(glob);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
return {
|
|
71
|
-
pathGlobs: [...pathGlobs],
|
|
72
|
-
nameGlobs: [...nameGlobs],
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
const DENY_PATTERNS = toPatternSet(compilePatterns(SENSITIVE_FILE_DENYLIST));
|
|
76
|
-
const ALLOW_PATTERNS = toPatternSet(compilePatterns(SENSITIVE_FILE_ALLOWLIST));
|
|
77
|
-
function uniquePair(primary, secondary) {
|
|
78
|
-
if (!secondary || secondary === primary)
|
|
79
|
-
return [primary];
|
|
80
|
-
return [primary, secondary];
|
|
81
|
-
}
|
|
82
|
-
function matchesAnyGlobs(globs, candidates) {
|
|
83
|
-
if (globs.length === 0 || candidates.length === 0)
|
|
84
|
-
return false;
|
|
85
|
-
for (const candidate of candidates) {
|
|
86
|
-
for (const glob of globs) {
|
|
87
|
-
if (posix.matchesGlob(candidate, glob))
|
|
88
|
-
return true;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
93
|
-
export function isSensitivePath(requestedPath, resolvedPath) {
|
|
94
|
-
if (DENY_PATTERNS.pathGlobs.length === 0 &&
|
|
95
|
-
DENY_PATTERNS.nameGlobs.length === 0) {
|
|
96
|
-
return false;
|
|
97
|
-
}
|
|
98
|
-
const normalizedRequested = normalizeForMatch(requestedPath);
|
|
99
|
-
const normalizedResolved = resolvedPath
|
|
100
|
-
? normalizeForMatch(resolvedPath)
|
|
101
|
-
: undefined;
|
|
102
|
-
const pathCandidates = uniquePair(normalizedRequested, normalizedResolved);
|
|
103
|
-
const nameCandidates = uniquePair(posix.basename(normalizedRequested), normalizedResolved ? posix.basename(normalizedResolved) : undefined);
|
|
104
|
-
if (matchesAnyGlobs(ALLOW_PATTERNS.pathGlobs, pathCandidates) ||
|
|
105
|
-
matchesAnyGlobs(ALLOW_PATTERNS.nameGlobs, nameCandidates)) {
|
|
106
|
-
return false;
|
|
107
|
-
}
|
|
108
|
-
return (matchesAnyGlobs(DENY_PATTERNS.pathGlobs, pathCandidates) ||
|
|
109
|
-
matchesAnyGlobs(DENY_PATTERNS.nameGlobs, nameCandidates));
|
|
110
|
-
}
|
|
111
|
-
export function assertAllowedFileAccess(requestedPath, resolvedPath) {
|
|
112
|
-
if (!isSensitivePath(requestedPath, resolvedPath))
|
|
113
|
-
return;
|
|
114
|
-
Logger.warn(`Access denied: sensitive file blocked by policy (${requestedPath})`);
|
|
115
|
-
throw new McpError(ErrorCode.ACCESS_DENIED, 'Sensitive file blocked. Set FS_CONTEXT_ALLOW_SENSITIVE=1 to override.', requestedPath);
|
|
116
|
-
}
|
|
117
|
-
const HOMEDIR = homedir();
|
|
118
|
-
const PATH_SEPARATOR = sep;
|
|
119
|
-
const DRIVE_LETTER_REGEX = /^[A-Za-z]:/;
|
|
120
|
-
const WINDOWS_DRIVE_REL_REGEX = /^[A-Za-z]:$/u;
|
|
121
|
-
const LEADING_SEPARATORS_RE = /^[/\\]+/;
|
|
122
|
-
const RESERVED_DEVICE_NAMES = new Set([
|
|
123
|
-
'CON',
|
|
124
|
-
'PRN',
|
|
125
|
-
'AUX',
|
|
126
|
-
'NUL',
|
|
127
|
-
'COM1',
|
|
128
|
-
'COM2',
|
|
129
|
-
'COM3',
|
|
130
|
-
'COM4',
|
|
131
|
-
'COM5',
|
|
132
|
-
'COM6',
|
|
133
|
-
'COM7',
|
|
134
|
-
'COM8',
|
|
135
|
-
'COM9',
|
|
136
|
-
'LPT1',
|
|
137
|
-
'LPT2',
|
|
138
|
-
'LPT3',
|
|
139
|
-
'LPT4',
|
|
140
|
-
'LPT5',
|
|
141
|
-
'LPT6',
|
|
142
|
-
'LPT7',
|
|
143
|
-
'LPT8',
|
|
144
|
-
'LPT9',
|
|
145
|
-
]);
|
|
146
|
-
const allowedDirectoriesContext = new AsyncLocalStorage({
|
|
147
|
-
name: 'filesystem-mcp:allowed-directories',
|
|
148
|
-
});
|
|
149
|
-
function dedupePreserveOrder(items) {
|
|
150
|
-
return [...new Set(items)];
|
|
151
|
-
}
|
|
152
|
-
function cloneAllowedDirectoriesState(state) {
|
|
153
|
-
return {
|
|
154
|
-
primary: [...state.primary],
|
|
155
|
-
expanded: [...state.expanded],
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
function expandHome(filepath) {
|
|
159
|
-
if (filepath === '~')
|
|
160
|
-
return HOMEDIR;
|
|
161
|
-
// Accept both "~/" and "~\\" for cross-platform UX.
|
|
162
|
-
if (filepath.startsWith('~/') || filepath.startsWith('~\\')) {
|
|
163
|
-
// Avoid `join(HOMEDIR, "/foo")` resetting to the filesystem root.
|
|
164
|
-
const rest = filepath
|
|
165
|
-
.slice(HOME_PREFIX_LENGTH)
|
|
166
|
-
.replace(LEADING_SEPARATORS_RE, '');
|
|
167
|
-
return rest.length === 0 ? HOMEDIR : join(HOMEDIR, rest);
|
|
168
|
-
}
|
|
169
|
-
return filepath;
|
|
170
|
-
}
|
|
171
|
-
/**
|
|
172
|
-
* Normalizes any path-like input to an absolute path suitable for comparisons.
|
|
173
|
-
* - Expands "~" home directory shorthand.
|
|
174
|
-
* - Resolves against process CWD if relative.
|
|
175
|
-
* - Lowercases Windows drive letter for stable comparisons.
|
|
176
|
-
*/
|
|
177
|
-
export function normalizePath(p) {
|
|
178
|
-
const resolved = resolve(expandHome(p));
|
|
179
|
-
if (IS_WINDOWS && DRIVE_LETTER_REGEX.test(resolved)) {
|
|
180
|
-
return resolved.charAt(0).toLowerCase() + resolved.slice(1);
|
|
181
|
-
}
|
|
182
|
-
return resolved;
|
|
183
|
-
}
|
|
184
|
-
function normalizeCaseForComparison(value) {
|
|
185
|
-
return IS_WINDOWS ? value.toLowerCase() : value;
|
|
186
|
-
}
|
|
187
|
-
function normalizeForComparison(value) {
|
|
188
|
-
return normalizeCaseForComparison(value);
|
|
189
|
-
}
|
|
190
|
-
function rethrowIfAborted(error) {
|
|
191
|
-
if (isAbortError(error))
|
|
192
|
-
throw error;
|
|
193
|
-
}
|
|
194
|
-
function isSamePath(left, right) {
|
|
195
|
-
if (left === right)
|
|
196
|
-
return true;
|
|
197
|
-
const leftResolved = normalizeCaseForComparison(resolve(left));
|
|
198
|
-
const rightResolved = normalizeCaseForComparison(resolve(right));
|
|
199
|
-
return leftResolved === rightResolved;
|
|
200
|
-
}
|
|
201
|
-
function stripTrailingSeparator(normalized) {
|
|
202
|
-
return normalized.length > 1 && normalized.endsWith(PATH_SEPARATOR)
|
|
203
|
-
? normalized.slice(0, -1)
|
|
204
|
-
: normalized;
|
|
205
|
-
}
|
|
206
|
-
function isFileSystemRootPath(normalized, root) {
|
|
207
|
-
return isSamePath(normalized, root);
|
|
208
|
-
}
|
|
209
|
-
function normalizeAllowedDirectory(dir) {
|
|
210
|
-
const trimmed = dir.trim();
|
|
211
|
-
if (trimmed.length === 0)
|
|
212
|
-
return '';
|
|
213
|
-
const normalized = normalizePath(trimmed);
|
|
214
|
-
const { root } = parse(normalized);
|
|
215
|
-
// Keep filesystem roots as-is ("/", "c:\\", "\\\\server\\share\\").
|
|
216
|
-
if (isFileSystemRootPath(normalized, root)) {
|
|
217
|
-
return root;
|
|
218
|
-
}
|
|
219
|
-
return stripTrailingSeparator(normalized);
|
|
220
|
-
}
|
|
221
|
-
function normalizeAllowedDirectories(dirs) {
|
|
222
|
-
const normalized = [];
|
|
223
|
-
for (const dir of dirs) {
|
|
224
|
-
const entry = normalizeAllowedDirectory(dir);
|
|
225
|
-
if (entry.length > 0) {
|
|
226
|
-
normalized.push(entry);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
// Preserve first-seen order while deduping.
|
|
230
|
-
return dedupePreserveOrder(normalized);
|
|
231
|
-
}
|
|
232
|
-
// Process-global singleton state for allowed directory roots.
|
|
233
|
-
//
|
|
234
|
-
// These are set once at startup (via setAllowedDirectoriesResolved) and
|
|
235
|
-
// mutated only through setAllowedDirectoriesState. In stdio mode there is a
|
|
236
|
-
// single MCP session per process, so this is safe. In HTTP mode all HTTP
|
|
237
|
-
// sessions within the same process share one policy — multi-tenant isolation
|
|
238
|
-
// (different roots per session) requires separate server processes.
|
|
239
|
-
let defaultAllowedDirectoriesState = {
|
|
240
|
-
primary: [],
|
|
241
|
-
expanded: [],
|
|
242
|
-
};
|
|
243
|
-
function setAllowedDirectoriesState(primary, expanded) {
|
|
244
|
-
defaultAllowedDirectoriesState = {
|
|
245
|
-
primary: dedupePreserveOrder(primary),
|
|
246
|
-
expanded: dedupePreserveOrder(expanded),
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
function getActiveAllowedDirectoriesState() {
|
|
250
|
-
return allowedDirectoriesContext.getStore() ?? defaultAllowedDirectoriesState;
|
|
251
|
-
}
|
|
252
|
-
export function withAllowedDirectoriesState(state, run) {
|
|
253
|
-
return allowedDirectoriesContext.run(cloneAllowedDirectoriesState(state), run);
|
|
254
|
-
}
|
|
255
|
-
export function setAllowedDirectoriesStateResolved(state) {
|
|
256
|
-
setAllowedDirectoriesState(state.primary, state.expanded);
|
|
257
|
-
}
|
|
258
|
-
export function getAllowedDirectories() {
|
|
259
|
-
return [...getActiveAllowedDirectoriesState().expanded];
|
|
260
|
-
}
|
|
261
|
-
export function isAllowedDirectoryRoot(normalizedPath) {
|
|
262
|
-
for (const dir of getActiveAllowedDirectoriesState().expanded) {
|
|
263
|
-
if (isSamePath(normalizedPath, dir))
|
|
264
|
-
return true;
|
|
265
|
-
}
|
|
266
|
-
return false;
|
|
267
|
-
}
|
|
268
|
-
function getAllowedDirectoriesForRelativeResolution() {
|
|
269
|
-
const state = getActiveAllowedDirectoriesState();
|
|
270
|
-
return state.primary.length > 0 ? state.primary : state.expanded;
|
|
271
|
-
}
|
|
272
|
-
function isPathInsideDirectory(normalizedDirectory, normalizedCandidate) {
|
|
273
|
-
const root = normalizeForComparison(normalizedDirectory);
|
|
274
|
-
const candidate = normalizeForComparison(normalizedCandidate);
|
|
275
|
-
if (root === candidate)
|
|
276
|
-
return true;
|
|
277
|
-
const rel = relative(root, candidate);
|
|
278
|
-
if (rel.length === 0)
|
|
279
|
-
return true;
|
|
280
|
-
if (rel === '..')
|
|
281
|
-
return false;
|
|
282
|
-
return !rel.startsWith('..\\') && !rel.startsWith('../') && !isAbsolute(rel);
|
|
283
|
-
}
|
|
284
|
-
export function isPathWithinDirectories(normalizedPath, allowedDirs) {
|
|
285
|
-
for (const allowedDir of allowedDirs) {
|
|
286
|
-
if (isPathInsideDirectory(allowedDir, normalizedPath))
|
|
287
|
-
return true;
|
|
288
|
-
}
|
|
289
|
-
return false;
|
|
290
|
-
}
|
|
291
|
-
async function resolveRealPath(normalized, signal) {
|
|
292
|
-
try {
|
|
293
|
-
assertNotAborted(signal);
|
|
294
|
-
const realPath = await withAbort(realpath(normalized), signal);
|
|
295
|
-
return normalizeAllowedDirectory(realPath);
|
|
296
|
-
}
|
|
297
|
-
catch (error) {
|
|
298
|
-
rethrowIfAborted(error);
|
|
299
|
-
return null;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
async function expandAllowedDirectories(primaryDirs, signal) {
|
|
303
|
-
const realPaths = await Promise.all(primaryDirs.map((dir) => resolveRealPath(dir, signal)));
|
|
304
|
-
const expanded = [];
|
|
305
|
-
for (let i = 0; i < primaryDirs.length; i++) {
|
|
306
|
-
const primary = primaryDirs[i];
|
|
307
|
-
if (!primary)
|
|
308
|
-
continue;
|
|
309
|
-
expanded.push(primary);
|
|
310
|
-
const real = realPaths[i];
|
|
311
|
-
if (real && !isSamePath(real, primary)) {
|
|
312
|
-
expanded.push(real);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
return dedupePreserveOrder(expanded);
|
|
316
|
-
}
|
|
317
|
-
export async function resolveAllowedDirectoriesState(dirs, signal) {
|
|
318
|
-
const primary = normalizeAllowedDirectories(dirs);
|
|
319
|
-
const expanded = await expandAllowedDirectories(primary, signal);
|
|
320
|
-
return { primary, expanded };
|
|
321
|
-
}
|
|
322
|
-
export async function setAllowedDirectoriesResolved(dirs, signal) {
|
|
323
|
-
const state = await resolveAllowedDirectoriesState(dirs, signal);
|
|
324
|
-
setAllowedDirectoriesStateResolved(state);
|
|
325
|
-
}
|
|
326
|
-
function ensureNonEmptyPath(requestedPath) {
|
|
327
|
-
if (!requestedPath || requestedPath.trim().length === 0) {
|
|
328
|
-
throw new McpError(ErrorCode.INVALID_INPUT, 'Path cannot be empty or whitespace', requestedPath);
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
function ensureNoNullBytes(requestedPath) {
|
|
332
|
-
if (requestedPath.includes('\0')) {
|
|
333
|
-
throw new McpError(ErrorCode.INVALID_INPUT, 'Path contains null bytes', requestedPath);
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
function getReservedDeviceName(segment) {
|
|
337
|
-
// Trim trailing dots/spaces (Windows ignores these in path segments).
|
|
338
|
-
let end = segment.length;
|
|
339
|
-
while (end > 0) {
|
|
340
|
-
const c = segment.charCodeAt(end - 1);
|
|
341
|
-
if (c === CHAR_CODE_SPACE || c === CHAR_CODE_DOT)
|
|
342
|
-
end--; // space or dot
|
|
343
|
-
else
|
|
344
|
-
break;
|
|
345
|
-
}
|
|
346
|
-
const trimmed = segment.slice(0, end);
|
|
347
|
-
// Remove alternate data stream suffix (e.g. "file.txt:stream").
|
|
348
|
-
const streamIdx = trimmed.indexOf(':');
|
|
349
|
-
const withoutStream = streamIdx !== -1 ? trimmed.slice(0, streamIdx) : trimmed;
|
|
350
|
-
// Remove extension (e.g. "CON.txt" => "CON").
|
|
351
|
-
const dotIdx = withoutStream.indexOf('.');
|
|
352
|
-
const baseName = (dotIdx !== -1 ? withoutStream.slice(0, dotIdx) : withoutStream).toUpperCase();
|
|
353
|
-
return RESERVED_DEVICE_NAMES.has(baseName) ? baseName : undefined;
|
|
354
|
-
}
|
|
355
|
-
export function getReservedDeviceNameForPath(requestedPath) {
|
|
356
|
-
if (!IS_WINDOWS)
|
|
357
|
-
return undefined;
|
|
358
|
-
const segments = requestedPath.split(/[\\/]/);
|
|
359
|
-
for (const segment of segments) {
|
|
360
|
-
const reserved = getReservedDeviceName(segment);
|
|
361
|
-
if (reserved)
|
|
362
|
-
return reserved;
|
|
363
|
-
}
|
|
364
|
-
return undefined;
|
|
365
|
-
}
|
|
366
|
-
function ensureNoReservedWindowsNames(requestedPath) {
|
|
367
|
-
if (!IS_WINDOWS)
|
|
368
|
-
return;
|
|
369
|
-
const reserved = getReservedDeviceNameForPath(requestedPath);
|
|
370
|
-
if (!reserved)
|
|
371
|
-
return;
|
|
372
|
-
throw new McpError(ErrorCode.INVALID_INPUT, `Windows reserved device name not allowed: ${reserved}`, requestedPath);
|
|
373
|
-
}
|
|
374
|
-
export function isWindowsDriveRelativePath(requestedPath) {
|
|
375
|
-
if (!IS_WINDOWS)
|
|
376
|
-
return false;
|
|
377
|
-
const parsed = win32.parse(requestedPath);
|
|
378
|
-
if (!WINDOWS_DRIVE_REL_REGEX.test(parsed.root))
|
|
379
|
-
return false;
|
|
380
|
-
return !win32.isAbsolute(requestedPath);
|
|
381
|
-
}
|
|
382
|
-
function ensureNoWindowsDriveRelativePath(requestedPath) {
|
|
383
|
-
if (!isWindowsDriveRelativePath(requestedPath))
|
|
384
|
-
return;
|
|
385
|
-
throw new McpError(ErrorCode.INVALID_INPUT, 'Drive-relative path not allowed. Use C:\\path instead of C:path.', requestedPath);
|
|
386
|
-
}
|
|
387
|
-
function resolveRequestedPath(requestedPath) {
|
|
388
|
-
const expanded = expandHome(requestedPath);
|
|
389
|
-
if (!isAbsolute(expanded)) {
|
|
390
|
-
const roots = getAllowedDirectoriesForRelativeResolution();
|
|
391
|
-
if (roots.length > 1) {
|
|
392
|
-
throw new McpError(ErrorCode.INVALID_INPUT, 'Ambiguous relative path with multiple roots. Use an absolute path.', requestedPath);
|
|
393
|
-
}
|
|
394
|
-
const baseDir = roots[0];
|
|
395
|
-
if (baseDir) {
|
|
396
|
-
return normalizePath(resolve(baseDir, expanded));
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
return normalizePath(expanded);
|
|
400
|
-
}
|
|
401
|
-
function validateRequestedPath(requestedPath) {
|
|
402
|
-
ensureNonEmptyPath(requestedPath);
|
|
403
|
-
ensureNoNullBytes(requestedPath);
|
|
404
|
-
ensureNoReservedWindowsNames(requestedPath);
|
|
405
|
-
ensureNoWindowsDriveRelativePath(requestedPath);
|
|
406
|
-
return resolveRequestedPath(requestedPath);
|
|
407
|
-
}
|
|
408
|
-
const NODE_ERROR_MAP = {
|
|
409
|
-
ENOENT: {
|
|
410
|
-
code: ErrorCode.NOT_FOUND,
|
|
411
|
-
message: () => 'Path does not exist',
|
|
412
|
-
},
|
|
413
|
-
EACCES: {
|
|
414
|
-
code: ErrorCode.PERMISSION_DENIED,
|
|
415
|
-
message: () => 'Permission denied',
|
|
416
|
-
},
|
|
417
|
-
EPERM: {
|
|
418
|
-
code: ErrorCode.PERMISSION_DENIED,
|
|
419
|
-
message: () => 'Permission denied',
|
|
420
|
-
},
|
|
421
|
-
ELOOP: {
|
|
422
|
-
code: ErrorCode.SYMLINK_NOT_ALLOWED,
|
|
423
|
-
message: () => 'Too many symbolic links (circular reference)',
|
|
424
|
-
},
|
|
425
|
-
ENAMETOOLONG: {
|
|
426
|
-
code: ErrorCode.INVALID_INPUT,
|
|
427
|
-
message: () => 'Path name too long',
|
|
428
|
-
},
|
|
429
|
-
};
|
|
430
|
-
function buildAllowedDirectoriesHint() {
|
|
431
|
-
const dirs = getAllowedDirectories();
|
|
432
|
-
return dirs.length > 0
|
|
433
|
-
? `Allowed: ${dirs.join(', ')}`
|
|
434
|
-
: 'No allowed directories configured.';
|
|
435
|
-
}
|
|
436
|
-
function toMcpError(requestedPath, error) {
|
|
437
|
-
const code = isNodeError(error) ? error.code : undefined;
|
|
438
|
-
const mapping = code ? NODE_ERROR_MAP[code] : undefined;
|
|
439
|
-
if (mapping) {
|
|
440
|
-
return new McpError(mapping.code, mapping.message(requestedPath), requestedPath, { originalCode: code }, error);
|
|
441
|
-
}
|
|
442
|
-
let originalMessage = '';
|
|
443
|
-
if (error instanceof Error) {
|
|
444
|
-
originalMessage = error.message;
|
|
445
|
-
}
|
|
446
|
-
else if (typeof error === 'string') {
|
|
447
|
-
originalMessage = error;
|
|
448
|
-
}
|
|
449
|
-
return new McpError(ErrorCode.NOT_FOUND, 'Path is not accessible', requestedPath, { originalCode: code, originalMessage }, error);
|
|
450
|
-
}
|
|
451
|
-
function toAccessDeniedWithHint(requestedPath, resolvedPath, normalizedResolved) {
|
|
452
|
-
const suggestion = buildAllowedDirectoriesHint();
|
|
453
|
-
return new McpError(ErrorCode.ACCESS_DENIED, `Outside allowed directories. ${suggestion}`, requestedPath, { resolvedPath, normalizedResolvedPath: normalizedResolved });
|
|
454
|
-
}
|
|
455
|
-
function ensureWithinAllowedDirectories(options) {
|
|
456
|
-
const { normalizedPath, requestedPath, allowedDirs, details } = options;
|
|
457
|
-
if (isPathWithinDirectories(normalizedPath, allowedDirs))
|
|
458
|
-
return;
|
|
459
|
-
if (allowedDirs.length === 0) {
|
|
460
|
-
Logger.warn('Access denied: no allowed directories configured');
|
|
461
|
-
throw new McpError(ErrorCode.ACCESS_DENIED, 'No allowed directories configured. Use --allow-cwd or configure roots.', requestedPath, details);
|
|
462
|
-
}
|
|
463
|
-
Logger.warn(`Access denied: path outside allowed directories (${requestedPath})`);
|
|
464
|
-
throw new McpError(ErrorCode.ACCESS_DENIED, 'Outside allowed directories', requestedPath, details);
|
|
465
|
-
}
|
|
466
|
-
async function resolveRealPathOrThrow(options) {
|
|
467
|
-
const { requestedPath, normalizedRequested, signal } = options;
|
|
468
|
-
try {
|
|
469
|
-
assertNotAborted(signal);
|
|
470
|
-
return await withAbort(realpath(normalizedRequested), signal);
|
|
471
|
-
}
|
|
472
|
-
catch (error) {
|
|
473
|
-
rethrowIfAborted(error);
|
|
474
|
-
throw toMcpError(requestedPath, error);
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
function preparePathAccess(requestedPath) {
|
|
478
|
-
const normalizedRequested = validateRequestedPath(requestedPath);
|
|
479
|
-
const allowedDirs = getAllowedDirectories();
|
|
480
|
-
ensureWithinAllowedDirectories({
|
|
481
|
-
normalizedPath: normalizedRequested,
|
|
482
|
-
requestedPath,
|
|
483
|
-
allowedDirs,
|
|
484
|
-
details: { normalizedPath: normalizedRequested },
|
|
485
|
-
});
|
|
486
|
-
return { allowedDirs, normalizedRequested };
|
|
487
|
-
}
|
|
488
|
-
function ensureResolvedPathAllowed(options) {
|
|
489
|
-
const { requestedPath, resolvedPath, normalizedResolved, allowedDirs } = options;
|
|
490
|
-
if (isPathWithinDirectories(normalizedResolved, allowedDirs))
|
|
491
|
-
return;
|
|
492
|
-
throw toAccessDeniedWithHint(requestedPath, resolvedPath, normalizedResolved);
|
|
493
|
-
}
|
|
494
|
-
async function statPathOrThrow(requestedPath, resolvedPath, signal) {
|
|
495
|
-
try {
|
|
496
|
-
assertNotAborted(signal);
|
|
497
|
-
return await withAbort(stat(resolvedPath), signal);
|
|
498
|
-
}
|
|
499
|
-
catch (error) {
|
|
500
|
-
rethrowIfAborted(error);
|
|
501
|
-
throw toMcpError(requestedPath, error);
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
async function resolveNearestExistingRealPathOrThrow(options) {
|
|
505
|
-
const { requestedPath, startPath, signal } = options;
|
|
506
|
-
let current = startPath;
|
|
507
|
-
for (;;) {
|
|
508
|
-
try {
|
|
509
|
-
assertNotAborted(signal);
|
|
510
|
-
return await withAbort(realpath(current), signal);
|
|
511
|
-
}
|
|
512
|
-
catch (error) {
|
|
513
|
-
rethrowIfAborted(error);
|
|
514
|
-
const code = isNodeError(error) ? error.code : undefined;
|
|
515
|
-
if (code !== 'ENOENT') {
|
|
516
|
-
throw toMcpError(requestedPath, error);
|
|
517
|
-
}
|
|
518
|
-
const parent = dirname(current);
|
|
519
|
-
if (parent === current) {
|
|
520
|
-
throw toMcpError(requestedPath, error);
|
|
521
|
-
}
|
|
522
|
-
current = parent;
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
async function validateExistingPathDetailsInternal(requestedPath, signal) {
|
|
527
|
-
const { allowedDirs, normalizedRequested } = preparePathAccess(requestedPath);
|
|
528
|
-
const realPath = await resolveRealPathOrThrow({
|
|
529
|
-
requestedPath,
|
|
530
|
-
normalizedRequested,
|
|
531
|
-
...(signal ? { signal } : {}),
|
|
532
|
-
});
|
|
533
|
-
const normalizedReal = normalizePath(realPath);
|
|
534
|
-
ensureResolvedPathAllowed({
|
|
535
|
-
requestedPath,
|
|
536
|
-
resolvedPath: realPath,
|
|
537
|
-
normalizedResolved: normalizedReal,
|
|
538
|
-
allowedDirs,
|
|
539
|
-
});
|
|
540
|
-
return {
|
|
541
|
-
requestedPath: normalizedRequested,
|
|
542
|
-
resolvedPath: normalizedReal,
|
|
543
|
-
isSymlink: !isSamePath(normalizedRequested, normalizedReal),
|
|
544
|
-
};
|
|
545
|
-
}
|
|
546
|
-
export async function validateExistingPathDetailed(requestedPath, signal) {
|
|
547
|
-
return validateExistingPathDetailsInternal(requestedPath, signal);
|
|
548
|
-
}
|
|
549
|
-
export async function validateExistingPath(requestedPath, signal) {
|
|
550
|
-
const details = await validateExistingPathDetailsInternal(requestedPath, signal);
|
|
551
|
-
return details.resolvedPath;
|
|
552
|
-
}
|
|
553
|
-
export async function validateExistingDirectory(requestedPath, signal) {
|
|
554
|
-
const details = await validateExistingPathDetailsInternal(requestedPath, signal);
|
|
555
|
-
const stats = await statPathOrThrow(requestedPath, details.resolvedPath, signal);
|
|
556
|
-
if (!stats.isDirectory()) {
|
|
557
|
-
throw new McpError(ErrorCode.NOT_DIRECTORY, 'Not a directory', requestedPath);
|
|
558
|
-
}
|
|
559
|
-
return details.resolvedPath;
|
|
560
|
-
}
|
|
561
|
-
export async function validatePathForWrite(requestedPath, signal) {
|
|
562
|
-
const { allowedDirs, normalizedRequested } = preparePathAccess(requestedPath);
|
|
563
|
-
assertAllowedFileAccess(requestedPath, normalizedRequested);
|
|
564
|
-
const realPath = await resolveNearestExistingRealPathOrThrow({
|
|
565
|
-
requestedPath,
|
|
566
|
-
startPath: normalizedRequested,
|
|
567
|
-
...(signal ? { signal } : {}),
|
|
568
|
-
});
|
|
569
|
-
const normalizedReal = normalizePath(realPath);
|
|
570
|
-
ensureResolvedPathAllowed({
|
|
571
|
-
requestedPath,
|
|
572
|
-
resolvedPath: realPath,
|
|
573
|
-
normalizedResolved: normalizedReal,
|
|
574
|
-
allowedDirs,
|
|
575
|
-
});
|
|
576
|
-
return normalizedRequested;
|
|
577
|
-
}
|
|
578
|
-
function isFileRoot(root) {
|
|
579
|
-
return root.uri.startsWith('file://');
|
|
580
|
-
}
|
|
581
|
-
async function maybeAddRealPath(normalizedPath, validDirs, signal) {
|
|
582
|
-
try {
|
|
583
|
-
assertNotAborted(signal);
|
|
584
|
-
const realPath = await withAbort(realpath(normalizedPath), signal);
|
|
585
|
-
const normalizedReal = normalizePath(realPath);
|
|
586
|
-
if (!isSamePath(normalizedReal, normalizedPath)) {
|
|
587
|
-
validDirs.push(normalizedReal);
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
catch (error) {
|
|
591
|
-
rethrowIfAborted(error);
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
async function resolveRootDirectory(root, signal) {
|
|
595
|
-
try {
|
|
596
|
-
const dirPath = fileURLToPath(root.uri);
|
|
597
|
-
const normalizedPath = normalizePath(dirPath);
|
|
598
|
-
assertNotAborted(signal);
|
|
599
|
-
const stats = await withAbort(stat(normalizedPath), signal);
|
|
600
|
-
if (!stats.isDirectory())
|
|
601
|
-
return null;
|
|
602
|
-
return normalizedPath;
|
|
603
|
-
}
|
|
604
|
-
catch (error) {
|
|
605
|
-
rethrowIfAborted(error);
|
|
606
|
-
return null;
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
export async function getValidRootDirectories(roots, signal) {
|
|
610
|
-
const fileRoots = roots.filter(isFileRoot);
|
|
611
|
-
if (fileRoots.length === 0)
|
|
612
|
-
return [];
|
|
613
|
-
// Phase 1: Resolve all roots in parallel (order-preserving via index).
|
|
614
|
-
const resolvedResults = await Promise.all(fileRoots.map((root) => resolveRootDirectory(root, signal)));
|
|
615
|
-
const validPaths = resolvedResults.filter((p) => p !== null);
|
|
616
|
-
if (validPaths.length === 0)
|
|
617
|
-
return [];
|
|
618
|
-
// Phase 2: Expand real paths for each valid directory in parallel.
|
|
619
|
-
const realExpansions = await Promise.all(validPaths.map(async (normalizedPath) => {
|
|
620
|
-
const extra = [];
|
|
621
|
-
await maybeAddRealPath(normalizedPath, extra, signal);
|
|
622
|
-
return extra[0] ?? null;
|
|
623
|
-
}));
|
|
624
|
-
// Build output preserving insertion order: [normalizedPath, realPath?] per root.
|
|
625
|
-
const validDirs = [];
|
|
626
|
-
validPaths.forEach((p, i) => {
|
|
627
|
-
validDirs.push(p);
|
|
628
|
-
const expanded = realExpansions[i];
|
|
629
|
-
if (expanded !== null && expanded !== undefined) {
|
|
630
|
-
validDirs.push(expanded);
|
|
631
|
-
}
|
|
632
|
-
});
|
|
633
|
-
return validDirs;
|
|
634
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
export interface TextResourceEntry {
|
|
2
|
-
uri: string;
|
|
3
|
-
name: string;
|
|
4
|
-
mimeType: string;
|
|
5
|
-
text: string;
|
|
6
|
-
hash: string;
|
|
7
|
-
size: number;
|
|
8
|
-
storedAt: string;
|
|
9
|
-
expiresAt: string;
|
|
10
|
-
}
|
|
11
|
-
export interface ResourceStore {
|
|
12
|
-
putText(params: {
|
|
13
|
-
name: string;
|
|
14
|
-
mimeType?: string;
|
|
15
|
-
text: string;
|
|
16
|
-
}): TextResourceEntry;
|
|
17
|
-
getText(uri: string): TextResourceEntry;
|
|
18
|
-
clear(): void;
|
|
19
|
-
keys(): string[];
|
|
20
|
-
}
|
|
21
|
-
interface ResourceStoreOptions {
|
|
22
|
-
maxEntries: number;
|
|
23
|
-
maxTotalBytes: number;
|
|
24
|
-
maxEntryBytes: number;
|
|
25
|
-
entryTtlMs: number;
|
|
26
|
-
}
|
|
27
|
-
export declare function createInMemoryResourceStore(options?: Partial<ResourceStoreOptions>): ResourceStore;
|
|
28
|
-
export {};
|