@j0hanz/filesystem-mcp 2.1.4 → 2.1.6

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 (148) hide show
  1. package/dist/cli.d.ts +2 -14
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +14 -22
  4. package/dist/cli.js.map +1 -1
  5. package/dist/core/cursor.d.ts +2 -14
  6. package/dist/core/cursor.d.ts.map +1 -1
  7. package/dist/core/cursor.js +25 -39
  8. package/dist/core/cursor.js.map +1 -1
  9. package/dist/core/diff.d.ts +6 -0
  10. package/dist/core/diff.d.ts.map +1 -0
  11. package/dist/core/diff.js +21 -0
  12. package/dist/core/diff.js.map +1 -0
  13. package/dist/core/errors.d.ts +2 -31
  14. package/dist/core/errors.d.ts.map +1 -1
  15. package/dist/core/errors.js +15 -86
  16. package/dist/core/errors.js.map +1 -1
  17. package/dist/core/file-uri.d.ts +18 -2
  18. package/dist/core/file-uri.d.ts.map +1 -1
  19. package/dist/core/file-uri.js +34 -6
  20. package/dist/core/file-uri.js.map +1 -1
  21. package/dist/core/fmt.js +5 -5
  22. package/dist/core/fmt.js.map +1 -1
  23. package/dist/core/fs.d.ts +4 -13
  24. package/dist/core/fs.d.ts.map +1 -1
  25. package/dist/core/fs.js +4 -41
  26. package/dist/core/fs.js.map +1 -1
  27. package/dist/core/glob.d.ts +1 -7
  28. package/dist/core/glob.d.ts.map +1 -1
  29. package/dist/core/glob.js +1 -11
  30. package/dist/core/glob.js.map +1 -1
  31. package/dist/core/input-required.d.ts +2 -10
  32. package/dist/core/input-required.d.ts.map +1 -1
  33. package/dist/core/input-required.js +3 -12
  34. package/dist/core/input-required.js.map +1 -1
  35. package/dist/core/path-completer.d.ts.map +1 -1
  36. package/dist/core/path-completer.js +3 -7
  37. package/dist/core/path-completer.js.map +1 -1
  38. package/dist/core/path-discovery.js +2 -2
  39. package/dist/core/path-discovery.js.map +1 -1
  40. package/dist/core/path.d.ts +10 -25
  41. package/dist/core/path.d.ts.map +1 -1
  42. package/dist/core/path.js +28 -66
  43. package/dist/core/path.js.map +1 -1
  44. package/dist/core/primitives.d.ts +8 -3
  45. package/dist/core/primitives.d.ts.map +1 -1
  46. package/dist/core/primitives.js +10 -5
  47. package/dist/core/primitives.js.map +1 -1
  48. package/dist/core/read.d.ts +0 -16
  49. package/dist/core/read.d.ts.map +1 -1
  50. package/dist/core/read.js +19 -56
  51. package/dist/core/read.js.map +1 -1
  52. package/dist/core/schema.d.ts +1 -32
  53. package/dist/core/schema.d.ts.map +1 -1
  54. package/dist/core/schema.js +7 -66
  55. package/dist/core/schema.js.map +1 -1
  56. package/dist/core/search.d.ts +16 -5
  57. package/dist/core/search.d.ts.map +1 -1
  58. package/dist/core/search.js +118 -108
  59. package/dist/core/search.js.map +1 -1
  60. package/dist/core/sensitive.d.ts.map +1 -1
  61. package/dist/core/sensitive.js +7 -15
  62. package/dist/core/sensitive.js.map +1 -1
  63. package/dist/core/store.d.ts +1 -3
  64. package/dist/core/store.d.ts.map +1 -1
  65. package/dist/core/store.js +1 -5
  66. package/dist/core/store.js.map +1 -1
  67. package/dist/core/watcher-registry.d.ts +1 -8
  68. package/dist/core/watcher-registry.d.ts.map +1 -1
  69. package/dist/core/watcher-registry.js +3 -12
  70. package/dist/core/watcher-registry.js.map +1 -1
  71. package/dist/index.js +1 -8
  72. package/dist/index.js.map +1 -1
  73. package/dist/prompts.d.ts.map +1 -1
  74. package/dist/prompts.js +7 -33
  75. package/dist/prompts.js.map +1 -1
  76. package/dist/resources.d.ts +1 -1
  77. package/dist/resources.d.ts.map +1 -1
  78. package/dist/resources.js +3 -9
  79. package/dist/resources.js.map +1 -1
  80. package/dist/server.d.ts +0 -3
  81. package/dist/server.d.ts.map +1 -1
  82. package/dist/server.js +1 -5
  83. package/dist/server.js.map +1 -1
  84. package/dist/tools/batch.d.ts +5 -69
  85. package/dist/tools/batch.d.ts.map +1 -1
  86. package/dist/tools/batch.js +7 -117
  87. package/dist/tools/batch.js.map +1 -1
  88. package/dist/tools/create.d.ts.map +1 -1
  89. package/dist/tools/create.js +8 -18
  90. package/dist/tools/create.js.map +1 -1
  91. package/dist/tools/define.d.ts +1 -4
  92. package/dist/tools/define.d.ts.map +1 -1
  93. package/dist/tools/define.js +6 -24
  94. package/dist/tools/define.js.map +1 -1
  95. package/dist/tools/delete-file.d.ts.map +1 -1
  96. package/dist/tools/delete-file.js +6 -6
  97. package/dist/tools/delete-file.js.map +1 -1
  98. package/dist/tools/diff.d.ts.map +1 -1
  99. package/dist/tools/diff.js +3 -10
  100. package/dist/tools/diff.js.map +1 -1
  101. package/dist/tools/edit.d.ts.map +1 -1
  102. package/dist/tools/edit.js +43 -87
  103. package/dist/tools/edit.js.map +1 -1
  104. package/dist/tools/index.d.ts +0 -1
  105. package/dist/tools/index.d.ts.map +1 -1
  106. package/dist/tools/index.js +0 -1
  107. package/dist/tools/index.js.map +1 -1
  108. package/dist/tools/list.js +3 -2
  109. package/dist/tools/list.js.map +1 -1
  110. package/dist/tools/move.d.ts.map +1 -1
  111. package/dist/tools/move.js +169 -119
  112. package/dist/tools/move.js.map +1 -1
  113. package/dist/tools/patch.d.ts.map +1 -1
  114. package/dist/tools/patch.js +5 -23
  115. package/dist/tools/patch.js.map +1 -1
  116. package/dist/tools/progress.d.ts +0 -1
  117. package/dist/tools/progress.d.ts.map +1 -1
  118. package/dist/tools/progress.js +2 -8
  119. package/dist/tools/progress.js.map +1 -1
  120. package/dist/tools/read.d.ts.map +1 -1
  121. package/dist/tools/read.js +21 -25
  122. package/dist/tools/read.js.map +1 -1
  123. package/dist/tools/replace-in-files.d.ts.map +1 -1
  124. package/dist/tools/replace-in-files.js +25 -44
  125. package/dist/tools/replace-in-files.js.map +1 -1
  126. package/dist/tools/search-content.d.ts.map +1 -1
  127. package/dist/tools/search-content.js +17 -49
  128. package/dist/tools/search-content.js.map +1 -1
  129. package/dist/tools/search-files.d.ts.map +1 -1
  130. package/dist/tools/search-files.js +1 -2
  131. package/dist/tools/search-files.js.map +1 -1
  132. package/dist/tools/stat.d.ts.map +1 -1
  133. package/dist/tools/stat.js +5 -6
  134. package/dist/tools/stat.js.map +1 -1
  135. package/dist/transport/http-policy.d.ts +6 -0
  136. package/dist/transport/http-policy.d.ts.map +1 -1
  137. package/dist/transport/http-policy.js +2 -2
  138. package/dist/transport/http-policy.js.map +1 -1
  139. package/dist/transport/http.d.ts.map +1 -1
  140. package/dist/transport/http.js +4 -11
  141. package/dist/transport/http.js.map +1 -1
  142. package/dist/transport/stdio.js +1 -1
  143. package/dist/transport/stdio.js.map +1 -1
  144. package/dist/transport.d.ts +1 -3
  145. package/dist/transport.d.ts.map +1 -1
  146. package/dist/transport.js +5 -2
  147. package/dist/transport.js.map +1 -1
  148. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"sensitive.js","sourceRoot":"","sources":["../../src/core/sensitive.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,4EAA4E;AAC5E,4EAA4E;AAC5E,wEAAwE;AACxE,2EAA2E;AAC3E,wEAAwE;AACxE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAElD,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAErF,MAAM,UAAU,GAAG,EAAE,CAAC;AACtB,MAAM,kBAAkB,GAAG,EAAE,CAAC;AAE9B,SAAS,iBAAiB,CAAC,KAAa;IACtC,4EAA4E;IAC5E,OAAO,WAAW,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;AACrD,CAAC;AAYD,SAAS,0BAA0B,CAAC,iBAAyB;IAC3D,OAAO,CACL,iBAAiB,CAAC,MAAM,IAAI,CAAC;QAC7B,iBAAiB,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,UAAU;QAC9C,iBAAiB,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,kBAAkB;QACtD,OAAO,CAAC,iBAAiB,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CACzC,CAAC;AACJ,CAAC;AAED,SAAS,mBAAmB,CAAC,iBAAyB;IACpD,MAAM,KAAK,GAAG,IAAI,GAAG,CAAS,CAAC,iBAAiB,CAAC,CAAC,CAAC;IAEnD,MAAM,QAAQ,GACZ,iBAAiB,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,0BAA0B,CAAC,iBAAiB,CAAC,CAAC;IACvF,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,WAAW,GAAG,iBAAiB,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAC1D,IAAI,WAAW,EAAE,CAAC;YAChB,KAAK,CAAC,GAAG,CAAC,MAAM,WAAW,EAAE,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC3B,CAAC;AAED,SAAS,eAAe,CAAC,QAA2B;IAClD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/F,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,UAAU,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;QAC9C,MAAM,WAAW,GAAG,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QAC7C,OAAO;YACL,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,mBAAmB,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;YACnE,WAAW;SACZ,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,YAAY,CAAC,QAAoC;IACxD,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU,CAAC;IACpC,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU,CAAC;IAEpC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;QAC3D,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;YACjC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACnB,CAAC;IACH,CAAC;IAED,OAAO;QACL,SAAS,EAAE,CAAC,GAAG,SAAS,CAAC;QACzB,SAAS,EAAE,CAAC,GAAG,SAAS,CAAC;KAC1B,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,KAAwB,EAAE,SAAiB;IACjE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAErC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,KAAK,CAAC,WAAW,CAAC,SAAS,EAAE,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;IACtD,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,4EAA4E;AAC5E,gFAAgF;AAChF,0EAA0E;AAC1E,6EAA6E;AAC7E,gFAAgF;AAChF,8EAA8E;AAC9E,gFAAgF;AAChF,SAAS,yBAAyB,CAAC,QAAgB;IACjD,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACtC,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE;QACxC,IACE,CAAC,KAAK,CAAC;YACP,OAAO,CAAC,MAAM,KAAK,CAAC;YACpB,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;YAC9B,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,UAAU,EACpC,CAAC;YACD,OAAO,OAAO,CAAC;QACjB,CAAC;QACD,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACtC,MAAM,aAAa,GAAG,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;QAC7E,sEAAsE;QACtE,0EAA0E;QAC1E,yEAAyE;QACzE,OAAO,yBAAyB,CAAC,aAAa,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IACH,OAAO,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC5B,CAAC;AAED,MAAM,yBAAyB,GAAG,CAAC,OAAe,EAAU,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;AAE7F,MAAM,0BAA0B,GAAG;IACjC,MAAM;IACN,QAAQ;IACR,QAAQ;IACR,SAAS;IACT,kBAAkB;IAClB,aAAa;IACb,sBAAsB;IACtB,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,OAAO;IACP,UAAU;IACV,UAAU;CACF,CAAC;AAEX,SAAS,sBAAsB;IAC7B,MAAM,cAAc,GAClB,GAAG,CAAC,cAAc,IAAI,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,EAAE,oBAAoB,CAAC,CAAC;IAClG,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IAC5C,MAAM,WAAW,GAAG,QAAQ;QAC1B,CAAC,CAAC,QAAQ;aACL,KAAK,CAAC,QAAQ,CAAC;aACf,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;aACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;QAChC,CAAC,CAAC,EAAE,CAAC;IACP,MAAM,YAAY,GAAG,GAAG,CAAC,YAAY,IAAI,EAAE,CAAC;IAC5C,8EAA8E;IAC9E,sEAAsE;IACtE,OAAO;QACL,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,0BAA0B,CAAC;QACrD,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,WAAW,EAAE,GAAG,YAAY,CAAC,CAAC;KAC9C,CAAC;AACJ,CAAC;AAED,MAAM,OAAO,gBAAgB;IACV,QAAQ,CAAqB;IAE9C,YAAY,WAA8B,sBAAsB,EAAE;QAChE,IAAI,CAAC,QAAQ,GAAG,YAAY,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC1D,CAAC;IAED,WAAW,CAAC,QAAgB;QAC1B,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACjF,OAAO,KAAK,CAAC;QACf,CAAC;QACD,MAAM,WAAW,GAAG,UAAU,CAAC,CAAC,CAAC,yBAAyB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;QAChF,MAAM,cAAc,GAAG,iBAAiB,CAAC,WAAW,CAAC,CAAC;QACtD,OAAO,CACL,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC;YACvD,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,KAAK,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC,CACxE,CAAC;IACJ,CAAC;CACF","sourcesContent":["// Sensitive-file denylist policy: the compiled pattern set, the path\n// normalization that feeds it, and the NTFS alternate-data-stream stripping\n// Windows needs. Split out of path.ts so the denylist has its own home — it\n// shares only isAlpha / toPosixPath / IS_WINDOWS with the primitives in\n// primitives.ts, not the allowed-directory assembly. path-completer.ts and\n// glob.ts reach it through PathGuard.isSensitive, which delegates here.\nimport { normalize, posix, sep } from 'node:path';\n\nimport { cli } from './config.js';\nimport { IS_WINDOWS, isAlpha, parseTrueEnvFlag, toPosixPath } from './primitives.js';\n\nconst CHAR_COLON = 58;\nconst CHAR_FORWARD_SLASH = 47;\n\nfunction normalizeForMatch(input: string): string {\n // Always lowercase for case-insensitive denylist matching on all platforms.\n return toPosixPath(normalize(input)).toLowerCase();\n}\n\ninterface CompiledPattern {\n globs: readonly string[];\n matchesPath: boolean;\n}\n\ninterface CompiledPatternSet {\n pathGlobs: readonly string[];\n nameGlobs: readonly string[];\n}\n\nfunction isWindowsAbsolutePosixPath(normalizedPattern: string): boolean {\n return (\n normalizedPattern.length >= 3 &&\n normalizedPattern.charCodeAt(1) === CHAR_COLON &&\n normalizedPattern.charCodeAt(2) === CHAR_FORWARD_SLASH &&\n isAlpha(normalizedPattern.charCodeAt(0))\n );\n}\n\nfunction compilePatternGlobs(normalizedPattern: string): readonly string[] {\n const globs = new Set<string>([normalizedPattern]);\n\n const isRooted =\n normalizedPattern.startsWith('**/') || isWindowsAbsolutePosixPath(normalizedPattern);\n if (!isRooted) {\n const withoutRoot = normalizedPattern.replace(/^\\/+/, '');\n if (withoutRoot) {\n globs.add(`**/${withoutRoot}`);\n }\n }\n\n return Array.from(globs);\n}\n\nfunction compilePatterns(patterns: readonly string[]): CompiledPattern[] {\n const deduped = Array.from(new Set(patterns.map((p) => p.trim()).filter((p) => p.length > 0)));\n return deduped.map((pattern) => {\n const normalized = normalizeForMatch(pattern);\n const matchesPath = normalized.includes('/');\n return {\n globs: matchesPath ? compilePatternGlobs(normalized) : [normalized],\n matchesPath,\n };\n });\n}\n\nfunction toPatternSet(patterns: readonly CompiledPattern[]): CompiledPatternSet {\n const pathGlobs = new Set<string>();\n const nameGlobs = new Set<string>();\n\n for (const pattern of patterns) {\n const target = pattern.matchesPath ? pathGlobs : nameGlobs;\n for (const glob of pattern.globs) {\n target.add(glob);\n }\n }\n\n return {\n pathGlobs: [...pathGlobs],\n nameGlobs: [...nameGlobs],\n };\n}\n\nfunction matchesAnyGlob(globs: readonly string[], candidate: string): boolean {\n if (globs.length === 0) return false;\n\n for (const glob of globs) {\n if (posix.matchesGlob(candidate, glob)) return true;\n }\n\n return false;\n}\n\n// Strip NTFS alternate-data-stream suffixes (the \":stream\" after a segment)\n// before denylist matching, so `.env:secret` is caught as `.env`. Preserves the\n// drive-letter colon in the first segment of an absolute path. Also trims\n// trailing dots/spaces: Win32 strips them at the syscall boundary (a request\n// to create `.env ` actually creates `.env`), so without trimming them here the\n// exact-name patterns (`.env`, `.npmrc`) are bypassed on Windows. Mirrors the\n// precedent in getReservedDeviceName (path.ts) which trims for the same reason.\nfunction stripAlternateDataStreams(filePath: string): string {\n const parts = filePath.split(/[\\\\/]/);\n const stripped = parts.map((segment, i) => {\n if (\n i === 0 &&\n segment.length === 2 &&\n isAlpha(segment.charCodeAt(0)) &&\n segment.charCodeAt(1) === CHAR_COLON\n ) {\n return segment;\n }\n const colonIdx = segment.indexOf(':');\n const withoutStream = colonIdx !== -1 ? segment.slice(0, colonIdx) : segment;\n // Win32 strips trailing dots and spaces at the syscall boundary, so a\n // request for \".env \" creates \".env\". Strip them before denylist matching\n // or the exact-name patterns (\".env\", \".npmrc\") are bypassed on Windows.\n return trimTrailingDotsAndSpaces(withoutStream);\n });\n return stripped.join(sep);\n}\n\nconst trimTrailingDotsAndSpaces = (segment: string): string => segment.replace(/[. ]+$/, '');\n\nconst DEFAULT_SENSITIVE_PATTERNS = [\n '.env',\n '.env.*',\n '.npmrc',\n '.pypirc',\n '.aws/credentials',\n '.aws/config',\n '.mcpregistry_*_token',\n '*.pem',\n '*.key',\n '*.p12',\n '*.pfx',\n '*.crt',\n '*.cer',\n '*id_rsa*',\n '*id_dsa*',\n] as const;\n\nfunction buildSensitivePatterns(): readonly string[] {\n const allowSensitive =\n cli.allowSensitive ?? parseTrueEnvFlag(process.env['FS_ALLOW_SENSITIVE'], 'FS_ALLOW_SENSITIVE');\n const envValue = process.env['FS_DENYLIST'];\n const envDenylist = envValue\n ? envValue\n .split(/[,\\n]/u)\n .map((t) => t.trim())\n .filter((t) => t.length > 0)\n : [];\n const flagDenylist = cli.denyPatterns ?? [];\n // FS_ALLOW_SENSITIVE suppresses built-ins only; deny entries (env and --deny)\n // always apply. Set-dedupe so a pattern in both sources matches once.\n return [\n ...(allowSensitive ? [] : DEFAULT_SENSITIVE_PATTERNS),\n ...new Set([...envDenylist, ...flagDenylist]),\n ];\n}\n\nexport class SensitiveMatcher {\n private readonly patterns: CompiledPatternSet;\n\n constructor(patterns: readonly string[] = buildSensitivePatterns()) {\n this.patterns = toPatternSet(compilePatterns(patterns));\n }\n\n isSensitive(filePath: string): boolean {\n if (this.patterns.pathGlobs.length === 0 && this.patterns.nameGlobs.length === 0) {\n return false;\n }\n const pathToCheck = IS_WINDOWS ? stripAlternateDataStreams(filePath) : filePath;\n const normalizedPath = normalizeForMatch(pathToCheck);\n return (\n matchesAnyGlob(this.patterns.pathGlobs, normalizedPath) ||\n matchesAnyGlob(this.patterns.nameGlobs, posix.basename(normalizedPath))\n );\n }\n}\n"]}
1
+ {"version":3,"file":"sensitive.js","sourceRoot":"","sources":["../../src/core/sensitive.ts"],"names":[],"mappings":"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","sourcesContent":["// Sensitive-file denylist policy: the compiled pattern set, the path\n// normalization that feeds it, and the NTFS alternate-data-stream stripping\n// Windows needs. Split out of path.ts so the denylist has its own home — it\n// shares only isAlpha / toPosixPath / IS_WINDOWS with the primitives in\n// primitives.ts, not the allowed-directory assembly. path-completer.ts and\n// glob.ts reach it through PathGuard.isSensitive, which delegates here.\nimport { normalize, posix, sep } from 'node:path';\n\nimport { cli } from './config.js';\nimport { IS_WINDOWS, isAlpha, parseTrueEnvFlag, toPosixPath } from './primitives.js';\n\nconst CHAR_COLON = 58;\nconst CHAR_FORWARD_SLASH = 47;\n\nfunction normalizeForMatch(input: string): string {\n // Always lowercase for case-insensitive denylist matching on all platforms.\n return toPosixPath(normalize(input)).toLowerCase();\n}\n\ninterface CompiledPatternSet {\n pathGlobs: readonly string[];\n nameGlobs: readonly string[];\n}\n\nfunction isWindowsAbsolutePosixPath(normalizedPattern: string): boolean {\n return (\n normalizedPattern.length >= 3 &&\n normalizedPattern.charCodeAt(1) === CHAR_COLON &&\n normalizedPattern.charCodeAt(2) === CHAR_FORWARD_SLASH &&\n isAlpha(normalizedPattern.charCodeAt(0))\n );\n}\n\nfunction compilePatternGlobs(normalizedPattern: string): readonly string[] {\n const globs = new Set<string>([normalizedPattern]);\n\n const isRooted =\n normalizedPattern.startsWith('**/') || isWindowsAbsolutePosixPath(normalizedPattern);\n if (!isRooted) {\n const withoutRoot = normalizedPattern.replace(/^\\/+/, '');\n if (withoutRoot) {\n globs.add(`**/${withoutRoot}`);\n }\n }\n\n return Array.from(globs);\n}\n\nfunction toPatternSet(patterns: readonly string[]): CompiledPatternSet {\n const pathGlobs = new Set<string>();\n const nameGlobs = new Set<string>();\n\n const deduped = Array.from(new Set(patterns.map((p) => p.trim()).filter((p) => p.length > 0)));\n for (const pattern of deduped) {\n const normalized = normalizeForMatch(pattern);\n const matchesPath = normalized.includes('/');\n const target = matchesPath ? pathGlobs : nameGlobs;\n for (const glob of matchesPath ? compilePatternGlobs(normalized) : [normalized]) {\n target.add(glob);\n }\n }\n\n return {\n pathGlobs: [...pathGlobs],\n nameGlobs: [...nameGlobs],\n };\n}\n\nfunction matchesAnyGlob(globs: readonly string[], candidate: string): boolean {\n if (globs.length === 0) return false;\n\n for (const glob of globs) {\n if (posix.matchesGlob(candidate, glob)) return true;\n }\n\n return false;\n}\n\n// Strip NTFS alternate-data-stream suffixes (the \":stream\" after a segment)\n// before denylist matching, so `.env:secret` is caught as `.env`. Preserves the\n// drive-letter colon in the first segment of an absolute path. Also trims\n// trailing dots/spaces: Win32 strips them at the syscall boundary (a request\n// to create `.env ` actually creates `.env`), so without trimming them here the\n// exact-name patterns (`.env`, `.npmrc`) are bypassed on Windows. Mirrors the\n// precedent in getReservedDeviceName (path.ts) which trims for the same reason.\nfunction stripAlternateDataStreams(filePath: string): string {\n const parts = filePath.split(/[\\\\/]/);\n const stripped = parts.map((segment, i) => {\n if (\n i === 0 &&\n segment.length === 2 &&\n isAlpha(segment.charCodeAt(0)) &&\n segment.charCodeAt(1) === CHAR_COLON\n ) {\n return segment;\n }\n const colonIdx = segment.indexOf(':');\n const withoutStream = colonIdx !== -1 ? segment.slice(0, colonIdx) : segment;\n // Win32 strips trailing dots and spaces at the syscall boundary, so a\n // request for \".env \" creates \".env\". Strip them before denylist matching\n // or the exact-name patterns (\".env\", \".npmrc\") are bypassed on Windows.\n return trimTrailingDotsAndSpaces(withoutStream);\n });\n return stripped.join(sep);\n}\n\nconst trimTrailingDotsAndSpaces = (segment: string): string => segment.replace(/[. ]+$/, '');\n\nconst DEFAULT_SENSITIVE_PATTERNS = [\n '.env',\n '.env.*',\n '.npmrc',\n '.pypirc',\n '.aws/credentials',\n '.aws/config',\n '.mcpregistry_*_token',\n '*.pem',\n '*.key',\n '*.p12',\n '*.pfx',\n '*.crt',\n '*.cer',\n '*id_rsa*',\n '*id_dsa*',\n] as const;\n\nfunction buildSensitivePatterns(): readonly string[] {\n const allowSensitive =\n cli.allowSensitive ?? parseTrueEnvFlag(process.env['FS_ALLOW_SENSITIVE'], 'FS_ALLOW_SENSITIVE');\n const envValue = process.env['FS_DENYLIST'];\n const envDenylist = envValue\n ? envValue\n .split(/[,\\n]/u)\n .map((t) => t.trim())\n .filter((t) => t.length > 0)\n : [];\n const flagDenylist = cli.denyPatterns ?? [];\n // FS_ALLOW_SENSITIVE suppresses built-ins only; deny entries (env and --deny)\n // always apply. Set-dedupe so a pattern in both sources matches once.\n return [\n ...(allowSensitive ? [] : DEFAULT_SENSITIVE_PATTERNS),\n ...new Set([...envDenylist, ...flagDenylist]),\n ];\n}\n\nexport class SensitiveMatcher {\n private readonly patterns: CompiledPatternSet;\n\n constructor(patterns: readonly string[] = buildSensitivePatterns()) {\n this.patterns = toPatternSet(patterns);\n }\n\n isSensitive(filePath: string): boolean {\n if (this.patterns.pathGlobs.length === 0 && this.patterns.nameGlobs.length === 0) {\n return false;\n }\n const pathToCheck = IS_WINDOWS ? stripAlternateDataStreams(filePath) : filePath;\n const normalizedPath = normalizeForMatch(pathToCheck);\n return (\n matchesAnyGlob(this.patterns.pathGlobs, normalizedPath) ||\n matchesAnyGlob(this.patterns.nameGlobs, posix.basename(normalizedPath))\n );\n }\n}\n"]}
@@ -4,7 +4,6 @@ interface ResourceEntry {
4
4
  name: string;
5
5
  mimeType: string;
6
6
  size: number;
7
- storedAt: string;
8
7
  expiresAt: string;
9
8
  text: string;
10
9
  }
@@ -19,14 +18,13 @@ export declare class ResourceStore {
19
18
  private evictOldest;
20
19
  private pruneExpiredEntries;
21
20
  private enforceLimits;
22
- private getExisting;
21
+ getEntry(uri: string): ResourceEntry;
23
22
  private checkBeforePut;
24
23
  putText(params: {
25
24
  name: string;
26
25
  mimeType?: string;
27
26
  text: string;
28
27
  }): ResourceEntry;
29
- getEntry(uri: string): ResourceEntry;
30
28
  /**
31
29
  * Returns live URIs currently in the store.
32
30
  * Side effect: prunes expired entries before returning.
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/core/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAQjE,UAAU,aAAa;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd;AAeD,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoC;IAC1D,OAAO,CAAC,WAAW,CAAK;IAExB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA2B;gBAE7C,aAAa,CAAC,EAAE,MAAM,IAAI;IAItC,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,OAAO;IAQf,OAAO,CAAC,WAAW;IAUnB,OAAO,CAAC,WAAW;IAQnB,OAAO,CAAC,mBAAmB;IAa3B,OAAO,CAAC,aAAa;IAerB,OAAO,CAAC,WAAW;IAuBnB,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,aAAa;IAqBjF,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa;IAIpC;;;;;;;OAOG;IACH,IAAI,IAAI,MAAM,EAAE;CAIjB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE;QACL,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,IAAI,EAAE,YAAY,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,aAAa,EACpB,IAAI,EAAE,MAAM,EAIZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,OAAO,EAAE,GAClD,kBAAkB,CAuBpB"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/core/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAQjE,UAAU,aAAa;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd;AAeD,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoC;IAC1D,OAAO,CAAC,WAAW,CAAK;IAExB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA2B;gBAE7C,aAAa,CAAC,EAAE,MAAM,IAAI;IAItC,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,OAAO;IAQf,OAAO,CAAC,WAAW;IAUnB,OAAO,CAAC,WAAW;IAQnB,OAAO,CAAC,mBAAmB;IAa3B,OAAO,CAAC,aAAa;IAerB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa;IAuBpC,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,aAAa;IAoBjF;;;;;;;OAOG;IACH,IAAI,IAAI,MAAM,EAAE;CAIjB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE;QACL,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,IAAI,EAAE,YAAY,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,aAAa,EACpB,IAAI,EAAE,MAAM,EAIZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,OAAO,EAAE,GAClD,kBAAkB,CAuBpB"}
@@ -71,7 +71,7 @@ export class ResourceStore {
71
71
  this.evictOldest();
72
72
  }
73
73
  }
74
- getExisting(uri) {
74
+ getEntry(uri) {
75
75
  const existing = this.byUri.get(uri);
76
76
  if (!existing) {
77
77
  throw new FsError(ErrorCode.NOT_FOUND, `Resource not found: ${uri}. Re-run the tool to regenerate.`);
@@ -100,7 +100,6 @@ export class ResourceStore {
100
100
  name: params.name,
101
101
  mimeType: params.mimeType ?? 'text/plain',
102
102
  size: entryBytes,
103
- storedAt: storedAt.toISOString(),
104
103
  expiresAt: new Date(storedAt.getTime() + ENTRY_TTL_MS).toISOString(),
105
104
  text: params.text,
106
105
  };
@@ -110,9 +109,6 @@ export class ResourceStore {
110
109
  this.emitListChanged(true);
111
110
  return entry;
112
111
  }
113
- getEntry(uri) {
114
- return this.getExisting(uri);
115
- }
116
112
  /**
117
113
  * Returns live URIs currently in the store.
118
114
  * Side effect: prunes expired entries before returning.
@@ -1 +1 @@
1
- {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/core/store.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ContentBlock } from '@modelcontextprotocol/server';\n\nimport { randomUUID } from 'node:crypto';\n\nimport { ErrorCode, FsError } from './errors.js';\nimport { Logger } from './observability.js';\nimport { MIB } from './util.js';\n\ninterface ResourceEntry {\n uri: string;\n name: string;\n mimeType: string;\n size: number;\n storedAt: string;\n expiresAt: string;\n text: string;\n}\n\nconst MAX_ENTRIES = 64;\nconst MAX_TOTAL_BYTES = 25 * MIB;\nconst MAX_ENTRY_BYTES = 10 * MIB;\nconst ENTRY_TTL_MS = 60 * 1000;\n\nfunction isExpired(entry: ResourceEntry, now = Date.now()): boolean {\n const expiresAt = Date.parse(entry.expiresAt);\n if (!Number.isFinite(expiresAt)) {\n return true;\n }\n return expiresAt <= now;\n}\n\nexport class ResourceStore {\n private readonly byUri = new Map<string, ResourceEntry>();\n private _totalBytes = 0;\n\n private readonly onListChanged: (() => void) | undefined;\n\n constructor(onListChanged?: () => void) {\n this.onListChanged = onListChanged;\n }\n\n private emitListChanged(changed: boolean): void {\n if (changed) this.onListChanged?.();\n }\n\n private bumpLru(uri: string, entry: ResourceEntry): void {\n if (!this.byUri.has(uri)) {\n return;\n }\n this.byUri.delete(uri);\n this.byUri.set(uri, entry);\n }\n\n private removeEntry(uri: string): ResourceEntry | undefined {\n const existing = this.byUri.get(uri);\n if (!existing) {\n return undefined;\n }\n this._totalBytes -= existing.size;\n this.byUri.delete(uri);\n return existing;\n }\n\n private evictOldest(): void {\n const first = this.byUri.values().next();\n if (first.done) {\n return;\n }\n this.removeEntry(first.value.uri);\n }\n\n private pruneExpiredEntries(now = Date.now()): boolean {\n const toRemove: string[] = [];\n for (const entry of this.byUri.values()) {\n if (isExpired(entry, now)) {\n toRemove.push(entry.uri);\n }\n }\n for (const uri of toRemove) {\n this.removeEntry(uri);\n }\n return toRemove.length > 0;\n }\n\n private enforceLimits(): void {\n while (this.byUri.size > MAX_ENTRIES) {\n this.evictOldest();\n }\n while (this._totalBytes > MAX_TOTAL_BYTES) {\n if (this.byUri.size === 0) {\n Logger.error(\n `[resource-store] enforceLimits invariant violation: totalBytes=${this._totalBytes} but entryCount=0`,\n );\n break;\n }\n this.evictOldest();\n }\n }\n\n private getExisting(uri: string): ResourceEntry {\n const existing = this.byUri.get(uri);\n\n if (!existing) {\n throw new FsError(\n ErrorCode.NOT_FOUND,\n `Resource not found: ${uri}. Re-run the tool to regenerate.`,\n );\n }\n\n if (isExpired(existing)) {\n this.removeEntry(uri);\n this.emitListChanged(true);\n throw new FsError(\n ErrorCode.NOT_FOUND,\n `Resource expired: ${uri}. Re-run the tool to regenerate.`,\n );\n }\n\n this.bumpLru(uri, existing);\n return existing;\n }\n\n private checkBeforePut(entryBytes: number): void {\n const changed = this.pruneExpiredEntries();\n if (entryBytes > MAX_ENTRY_BYTES) {\n this.emitListChanged(changed);\n throw new FsError(ErrorCode.TOO_LARGE, `Resource too large to cache (${entryBytes} bytes).`);\n }\n }\n\n putText(params: { name: string; mimeType?: string; text: string }): ResourceEntry {\n const entryBytes = Buffer.byteLength(params.text, 'utf8');\n this.checkBeforePut(entryBytes);\n\n const storedAt = new Date();\n const entry: ResourceEntry = {\n uri: `filesystem-mcp://result/${randomUUID()}`,\n name: params.name,\n mimeType: params.mimeType ?? 'text/plain',\n size: entryBytes,\n storedAt: storedAt.toISOString(),\n expiresAt: new Date(storedAt.getTime() + ENTRY_TTL_MS).toISOString(),\n text: params.text,\n };\n this.byUri.set(entry.uri, entry);\n this._totalBytes += entryBytes;\n this.enforceLimits();\n this.emitListChanged(true);\n return entry;\n }\n\n getEntry(uri: string): ResourceEntry {\n return this.getExisting(uri);\n }\n\n /**\n * Returns live URIs currently in the store.\n * Side effect: prunes expired entries before returning.\n *\n * Deliberately silent: this is the read path `resources/list` runs through,\n * and the caller is already receiving the post-prune list. Emitting here\n * would tell the client its list changed while handing it that same list.\n */\n keys(): string[] {\n this.pruneExpiredEntries();\n return Array.from(this.byUri.keys());\n }\n}\n\nexport interface JsonResourceResult {\n entry: {\n uri: string;\n size: number;\n mimeType: string;\n expiresAt: string;\n };\n link: ContentBlock;\n}\n\n/**\n * Owner of the externalize-a-payload rule: a tool whose inline response is\n * truncated publishes the full value to the resource store as pretty-printed\n * JSON and hands back the URI to reach it by, plus the link block that offers\n * it to the user.\n */\nexport function putJsonResource(\n store: ResourceStore,\n name: string,\n // `Record<string, unknown> | readonly unknown[]`, not `unknown` or `object`:\n // JSON.stringify returns undefined for undefined and for functions, and `null`\n // is excluded so non-string payloads cannot enter the store's text entry.\n value: Record<string, unknown> | readonly unknown[],\n): JsonResourceResult {\n const entry = store.putText({\n name,\n mimeType: 'application/json',\n text: JSON.stringify(value, null, 2),\n });\n\n return {\n entry: {\n uri: entry.uri,\n size: entry.size,\n mimeType: entry.mimeType,\n expiresAt: entry.expiresAt,\n },\n link: {\n type: 'resource_link',\n uri: entry.uri,\n name: entry.name,\n mimeType: entry.mimeType,\n size: entry.size,\n annotations: { audience: ['user'] },\n },\n };\n}\n"]}
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/core/store.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ContentBlock } from '@modelcontextprotocol/server';\n\nimport { randomUUID } from 'node:crypto';\n\nimport { ErrorCode, FsError } from './errors.js';\nimport { Logger } from './observability.js';\nimport { MIB } from './util.js';\n\ninterface ResourceEntry {\n uri: string;\n name: string;\n mimeType: string;\n size: number;\n expiresAt: string;\n text: string;\n}\n\nconst MAX_ENTRIES = 64;\nconst MAX_TOTAL_BYTES = 25 * MIB;\nconst MAX_ENTRY_BYTES = 10 * MIB;\nconst ENTRY_TTL_MS = 60 * 1000;\n\nfunction isExpired(entry: ResourceEntry, now = Date.now()): boolean {\n const expiresAt = Date.parse(entry.expiresAt);\n if (!Number.isFinite(expiresAt)) {\n return true;\n }\n return expiresAt <= now;\n}\n\nexport class ResourceStore {\n private readonly byUri = new Map<string, ResourceEntry>();\n private _totalBytes = 0;\n\n private readonly onListChanged: (() => void) | undefined;\n\n constructor(onListChanged?: () => void) {\n this.onListChanged = onListChanged;\n }\n\n private emitListChanged(changed: boolean): void {\n if (changed) this.onListChanged?.();\n }\n\n private bumpLru(uri: string, entry: ResourceEntry): void {\n if (!this.byUri.has(uri)) {\n return;\n }\n this.byUri.delete(uri);\n this.byUri.set(uri, entry);\n }\n\n private removeEntry(uri: string): ResourceEntry | undefined {\n const existing = this.byUri.get(uri);\n if (!existing) {\n return undefined;\n }\n this._totalBytes -= existing.size;\n this.byUri.delete(uri);\n return existing;\n }\n\n private evictOldest(): void {\n const first = this.byUri.values().next();\n if (first.done) {\n return;\n }\n this.removeEntry(first.value.uri);\n }\n\n private pruneExpiredEntries(now = Date.now()): boolean {\n const toRemove: string[] = [];\n for (const entry of this.byUri.values()) {\n if (isExpired(entry, now)) {\n toRemove.push(entry.uri);\n }\n }\n for (const uri of toRemove) {\n this.removeEntry(uri);\n }\n return toRemove.length > 0;\n }\n\n private enforceLimits(): void {\n while (this.byUri.size > MAX_ENTRIES) {\n this.evictOldest();\n }\n while (this._totalBytes > MAX_TOTAL_BYTES) {\n if (this.byUri.size === 0) {\n Logger.error(\n `[resource-store] enforceLimits invariant violation: totalBytes=${this._totalBytes} but entryCount=0`,\n );\n break;\n }\n this.evictOldest();\n }\n }\n\n getEntry(uri: string): ResourceEntry {\n const existing = this.byUri.get(uri);\n\n if (!existing) {\n throw new FsError(\n ErrorCode.NOT_FOUND,\n `Resource not found: ${uri}. Re-run the tool to regenerate.`,\n );\n }\n\n if (isExpired(existing)) {\n this.removeEntry(uri);\n this.emitListChanged(true);\n throw new FsError(\n ErrorCode.NOT_FOUND,\n `Resource expired: ${uri}. Re-run the tool to regenerate.`,\n );\n }\n\n this.bumpLru(uri, existing);\n return existing;\n }\n\n private checkBeforePut(entryBytes: number): void {\n const changed = this.pruneExpiredEntries();\n if (entryBytes > MAX_ENTRY_BYTES) {\n this.emitListChanged(changed);\n throw new FsError(ErrorCode.TOO_LARGE, `Resource too large to cache (${entryBytes} bytes).`);\n }\n }\n\n putText(params: { name: string; mimeType?: string; text: string }): ResourceEntry {\n const entryBytes = Buffer.byteLength(params.text, 'utf8');\n this.checkBeforePut(entryBytes);\n\n const storedAt = new Date();\n const entry: ResourceEntry = {\n uri: `filesystem-mcp://result/${randomUUID()}`,\n name: params.name,\n mimeType: params.mimeType ?? 'text/plain',\n size: entryBytes,\n expiresAt: new Date(storedAt.getTime() + ENTRY_TTL_MS).toISOString(),\n text: params.text,\n };\n this.byUri.set(entry.uri, entry);\n this._totalBytes += entryBytes;\n this.enforceLimits();\n this.emitListChanged(true);\n return entry;\n }\n\n /**\n * Returns live URIs currently in the store.\n * Side effect: prunes expired entries before returning.\n *\n * Deliberately silent: this is the read path `resources/list` runs through,\n * and the caller is already receiving the post-prune list. Emitting here\n * would tell the client its list changed while handing it that same list.\n */\n keys(): string[] {\n this.pruneExpiredEntries();\n return Array.from(this.byUri.keys());\n }\n}\n\nexport interface JsonResourceResult {\n entry: {\n uri: string;\n size: number;\n mimeType: string;\n expiresAt: string;\n };\n link: ContentBlock;\n}\n\n/**\n * Owner of the externalize-a-payload rule: a tool whose inline response is\n * truncated publishes the full value to the resource store as pretty-printed\n * JSON and hands back the URI to reach it by, plus the link block that offers\n * it to the user.\n */\nexport function putJsonResource(\n store: ResourceStore,\n name: string,\n // `Record<string, unknown> | readonly unknown[]`, not `unknown` or `object`:\n // JSON.stringify returns undefined for undefined and for functions, and `null`\n // is excluded so non-string payloads cannot enter the store's text entry.\n value: Record<string, unknown> | readonly unknown[],\n): JsonResourceResult {\n const entry = store.putText({\n name,\n mimeType: 'application/json',\n text: JSON.stringify(value, null, 2),\n });\n\n return {\n entry: {\n uri: entry.uri,\n size: entry.size,\n mimeType: entry.mimeType,\n expiresAt: entry.expiresAt,\n },\n link: {\n type: 'resource_link',\n uri: entry.uri,\n name: entry.name,\n mimeType: entry.mimeType,\n size: entry.size,\n annotations: { audience: ['user'] },\n },\n };\n}\n"]}
@@ -22,18 +22,12 @@ export type WatcherAttachResult = {
22
22
  * plus `acquire`, the one ladder that sequences them correctly. `acquire` awaits
23
23
  * path validation midway and re-checks `isStale` and `hasWatcher` itself
24
24
  * afterwards, so callers do not: they take a lease and release it. The
25
- * individual primitives stay public for the state-machine tests.
25
+ * individual primitives stay closure-private.
26
26
  */
27
27
  export declare function createWatcherRegistry(): {
28
28
  hasWatcher: (uri: string) => boolean;
29
- isAtCap: () => boolean;
30
29
  /** Live watcher count, for pre-checking a batched listen against remaining capacity. */
31
30
  size: () => number;
32
- isStale: (uri: string) => boolean;
33
- startSubscribe: (uri: string) => void;
34
- cancelSubscribe: (uri: string) => void;
35
- addCallback: (uri: string, notify: (uri: string) => void) => void;
36
- retain: (uri: string) => void;
37
31
  release: (uri: string) => void;
38
32
  /**
39
33
  * The one attach ladder both watcher entry points run: `resources/subscribe`
@@ -61,7 +55,6 @@ export declare function createWatcherRegistry(): {
61
55
  acquire(pathGuard: PathGuard, uri: string, notify: (uri: string) => void, { markSubscribe }?: {
62
56
  markSubscribe?: boolean;
63
57
  }): Promise<WatcherAttachResult>;
64
- attach: (uri: string, resolvedPath: string) => boolean;
65
58
  destroy(): void;
66
59
  };
67
60
  export type WatcherRegistry = ReturnType<typeof createWatcherRegistry>;
@@ -1 +1 @@
1
- {"version":3,"file":"watcher-registry.d.ts","sourceRoot":"","sources":["../../src/core/watcher-registry.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAK3C,eAAO,MAAM,YAAY,QAA+C,CAAC;AAMzE;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAC3B;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GACZ;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,eAAe,CAAA;CAAE,GACvE;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,cAAc,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAE1D;;;;;;;GAOG;AACH,wBAAgB,qBAAqB;sBAmGV,MAAM,KAAG,OAAO;mBAErB,OAAO;IAwFzB,wFAAwF;gBAC9E,MAAM;mBAtFI,MAAM,KAAG,OAAO;0BAET,MAAM,KAAG,IAAI;2BAcZ,MAAM,KAAG,IAAI;uBAIjB,MAAM,UAAU,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,KAAG,IAAI;kBAUjD,MAAM,KAAG,IAAI;mBASZ,MAAM,KAAG,IAAI;IA6DjC;;;;;;;;;;;;;;;;;;;;;;OAsBG;uBAEU,SAAS,OACf,MAAM,UACH,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,sBACF;QAAE,aAAa,CAAC,EAAE,OAAO,CAAA;KAAE,GACrD,OAAO,CAAC,mBAAmB,CAAC;kBAhFZ,MAAM,gBAAgB,MAAM,KAAG,OAAO;eA+I9C,IAAI;EAmBlB;AAED,MAAM,MAAM,eAAe,GAAG,UAAU,CAAC,OAAO,qBAAqB,CAAC,CAAC"}
1
+ {"version":3,"file":"watcher-registry.d.ts","sourceRoot":"","sources":["../../src/core/watcher-registry.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAK3C,eAAO,MAAM,YAAY,QAA+C,CAAC;AAMzE;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAC3B;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GACZ;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,eAAe,CAAA;CAAE,GACvE;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,cAAc,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAE1D;;;;;;;GAOG;AACH,wBAAgB,qBAAqB;sBAiGV,MAAM,KAAG,OAAO;IAwFvC,wFAAwF;gBAC9E,MAAM;mBA7CI,MAAM,KAAG,IAAI;IAiDjC;;;;;;;;;;;;;;;;;;;;;;OAsBG;uBAEU,SAAS,OACf,MAAM,UACH,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,sBACF;QAAE,aAAa,CAAC,EAAE,OAAO,CAAA;KAAE,GACrD,OAAO,CAAC,mBAAmB,CAAC;eA6DpB,IAAI;EAmBlB;AAED,MAAM,MAAM,eAAe,GAAG,UAAU,CAAC,OAAO,qBAAqB,CAAC,CAAC"}
@@ -15,7 +15,7 @@ function warnWatcherCap(uri) {
15
15
  * plus `acquire`, the one ladder that sequences them correctly. `acquire` awaits
16
16
  * path validation midway and re-checks `isStale` and `hasWatcher` itself
17
17
  * afterwards, so callers do not: they take a lease and release it. The
18
- * individual primitives stay public for the state-machine tests.
18
+ * individual primitives stay closure-private.
19
19
  */
20
20
  export function createWatcherRegistry() {
21
21
  const watchers = new Map();
@@ -112,10 +112,8 @@ export function createWatcherRegistry() {
112
112
  timer.unref();
113
113
  debounceTimers.set(uri, timer);
114
114
  };
115
- // The seven members below are hoisted out of the returned literal so `acquire`
116
- // can sequence them from inside the closure. Their signatures and the shape of
117
- // the returned object are unchanged — they are still public members, listed
118
- // shorthand in the `return` at the bottom.
115
+ // The primitives below are hoisted out of the returned literal so `acquire`
116
+ // can sequence them from inside the closure. They are closure-private.
119
117
  const hasWatcher = (uri) => watchers.has(uri);
120
118
  const isAtCap = () => watchers.size >= MAX_WATCHERS;
121
119
  /** The registry was destroyed, or this uri was unsubscribed, mid-await. */
@@ -196,14 +194,8 @@ export function createWatcherRegistry() {
196
194
  };
197
195
  return {
198
196
  hasWatcher,
199
- isAtCap,
200
197
  /** Live watcher count, for pre-checking a batched listen against remaining capacity. */
201
198
  size: () => watchers.size,
202
- isStale,
203
- startSubscribe,
204
- cancelSubscribe,
205
- addCallback,
206
- retain,
207
199
  release,
208
200
  /**
209
201
  * The one attach ladder both watcher entry points run: `resources/subscribe`
@@ -287,7 +279,6 @@ export function createWatcherRegistry() {
287
279
  retain(uri);
288
280
  return { ok: true };
289
281
  },
290
- attach,
291
282
  destroy() {
292
283
  destroyed = true;
293
284
  for (const timer of debounceTimers.values()) {
@@ -1 +1 @@
1
- {"version":3,"file":"watcher-registry.js","sourceRoot":"","sources":["../../src/core/watcher-registry.ts"],"names":[],"mappings":"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","sourcesContent":["import type { FSWatcher } from 'node:fs';\nimport { statSync, watch } from 'node:fs';\n\nimport { formatUnknownErrorMessage } from './errors.js';\nimport { extractPath } from './file-uri.js';\nimport { Logger } from './observability.js';\nimport type { PathGuard } from './path.js';\nimport { parseEnvInt } from './util.js';\n\n// Cap concurrent file watchers to avoid exhausting OS-level watch handles\n// (e.g. Linux inotify, default ~8192/user). One subscription == one watcher.\nexport const MAX_WATCHERS = parseEnvInt('FS_MAX_WATCHERS', 256, 1, 4096);\n\nfunction warnWatcherCap(uri: string): void {\n Logger.warn(`Cannot subscribe to ${uri}: MAX_WATCHERS limit (${MAX_WATCHERS}) reached.`);\n}\n\n/**\n * `ok` means a watcher is live for this uri and `notify` is registered. Every\n * other outcome names why, and only `invalid-path` carries what\n * `validateExistingPath` threw — so `error` is unreachable on a branch that has\n * none.\n */\nexport type WatcherAttachResult =\n | { ok: true }\n | { ok: false; reason: 'stale' | 'capped' | 'bad-uri' | 'attach-failed' }\n | { ok: false; reason: 'invalid-path'; error: unknown };\n\n/**\n * Owns the uri → FSWatcher map and the subscription bookkeeping around it:\n * notify callbacks, desired subscribe/unsubscribe state, and the watcher cap —\n * plus `acquire`, the one ladder that sequences them correctly. `acquire` awaits\n * path validation midway and re-checks `isStale` and `hasWatcher` itself\n * afterwards, so callers do not: they take a lease and release it. The\n * individual primitives stay public for the state-machine tests.\n */\nexport function createWatcherRegistry() {\n const watchers = new Map<string, FSWatcher>();\n // A URI may have several notification sinks and several independent leases.\n // Callback identity and lifetime are intentionally separate: the HTTP leg\n // uses one stable bus-publishing callback for every listen stream, while each\n // stream still owns one lease that must be released on close.\n // ponytail: no per-subscription id in the SDK unsubscribe contract, so we\n // ref-count by URI. A departed direct-notification callback can linger until\n // the final lease ends; sends to a closed transport already fail harmlessly.\n // The count outlives the watcher (see `dropWatcher`), so a URI whose watcher\n // errored keeps one map entry per still-unreleased lease until they drain —\n // no watcher slot, just bookkeeping. Per-subscription-id keying retires both\n // this and the unsubscribe-by-URI over-release if that churn is observed.\n const activeCallbacks = new Map<string, Set<(uri: string) => void>>();\n const subscriberCounts = new Map<string, number>();\n const desiredState = new Map<string, 'subscribed' | 'unsubscribed' | 'subscribing'>();\n const debounceTimers = new Map<string, NodeJS.Timeout>();\n let destroyed = false;\n\n // 'unsubscribed' exists only to abort a subscribe that is mid-await\n // (isStale). If no subscribe is in flight, keep no entry at all: a lingering\n // 'unsubscribed' would permanently block the modern attach path (which never\n // calls startSubscribe) and leak one map entry per URI ever watched. This\n // must be idempotent: remove()'s teardown branch calls this directly and\n // then unconditionally calls dropWatcher (which also calls this) for the\n // same uri — the second call must not clobber what the first one set.\n const settleDesiredState = (uri: string): void => {\n const current = desiredState.get(uri);\n if (current === 'subscribing') {\n desiredState.set(uri, 'unsubscribed');\n } else if (current !== 'unsubscribed') {\n desiredState.delete(uri);\n }\n };\n\n // Tears down the watcher itself, NOT the ref-count. The 'error' event calls\n // this with leases still outstanding: clearing the count there would let the\n // next release from one of those leases decrement from zero and drop a\n // watcher a later subscriber re-established for the same URI. `drop` below is\n // the only path that ends leases, and it is the only one that clears them.\n const dropWatcher = (uri: string, watcher: FSWatcher): void => {\n const current = watchers.get(uri);\n if (current !== watcher) return;\n const timer = debounceTimers.get(uri);\n if (timer) {\n clearTimeout(timer);\n debounceTimers.delete(uri);\n }\n watcher.close();\n watchers.delete(uri);\n activeCallbacks.delete(uri);\n settleDesiredState(uri);\n };\n\n /** The last lease ended: drop the watcher and every trace of the URI. */\n const drop = (uri: string): void => {\n const timer = debounceTimers.get(uri);\n if (timer) {\n clearTimeout(timer);\n debounceTimers.delete(uri);\n }\n const watcher = watchers.get(uri);\n if (watcher) dropWatcher(uri, watcher);\n activeCallbacks.delete(uri);\n subscriberCounts.delete(uri);\n settleDesiredState(uri);\n };\n\n const notifyAll = (uri: string): void => {\n const callbacks = activeCallbacks.get(uri);\n if (!callbacks || callbacks.size === 0) return;\n const existing = debounceTimers.get(uri);\n if (existing) {\n clearTimeout(existing);\n }\n const timer = setTimeout(() => {\n debounceTimers.delete(uri);\n const cbs = activeCallbacks.get(uri);\n if (!cbs) return;\n // One debounce timer per URI — fan out to every subscriber inside it, so\n // a burst of changes still fires one notification round per URI, not per\n // subscriber.\n for (const cb of cbs) {\n try {\n cb(uri);\n } catch (err) {\n Logger.warn(`Notify callback error for ${uri}: ${formatUnknownErrorMessage(err)}`);\n }\n }\n }, 50);\n timer.unref();\n debounceTimers.set(uri, timer);\n };\n\n // The seven members below are hoisted out of the returned literal so `acquire`\n // can sequence them from inside the closure. Their signatures and the shape of\n // the returned object are unchanged — they are still public members, listed\n // shorthand in the `return` at the bottom.\n\n const hasWatcher = (uri: string): boolean => watchers.has(uri);\n\n const isAtCap = (): boolean => watchers.size >= MAX_WATCHERS;\n\n /** The registry was destroyed, or this uri was unsubscribed, mid-await. */\n const isStale = (uri: string): boolean => destroyed || desiredState.get(uri) === 'unsubscribed';\n\n const startSubscribe = (uri: string): void => {\n desiredState.set(uri, 'subscribing');\n };\n\n /**\n * A subscribe that declared intent and then failed — bad URI, unvalidatable\n * path, cap, fs.watch refusal — drops the entry entirely. Not\n * `settleDesiredState`: that maps 'subscribing' onto 'unsubscribed' to abort\n * an attach still mid-await, and a *finished* failure leaving 'unsubscribed'\n * behind would make `isStale` true forever, so the modern attach path (which\n * never calls `startSubscribe`) could never watch this uri again. Only\n * clears its own 'subscribing' — a concurrent unsubscribe that already set\n * 'unsubscribed' still wins.\n */\n const cancelSubscribe = (uri: string): void => {\n if (desiredState.get(uri) === 'subscribing') desiredState.delete(uri);\n };\n\n const addCallback = (uri: string, notify: (uri: string) => void): void => {\n let callbacks = activeCallbacks.get(uri);\n if (!callbacks) {\n callbacks = new Set();\n activeCallbacks.set(uri, callbacks);\n }\n callbacks.add(notify);\n desiredState.set(uri, 'subscribed');\n };\n\n const retain = (uri: string): void => {\n subscriberCounts.set(uri, (subscriberCounts.get(uri) ?? 0) + 1);\n };\n\n /**\n * Ref-count by URI: only tear down the shared watcher when the last lease\n * ends. Callback identity is independent from this count. A release with no\n * lease outstanding (a rolled-back attach) drops the watcher outright.\n */\n const release = (uri: string): void => {\n const remaining = (subscriberCounts.get(uri) ?? 0) - 1;\n if (remaining > 0) {\n subscriberCounts.set(uri, remaining);\n return;\n }\n drop(uri);\n };\n\n const attach = (uri: string, resolvedPath: string): boolean => {\n try {\n // Watch directories recursively (children included) and files as-is.\n // `fs.watch` async errors arrive via the 'error' event below, not as a\n // sync throw, so no recursive-fallback try/catch is needed here — the\n // outer catch handles sync throws (inotify exhaustion, path-race).\n // `{ recursive: true }` is honored on macOS, Windows, and — since Node\n // 20.13 — Linux; `engines.node` is >=24, so all three are covered.\n const recursive = statSync(resolvedPath).isDirectory();\n const watcher = watch(resolvedPath, recursive ? { recursive: true } : undefined, () => {\n notifyAll(uri);\n });\n watcher.on('error', (err: Error) => {\n Logger.warn(`Watcher error for ${uri}: ${err.message}`);\n dropWatcher(uri, watcher);\n });\n // Two attaches that both cleared `hasWatcher` before either finished\n // validating land here for the same uri. Keep the one already wired to\n // the callback set and close this one — overwriting the map entry would\n // strand the first watcher's fd with nothing left holding a reference.\n if (watchers.has(uri)) {\n watcher.close();\n return true;\n }\n watchers.set(uri, watcher);\n return true;\n } catch (err) {\n Logger.error(`Failed to create watcher for ${uri}: ${formatUnknownErrorMessage(err)}`);\n return false;\n }\n };\n\n return {\n hasWatcher,\n\n isAtCap,\n\n /** Live watcher count, for pre-checking a batched listen against remaining capacity. */\n size: (): number => watchers.size,\n\n isStale,\n\n startSubscribe,\n\n cancelSubscribe,\n\n addCallback,\n\n retain,\n\n release,\n\n /**\n * The one attach ladder both watcher entry points run: `resources/subscribe`\n * (2025 era) and the `subscriptions/listen` filter (modern era, HTTP and\n * stdio). Never throws — it reports the outcome and lets each caller decide\n * what that is worth: subscribe owes its caller a precise error, and listen\n * (`prepareListenWatchers`) treats the batch as all-or-nothing, releasing\n * every lease it already took and rejecting the request. Idempotent per URI\n * — a second call for an already-watched URI re-registers the notify\n * callback (one watcher per URI).\n *\n * `markSubscribe` is the one branch that differs: only `resources/subscribe`\n * declares desired state (what `isStale` aborts against). The listen path\n * must not, or a rejected attach past that point strands a `'subscribing'`\n * entry nothing settles. Whoever declares it, this function settles it:\n * every failing exit past that point cancels the declaration, so no uri is\n * poisoned for a later attach.\n *\n * Every `ok` return takes one lease; lifetime is the caller's to manage.\n * HTTP releases when the listen response closes. Stdio tracks the listen\n * request ID and releases on cancellation, SDK rejection, or graceful\n * completion. In both legs the ref-count is by URI, so a watcher another\n * stream still holds survives.\n */\n async acquire(\n pathGuard: PathGuard,\n uri: string,\n notify: (uri: string) => void,\n { markSubscribe = false }: { markSubscribe?: boolean } = {},\n ): Promise<WatcherAttachResult> {\n // Every failing exit below routes through here, so the declaration made by\n // `startSubscribe` can never outlive the attach that made it.\n const fail = (result: WatcherAttachResult & { ok: false }): WatcherAttachResult => {\n if (markSubscribe) cancelSubscribe(uri);\n return result;\n };\n\n if (hasWatcher(uri)) {\n // A watcher already tracks this uri; just (re)register the callback so\n // its change events reach the new subscriber. No validation or cap work\n // is needed for an already-live watcher.\n addCallback(uri, notify);\n retain(uri);\n return { ok: true };\n }\n // A cap hit before validation and one found after the await are the same\n // condition, and both are reported the same way.\n if (isAtCap()) {\n warnWatcherCap(uri);\n return { ok: false, reason: 'capped' };\n }\n\n if (markSubscribe) startSubscribe(uri);\n\n const filePath = extractPath(uri);\n if (!filePath) return fail({ ok: false, reason: 'bad-uri' });\n\n let resolved: string;\n try {\n resolved = await pathGuard.validateExistingPath(filePath);\n } catch (error: unknown) {\n return fail({ ok: false, reason: 'invalid-path', error });\n }\n\n // Re-check what the await could have changed. A stale uri is the one\n // failure that must NOT cancel: an unsubscribe landed mid-await and its\n // 'unsubscribed' marker is the thing that aborted this attach.\n if (isStale(uri)) return { ok: false, reason: 'stale' };\n if (hasWatcher(uri)) {\n addCallback(uri, notify);\n retain(uri);\n return { ok: true };\n }\n if (isAtCap()) {\n warnWatcherCap(uri);\n return fail({ ok: false, reason: 'capped' });\n }\n\n addCallback(uri, notify);\n if (!attach(uri, resolved)) {\n // fs.watch threw (inotify exhaustion, or a race deleted the path): roll\n // back so no dangling callback is left believing a watcher exists. No\n // lease was taken yet, so `release` drops the entry outright.\n release(uri);\n return fail({ ok: false, reason: 'attach-failed' });\n }\n retain(uri);\n return { ok: true };\n },\n\n attach,\n\n destroy(): void {\n destroyed = true;\n for (const timer of debounceTimers.values()) {\n clearTimeout(timer);\n }\n debounceTimers.clear();\n for (const watcher of watchers.values()) {\n try {\n watcher.close();\n } catch {\n /* ignore close errors so all watchers are attempted */\n }\n }\n watchers.clear();\n activeCallbacks.clear();\n subscriberCounts.clear();\n desiredState.clear();\n },\n };\n}\n\nexport type WatcherRegistry = ReturnType<typeof createWatcherRegistry>;\n"]}
1
+ {"version":3,"file":"watcher-registry.js","sourceRoot":"","sources":["../../src/core/watcher-registry.ts"],"names":[],"mappings":"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","sourcesContent":["import type { FSWatcher } from 'node:fs';\nimport { statSync, watch } from 'node:fs';\n\nimport { formatUnknownErrorMessage } from './errors.js';\nimport { extractPath } from './file-uri.js';\nimport { Logger } from './observability.js';\nimport type { PathGuard } from './path.js';\nimport { parseEnvInt } from './util.js';\n\n// Cap concurrent file watchers to avoid exhausting OS-level watch handles\n// (e.g. Linux inotify, default ~8192/user). One subscription == one watcher.\nexport const MAX_WATCHERS = parseEnvInt('FS_MAX_WATCHERS', 256, 1, 4096);\n\nfunction warnWatcherCap(uri: string): void {\n Logger.warn(`Cannot subscribe to ${uri}: MAX_WATCHERS limit (${MAX_WATCHERS}) reached.`);\n}\n\n/**\n * `ok` means a watcher is live for this uri and `notify` is registered. Every\n * other outcome names why, and only `invalid-path` carries what\n * `validateExistingPath` threw — so `error` is unreachable on a branch that has\n * none.\n */\nexport type WatcherAttachResult =\n | { ok: true }\n | { ok: false; reason: 'stale' | 'capped' | 'bad-uri' | 'attach-failed' }\n | { ok: false; reason: 'invalid-path'; error: unknown };\n\n/**\n * Owns the uri → FSWatcher map and the subscription bookkeeping around it:\n * notify callbacks, desired subscribe/unsubscribe state, and the watcher cap —\n * plus `acquire`, the one ladder that sequences them correctly. `acquire` awaits\n * path validation midway and re-checks `isStale` and `hasWatcher` itself\n * afterwards, so callers do not: they take a lease and release it. The\n * individual primitives stay closure-private.\n */\nexport function createWatcherRegistry() {\n const watchers = new Map<string, FSWatcher>();\n // A URI may have several notification sinks and several independent leases.\n // Callback identity and lifetime are intentionally separate: the HTTP leg\n // uses one stable bus-publishing callback for every listen stream, while each\n // stream still owns one lease that must be released on close.\n // ponytail: no per-subscription id in the SDK unsubscribe contract, so we\n // ref-count by URI. A departed direct-notification callback can linger until\n // the final lease ends; sends to a closed transport already fail harmlessly.\n // The count outlives the watcher (see `dropWatcher`), so a URI whose watcher\n // errored keeps one map entry per still-unreleased lease until they drain —\n // no watcher slot, just bookkeeping. Per-subscription-id keying retires both\n // this and the unsubscribe-by-URI over-release if that churn is observed.\n const activeCallbacks = new Map<string, Set<(uri: string) => void>>();\n const subscriberCounts = new Map<string, number>();\n const desiredState = new Map<string, 'subscribed' | 'unsubscribed' | 'subscribing'>();\n const debounceTimers = new Map<string, NodeJS.Timeout>();\n let destroyed = false;\n\n // 'unsubscribed' exists only to abort a subscribe that is mid-await\n // (isStale). If no subscribe is in flight, keep no entry at all: a lingering\n // 'unsubscribed' would permanently block the modern attach path (which never\n // calls startSubscribe) and leak one map entry per URI ever watched. This\n // must be idempotent: remove()'s teardown branch calls this directly and\n // then unconditionally calls dropWatcher (which also calls this) for the\n // same uri — the second call must not clobber what the first one set.\n const settleDesiredState = (uri: string): void => {\n const current = desiredState.get(uri);\n if (current === 'subscribing') {\n desiredState.set(uri, 'unsubscribed');\n } else if (current !== 'unsubscribed') {\n desiredState.delete(uri);\n }\n };\n\n // Tears down the watcher itself, NOT the ref-count. The 'error' event calls\n // this with leases still outstanding: clearing the count there would let the\n // next release from one of those leases decrement from zero and drop a\n // watcher a later subscriber re-established for the same URI. `drop` below is\n // the only path that ends leases, and it is the only one that clears them.\n const dropWatcher = (uri: string, watcher: FSWatcher): void => {\n const current = watchers.get(uri);\n if (current !== watcher) return;\n const timer = debounceTimers.get(uri);\n if (timer) {\n clearTimeout(timer);\n debounceTimers.delete(uri);\n }\n watcher.close();\n watchers.delete(uri);\n activeCallbacks.delete(uri);\n settleDesiredState(uri);\n };\n\n /** The last lease ended: drop the watcher and every trace of the URI. */\n const drop = (uri: string): void => {\n const timer = debounceTimers.get(uri);\n if (timer) {\n clearTimeout(timer);\n debounceTimers.delete(uri);\n }\n const watcher = watchers.get(uri);\n if (watcher) dropWatcher(uri, watcher);\n activeCallbacks.delete(uri);\n subscriberCounts.delete(uri);\n settleDesiredState(uri);\n };\n\n const notifyAll = (uri: string): void => {\n const callbacks = activeCallbacks.get(uri);\n if (!callbacks || callbacks.size === 0) return;\n const existing = debounceTimers.get(uri);\n if (existing) {\n clearTimeout(existing);\n }\n const timer = setTimeout(() => {\n debounceTimers.delete(uri);\n const cbs = activeCallbacks.get(uri);\n if (!cbs) return;\n // One debounce timer per URI — fan out to every subscriber inside it, so\n // a burst of changes still fires one notification round per URI, not per\n // subscriber.\n for (const cb of cbs) {\n try {\n cb(uri);\n } catch (err) {\n Logger.warn(`Notify callback error for ${uri}: ${formatUnknownErrorMessage(err)}`);\n }\n }\n }, 50);\n timer.unref();\n debounceTimers.set(uri, timer);\n };\n\n // The primitives below are hoisted out of the returned literal so `acquire`\n // can sequence them from inside the closure. They are closure-private.\n\n const hasWatcher = (uri: string): boolean => watchers.has(uri);\n\n const isAtCap = (): boolean => watchers.size >= MAX_WATCHERS;\n\n /** The registry was destroyed, or this uri was unsubscribed, mid-await. */\n const isStale = (uri: string): boolean => destroyed || desiredState.get(uri) === 'unsubscribed';\n\n const startSubscribe = (uri: string): void => {\n desiredState.set(uri, 'subscribing');\n };\n\n /**\n * A subscribe that declared intent and then failed — bad URI, unvalidatable\n * path, cap, fs.watch refusal — drops the entry entirely. Not\n * `settleDesiredState`: that maps 'subscribing' onto 'unsubscribed' to abort\n * an attach still mid-await, and a *finished* failure leaving 'unsubscribed'\n * behind would make `isStale` true forever, so the modern attach path (which\n * never calls `startSubscribe`) could never watch this uri again. Only\n * clears its own 'subscribing' — a concurrent unsubscribe that already set\n * 'unsubscribed' still wins.\n */\n const cancelSubscribe = (uri: string): void => {\n if (desiredState.get(uri) === 'subscribing') desiredState.delete(uri);\n };\n\n const addCallback = (uri: string, notify: (uri: string) => void): void => {\n let callbacks = activeCallbacks.get(uri);\n if (!callbacks) {\n callbacks = new Set();\n activeCallbacks.set(uri, callbacks);\n }\n callbacks.add(notify);\n desiredState.set(uri, 'subscribed');\n };\n\n const retain = (uri: string): void => {\n subscriberCounts.set(uri, (subscriberCounts.get(uri) ?? 0) + 1);\n };\n\n /**\n * Ref-count by URI: only tear down the shared watcher when the last lease\n * ends. Callback identity is independent from this count. A release with no\n * lease outstanding (a rolled-back attach) drops the watcher outright.\n */\n const release = (uri: string): void => {\n const remaining = (subscriberCounts.get(uri) ?? 0) - 1;\n if (remaining > 0) {\n subscriberCounts.set(uri, remaining);\n return;\n }\n drop(uri);\n };\n\n const attach = (uri: string, resolvedPath: string): boolean => {\n try {\n // Watch directories recursively (children included) and files as-is.\n // `fs.watch` async errors arrive via the 'error' event below, not as a\n // sync throw, so no recursive-fallback try/catch is needed here — the\n // outer catch handles sync throws (inotify exhaustion, path-race).\n // `{ recursive: true }` is honored on macOS, Windows, and — since Node\n // 20.13 — Linux; `engines.node` is >=24, so all three are covered.\n const recursive = statSync(resolvedPath).isDirectory();\n const watcher = watch(resolvedPath, recursive ? { recursive: true } : undefined, () => {\n notifyAll(uri);\n });\n watcher.on('error', (err: Error) => {\n Logger.warn(`Watcher error for ${uri}: ${err.message}`);\n dropWatcher(uri, watcher);\n });\n // Two attaches that both cleared `hasWatcher` before either finished\n // validating land here for the same uri. Keep the one already wired to\n // the callback set and close this one — overwriting the map entry would\n // strand the first watcher's fd with nothing left holding a reference.\n if (watchers.has(uri)) {\n watcher.close();\n return true;\n }\n watchers.set(uri, watcher);\n return true;\n } catch (err) {\n Logger.error(`Failed to create watcher for ${uri}: ${formatUnknownErrorMessage(err)}`);\n return false;\n }\n };\n\n return {\n hasWatcher,\n\n /** Live watcher count, for pre-checking a batched listen against remaining capacity. */\n size: (): number => watchers.size,\n\n release,\n\n /**\n * The one attach ladder both watcher entry points run: `resources/subscribe`\n * (2025 era) and the `subscriptions/listen` filter (modern era, HTTP and\n * stdio). Never throws — it reports the outcome and lets each caller decide\n * what that is worth: subscribe owes its caller a precise error, and listen\n * (`prepareListenWatchers`) treats the batch as all-or-nothing, releasing\n * every lease it already took and rejecting the request. Idempotent per URI\n * — a second call for an already-watched URI re-registers the notify\n * callback (one watcher per URI).\n *\n * `markSubscribe` is the one branch that differs: only `resources/subscribe`\n * declares desired state (what `isStale` aborts against). The listen path\n * must not, or a rejected attach past that point strands a `'subscribing'`\n * entry nothing settles. Whoever declares it, this function settles it:\n * every failing exit past that point cancels the declaration, so no uri is\n * poisoned for a later attach.\n *\n * Every `ok` return takes one lease; lifetime is the caller's to manage.\n * HTTP releases when the listen response closes. Stdio tracks the listen\n * request ID and releases on cancellation, SDK rejection, or graceful\n * completion. In both legs the ref-count is by URI, so a watcher another\n * stream still holds survives.\n */\n async acquire(\n pathGuard: PathGuard,\n uri: string,\n notify: (uri: string) => void,\n { markSubscribe = false }: { markSubscribe?: boolean } = {},\n ): Promise<WatcherAttachResult> {\n // Every failing exit below routes through here, so the declaration made by\n // `startSubscribe` can never outlive the attach that made it.\n const fail = (result: WatcherAttachResult & { ok: false }): WatcherAttachResult => {\n if (markSubscribe) cancelSubscribe(uri);\n return result;\n };\n\n if (hasWatcher(uri)) {\n // A watcher already tracks this uri; just (re)register the callback so\n // its change events reach the new subscriber. No validation or cap work\n // is needed for an already-live watcher.\n addCallback(uri, notify);\n retain(uri);\n return { ok: true };\n }\n // A cap hit before validation and one found after the await are the same\n // condition, and both are reported the same way.\n if (isAtCap()) {\n warnWatcherCap(uri);\n return { ok: false, reason: 'capped' };\n }\n\n if (markSubscribe) startSubscribe(uri);\n\n const filePath = extractPath(uri);\n if (!filePath) return fail({ ok: false, reason: 'bad-uri' });\n\n let resolved: string;\n try {\n resolved = await pathGuard.validateExistingPath(filePath);\n } catch (error: unknown) {\n return fail({ ok: false, reason: 'invalid-path', error });\n }\n\n // Re-check what the await could have changed. A stale uri is the one\n // failure that must NOT cancel: an unsubscribe landed mid-await and its\n // 'unsubscribed' marker is the thing that aborted this attach.\n if (isStale(uri)) return { ok: false, reason: 'stale' };\n if (hasWatcher(uri)) {\n addCallback(uri, notify);\n retain(uri);\n return { ok: true };\n }\n if (isAtCap()) {\n warnWatcherCap(uri);\n return fail({ ok: false, reason: 'capped' });\n }\n\n addCallback(uri, notify);\n if (!attach(uri, resolved)) {\n // fs.watch threw (inotify exhaustion, or a race deleted the path): roll\n // back so no dangling callback is left believing a watcher exists. No\n // lease was taken yet, so `release` drops the entry outright.\n release(uri);\n return fail({ ok: false, reason: 'attach-failed' });\n }\n retain(uri);\n return { ok: true };\n },\n\n destroy(): void {\n destroyed = true;\n for (const timer of debounceTimers.values()) {\n clearTimeout(timer);\n }\n debounceTimers.clear();\n for (const watcher of watchers.values()) {\n try {\n watcher.close();\n } catch {\n /* ignore close errors so all watchers are attempted */\n }\n }\n watchers.clear();\n activeCallbacks.clear();\n subscriberCounts.clear();\n desiredState.clear();\n },\n };\n}\n\nexport type WatcherRegistry = ReturnType<typeof createWatcherRegistry>;\n"]}
package/dist/index.js CHANGED
@@ -29,7 +29,6 @@ async function shutdown(reason, exitCode = 0) {
29
29
  }
30
30
  shutdownStarted = true;
31
31
  process.exitCode = exitCode;
32
- let keepForceExitTimer = true;
33
32
  const timer = setTimeout(() => {
34
33
  logRuntimeFailure('shutdown_timeout', 'process', 'shutdown', `Shutdown timed out (${reason}), forcing exit.`);
35
34
  process.exit(exitCode);
@@ -60,15 +59,9 @@ async function shutdown(reason, exitCode = 0) {
60
59
  logRuntimeFailure('shutdown_mcp_error', 'process', 'shutdown', error);
61
60
  }
62
61
  }
63
- keepForceExitTimer = false;
64
- }
65
- catch (error) {
66
- logRuntimeFailure('shutdown_error', 'process', 'shutdown', error);
67
62
  }
68
63
  finally {
69
- if (!keepForceExitTimer) {
70
- clearTimeout(timer);
71
- }
64
+ clearTimeout(timer);
72
65
  }
73
66
  }
74
67
  async function main() {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n// Type-only imports are erased at runtime — safe to keep static.\nimport type { StdioServerHandle } from '@modelcontextprotocol/server/stdio';\n\nimport type * as http from 'node:http';\nimport process from 'node:process';\n\nimport * as z from 'zod/v4';\n\nimport { CliExitError, parseArgs, runPrintConfig } from './cli.js';\nimport { NO_POSITIONAL_ROOTS_GUIDANCE } from './core/config.js';\nimport { logRuntimeFailure } from './core/observability.js';\nimport { startHttpServer, startServer } from './transport.js';\n\nz.config(z.locales.en());\n\nconst SHUTDOWN_TIMEOUT_MS = 5000;\nlet activeStdioHandle: StdioServerHandle | undefined;\nlet activeHttpServer: http.Server | undefined;\nlet shutdownStarted = false;\n\nfunction isStdinEvent(event: NodeJS.Signals | 'end' | 'close'): boolean {\n return event === 'end' || event === 'close';\n}\n\nfunction registerShutdownTrigger(event: NodeJS.Signals | 'end' | 'close'): void {\n const target = isStdinEvent(event) ? process.stdin : process;\n target.once(event, () => {\n const reason = isStdinEvent(event) ? `stdin ${event}` : event;\n void shutdown(reason, 0);\n });\n}\n\nasync function shutdown(reason: string, exitCode = 0): Promise<void> {\n if (shutdownStarted) {\n if (reason === 'SIGINT' || reason === 'SIGTERM') {\n process.exit(130);\n }\n return;\n }\n shutdownStarted = true;\n\n process.exitCode = exitCode;\n let keepForceExitTimer = true;\n\n const timer = setTimeout(() => {\n logRuntimeFailure(\n 'shutdown_timeout',\n 'process',\n 'shutdown',\n `Shutdown timed out (${reason}), forcing exit.`,\n );\n process.exit(exitCode);\n }, SHUTDOWN_TIMEOUT_MS);\n timer.unref();\n\n try {\n if (activeHttpServer) {\n const server = activeHttpServer;\n try {\n await new Promise<void>((resolve, reject) => {\n server.close((err) => {\n if (err) reject(err);\n else resolve();\n });\n });\n } catch (error: unknown) {\n logRuntimeFailure('shutdown_http_error', 'process', 'shutdown', error);\n }\n }\n if (activeStdioHandle) {\n try {\n await activeStdioHandle.close();\n } catch (error: unknown) {\n logRuntimeFailure('shutdown_mcp_error', 'process', 'shutdown', error);\n }\n }\n keepForceExitTimer = false;\n } catch (error: unknown) {\n logRuntimeFailure('shutdown_error', 'process', 'shutdown', error);\n } finally {\n if (!keepForceExitTimer) {\n clearTimeout(timer);\n }\n }\n}\n\nasync function main(): Promise<void> {\n let allowedDirs: string[];\n let allowCwd: boolean;\n let port: number | undefined;\n let readOnly: boolean;\n let printConfig: boolean;\n let json: boolean;\n let httpHost: string | undefined;\n let cliApiKey: string | undefined;\n\n try {\n const parsed = await parseArgs();\n allowedDirs = parsed.allowedDirs;\n allowCwd = parsed.allowCwd;\n port = parsed.port;\n readOnly = parsed.readOnly;\n printConfig = parsed.printConfig;\n json = parsed.json;\n httpHost = parsed.httpHost;\n cliApiKey = parsed.apiKey;\n } catch (error: unknown) {\n if (error instanceof CliExitError) {\n if (error.message.length > 0) {\n logRuntimeFailure('cli_exit', 'startup', 'parse_args', error.message);\n }\n process.exitCode = error.exitCode;\n return;\n }\n throw error;\n }\n\n if (printConfig) {\n const apiKey = cliApiKey;\n await runPrintConfig({\n allowedDirs,\n allowCwd,\n readOnly,\n json,\n ...(port !== undefined ? { port } : {}),\n ...(httpHost !== undefined ? { httpHost } : {}),\n ...(apiKey !== undefined ? { apiKey } : {}),\n });\n return;\n }\n\n if (allowedDirs.length > 0) {\n console.error('Allowed directories (from CLI):');\n for (const dir of allowedDirs) {\n console.error(`- ${dir}`);\n }\n } else {\n console.error(NO_POSITIONAL_ROOTS_GUIDANCE);\n }\n\n if (readOnly) {\n console.error('Read-only mode: mutating tools disabled.');\n }\n\n const serverOptions = { allowCwd, cliAllowedDirs: allowedDirs, readOnly };\n const runtimeConfig = {\n ...(httpHost !== undefined ? { httpHost } : {}),\n ...(cliApiKey !== undefined ? { apiKey: cliApiKey } : {}),\n };\n\n if (port !== undefined) {\n activeHttpServer = await startHttpServer(port, serverOptions, runtimeConfig);\n } else {\n registerShutdownTrigger('end');\n registerShutdownTrigger('close');\n activeStdioHandle = startServer(serverOptions, runtimeConfig);\n }\n}\n\nregisterShutdownTrigger('SIGTERM');\nregisterShutdownTrigger('SIGINT');\n\nprocess.once('unhandledRejection', (reason: unknown) => {\n logRuntimeFailure('unhandled_rejection', 'process', 'unhandledRejection', reason);\n void shutdown('unhandledRejection', 1);\n});\n\nprocess.once('uncaughtException', (error: Error) => {\n logRuntimeFailure('uncaught_exception', 'process', 'uncaughtException', error);\n void shutdown('uncaughtException', 1);\n});\n\nmain().catch((error: unknown) => {\n logRuntimeFailure('fatal', 'startup', 'main', error);\n void shutdown('fatal', 1);\n});\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n// Type-only imports are erased at runtime — safe to keep static.\nimport type { StdioServerHandle } from '@modelcontextprotocol/server/stdio';\n\nimport type * as http from 'node:http';\nimport process from 'node:process';\n\nimport * as z from 'zod/v4';\n\nimport { CliExitError, parseArgs, runPrintConfig } from './cli.js';\nimport { NO_POSITIONAL_ROOTS_GUIDANCE } from './core/config.js';\nimport { logRuntimeFailure } from './core/observability.js';\nimport { startHttpServer, startServer } from './transport.js';\n\nz.config(z.locales.en());\n\nconst SHUTDOWN_TIMEOUT_MS = 5000;\nlet activeStdioHandle: StdioServerHandle | undefined;\nlet activeHttpServer: http.Server | undefined;\nlet shutdownStarted = false;\n\nfunction isStdinEvent(event: NodeJS.Signals | 'end' | 'close'): boolean {\n return event === 'end' || event === 'close';\n}\n\nfunction registerShutdownTrigger(event: NodeJS.Signals | 'end' | 'close'): void {\n const target = isStdinEvent(event) ? process.stdin : process;\n target.once(event, () => {\n const reason = isStdinEvent(event) ? `stdin ${event}` : event;\n void shutdown(reason, 0);\n });\n}\n\nasync function shutdown(reason: string, exitCode = 0): Promise<void> {\n if (shutdownStarted) {\n if (reason === 'SIGINT' || reason === 'SIGTERM') {\n process.exit(130);\n }\n return;\n }\n shutdownStarted = true;\n\n process.exitCode = exitCode;\n\n const timer = setTimeout(() => {\n logRuntimeFailure(\n 'shutdown_timeout',\n 'process',\n 'shutdown',\n `Shutdown timed out (${reason}), forcing exit.`,\n );\n process.exit(exitCode);\n }, SHUTDOWN_TIMEOUT_MS);\n timer.unref();\n\n try {\n if (activeHttpServer) {\n const server = activeHttpServer;\n try {\n await new Promise<void>((resolve, reject) => {\n server.close((err) => {\n if (err) reject(err);\n else resolve();\n });\n });\n } catch (error: unknown) {\n logRuntimeFailure('shutdown_http_error', 'process', 'shutdown', error);\n }\n }\n if (activeStdioHandle) {\n try {\n await activeStdioHandle.close();\n } catch (error: unknown) {\n logRuntimeFailure('shutdown_mcp_error', 'process', 'shutdown', error);\n }\n }\n } finally {\n clearTimeout(timer);\n }\n}\n\nasync function main(): Promise<void> {\n let allowedDirs: string[];\n let allowCwd: boolean;\n let port: number | undefined;\n let readOnly: boolean;\n let printConfig: boolean;\n let json: boolean;\n let httpHost: string | undefined;\n let cliApiKey: string | undefined;\n\n try {\n const parsed = await parseArgs();\n allowedDirs = parsed.allowedDirs;\n allowCwd = parsed.allowCwd;\n port = parsed.port;\n readOnly = parsed.readOnly;\n printConfig = parsed.printConfig;\n json = parsed.json;\n httpHost = parsed.httpHost;\n cliApiKey = parsed.apiKey;\n } catch (error: unknown) {\n if (error instanceof CliExitError) {\n if (error.message.length > 0) {\n logRuntimeFailure('cli_exit', 'startup', 'parse_args', error.message);\n }\n process.exitCode = error.exitCode;\n return;\n }\n throw error;\n }\n\n if (printConfig) {\n const apiKey = cliApiKey;\n await runPrintConfig({\n allowedDirs,\n allowCwd,\n readOnly,\n json,\n ...(port !== undefined ? { port } : {}),\n ...(httpHost !== undefined ? { httpHost } : {}),\n ...(apiKey !== undefined ? { apiKey } : {}),\n });\n return;\n }\n\n if (allowedDirs.length > 0) {\n console.error('Allowed directories (from CLI):');\n for (const dir of allowedDirs) {\n console.error(`- ${dir}`);\n }\n } else {\n console.error(NO_POSITIONAL_ROOTS_GUIDANCE);\n }\n\n if (readOnly) {\n console.error('Read-only mode: mutating tools disabled.');\n }\n\n const serverOptions = { allowCwd, cliAllowedDirs: allowedDirs, readOnly };\n const runtimeConfig = {\n ...(httpHost !== undefined ? { httpHost } : {}),\n ...(cliApiKey !== undefined ? { apiKey: cliApiKey } : {}),\n };\n\n if (port !== undefined) {\n activeHttpServer = await startHttpServer(port, serverOptions, runtimeConfig);\n } else {\n registerShutdownTrigger('end');\n registerShutdownTrigger('close');\n activeStdioHandle = startServer(serverOptions, runtimeConfig);\n }\n}\n\nregisterShutdownTrigger('SIGTERM');\nregisterShutdownTrigger('SIGINT');\n\nprocess.once('unhandledRejection', (reason: unknown) => {\n logRuntimeFailure('unhandled_rejection', 'process', 'unhandledRejection', reason);\n void shutdown('unhandledRejection', 1);\n});\n\nprocess.once('uncaughtException', (error: Error) => {\n logRuntimeFailure('uncaught_exception', 'process', 'uncaughtException', error);\n void shutdown('uncaughtException', 1);\n});\n\nmain().catch((error: unknown) => {\n logRuntimeFailure('fatal', 'startup', 'main', error);\n void shutdown('fatal', 1);\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,SAAS,EAGV,MAAM,8BAA8B,CAAC;AAiBtC,UAAU,mBAAmB;IAC3B,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B;AAyDD,wBAAgB,eAAe,CAAC,IAAI,EAAE,mBAAmB,GAAG,IAAI,CAoC/D"}
1
+ {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,SAAS,EAGV,MAAM,8BAA8B,CAAC;AAUtC,UAAU,mBAAmB;IAC3B,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B;AA+BD,wBAAgB,eAAe,CAAC,IAAI,EAAE,mBAAmB,GAAG,IAAI,CAmC/D"}
package/dist/prompts.js CHANGED
@@ -1,6 +1,5 @@
1
- import { completable, getDisplayName, ProtocolError } from '@modelcontextprotocol/server';
1
+ import { completable } from '@modelcontextprotocol/server';
2
2
  import * as z from 'zod/v4';
3
- import { formatUnknownErrorMessage, fsErrorCode, hasErrorShape } from './core/errors.js';
4
3
  import { Logger } from './core/observability.js';
5
4
  import { buildSectionsRecord, INSTRUCTIONS_SUMMARY, renderSections } from './instructions.js';
6
5
  // --- Helpers ---
@@ -22,43 +21,18 @@ function userText(text) {
22
21
  };
23
22
  return { role: 'user', content };
24
23
  }
25
- async function wrapHandler(contract, fn) {
26
- const displayName = getDisplayName(contract);
27
- try {
28
- const result = await fn();
29
- Logger.debug(`prompt resolved`, { name: contract.name, displayName });
30
- return result;
31
- }
32
- catch (error) {
33
- if (hasErrorShape(error, 'ProtocolError'))
34
- throw error;
35
- const message = formatUnknownErrorMessage(error);
36
- Logger.error(`Prompt handler failed: ${message}`, {
37
- promptName: contract.name,
38
- error,
39
- });
40
- const protocolError = new ProtocolError(fsErrorCode(error), message);
41
- protocolError.cause = error;
42
- throw protocolError;
43
- }
44
- }
45
24
  // --- Prompt entries ---
46
- const GET_HELP = {
47
- name: 'get-help',
48
- title: 'Get Help',
49
- description: INSTRUCTIONS_SUMMARY,
50
- };
51
25
  export function registerPrompts(deps) {
52
26
  const sections = buildSectionsRecord(deps.readOnly ?? false);
53
27
  const instructions = renderSections(sections);
54
28
  const topics = Object.keys(sections);
55
- deps.server.registerPrompt(GET_HELP.name, {
56
- title: GET_HELP.title,
57
- description: GET_HELP.description,
29
+ deps.server.registerPrompt('get-help', {
30
+ title: 'Get Help',
31
+ description: INSTRUCTIONS_SUMMARY,
58
32
  argsSchema: z.strictObject({
59
33
  topic: topicArg(topics, `Section key to filter instructions (one of: ${topics.join(', ')}); omit to return all instructions.`).optional(),
60
34
  }),
61
- }, ({ topic }) => wrapHandler(GET_HELP, () => {
35
+ }, ({ topic }) => {
62
36
  const lowerTopic = topic?.toLowerCase();
63
37
  const section = lowerTopic && Object.hasOwn(sections, lowerTopic) ? sections[lowerTopic] : undefined;
64
38
  if (topic && !section) {
@@ -69,9 +43,9 @@ export function registerPrompts(deps) {
69
43
  ? `Section '${topic}' not found. Available: ${topics.join(', ')}\n\n${instructions}`
70
44
  : instructions);
71
45
  return {
72
- description: GET_HELP.description,
46
+ description: INSTRUCTIONS_SUMMARY,
73
47
  messages: [userText(text)],
74
48
  };
75
- }));
49
+ });
76
50
  }
77
51
  //# sourceMappingURL=prompts.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"prompts.js","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n GetPromptResult,\n McpServer,\n PromptMessage,\n TextContent,\n} from '@modelcontextprotocol/server';\nimport { completable, getDisplayName, ProtocolError } from '@modelcontextprotocol/server';\n\nimport * as z from 'zod/v4';\n\nimport { formatUnknownErrorMessage, fsErrorCode, hasErrorShape } from './core/errors.js';\nimport { Logger } from './core/observability.js';\nimport { buildSectionsRecord, INSTRUCTIONS_SUMMARY, renderSections } from './instructions.js';\n\n// --- Types ---\n\ninterface PromptContract {\n readonly name: string;\n readonly title: string;\n readonly description: string;\n}\n\ninterface PromptRegistrarDeps {\n readonly server: McpServer;\n readonly readOnly?: boolean;\n}\n\n// --- Helpers ---\n\nfunction topicArg(\n topics: readonly string[],\n description: string,\n): ReturnType<typeof completable<z.ZodString>> {\n return completable(\n // No content validation beyond non-empty: the handler resolves a topic by\n // `Object.hasOwn` against a frozen record, so anything unrecognized already\n // falls through to the not-found reply without reaching an interpreter.\n z.string().min(1, { message: 'Topic required' }).describe(description),\n (value) => {\n const lower = value.toLowerCase();\n return lower ? topics.filter((t) => t.startsWith(lower)) : [...topics];\n },\n );\n}\n\nfunction userText(text: string): PromptMessage {\n const content: TextContent = {\n type: 'text',\n text,\n annotations: { audience: ['assistant'], priority: 1 },\n };\n return { role: 'user', content };\n}\n\nasync function wrapHandler<T>(contract: PromptContract, fn: () => Promise<T> | T): Promise<T> {\n const displayName = getDisplayName(contract);\n\n try {\n const result = await fn();\n Logger.debug(`prompt resolved`, { name: contract.name, displayName });\n return result;\n } catch (error) {\n if (hasErrorShape(error, 'ProtocolError')) throw error;\n const message = formatUnknownErrorMessage(error);\n Logger.error(`Prompt handler failed: ${message}`, {\n promptName: contract.name,\n error,\n });\n const protocolError = new ProtocolError(fsErrorCode(error), message);\n protocolError.cause = error;\n throw protocolError;\n }\n}\n\n// --- Prompt entries ---\n\nconst GET_HELP: PromptContract = {\n name: 'get-help',\n title: 'Get Help',\n description: INSTRUCTIONS_SUMMARY,\n};\n\nexport function registerPrompts(deps: PromptRegistrarDeps): void {\n const sections = buildSectionsRecord(deps.readOnly ?? false);\n const instructions = renderSections(sections);\n const topics = Object.keys(sections);\n\n deps.server.registerPrompt(\n GET_HELP.name,\n {\n title: GET_HELP.title,\n description: GET_HELP.description,\n argsSchema: z.strictObject({\n topic: topicArg(\n topics,\n `Section key to filter instructions (one of: ${topics.join(', ')}); omit to return all instructions.`,\n ).optional(),\n }),\n },\n ({ topic }: { topic?: string | undefined }): GetPromptResult | Promise<GetPromptResult> =>\n wrapHandler(GET_HELP, () => {\n const lowerTopic = topic?.toLowerCase();\n const section =\n lowerTopic && Object.hasOwn(sections, lowerTopic) ? sections[lowerTopic] : undefined;\n if (topic && !section) {\n Logger.debug('get-help: unknown topic requested', { topic });\n }\n const text =\n section ??\n (topic\n ? `Section '${topic}' not found. Available: ${topics.join(', ')}\\n\\n${instructions}`\n : instructions);\n return {\n description: GET_HELP.description,\n messages: [userText(text)],\n };\n }),\n );\n}\n"]}
1
+ {"version":3,"file":"prompts.js","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAE3D,OAAO,KAAK,CAAC,MAAM,QAAQ,CAAC;AAE5B,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAS9F,kBAAkB;AAElB,SAAS,QAAQ,CACf,MAAyB,EACzB,WAAmB;IAEnB,OAAO,WAAW;IAChB,0EAA0E;IAC1E,4EAA4E;IAC5E,wEAAwE;IACxE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,gBAAgB,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,EACtE,CAAC,KAAK,EAAE,EAAE;QACR,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;QAClC,OAAO,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC;IACzE,CAAC,CACF,CAAC;AACJ,CAAC;AAED,SAAS,QAAQ,CAAC,IAAY;IAC5B,MAAM,OAAO,GAAgB;QAC3B,IAAI,EAAE,MAAM;QACZ,IAAI;QACJ,WAAW,EAAE,EAAE,QAAQ,EAAE,CAAC,WAAW,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE;KACtD,CAAC;IACF,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;AACnC,CAAC;AAED,yBAAyB;AAEzB,MAAM,UAAU,eAAe,CAAC,IAAyB;IACvD,MAAM,QAAQ,GAAG,mBAAmB,CAAC,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAC,CAAC;IAC7D,MAAM,YAAY,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC;IAC9C,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAErC,IAAI,CAAC,MAAM,CAAC,cAAc,CACxB,UAAU,EACV;QACE,KAAK,EAAE,UAAU;QACjB,WAAW,EAAE,oBAAoB;QACjC,UAAU,EAAE,CAAC,CAAC,YAAY,CAAC;YACzB,KAAK,EAAE,QAAQ,CACb,MAAM,EACN,+CAA+C,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,qCAAqC,CACtG,CAAC,QAAQ,EAAE;SACb,CAAC;KACH,EACD,CAAC,EAAE,KAAK,EAAkC,EAAmB,EAAE;QAC7D,MAAM,UAAU,GAAG,KAAK,EAAE,WAAW,EAAE,CAAC;QACxC,MAAM,OAAO,GACX,UAAU,IAAI,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACvF,IAAI,KAAK,IAAI,CAAC,OAAO,EAAE,CAAC;YACtB,MAAM,CAAC,KAAK,CAAC,mCAAmC,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QAC/D,CAAC;QACD,MAAM,IAAI,GACR,OAAO;YACP,CAAC,KAAK;gBACJ,CAAC,CAAC,YAAY,KAAK,2BAA2B,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,YAAY,EAAE;gBACpF,CAAC,CAAC,YAAY,CAAC,CAAC;QACpB,OAAO;YACL,WAAW,EAAE,oBAAoB;YACjC,QAAQ,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;SAC3B,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC","sourcesContent":["import type {\n GetPromptResult,\n McpServer,\n PromptMessage,\n TextContent,\n} from '@modelcontextprotocol/server';\nimport { completable } from '@modelcontextprotocol/server';\n\nimport * as z from 'zod/v4';\n\nimport { Logger } from './core/observability.js';\nimport { buildSectionsRecord, INSTRUCTIONS_SUMMARY, renderSections } from './instructions.js';\n\n// --- Types ---\n\ninterface PromptRegistrarDeps {\n readonly server: McpServer;\n readonly readOnly?: boolean;\n}\n\n// --- Helpers ---\n\nfunction topicArg(\n topics: readonly string[],\n description: string,\n): ReturnType<typeof completable<z.ZodString>> {\n return completable(\n // No content validation beyond non-empty: the handler resolves a topic by\n // `Object.hasOwn` against a frozen record, so anything unrecognized already\n // falls through to the not-found reply without reaching an interpreter.\n z.string().min(1, { message: 'Topic required' }).describe(description),\n (value) => {\n const lower = value.toLowerCase();\n return lower ? topics.filter((t) => t.startsWith(lower)) : [...topics];\n },\n );\n}\n\nfunction userText(text: string): PromptMessage {\n const content: TextContent = {\n type: 'text',\n text,\n annotations: { audience: ['assistant'], priority: 1 },\n };\n return { role: 'user', content };\n}\n\n// --- Prompt entries ---\n\nexport function registerPrompts(deps: PromptRegistrarDeps): void {\n const sections = buildSectionsRecord(deps.readOnly ?? false);\n const instructions = renderSections(sections);\n const topics = Object.keys(sections);\n\n deps.server.registerPrompt(\n 'get-help',\n {\n title: 'Get Help',\n description: INSTRUCTIONS_SUMMARY,\n argsSchema: z.strictObject({\n topic: topicArg(\n topics,\n `Section key to filter instructions (one of: ${topics.join(', ')}); omit to return all instructions.`,\n ).optional(),\n }),\n },\n ({ topic }: { topic?: string | undefined }): GetPromptResult => {\n const lowerTopic = topic?.toLowerCase();\n const section =\n lowerTopic && Object.hasOwn(sections, lowerTopic) ? sections[lowerTopic] : undefined;\n if (topic && !section) {\n Logger.debug('get-help: unknown topic requested', { topic });\n }\n const text =\n section ??\n (topic\n ? `Section '${topic}' not found. Available: ${topics.join(', ')}\\n\\n${instructions}`\n : instructions);\n return {\n description: INSTRUCTIONS_SUMMARY,\n messages: [userText(text)],\n };\n },\n );\n}\n"]}
@@ -4,7 +4,7 @@ import type { ResourceStore } from './core/store.js';
4
4
  import { type WatcherRegistry } from './core/watcher-registry.js';
5
5
  export interface ResourceRegistrationOptions {
6
6
  resourceStore: ResourceStore;
7
- pathGuard?: PathGuard;
7
+ pathGuard: PathGuard;
8
8
  /** Mirrors the `--read-only` gate so the instructions match the tools actually registered. */
9
9
  readOnly: boolean;
10
10
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"resources.d.ts","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"resources.d.ts","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":"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"}