@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.
Files changed (301) hide show
  1. package/README.md +333 -838
  2. package/dist/cli-help.d.ts +3 -0
  3. package/dist/cli-help.d.ts.map +1 -0
  4. package/dist/cli-help.js +169 -0
  5. package/dist/cli-help.js.map +1 -0
  6. package/dist/cli.d.ts +28 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +168 -123
  9. package/dist/cli.js.map +1 -0
  10. package/dist/core/concurrency.d.ts +70 -0
  11. package/dist/core/concurrency.d.ts.map +1 -0
  12. package/dist/core/concurrency.js +173 -0
  13. package/dist/core/concurrency.js.map +1 -0
  14. package/dist/core/config.d.ts +19 -0
  15. package/dist/core/config.d.ts.map +1 -0
  16. package/dist/core/config.js +11 -0
  17. package/dist/core/config.js.map +1 -0
  18. package/dist/core/cursor.d.ts +23 -0
  19. package/dist/core/cursor.d.ts.map +1 -0
  20. package/dist/core/cursor.js +62 -0
  21. package/dist/core/cursor.js.map +1 -0
  22. package/dist/core/errors.d.ts +96 -0
  23. package/dist/core/errors.d.ts.map +1 -0
  24. package/dist/core/errors.js +356 -0
  25. package/dist/core/errors.js.map +1 -0
  26. package/dist/core/file-uri.d.ts +24 -0
  27. package/dist/core/file-uri.d.ts.map +1 -0
  28. package/dist/core/file-uri.js +52 -0
  29. package/dist/core/file-uri.js.map +1 -0
  30. package/dist/core/fmt.d.ts +38 -0
  31. package/dist/core/fmt.d.ts.map +1 -0
  32. package/dist/core/fmt.js +109 -0
  33. package/dist/core/fmt.js.map +1 -0
  34. package/dist/core/fs.d.ts +108 -0
  35. package/dist/core/fs.d.ts.map +1 -0
  36. package/dist/core/fs.js +243 -0
  37. package/dist/core/fs.js.map +1 -0
  38. package/dist/core/glob.d.ts +41 -0
  39. package/dist/core/glob.d.ts.map +1 -0
  40. package/dist/core/glob.js +399 -0
  41. package/dist/core/glob.js.map +1 -0
  42. package/dist/core/input-required.d.ts +147 -0
  43. package/dist/core/input-required.d.ts.map +1 -0
  44. package/dist/core/input-required.js +261 -0
  45. package/dist/core/input-required.js.map +1 -0
  46. package/dist/core/mime.d.ts +15 -0
  47. package/dist/core/mime.d.ts.map +1 -0
  48. package/dist/core/mime.js +188 -0
  49. package/dist/core/mime.js.map +1 -0
  50. package/dist/core/observability.d.ts +10 -0
  51. package/dist/core/observability.d.ts.map +1 -0
  52. package/dist/core/observability.js +76 -0
  53. package/dist/core/observability.js.map +1 -0
  54. package/dist/core/page-store.d.ts +41 -0
  55. package/dist/core/page-store.d.ts.map +1 -0
  56. package/dist/core/page-store.js +74 -0
  57. package/dist/core/page-store.js.map +1 -0
  58. package/dist/core/path-completer.d.ts +10 -0
  59. package/dist/core/path-completer.d.ts.map +1 -0
  60. package/dist/core/path-completer.js +227 -0
  61. package/dist/core/path-completer.js.map +1 -0
  62. package/dist/core/path-discovery.d.ts +8 -0
  63. package/dist/core/path-discovery.d.ts.map +1 -0
  64. package/dist/core/path-discovery.js +105 -0
  65. package/dist/core/path-discovery.js.map +1 -0
  66. package/dist/core/path-utils.d.ts +9 -0
  67. package/dist/core/path-utils.d.ts.map +1 -0
  68. package/dist/core/path-utils.js +121 -0
  69. package/dist/core/path-utils.js.map +1 -0
  70. package/dist/core/path.d.ts +148 -0
  71. package/dist/core/path.d.ts.map +1 -0
  72. package/dist/core/path.js +674 -0
  73. package/dist/core/path.js.map +1 -0
  74. package/dist/core/primitives.d.ts +23 -0
  75. package/dist/core/primitives.d.ts.map +1 -0
  76. package/dist/core/primitives.js +54 -0
  77. package/dist/core/primitives.js.map +1 -0
  78. package/dist/core/read.d.ts +74 -0
  79. package/dist/core/read.d.ts.map +1 -0
  80. package/dist/core/read.js +562 -0
  81. package/dist/core/read.js.map +1 -0
  82. package/dist/core/schema.d.ts +139 -0
  83. package/dist/core/schema.d.ts.map +1 -0
  84. package/dist/core/schema.js +345 -0
  85. package/dist/core/schema.js.map +1 -0
  86. package/dist/core/search.d.ts +100 -0
  87. package/dist/core/search.d.ts.map +1 -0
  88. package/dist/core/search.js +248 -0
  89. package/dist/core/search.js.map +1 -0
  90. package/dist/core/sensitive.d.ts +6 -0
  91. package/dist/core/sensitive.d.ts.map +1 -0
  92. package/dist/core/sensitive.js +142 -0
  93. package/dist/core/sensitive.js.map +1 -0
  94. package/dist/core/store.d.ts +57 -0
  95. package/dist/core/store.d.ts.map +1 -0
  96. package/dist/core/store.js +162 -0
  97. package/dist/core/store.js.map +1 -0
  98. package/dist/core/util.d.ts +26 -0
  99. package/dist/core/util.d.ts.map +1 -0
  100. package/dist/core/util.js +66 -0
  101. package/dist/core/util.js.map +1 -0
  102. package/dist/core/watcher-registry.d.ts +68 -0
  103. package/dist/core/watcher-registry.d.ts.map +1 -0
  104. package/dist/core/watcher-registry.js +312 -0
  105. package/dist/core/watcher-registry.js.map +1 -0
  106. package/dist/http-policy.d.ts +121 -0
  107. package/dist/http-policy.d.ts.map +1 -0
  108. package/dist/http-policy.js +340 -0
  109. package/dist/http-policy.js.map +1 -0
  110. package/dist/index.d.ts +1 -0
  111. package/dist/index.d.ts.map +1 -0
  112. package/dist/index.js +77 -41
  113. package/dist/index.js.map +1 -0
  114. package/dist/instructions.d.ts +12 -0
  115. package/dist/instructions.d.ts.map +1 -0
  116. package/dist/instructions.js +71 -0
  117. package/dist/instructions.js.map +1 -0
  118. package/dist/prompts.d.ts +8 -6
  119. package/dist/prompts.d.ts.map +1 -0
  120. package/dist/prompts.js +74 -168
  121. package/dist/prompts.js.map +1 -0
  122. package/dist/resources.d.ts +73 -9
  123. package/dist/resources.d.ts.map +1 -0
  124. package/dist/resources.js +392 -163
  125. package/dist/resources.js.map +1 -0
  126. package/dist/server.d.ts +62 -1
  127. package/dist/server.d.ts.map +1 -0
  128. package/dist/server.js +139 -1
  129. package/dist/server.js.map +1 -0
  130. package/dist/tools/batch.d.ts +89 -0
  131. package/dist/tools/batch.d.ts.map +1 -0
  132. package/dist/tools/batch.js +157 -0
  133. package/dist/tools/batch.js.map +1 -0
  134. package/dist/tools/create.d.ts +2 -0
  135. package/dist/tools/create.d.ts.map +1 -0
  136. package/dist/tools/create.js +142 -0
  137. package/dist/tools/create.js.map +1 -0
  138. package/dist/tools/define.d.ts +110 -0
  139. package/dist/tools/define.d.ts.map +1 -0
  140. package/dist/tools/define.js +394 -0
  141. package/dist/tools/define.js.map +1 -0
  142. package/dist/tools/delete-file.d.ts +2 -4
  143. package/dist/tools/delete-file.d.ts.map +1 -0
  144. package/dist/tools/delete-file.js +330 -87
  145. package/dist/tools/delete-file.js.map +1 -0
  146. package/dist/tools/diff.d.ts +2 -0
  147. package/dist/tools/diff.d.ts.map +1 -0
  148. package/dist/tools/diff.js +94 -0
  149. package/dist/tools/diff.js.map +1 -0
  150. package/dist/tools/edit.d.ts +2 -0
  151. package/dist/tools/edit.d.ts.map +1 -0
  152. package/dist/tools/edit.js +442 -0
  153. package/dist/tools/edit.js.map +1 -0
  154. package/dist/tools/index.d.ts +26 -0
  155. package/dist/tools/index.d.ts.map +1 -0
  156. package/dist/tools/index.js +52 -0
  157. package/dist/tools/index.js.map +1 -0
  158. package/dist/tools/list.d.ts +2 -0
  159. package/dist/tools/list.d.ts.map +1 -0
  160. package/dist/tools/list.js +291 -0
  161. package/dist/tools/list.js.map +1 -0
  162. package/dist/tools/move.d.ts +2 -0
  163. package/dist/tools/move.d.ts.map +1 -0
  164. package/dist/tools/move.js +321 -0
  165. package/dist/tools/move.js.map +1 -0
  166. package/dist/tools/patch.d.ts +2 -0
  167. package/dist/tools/patch.d.ts.map +1 -0
  168. package/dist/tools/patch.js +155 -0
  169. package/dist/tools/patch.js.map +1 -0
  170. package/dist/tools/progress.d.ts +52 -0
  171. package/dist/tools/progress.d.ts.map +1 -0
  172. package/dist/tools/progress.js +172 -0
  173. package/dist/tools/progress.js.map +1 -0
  174. package/dist/tools/read.d.ts +2 -4
  175. package/dist/tools/read.d.ts.map +1 -0
  176. package/dist/tools/read.js +398 -152
  177. package/dist/tools/read.js.map +1 -0
  178. package/dist/tools/replace-in-files.d.ts +2 -4
  179. package/dist/tools/replace-in-files.d.ts.map +1 -0
  180. package/dist/tools/replace-in-files.js +435 -264
  181. package/dist/tools/replace-in-files.js.map +1 -0
  182. package/dist/tools/roots.d.ts +2 -4
  183. package/dist/tools/roots.d.ts.map +1 -0
  184. package/dist/tools/roots.js +29 -52
  185. package/dist/tools/roots.js.map +1 -0
  186. package/dist/tools/search-content.d.ts +2 -4
  187. package/dist/tools/search-content.d.ts.map +1 -0
  188. package/dist/tools/search-content.js +262 -228
  189. package/dist/tools/search-content.js.map +1 -0
  190. package/dist/tools/search-files.d.ts +2 -4
  191. package/dist/tools/search-files.d.ts.map +1 -0
  192. package/dist/tools/search-files.js +162 -161
  193. package/dist/tools/search-files.js.map +1 -0
  194. package/dist/tools/stat.d.ts +2 -4
  195. package/dist/tools/stat.d.ts.map +1 -0
  196. package/dist/tools/stat.js +202 -62
  197. package/dist/tools/stat.js.map +1 -0
  198. package/dist/transport/http.d.ts +5 -0
  199. package/dist/transport/http.d.ts.map +1 -0
  200. package/dist/transport/http.js +276 -0
  201. package/dist/transport/http.js.map +1 -0
  202. package/dist/transport/shared.d.ts +45 -0
  203. package/dist/transport/shared.d.ts.map +1 -0
  204. package/dist/transport/shared.js +65 -0
  205. package/dist/transport/shared.js.map +1 -0
  206. package/dist/transport/stdio.d.ts +32 -0
  207. package/dist/transport/stdio.d.ts.map +1 -0
  208. package/dist/transport/stdio.js +279 -0
  209. package/dist/transport/stdio.js.map +1 -0
  210. package/dist/transport.d.ts +5 -0
  211. package/dist/transport.d.ts.map +1 -0
  212. package/dist/transport.js +4 -0
  213. package/dist/transport.js.map +1 -0
  214. package/package.json +37 -46
  215. package/dist/assets/logo.svg +0 -35
  216. package/dist/completions.d.ts +0 -2
  217. package/dist/completions.js +0 -572
  218. package/dist/config.d.ts +0 -119
  219. package/dist/config.js +0 -31
  220. package/dist/lib/abort.d.ts +0 -7
  221. package/dist/lib/abort.js +0 -81
  222. package/dist/lib/constants.d.ts +0 -42
  223. package/dist/lib/constants.js +0 -313
  224. package/dist/lib/errors.d.ts +0 -28
  225. package/dist/lib/errors.js +0 -301
  226. package/dist/lib/file-operations/core.d.ts +0 -53
  227. package/dist/lib/file-operations/core.js +0 -144
  228. package/dist/lib/file-operations/metadata.d.ts +0 -77
  229. package/dist/lib/file-operations/metadata.js +0 -878
  230. package/dist/lib/file-operations/search.d.ts +0 -46
  231. package/dist/lib/file-operations/search.js +0 -1194
  232. package/dist/lib/file-operations/traversal.d.ts +0 -40
  233. package/dist/lib/file-operations/traversal.js +0 -310
  234. package/dist/lib/fs-helpers.d.ts +0 -46
  235. package/dist/lib/fs-helpers.js +0 -606
  236. package/dist/lib/globs.d.ts +0 -2
  237. package/dist/lib/globs.js +0 -19
  238. package/dist/lib/logger.d.ts +0 -27
  239. package/dist/lib/logger.js +0 -91
  240. package/dist/lib/observability.d.ts +0 -33
  241. package/dist/lib/observability.js +0 -373
  242. package/dist/lib/paths.d.ts +0 -35
  243. package/dist/lib/paths.js +0 -634
  244. package/dist/lib/resource-store.d.ts +0 -28
  245. package/dist/lib/resource-store.js +0 -195
  246. package/dist/lib/utils.d.ts +0 -7
  247. package/dist/lib/utils.js +0 -37
  248. package/dist/lib/zod-codecs.d.ts +0 -2
  249. package/dist/lib/zod-codecs.js +0 -18
  250. package/dist/pkg-info.d.ts +0 -7
  251. package/dist/pkg-info.js +0 -9
  252. package/dist/resources/generated-instructions.d.ts +0 -1
  253. package/dist/resources/generated-instructions.js +0 -105
  254. package/dist/resources/tool-catalog.d.ts +0 -2
  255. package/dist/resources/tool-catalog.js +0 -78
  256. package/dist/resources/tool-info.d.ts +0 -9
  257. package/dist/resources/tool-info.js +0 -206
  258. package/dist/resources/workflows.d.ts +0 -1
  259. package/dist/resources/workflows.js +0 -72
  260. package/dist/schemas.d.ts +0 -505
  261. package/dist/schemas.js +0 -676
  262. package/dist/server/bootstrap.d.ts +0 -6
  263. package/dist/server/bootstrap.js +0 -563
  264. package/dist/server/event-store.d.ts +0 -18
  265. package/dist/server/event-store.js +0 -71
  266. package/dist/server/roots-manager.d.ts +0 -28
  267. package/dist/server/roots-manager.js +0 -227
  268. package/dist/server/task-store.d.ts +0 -10
  269. package/dist/server/task-store.js +0 -91
  270. package/dist/tools/apply-patch.d.ts +0 -4
  271. package/dist/tools/apply-patch.js +0 -235
  272. package/dist/tools/calculate-hash.d.ts +0 -4
  273. package/dist/tools/calculate-hash.js +0 -187
  274. package/dist/tools/contract.d.ts +0 -50
  275. package/dist/tools/contract.js +0 -1
  276. package/dist/tools/create-directory.d.ts +0 -4
  277. package/dist/tools/create-directory.js +0 -68
  278. package/dist/tools/diff-files.d.ts +0 -4
  279. package/dist/tools/diff-files.js +0 -136
  280. package/dist/tools/edit-file.d.ts +0 -4
  281. package/dist/tools/edit-file.js +0 -256
  282. package/dist/tools/icons.d.ts +0 -15
  283. package/dist/tools/icons.js +0 -24
  284. package/dist/tools/list-directory.d.ts +0 -4
  285. package/dist/tools/list-directory.js +0 -218
  286. package/dist/tools/move-file.d.ts +0 -4
  287. package/dist/tools/move-file.js +0 -178
  288. package/dist/tools/read-multiple.d.ts +0 -4
  289. package/dist/tools/read-multiple.js +0 -207
  290. package/dist/tools/shared.d.ts +0 -172
  291. package/dist/tools/shared.js +0 -599
  292. package/dist/tools/stat-many.d.ts +0 -4
  293. package/dist/tools/stat-many.js +0 -104
  294. package/dist/tools/task-support.d.ts +0 -29
  295. package/dist/tools/task-support.js +0 -472
  296. package/dist/tools/tree.d.ts +0 -4
  297. package/dist/tools/tree.js +0 -102
  298. package/dist/tools/write-file.d.ts +0 -4
  299. package/dist/tools/write-file.js +0 -60
  300. package/dist/tools.d.ts +0 -5
  301. 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 {};