@j0hanz/filesystem-mcp 1.19.1 → 2.1.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 +26 -0
  15. package/dist/core/config.d.ts.map +1 -0
  16. package/dist/core/config.js +18 -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 +37 -0
  31. package/dist/core/fmt.d.ts.map +1 -0
  32. package/dist/core/fmt.js +93 -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 +348 -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 +24 -0
  99. package/dist/core/util.d.ts.map +1 -0
  100. package/dist/core/util.js +65 -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 +11 -0
  115. package/dist/instructions.d.ts.map +1 -0
  116. package/dist/instructions.js +70 -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 +124 -0
  137. package/dist/tools/create.js.map +1 -0
  138. package/dist/tools/define.d.ts +102 -0
  139. package/dist/tools/define.d.ts.map +1 -0
  140. package/dist/tools/define.js +404 -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 +327 -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 +440 -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 +301 -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 +423 -150
  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 +432 -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 +45 -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 +189 -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 -585
  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 -473
  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
@@ -0,0 +1,3 @@
1
+ export declare function printHelpAndExit(): never;
2
+ export declare function printVersionAndExit(): never;
3
+ //# sourceMappingURL=cli-help.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-help.d.ts","sourceRoot":"","sources":["../src/cli-help.ts"],"names":[],"mappings":"AAgJA,wBAAgB,gBAAgB,IAAI,KAAK,CAkCxC;AAED,wBAAgB,mBAAmB,IAAI,KAAK,CAG3C"}
@@ -0,0 +1,169 @@
1
+ import packageJson from '../package.json' with { type: 'json' };
2
+ import { cliFmt, padEndVisible } from './core/fmt.js';
3
+ import { MUTATING_TOOL_NAMES } from './tools/index.js';
4
+ // Help text and its rendering. Split out of cli.ts so that module holds only
5
+ // argument parsing and directory validation: this file is static copy plus a
6
+ // formatter, and shares no state with the parser.
7
+ const { version: SERVER_VERSION } = packageJson;
8
+ const OPTIONS_HELP = [
9
+ { flags: '-h, --help', desc: 'Show this help message' },
10
+ { flags: '-v, --version', desc: 'Show the server version' },
11
+ { flags: '--allow-cwd', desc: 'Add the current working directory as an allowed root' },
12
+ {
13
+ flags: '--port <number>',
14
+ desc: 'Start HTTP transport on this port (env: FS_PORT)',
15
+ },
16
+ {
17
+ flags: '--read-only',
18
+ desc: `Disable write tools: ${[...MUTATING_TOOL_NAMES].sort().join(', ')}`,
19
+ },
20
+ { flags: '--safe', desc: 'Alias for --read-only' },
21
+ {
22
+ flags: '--print-config',
23
+ desc: 'Print the active configuration and exit (use with --json for machine output)',
24
+ },
25
+ { flags: '--json', desc: 'Output --print-config as JSON' },
26
+ {
27
+ flags: '--log-level <level>',
28
+ desc: 'Log level, RFC 5424: debug|info|notice|warn|error|critical|alert|emergency (env: FS_LOG_LEVEL)',
29
+ },
30
+ { flags: '--http-host <host>', desc: 'HTTP server bind address (env: FS_HTTP_HOST)' },
31
+ {
32
+ flags: '--api-key <key>',
33
+ desc: 'Require this API key on HTTP requests; prefer FS_API_KEY (argv is world-readable)',
34
+ },
35
+ {
36
+ flags: '--allow-sensitive',
37
+ desc: 'Allow access to sensitive system paths (env: FS_ALLOW_SENSITIVE)',
38
+ },
39
+ {
40
+ flags: '--root-boundary <path>',
41
+ desc: 'Require all allowed roots to fall under this path (env: FS_ROOT_BOUNDARY)',
42
+ },
43
+ {
44
+ flags: '--max-file-size <bytes>',
45
+ desc: 'Maximum file size for reads in bytes (env: FS_MAX_FILE_SIZE)',
46
+ },
47
+ { flags: '--walk-cwd', desc: 'Walk up from CWD to find a project root; implies --allow-cwd' },
48
+ { flags: '--deny <pattern>', desc: 'Block paths matching this pattern; repeatable' },
49
+ {
50
+ flags: '--allow-missing-roots',
51
+ desc: 'Start even if configured allowed directories do not exist',
52
+ },
53
+ ];
54
+ const ENV_HELP = [
55
+ {
56
+ flags: 'FS_LOG_LEVEL',
57
+ desc: 'Log level, RFC 5424: debug|info|notice|warn|error|critical|alert|emergency',
58
+ },
59
+ { flags: 'FS_PORT', desc: 'Start HTTP transport on this port (unset = stdio)' },
60
+ { flags: 'FS_HTTP_HOST', desc: 'HTTP bind address' },
61
+ { flags: 'FS_API_KEY', desc: 'HTTP API key' },
62
+ {
63
+ flags: 'FS_TRUST_PROXY',
64
+ desc: 'Express trust-proxy setting: hop count or expression (unset = do not trust X-Forwarded-*)',
65
+ },
66
+ {
67
+ flags: 'FS_ALLOW_SENSITIVE',
68
+ desc: 'Allow sensitive system paths ("true" or "1" enables this)',
69
+ },
70
+ { flags: 'FS_ROOT_BOUNDARY', desc: 'Path prefix all allowed roots must fall under' },
71
+ { flags: 'FS_MAX_FILE_SIZE', desc: 'Maximum file size for reads in bytes' },
72
+ {
73
+ flags: 'FS_ALLOWED_DIRS',
74
+ desc: 'Allowed dirs: colon-separated (Unix), semicolon-separated (Windows)',
75
+ },
76
+ {
77
+ flags: 'FS_ALLOW_CWD_WALK',
78
+ desc: 'Walk up from CWD to find a project root ("true" or "1" enables this)',
79
+ },
80
+ { flags: 'FS_DENYLIST', desc: 'Paths/patterns to block, comma-separated' },
81
+ {
82
+ flags: 'FS_ALLOW_MISSING_ROOTS',
83
+ desc: 'Start even if configured allowed directories do not exist ("true" or "1" enables this)',
84
+ },
85
+ {
86
+ flags: 'FS_ALLOWED_HOSTS',
87
+ desc: 'Comma-separated Host header values to accept (HTTP transport)',
88
+ },
89
+ { flags: 'FS_ALLOWED_ORIGINS', desc: 'Comma-separated origin hostnames for CORS' },
90
+ {
91
+ flags: 'FS_ALLOW_UNRESTRICTED_HOSTS',
92
+ desc: 'Bind a wildcard host with no Host validation, accepts the risk ("true" or "1" enables this)',
93
+ },
94
+ { flags: 'FS_PUBLIC_URL', desc: 'Resource identifier URL for RFC 9728 discovery' },
95
+ {
96
+ flags: 'FS_RATE_LIMIT_RPM',
97
+ desc: 'Per-client-IP requests/min (default 120 authenticated, 6000 keyless loopback; 1–100000)',
98
+ },
99
+ {
100
+ flags: 'FS_MAX_REQUEST_BYTES',
101
+ desc: 'Max HTTP request body bytes (default 4194304, 1024–268435456)',
102
+ },
103
+ {
104
+ flags: 'FS_KEEPALIVE_TIMEOUT_MS',
105
+ desc: 'HTTP keep-alive timeout; set above any fronting proxy idle timeout (default 5000, 1000–600000)',
106
+ },
107
+ {
108
+ flags: 'FS_MAX_WATCHERS',
109
+ desc: 'Max concurrent file watchers (default 256, 1–4096)',
110
+ },
111
+ {
112
+ flags: 'FS_MAX_INLINE_MATCHES',
113
+ desc: 'Max inline content matches per search (default 50, 1–10000)',
114
+ },
115
+ {
116
+ flags: 'FS_MAX_READ_MANY_BYTES',
117
+ desc: 'Max total bytes across one batch read (default 524288, 10240–104857600)',
118
+ },
119
+ { flags: 'FS_SEARCH_TIMEOUT_MS', desc: 'Search timeout in ms (default 5000, 100–60000)' },
120
+ { flags: 'NO_COLOR', desc: 'Any value disables ANSI color output' },
121
+ {
122
+ flags: 'FS_REQUEST_STATE_KEY',
123
+ desc: 'HMAC key for input_required state; optional outside fleet mode, shared and >=32 bytes in fleet mode',
124
+ },
125
+ ];
126
+ const EXAMPLES_HELP = [
127
+ '$ filesystem-mcp /path/to/allowed/dir',
128
+ '$ filesystem-mcp --allow-cwd',
129
+ '$ filesystem-mcp /project/src /project/tests --allow-cwd',
130
+ '$ filesystem-mcp --port 3000 /path/to/allowed/dir',
131
+ '$ filesystem-mcp --read-only /data/readonly',
132
+ '$ filesystem-mcp --print-config --json /project',
133
+ ];
134
+ export function printHelpAndExit() {
135
+ const { bold, dim, section, flag, placeholder, cyan } = cliFmt;
136
+ const COL = 27;
137
+ const optRow = (flags, desc) => {
138
+ const colored = flags
139
+ .replace(/<[^>]+>/g, (m) => placeholder(m))
140
+ .replace(/-{1,2}[\w-]+/g, (m) => flag(m));
141
+ return ` ${padEndVisible(colored, COL)}${desc}`;
142
+ };
143
+ const envRow = (name, desc) => {
144
+ return ` ${padEndVisible(cyan(name), COL - 1)} ${desc}`;
145
+ };
146
+ const lines = [
147
+ '',
148
+ `${bold('Filesystem MCP')} ${dim(`v${SERVER_VERSION}`)}`,
149
+ '',
150
+ dim('Pass one or more directories to set the allowed access roots.'),
151
+ '',
152
+ section('Options:'),
153
+ ...OPTIONS_HELP.map(({ flags, desc }) => optRow(flags, desc)),
154
+ '',
155
+ `${section('Environment variables:')} ${dim('(flags take precedence when both are set)')}`,
156
+ ...ENV_HELP.map(({ flags, desc }) => envRow(flags, desc)),
157
+ '',
158
+ section('Examples:'),
159
+ ...EXAMPLES_HELP.map((ex) => ` ${dim(ex)}`),
160
+ '',
161
+ ];
162
+ process.stdout.write(lines.join('\n'));
163
+ process.exit(0);
164
+ }
165
+ export function printVersionAndExit() {
166
+ process.stdout.write(`${cliFmt.cyan(SERVER_VERSION)}\n`);
167
+ process.exit(0);
168
+ }
169
+ //# sourceMappingURL=cli-help.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-help.js","sourceRoot":"","sources":["../src/cli-help.ts"],"names":[],"mappings":"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","sourcesContent":["import packageJson from '../package.json' with { type: 'json' };\nimport { cliFmt, padEndVisible } from './core/fmt.js';\nimport { MUTATING_TOOL_NAMES } from './tools/index.js';\n\n// Help text and its rendering. Split out of cli.ts so that module holds only\n// argument parsing and directory validation: this file is static copy plus a\n// formatter, and shares no state with the parser.\n\nconst { version: SERVER_VERSION } = packageJson;\n\ninterface HelpRow {\n flags: string;\n desc: string;\n}\n\nconst OPTIONS_HELP: HelpRow[] = [\n { flags: '-h, --help', desc: 'Show this help message' },\n { flags: '-v, --version', desc: 'Show the server version' },\n { flags: '--allow-cwd', desc: 'Add the current working directory as an allowed root' },\n {\n flags: '--port <number>',\n desc: 'Start HTTP transport on this port (env: FS_PORT)',\n },\n {\n flags: '--read-only',\n desc: `Disable write tools: ${[...MUTATING_TOOL_NAMES].sort().join(', ')}`,\n },\n { flags: '--safe', desc: 'Alias for --read-only' },\n {\n flags: '--print-config',\n desc: 'Print the active configuration and exit (use with --json for machine output)',\n },\n { flags: '--json', desc: 'Output --print-config as JSON' },\n {\n flags: '--log-level <level>',\n desc: 'Log level, RFC 5424: debug|info|notice|warn|error|critical|alert|emergency (env: FS_LOG_LEVEL)',\n },\n { flags: '--http-host <host>', desc: 'HTTP server bind address (env: FS_HTTP_HOST)' },\n {\n flags: '--api-key <key>',\n desc: 'Require this API key on HTTP requests; prefer FS_API_KEY (argv is world-readable)',\n },\n {\n flags: '--allow-sensitive',\n desc: 'Allow access to sensitive system paths (env: FS_ALLOW_SENSITIVE)',\n },\n {\n flags: '--root-boundary <path>',\n desc: 'Require all allowed roots to fall under this path (env: FS_ROOT_BOUNDARY)',\n },\n {\n flags: '--max-file-size <bytes>',\n desc: 'Maximum file size for reads in bytes (env: FS_MAX_FILE_SIZE)',\n },\n { flags: '--walk-cwd', desc: 'Walk up from CWD to find a project root; implies --allow-cwd' },\n { flags: '--deny <pattern>', desc: 'Block paths matching this pattern; repeatable' },\n {\n flags: '--allow-missing-roots',\n desc: 'Start even if configured allowed directories do not exist',\n },\n];\n\nconst ENV_HELP: HelpRow[] = [\n {\n flags: 'FS_LOG_LEVEL',\n desc: 'Log level, RFC 5424: debug|info|notice|warn|error|critical|alert|emergency',\n },\n { flags: 'FS_PORT', desc: 'Start HTTP transport on this port (unset = stdio)' },\n { flags: 'FS_HTTP_HOST', desc: 'HTTP bind address' },\n { flags: 'FS_API_KEY', desc: 'HTTP API key' },\n {\n flags: 'FS_TRUST_PROXY',\n desc: 'Express trust-proxy setting: hop count or expression (unset = do not trust X-Forwarded-*)',\n },\n {\n flags: 'FS_ALLOW_SENSITIVE',\n desc: 'Allow sensitive system paths (\"true\" or \"1\" enables this)',\n },\n { flags: 'FS_ROOT_BOUNDARY', desc: 'Path prefix all allowed roots must fall under' },\n { flags: 'FS_MAX_FILE_SIZE', desc: 'Maximum file size for reads in bytes' },\n {\n flags: 'FS_ALLOWED_DIRS',\n desc: 'Allowed dirs: colon-separated (Unix), semicolon-separated (Windows)',\n },\n {\n flags: 'FS_ALLOW_CWD_WALK',\n desc: 'Walk up from CWD to find a project root (\"true\" or \"1\" enables this)',\n },\n { flags: 'FS_DENYLIST', desc: 'Paths/patterns to block, comma-separated' },\n {\n flags: 'FS_ALLOW_MISSING_ROOTS',\n desc: 'Start even if configured allowed directories do not exist (\"true\" or \"1\" enables this)',\n },\n {\n flags: 'FS_ALLOWED_HOSTS',\n desc: 'Comma-separated Host header values to accept (HTTP transport)',\n },\n { flags: 'FS_ALLOWED_ORIGINS', desc: 'Comma-separated origin hostnames for CORS' },\n {\n flags: 'FS_ALLOW_UNRESTRICTED_HOSTS',\n desc: 'Bind a wildcard host with no Host validation, accepts the risk (\"true\" or \"1\" enables this)',\n },\n { flags: 'FS_PUBLIC_URL', desc: 'Resource identifier URL for RFC 9728 discovery' },\n {\n flags: 'FS_RATE_LIMIT_RPM',\n desc: 'Per-client-IP requests/min (default 120 authenticated, 6000 keyless loopback; 1–100000)',\n },\n {\n flags: 'FS_MAX_REQUEST_BYTES',\n desc: 'Max HTTP request body bytes (default 4194304, 1024–268435456)',\n },\n {\n flags: 'FS_KEEPALIVE_TIMEOUT_MS',\n desc: 'HTTP keep-alive timeout; set above any fronting proxy idle timeout (default 5000, 1000–600000)',\n },\n {\n flags: 'FS_MAX_WATCHERS',\n desc: 'Max concurrent file watchers (default 256, 1–4096)',\n },\n {\n flags: 'FS_MAX_INLINE_MATCHES',\n desc: 'Max inline content matches per search (default 50, 1–10000)',\n },\n {\n flags: 'FS_MAX_READ_MANY_BYTES',\n desc: 'Max total bytes across one batch read (default 524288, 10240–104857600)',\n },\n { flags: 'FS_SEARCH_TIMEOUT_MS', desc: 'Search timeout in ms (default 5000, 100–60000)' },\n { flags: 'NO_COLOR', desc: 'Any value disables ANSI color output' },\n {\n flags: 'FS_REQUEST_STATE_KEY',\n desc: 'HMAC key for input_required state; optional outside fleet mode, shared and >=32 bytes in fleet mode',\n },\n];\n\nconst EXAMPLES_HELP = [\n '$ filesystem-mcp /path/to/allowed/dir',\n '$ filesystem-mcp --allow-cwd',\n '$ filesystem-mcp /project/src /project/tests --allow-cwd',\n '$ filesystem-mcp --port 3000 /path/to/allowed/dir',\n '$ filesystem-mcp --read-only /data/readonly',\n '$ filesystem-mcp --print-config --json /project',\n];\n\nexport function printHelpAndExit(): never {\n const { bold, dim, section, flag, placeholder, cyan } = cliFmt;\n const COL = 27;\n\n const optRow = (flags: string, desc: string): string => {\n const colored = flags\n .replace(/<[^>]+>/g, (m) => placeholder(m))\n .replace(/-{1,2}[\\w-]+/g, (m) => flag(m));\n return ` ${padEndVisible(colored, COL)}${desc}`;\n };\n\n const envRow = (name: string, desc: string): string => {\n return ` ${padEndVisible(cyan(name), COL - 1)} ${desc}`;\n };\n\n const lines = [\n '',\n `${bold('Filesystem MCP')} ${dim(`v${SERVER_VERSION}`)}`,\n '',\n dim('Pass one or more directories to set the allowed access roots.'),\n '',\n section('Options:'),\n ...OPTIONS_HELP.map(({ flags, desc }) => optRow(flags, desc)),\n '',\n `${section('Environment variables:')} ${dim('(flags take precedence when both are set)')}`,\n ...ENV_HELP.map(({ flags, desc }) => envRow(flags, desc)),\n '',\n section('Examples:'),\n ...EXAMPLES_HELP.map((ex) => ` ${dim(ex)}`),\n '',\n ];\n\n process.stdout.write(lines.join('\\n'));\n process.exit(0);\n}\n\nexport function printVersionAndExit(): never {\n process.stdout.write(`${cliFmt.cyan(SERVER_VERSION)}\\n`);\n process.exit(0);\n}\n"]}
package/dist/cli.d.ts CHANGED
@@ -6,4 +6,32 @@ export declare function parseArgs(): Promise<{
6
6
  allowedDirs: string[];
7
7
  allowCwd: boolean;
8
8
  port: number | undefined;
9
+ readOnly: boolean;
10
+ printConfig: boolean;
11
+ json: boolean;
12
+ httpHost: string | undefined;
13
+ apiKey: string | undefined;
9
14
  }>;
15
+ export interface EffectiveConfig {
16
+ transport: string;
17
+ readOnly: boolean;
18
+ allowedRoots: string[];
19
+ tools: string[];
20
+ apiKey: string | null;
21
+ limits: {
22
+ maxFileSizeBytes: number;
23
+ };
24
+ }
25
+ export interface PrintConfigOptions {
26
+ allowedDirs: string[];
27
+ allowCwd: boolean;
28
+ readOnly: boolean;
29
+ json: boolean;
30
+ /** Resolved `--port`. Present means the launch this reports on is an HTTP bind. */
31
+ port?: number;
32
+ httpHost?: string;
33
+ apiKey?: string;
34
+ stdout?: (chunk: string) => void;
35
+ }
36
+ export declare function runPrintConfig(options: PrintConfigOptions): Promise<EffectiveConfig>;
37
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAyBA,qBAAa,YAAa,SAAQ,KAAK;IACrC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;gBAEd,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;CAK9C;AAkJD,wBAAsB,SAAS,IAAI,OAAO,CAAC;IACzC,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;IACrB,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B,CAAC,CAgFD;AAMD,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE;QAAE,gBAAgB,EAAE,MAAM,CAAA;KAAE,CAAC;CACtC;AAED,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,OAAO,CAAC;IACd,mFAAmF;IACnF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC;AAED,wBAAsB,cAAc,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,eAAe,CAAC,CAuC1F"}
package/dist/cli.js CHANGED
@@ -1,13 +1,19 @@
1
1
  import { stat } from 'node:fs/promises';
2
- import { getSystemErrorMessage, getSystemErrorName } from 'node:util';
3
- import { Command, CommanderError, InvalidArgumentError } from 'commander';
4
- import { processInParallel } from './lib/fs-helpers.js';
5
- import { getReservedDeviceNameForPath, isWindowsDriveRelativePath, normalizePath, } from './lib/paths.js';
6
- import { isRecord } from './lib/utils.js';
7
- import { pkgInfo } from './pkg-info.js';
8
- const { version: SERVER_VERSION } = pkgInfo;
9
- const IS_WINDOWS = process.platform === 'win32';
2
+ import { getSystemErrorMessage, getSystemErrorName, parseArgs as utilParseArgs } from 'node:util';
3
+ import { printHelpAndExit, printVersionAndExit } from './cli-help.js';
4
+ import { processInParallel } from './core/concurrency.js';
5
+ import { cli } from './core/config.js';
6
+ import { formatUnknownErrorMessage } from './core/errors.js';
7
+ import { cliFmt, padEndVisible } from './core/fmt.js';
8
+ import { getReservedDeviceNameForPath, isWindowsDriveRelativePath, normalizePath, } from './core/path-utils.js';
9
+ import { PathGuard } from './core/path.js';
10
+ import { IS_WINDOWS, isRecord, parseTrueEnvFlag } from './core/primitives.js';
11
+ import { getMaxTextFileSize } from './core/util.js';
12
+ import { registeredTools } from './tools/index.js';
10
13
  const CLI_VALIDATE_CONCURRENCY = 8;
14
+ // ════════════════════════════════════════════════════════════
15
+ // Path & Config Utilities — pure functions and error types
16
+ // ════════════════════════════════════════════════════════════
11
17
  export class CliExitError extends Error {
12
18
  exitCode;
13
19
  constructor(message, exitCode) {
@@ -18,45 +24,25 @@ export class CliExitError extends Error {
18
24
  }
19
25
  function validateCliPath(inputPath) {
20
26
  if (inputPath.includes('\0')) {
21
- throw new InvalidArgumentError('Path contains null bytes.');
27
+ throw new CliExitError('Path contains null bytes.', 1);
22
28
  }
23
29
  if (isWindowsDriveRelativePath(inputPath)) {
24
- throw new InvalidArgumentError('Windows drive-relative paths are not allowed. Use C:\\path or C:/path instead of C:path.');
30
+ throw new CliExitError('Windows drive-relative paths are not allowed. Use C:\\path or C:/path instead of C:path.', 1);
25
31
  }
26
32
  const reserved = getReservedDeviceNameForPath(inputPath);
27
33
  if (reserved) {
28
- throw new InvalidArgumentError(`Windows reserved device name not allowed: ${reserved}.`);
34
+ throw new CliExitError(`Windows reserved device name not allowed: ${reserved}.`, 1);
29
35
  }
30
36
  }
31
- function getNodeErrorProperty(error, key) {
37
+ function getSystemErrorDetails(error) {
32
38
  if (!isRecord(error))
33
- return undefined;
34
- const value = error[key];
35
- if (typeof value === 'string' || typeof value === 'number') {
36
- return value;
37
- }
38
- return undefined;
39
- }
40
- function collectStringValues(values) {
41
- const result = [];
42
- for (const value of values) {
43
- if (typeof value === 'string') {
44
- result.push(value);
45
- }
46
- }
47
- return result;
48
- }
49
- function getNodeErrorCode(error) {
50
- const code = getNodeErrorProperty(error, 'code');
51
- return typeof code === 'string' ? code : undefined;
52
- }
53
- function getNodeErrorErrno(error) {
54
- const errno = getNodeErrorProperty(error, 'errno');
55
- return typeof errno === 'number' ? errno : undefined;
39
+ return { code: undefined, errno: undefined };
40
+ const code = typeof error['code'] === 'string' ? error['code'] : undefined;
41
+ const errno = typeof error['errno'] === 'number' ? error['errno'] : undefined;
42
+ return { code, errno };
56
43
  }
57
44
  function normalizeDirectoryError(error, inputPath) {
58
- const code = getNodeErrorCode(error);
59
- const errno = getNodeErrorErrno(error);
45
+ const { code, errno } = getSystemErrorDetails(error);
60
46
  if (error instanceof Error && code === undefined && errno === undefined) {
61
47
  return error;
62
48
  }
@@ -67,8 +53,9 @@ function normalizeDirectoryError(error, inputPath) {
67
53
  return new Error(`Cannot access directory ${inputPath} (${name}: ${message})`);
68
54
  }
69
55
  catch {
70
- // Fall through to best-effort formatting.
56
+ // Fall through, but preserve raw errno value
71
57
  }
58
+ return new Error(`Cannot access directory ${inputPath} (errno ${errno})`);
72
59
  }
73
60
  if (code) {
74
61
  return new Error(`Cannot access directory ${inputPath} (${code})`);
@@ -80,7 +67,7 @@ function assertDirectory(stats, inputPath) {
80
67
  return;
81
68
  throw new Error(`${inputPath} is not a directory`);
82
69
  }
83
- async function validateDirectoryPath(inputPath) {
70
+ async function validateDirectoryPath(inputPath, allowMissing = false) {
84
71
  const normalized = normalizePath(inputPath);
85
72
  try {
86
73
  const stats = await stat(normalized);
@@ -88,77 +75,25 @@ async function validateDirectoryPath(inputPath) {
88
75
  return normalized;
89
76
  }
90
77
  catch (error) {
78
+ if (allowMissing) {
79
+ if (error instanceof Error && error.message.includes('is not a directory')) {
80
+ throw error;
81
+ }
82
+ return normalized;
83
+ }
91
84
  throw normalizeDirectoryError(error, inputPath);
92
85
  }
93
86
  }
94
- async function normalizeCliDirectories(args) {
95
- const { results, errors } = await processInParallel([...args], validateDirectoryPath, CLI_VALIDATE_CONCURRENCY);
87
+ async function normalizeAndValidateDirs(paths, allowMissing = false) {
88
+ const { results, errors } = await processInParallel([...paths], (p) => validateDirectoryPath(p, allowMissing), CLI_VALIDATE_CONCURRENCY);
96
89
  if (errors.length === 0) {
97
- return results;
98
- }
99
- let first = errors[0];
100
- for (const failure of errors) {
101
- if (first && failure.index < first.index) {
102
- first = failure;
103
- }
90
+ return deduplicateAllowedDirectories(results.map((r) => r.value));
104
91
  }
105
- throw first?.error ?? new Error('Failed to validate directories');
106
- }
107
- function parseAllowedDirArgument(value, previous) {
108
- validateCliPath(value);
109
- const values = Array.isArray(previous) ? collectStringValues(previous) : [];
110
- return [...values, value];
111
- }
112
- function getParsedAllowedDirs(cli) {
113
- const [allowedDirs] = cli.processedArgs;
114
- if (!Array.isArray(allowedDirs))
115
- return [];
116
- return collectStringValues(allowedDirs);
117
- }
118
- function createCliProgram(output) {
119
- const cli = new Command();
120
- cli
121
- .name('filesystem-mcp')
122
- .usage('[options] [allowedDirs...]')
123
- .description('MCP filesystem server. Positional directories define allowed access roots.')
124
- .argument('[allowedDirs...]', 'Directories the MCP server can access on disk', parseAllowedDirArgument)
125
- .option('--allow_cwd, --allow-cwd', 'Allow the current working directory as an additional root')
126
- .option('--port <number>', 'Enable HTTP transport on the given port (Node Streamable HTTP)')
127
- .helpOption('-h, --help', 'Display command help')
128
- .version(SERVER_VERSION, '-v, --version', 'Display server version')
129
- .addHelpText('after', `
130
- Examples:
131
- $ filesystem-mcp /path/to/allowed/dir
132
- $ filesystem-mcp --allow-cwd
133
- $ filesystem-mcp /project/src /project/tests --allow-cwd
134
- $ filesystem-mcp --port 3000 /path/to/allowed/dir
135
- `);
136
- cli.allowUnknownOption(false);
137
- cli.allowExcessArguments(false);
138
- cli.showHelpAfterError('(run with --help for usage)');
139
- cli.showSuggestionAfterError(true);
140
- cli.exitOverride();
141
- cli.configureOutput({
142
- writeOut(text) {
143
- output.push(text);
144
- },
145
- writeErr(text) {
146
- output.push(text);
147
- },
148
- outputError(text, write) {
149
- write(text);
150
- },
151
- });
152
- return cli;
153
- }
154
- function formatCliOutput(output, fallback) {
155
- const joined = output.join('').trimEnd();
156
- if (joined.length > 0)
157
- return joined;
158
- return fallback.trimEnd();
92
+ const first = errors.reduce((prev, curr) => (curr.index < prev.index ? curr : prev));
93
+ throw first.error;
159
94
  }
160
95
  function normalizeCliExitMessage(error) {
161
- const rawMessage = error instanceof Error ? error.message : String(error);
96
+ const rawMessage = formatUnknownErrorMessage(error);
162
97
  return rawMessage.startsWith('Error:') ? rawMessage : `Error: ${rawMessage}`;
163
98
  }
164
99
  function deduplicateAllowedDirectories(dirs) {
@@ -174,38 +109,148 @@ function deduplicateAllowedDirectories(dirs) {
174
109
  return deduplicated;
175
110
  }
176
111
  function parsePortOption(raw) {
177
- if (raw === undefined)
112
+ // '' reads as unset: FS_PORT set-but-empty (compose templating) means stdio.
113
+ if (raw === undefined || raw === '')
178
114
  return undefined;
179
115
  const n = Number(raw);
180
116
  if (!Number.isInteger(n) || n < 1 || n > 65535) {
181
- throw new CliExitError(`Error: --port must be an integer between 1 and 65535`, 1);
117
+ throw new CliExitError(`Error: --port / FS_PORT must be an integer between 1 and 65535`, 1);
182
118
  }
183
119
  return n;
184
120
  }
121
+ const CLI_PARSER_CONFIG = {
122
+ options: {
123
+ 'allow-cwd': { type: 'boolean', default: false },
124
+ 'read-only': { type: 'boolean', default: false },
125
+ safe: { type: 'boolean', default: false },
126
+ 'print-config': { type: 'boolean', default: false },
127
+ json: { type: 'boolean', default: false },
128
+ port: { type: 'string' },
129
+ help: { type: 'boolean', short: 'h' },
130
+ version: { type: 'boolean', short: 'v' },
131
+ 'log-level': { type: 'string' },
132
+ 'http-host': { type: 'string' },
133
+ 'api-key': { type: 'string' },
134
+ 'allow-sensitive': { type: 'boolean', default: false },
135
+ 'root-boundary': { type: 'string' },
136
+ 'max-file-size': { type: 'string' },
137
+ 'walk-cwd': { type: 'boolean', default: false },
138
+ deny: { type: 'string', multiple: true },
139
+ 'allow-missing-roots': { type: 'boolean', default: false },
140
+ },
141
+ strict: true,
142
+ allowPositionals: true,
143
+ };
185
144
  export async function parseArgs() {
186
- const output = [];
187
- const cli = createCliProgram(output);
188
145
  try {
189
- cli.parse(process.argv, { from: 'node' });
146
+ const parsed = utilParseArgs(CLI_PARSER_CONFIG);
147
+ if (parsed.values.help) {
148
+ printHelpAndExit();
149
+ }
150
+ if (parsed.values.version) {
151
+ printVersionAndExit();
152
+ }
153
+ for (const positional of parsed.positionals) {
154
+ validateCliPath(positional);
155
+ }
156
+ const vals = parsed.values;
157
+ // `--http-host` and `--api-key` are returned to the caller and handed to
158
+ // the server as config. Every other flag lands in the CLI-override store
159
+ // (core/config.ts) — the one owner of the flag-beats-env rule — which deep
160
+ // core readers (path, sensitive, observability, util) consult before
161
+ // falling back to the operator's environment. Nothing writes process.env.
162
+ const httpHost = typeof vals['http-host'] === 'string' ? vals['http-host'] : process.env['FS_HTTP_HOST'];
163
+ const apiKey = typeof vals['api-key'] === 'string' ? vals['api-key'] : process.env['FS_API_KEY'];
164
+ if (typeof vals['log-level'] === 'string')
165
+ cli.logLevel = vals['log-level'];
166
+ if (typeof vals['max-file-size'] === 'string')
167
+ cli.maxFileSize = vals['max-file-size'];
168
+ if (typeof vals['root-boundary'] === 'string')
169
+ cli.rootBoundary = vals['root-boundary'];
170
+ if (vals['allow-sensitive'] === true)
171
+ cli.allowSensitive = true;
172
+ if (vals['walk-cwd'] === true)
173
+ cli.allowCwdWalk = true;
174
+ if (vals['allow-missing-roots'] === true)
175
+ cli.allowMissingRoots = true;
176
+ if (Array.isArray(vals['deny'])) {
177
+ const denyPatterns = vals['deny']
178
+ .filter((entry) => typeof entry === 'string')
179
+ .map((entry) => entry.trim())
180
+ .filter(Boolean);
181
+ if (denyPatterns.length > 0)
182
+ cli.denyPatterns = [...new Set(denyPatterns)];
183
+ }
184
+ const allowCwd = vals['allow-cwd'] ||
185
+ vals['walk-cwd'] ||
186
+ parseTrueEnvFlag(process.env['FS_ALLOW_CWD_WALK'], 'FS_ALLOW_CWD_WALK');
187
+ const readOnly = vals['read-only'] || vals['safe'];
188
+ const printConfig = vals['print-config'];
189
+ const json = vals['json'];
190
+ const port = parsePortOption(parsed.values.port ?? process.env['FS_PORT']);
191
+ const allowMissingRoots = vals['allow-missing-roots'] ||
192
+ parseTrueEnvFlag(process.env['FS_ALLOW_MISSING_ROOTS'], 'FS_ALLOW_MISSING_ROOTS');
193
+ let allowedDirs = [];
194
+ try {
195
+ allowedDirs =
196
+ parsed.positionals.length > 0
197
+ ? await normalizeAndValidateDirs(parsed.positionals, allowMissingRoots)
198
+ : [];
199
+ }
200
+ catch (error) {
201
+ throw new CliExitError(normalizeCliExitMessage(error), 1);
202
+ }
203
+ return {
204
+ allowedDirs,
205
+ allowCwd,
206
+ port,
207
+ readOnly,
208
+ printConfig,
209
+ json,
210
+ httpHost,
211
+ apiKey,
212
+ };
190
213
  }
191
214
  catch (error) {
192
- if (error instanceof CommanderError) {
193
- throw new CliExitError(formatCliOutput(output, error.message), error.exitCode);
215
+ if (error instanceof CliExitError) {
216
+ throw error;
194
217
  }
195
- throw error;
218
+ throw new CliExitError(normalizeCliExitMessage(error), 1);
196
219
  }
197
- const options = cli.opts();
198
- const allowCwd = Boolean(options.allowCwd);
199
- const port = parsePortOption(options.port);
200
- const positionals = getParsedAllowedDirs(cli);
201
- let allowedDirs;
202
- try {
203
- allowedDirs =
204
- positionals.length > 0 ? await normalizeCliDirectories(positionals) : [];
220
+ }
221
+ export async function runPrintConfig(options) {
222
+ const write = options.stdout ?? ((s) => process.stdout.write(s));
223
+ const pathGuard = new PathGuard({
224
+ allowCwd: options.allowCwd,
225
+ cliAllowedDirs: options.allowedDirs,
226
+ });
227
+ await pathGuard.recomputeAllowedDirectories();
228
+ const allowedRoots = pathGuard.getAllowedDirectories();
229
+ const tools = registeredTools(options.readOnly).map((t) => t.name);
230
+ // Derived, never assumed: `--print-config --port 3000` reports the HTTP bind
231
+ // that `--port` would actually have started, not the stdio default.
232
+ const config = {
233
+ transport: options.port !== undefined
234
+ ? `http://${options.httpHost ?? '127.0.0.1'}:${String(options.port)}`
235
+ : 'stdio',
236
+ readOnly: options.readOnly,
237
+ allowedRoots,
238
+ tools,
239
+ apiKey: options.apiKey ? '***' : null,
240
+ limits: { maxFileSizeBytes: getMaxTextFileSize() },
241
+ };
242
+ if (options.json) {
243
+ write(JSON.stringify(config, null, 2));
205
244
  }
206
- catch (error) {
207
- throw new CliExitError(normalizeCliExitMessage(error), 1);
245
+ else {
246
+ const key = (k) => padEndVisible(cliFmt.cyan(k), 14);
247
+ write(`${key('transport:')}${config.transport}\n`);
248
+ write(`${key('readOnly:')}${cliFmt.bool(config.readOnly)}\n`);
249
+ write(`${key('apiKey:')}${cliFmt.dim(config.apiKey ?? 'none')}\n`);
250
+ write(`${key('allowedRoots:')}${config.allowedRoots.join(', ') || cliFmt.dim('(none)')}\n`);
251
+ write(`${key('tools:')}${cliFmt.dim(config.tools.join(', '))}\n`);
252
+ write(`${key('maxFileSize:')}${cliFmt.yellow(String(config.limits.maxFileSizeBytes))}\n`);
208
253
  }
209
- const deduplicatedDirs = deduplicateAllowedDirectories(allowedDirs);
210
- return { allowedDirs: deduplicatedDirs, allowCwd, port };
254
+ return config;
211
255
  }
256
+ //# sourceMappingURL=cli.js.map