@j0hanz/filesystem-mcp 2.5.2 → 2.6.1

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 (147) hide show
  1. package/README.md +26 -19
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +12 -3
  4. package/dist/cli.js.map +1 -1
  5. package/dist/core/concurrency.d.ts +7 -4
  6. package/dist/core/concurrency.d.ts.map +1 -1
  7. package/dist/core/concurrency.js +9 -4
  8. package/dist/core/concurrency.js.map +1 -1
  9. package/dist/core/config.d.ts +1 -1
  10. package/dist/core/config.d.ts.map +1 -1
  11. package/dist/core/config.js.map +1 -1
  12. package/dist/core/cursor.d.ts.map +1 -1
  13. package/dist/core/cursor.js +11 -1
  14. package/dist/core/cursor.js.map +1 -1
  15. package/dist/core/diff.d.ts +15 -2
  16. package/dist/core/diff.d.ts.map +1 -1
  17. package/dist/core/diff.js +38 -7
  18. package/dist/core/diff.js.map +1 -1
  19. package/dist/core/errors.d.ts +4 -5
  20. package/dist/core/errors.d.ts.map +1 -1
  21. package/dist/core/errors.js +14 -14
  22. package/dist/core/errors.js.map +1 -1
  23. package/dist/core/file-uri.d.ts +23 -9
  24. package/dist/core/file-uri.d.ts.map +1 -1
  25. package/dist/core/file-uri.js +29 -14
  26. package/dist/core/file-uri.js.map +1 -1
  27. package/dist/core/fmt.d.ts +3 -1
  28. package/dist/core/fmt.d.ts.map +1 -1
  29. package/dist/core/fmt.js +3 -3
  30. package/dist/core/fmt.js.map +1 -1
  31. package/dist/core/fs.d.ts +2 -8
  32. package/dist/core/fs.d.ts.map +1 -1
  33. package/dist/core/fs.js +32 -20
  34. package/dist/core/fs.js.map +1 -1
  35. package/dist/core/glob.d.ts +1 -1
  36. package/dist/core/glob.d.ts.map +1 -1
  37. package/dist/core/glob.js +77 -80
  38. package/dist/core/glob.js.map +1 -1
  39. package/dist/core/mime.d.ts +1 -0
  40. package/dist/core/mime.d.ts.map +1 -1
  41. package/dist/core/mime.js +1 -1
  42. package/dist/core/mime.js.map +1 -1
  43. package/dist/core/observability.js +1 -1
  44. package/dist/core/observability.js.map +1 -1
  45. package/dist/core/path-completer.d.ts +2 -1
  46. package/dist/core/path-completer.d.ts.map +1 -1
  47. package/dist/core/path-completer.js +14 -13
  48. package/dist/core/path-completer.js.map +1 -1
  49. package/dist/core/path-discovery.d.ts.map +1 -1
  50. package/dist/core/path-discovery.js +29 -21
  51. package/dist/core/path-discovery.js.map +1 -1
  52. package/dist/core/path-utils.d.ts +8 -0
  53. package/dist/core/path-utils.d.ts.map +1 -1
  54. package/dist/core/path-utils.js +24 -17
  55. package/dist/core/path-utils.js.map +1 -1
  56. package/dist/core/path.d.ts +13 -1
  57. package/dist/core/path.d.ts.map +1 -1
  58. package/dist/core/path.js +27 -11
  59. package/dist/core/path.js.map +1 -1
  60. package/dist/core/read.d.ts +1 -0
  61. package/dist/core/read.d.ts.map +1 -1
  62. package/dist/core/read.js +23 -12
  63. package/dist/core/read.js.map +1 -1
  64. package/dist/core/schema.d.ts +10 -0
  65. package/dist/core/schema.d.ts.map +1 -1
  66. package/dist/core/schema.js +20 -6
  67. package/dist/core/schema.js.map +1 -1
  68. package/dist/core/search.d.ts +12 -4
  69. package/dist/core/search.d.ts.map +1 -1
  70. package/dist/core/search.js +42 -26
  71. package/dist/core/search.js.map +1 -1
  72. package/dist/core/sensitive.d.ts +7 -0
  73. package/dist/core/sensitive.d.ts.map +1 -1
  74. package/dist/core/sensitive.js +21 -2
  75. package/dist/core/sensitive.js.map +1 -1
  76. package/dist/core/watcher-registry.d.ts.map +1 -1
  77. package/dist/core/watcher-registry.js +24 -9
  78. package/dist/core/watcher-registry.js.map +1 -1
  79. package/dist/instructions.js +4 -4
  80. package/dist/instructions.js.map +1 -1
  81. package/dist/resources.d.ts.map +1 -1
  82. package/dist/resources.js +21 -3
  83. package/dist/resources.js.map +1 -1
  84. package/dist/server.d.ts +1 -1
  85. package/dist/server.d.ts.map +1 -1
  86. package/dist/server.js +16 -3
  87. package/dist/server.js.map +1 -1
  88. package/dist/tools/batch.d.ts +2 -5
  89. package/dist/tools/batch.d.ts.map +1 -1
  90. package/dist/tools/batch.js +1 -2
  91. package/dist/tools/batch.js.map +1 -1
  92. package/dist/tools/create.d.ts.map +1 -1
  93. package/dist/tools/create.js +32 -19
  94. package/dist/tools/create.js.map +1 -1
  95. package/dist/tools/define.d.ts +1 -1
  96. package/dist/tools/define.d.ts.map +1 -1
  97. package/dist/tools/define.js.map +1 -1
  98. package/dist/tools/delete.d.ts.map +1 -1
  99. package/dist/tools/delete.js +2 -7
  100. package/dist/tools/delete.js.map +1 -1
  101. package/dist/tools/diff.d.ts.map +1 -1
  102. package/dist/tools/diff.js +8 -5
  103. package/dist/tools/diff.js.map +1 -1
  104. package/dist/tools/edit.d.ts.map +1 -1
  105. package/dist/tools/edit.js +89 -46
  106. package/dist/tools/edit.js.map +1 -1
  107. package/dist/tools/find-files.d.ts.map +1 -1
  108. package/dist/tools/find-files.js +1 -2
  109. package/dist/tools/find-files.js.map +1 -1
  110. package/dist/tools/list-roots.d.ts.map +1 -1
  111. package/dist/tools/list-roots.js +1 -3
  112. package/dist/tools/list-roots.js.map +1 -1
  113. package/dist/tools/list.js +4 -4
  114. package/dist/tools/list.js.map +1 -1
  115. package/dist/tools/move.d.ts +2 -0
  116. package/dist/tools/move.d.ts.map +1 -1
  117. package/dist/tools/move.js +10 -4
  118. package/dist/tools/move.js.map +1 -1
  119. package/dist/tools/patch.d.ts.map +1 -1
  120. package/dist/tools/patch.js +11 -3
  121. package/dist/tools/patch.js.map +1 -1
  122. package/dist/tools/read.d.ts.map +1 -1
  123. package/dist/tools/read.js +7 -29
  124. package/dist/tools/read.js.map +1 -1
  125. package/dist/tools/replace-text.d.ts.map +1 -1
  126. package/dist/tools/replace-text.js +20 -17
  127. package/dist/tools/replace-text.js.map +1 -1
  128. package/dist/tools/search-text.d.ts.map +1 -1
  129. package/dist/tools/search-text.js +3 -1
  130. package/dist/tools/search-text.js.map +1 -1
  131. package/dist/tools/stat.d.ts.map +1 -1
  132. package/dist/tools/stat.js +4 -9
  133. package/dist/tools/stat.js.map +1 -1
  134. package/dist/transport/http-policy.d.ts +3 -3
  135. package/dist/transport/http-policy.d.ts.map +1 -1
  136. package/dist/transport/http-policy.js +10 -9
  137. package/dist/transport/http-policy.js.map +1 -1
  138. package/dist/transport/http.d.ts.map +1 -1
  139. package/dist/transport/http.js +38 -14
  140. package/dist/transport/http.js.map +1 -1
  141. package/dist/transport/shared.d.ts +2 -2
  142. package/dist/transport/shared.d.ts.map +1 -1
  143. package/dist/transport/shared.js.map +1 -1
  144. package/dist/transport/stdio.d.ts +1 -1
  145. package/dist/transport/stdio.js +1 -1
  146. package/dist/transport/stdio.js.map +1 -1
  147. package/package.json +1 -1
package/dist/resources.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { ProtocolError, ProtocolErrorCode, ResourceNotFoundError, ResourceTemplate, UriTemplate, } from '@modelcontextprotocol/server';
2
- import { ErrorCode, formatUnknownErrorMessage, fsErrorCode, isFsError } from "./core/errors.js";
2
+ import { ErrorCode, formatUnknownErrorMessage, isFsError } from "./core/errors.js";
3
3
  import { decodeFileUriPath, encodeFileUriPath, extractPath, FILESYSTEM_FILE_URI_TEMPLATE, } from "./core/file-uri.js";
4
4
  import { GuardedFileSystem } from "./core/fs.js";
5
5
  import { Logger } from "./core/observability.js";
@@ -144,6 +144,8 @@ function createFilesystemResource(options) {
144
144
  if (isNotFoundish(err)) {
145
145
  throw new ResourceNotFoundError(uri, `Cannot subscribe to ${uri}: ${err.message}`);
146
146
  }
147
+ if (isFsError(err))
148
+ throw fsErrorToProtocolError(err);
147
149
  Logger.warn(`Unexpected error validating path for watcher ${uri}: ${formatUnknownErrorMessage(err)}`);
148
150
  throw err;
149
151
  }
@@ -247,6 +249,21 @@ export function getResourceContracts(options) {
247
249
  // ═══════════════════════════════════════════════════════════════
248
250
  // registrar
249
251
  // ═══════════════════════════════════════════════════════════════
252
+ /**
253
+ * A caller-traceable FsError as the SDK's InvalidParams error. The Problem's
254
+ * identity fields ride the SDK's error.data channel: `message` stays the
255
+ * wire message, and code/path/suggestion give clients a machine-readable
256
+ * view. Keyed by `code`, so it cannot collide with the
257
+ * ResourceNotFoundError convention (data: { uri } and nothing else).
258
+ */
259
+ function fsErrorToProtocolError(error) {
260
+ const { code, path, suggestion } = error.problem;
261
+ return new ProtocolError(ProtocolErrorCode.InvalidParams, error.message, {
262
+ code,
263
+ ...(path !== undefined ? { path } : {}),
264
+ ...(suggestion !== undefined ? { suggestion } : {}),
265
+ });
266
+ }
250
267
  function wrapRead(contract) {
251
268
  return async (uri, variables, ctx) => {
252
269
  try {
@@ -267,8 +284,9 @@ function wrapRead(contract) {
267
284
  // A remaining FsError (NOT_FILE, TOO_LARGE, ...) traces to the
268
285
  // caller-supplied URI; anything else is a server-side failure and must
269
286
  // not be blamed on the request.
270
- const msg = isFsError(error) ? error.message : formatUnknownErrorMessage(error);
271
- throw new ProtocolError(fsErrorCode(error), msg);
287
+ if (isFsError(error))
288
+ throw fsErrorToProtocolError(error);
289
+ throw new ProtocolError(ProtocolErrorCode.InternalError, formatUnknownErrorMessage(error));
272
290
  }
273
291
  };
274
292
  }
@@ -1 +1 @@
1
- {"version":3,"file":"resources.js","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":"AAaA,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,qBAAqB,EACrB,gBAAgB,EAChB,WAAW,GACZ,MAAM,8BAA8B,CAAC;AAGtC,OAAO,EAAE,SAAS,EAAE,yBAAyB,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAChG,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,WAAW,EACX,4BAA4B,GAC7B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAGxD,OAAO,EACL,qBAAqB,EACrB,YAAY,GAEb,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,mBAAmB,EACnB,oBAAoB,EACpB,gBAAgB,EAChB,cAAc,GACf,MAAM,mBAAmB,CAAC;AAoC3B;;;;;;GAMG;AACH,SAAS,aAAa,CAAC,KAAc;IACnC,OAAO,CACL,SAAS,CAAC,KAAK,CAAC;QAChB,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,SAAS,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,aAAa,CAAC,CAC/E,CAAC;AACJ,CAAC;AAyCD,kEAAkE;AAClE,eAAe;AACf,kEAAkE;AAElE,SAAS,0BAA0B,CAAC,OAAoC;IACtE,MAAM,IAAI,GAAG,cAAc,CAAC,mBAAmB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IACnE,OAAO;QACL,IAAI,EAAE,6BAA6B;QACnC,KAAK,EAAE,qBAAqB;QAC5B,WAAW,EAAE,oBAAoB;QACjC,QAAQ,EAAE,eAAe;QACzB,GAAG,EAAE,gBAAgB;QACrB,WAAW,EAAE,EAAE,QAAQ,EAAE,CAAC,WAAW,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE;QACvD,SAAS,EAAE,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE;QACzE,IAAI,CAAC,GAAG;YACN,OAAO;gBACL,QAAQ,EAAE;oBACR;wBACE,GAAG,EAAE,GAAG,CAAC,IAAI;wBACb,QAAQ,EAAE,eAAe;wBACzB,IAAI;qBACL;iBACF;aACF,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC;AAED,kEAAkE;AAClE,aAAa;AACb,kEAAkE;AAElE,SAAS,wBAAwB,CAAC,OAAoC;IACpE,MAAM,QAAQ,GAAG,OAAO,CAAC,eAAe,IAAI,qBAAqB,EAAE,CAAC;IACpE,4EAA4E;IAC5E,4EAA4E;IAC5E,oCAAoC;IACpC,MAAM,YAAY,GAAG,OAAO,CAAC,eAAe,KAAK,SAAS,CAAC;IAC3D,6EAA6E;IAC7E,kEAAkE;IAClE,0EAA0E;IAC1E,qEAAqE;IACrE,2EAA2E;IAC3E,0EAA0E;IAC1E,iEAAiE;IACjE,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;IACrC,0EAA0E;IAC1E,uEAAuE;IACvE,4EAA4E;IAC5E,4EAA4E;IAC5E,2CAA2C;IAC3C,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAwC,CAAC;IAEjE,OAAO;QACL,IAAI,EAAE,qBAAqB;QAC3B,KAAK,EAAE,gBAAgB;QACvB,WAAW,EAAE,iFAAiF;QAC9F,WAAW,EAAE,4BAA4B;QACzC,WAAW,EAAE,EAAE,QAAQ,EAAE,CAAC,WAAW,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE;QACvD,mEAAmE;QACnE,0EAA0E;QAC1E,uEAAuE;QACvE,2EAA2E;QAC3E,2EAA2E;QAC3E,SAAS,EAAE,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE;QAEvE,0EAA0E;QAC1E,mEAAmE;QACnE,uEAAuE;QACvE,oCAAoC;QAEpC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,EAAE,IAAmB;YAC7C,0EAA0E;YAC1E,yEAAyE;YACzE,yEAAyE;YACzE,uEAAuE;YACvE,uEAAuE;YACvE,yEAAyE;YACzE,yDAAyD;YACzD,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACtC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;gBAC1B,MAAM,IAAI,aAAa,CACrB,iBAAiB,CAAC,aAAa,EAC/B,wDAAwD,CACzD,CAAC;YACJ,CAAC;YACD,MAAM,OAAO,CAAC,SAAS,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC;YACtD,MAAM,EAAE,GAAG,IAAI,iBAAiB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;YACpD,MAAM,UAAU,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YAE7C,OAAO;gBACL,QAAQ,EAAE;oBACR;wBACE,GAAG,EAAE,GAAG,CAAC,IAAI;wBACb,QAAQ,EAAE,UAAU,CAAC,QAAQ,IAAI,0BAA0B;wBAC3D,GAAG,CAAC,UAAU,CAAC,QAAQ;4BACrB,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE;4BACjD,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;qBACpD;iBACF;aACF,CAAC;QACJ,CAAC;QAED,QAAQ,EAAE;YACR,yEAAyE;YACzE,0EAA0E;YAC1E,yEAAyE;YACzE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;gBACpB,MAAM,WAAW,GAAG,MAAM,YAAY,CACpC,OAAO,CAAC,SAAS,EACjB,iBAAiB,CAAC,KAAK,CAAC,IAAI,KAAK,CAClC,CAAC;gBACF,OAAO,WAAW,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;YAC5C,CAAC;SACF;QAED,SAAS,CAAC,GAAG,EAAE,MAAM;YACnB,0EAA0E;YAC1E,wEAAwE;YACxE,IAAI,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,OAAO,SAAS,CAAC;YAC1C,wEAAwE;YACxE,wDAAwD;YACxD,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAClC,IAAI,OAAO;gBAAE,OAAO,OAAO,CAAC;YAE5B,MAAM,OAAO,GAAG,KAAK,IAAkC,EAAE;gBACvD,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE;oBACpE,aAAa,EAAE,IAAI;iBACpB,CAAC,CAAC;gBACH,IAAI,MAAM,CAAC,EAAE,EAAE,CAAC;oBACd,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;oBACpB,OAAO,SAAS,CAAC;gBACnB,CAAC;gBAED,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;oBAChC,MAAM,IAAI,qBAAqB,CAAC,GAAG,EAAE,wCAAwC,CAAC,CAAC;gBACjF,CAAC;gBACD,IAAI,MAAM,CAAC,MAAM,KAAK,cAAc,EAAE,CAAC;oBACrC,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC;oBACzB,IAAI,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC;wBACvB,MAAM,IAAI,qBAAqB,CAAC,GAAG,EAAE,uBAAuB,GAAG,KAAK,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;oBACrF,CAAC;oBACD,MAAM,CAAC,IAAI,CACT,gDAAgD,GAAG,KAAK,yBAAyB,CAAC,GAAG,CAAC,EAAE,CACzF,CAAC;oBACF,MAAM,GAAG,CAAC;gBACZ,CAAC;gBACD,yEAAyE;gBACzE,yDAAyD;gBACzD,IAAI,MAAM,CAAC,MAAM,KAAK,OAAO;oBAAE,OAAO,SAAS,CAAC;gBAChD,qEAAqE;gBACrE,2DAA2D;gBAC3D,OAAO,KAAK,CAAC;YACf,CAAC,CAAC;YAEF,MAAM,OAAO,GAAG,OAAO,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;YAC9D,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;YAC3B,OAAO,OAAO,CAAC;QACjB,CAAC;QAED,WAAW,CAAC,GAAG;YACb,sDAAsD;YACtD,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC;gBAAE,OAAO;YACpC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACxB,CAAC;QAED,OAAO;YACL,wEAAwE;YACxE,yEAAyE;YACzE,KAAK,MAAM,GAAG,IAAI,UAAU;gBAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACpD,UAAU,CAAC,KAAK,EAAE,CAAC;YACnB,IAAI,YAAY;gBAAE,QAAQ,CAAC,OAAO,EAAE,CAAC;QACvC,CAAC;KACF,CAAC;AACJ,CAAC;AAED,kEAAkE;AAClE,SAAS;AACT,kEAAkE;AAElE,SAAS,oBAAoB,CAAC,OAAoC;IAChE,OAAO;QACL,IAAI,EAAE,uBAAuB;QAC7B,KAAK,EAAE,oBAAoB;QAC3B,WAAW,EACT,8FAA8F;QAChG,QAAQ,EAAE,kBAAkB;QAC5B,WAAW,EAAE,8BAA8B;QAC3C,WAAW,EAAE,EAAE,QAAQ,EAAE,CAAC,WAAW,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE;QACvD,SAAS,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE;QACnD,IAAI;YACF,MAAM,KAAK,GAAG,OAAO,CAAC,aAAa,CAAC;YACpC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,uBAAuB;YAClD,MAAM,SAAS,GAAe,EAAE,CAAC;YACjC,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;gBACvB,IAAI,CAAC;oBACH,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;oBAClC,SAAS,CAAC,IAAI,CAAC;wBACb,GAAG,EAAE,KAAK,CAAC,GAAG;wBACd,IAAI,EAAE,KAAK,CAAC,IAAI;wBAChB,QAAQ,EAAE,KAAK,CAAC,QAAQ;wBACxB,IAAI,EAAE,KAAK,CAAC,IAAI;qBACjB,CAAC,CAAC;gBACL,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,4DAA4D;oBAC5D,IAAI,SAAS,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,SAAS;wBAAE,SAAS;oBACjE,MAAM,GAAG,CAAC;gBACZ,CAAC;YACH,CAAC;YACD,uEAAuE;YACvE,SAAS,CAAC,OAAO,EAAE,CAAC;YACpB,OAAO,EAAE,SAAS,EAAE,CAAC;QACvB,CAAC;QACD,IAAI,CAAC,GAAG,EAAE,SAAS;YACjB,MAAM,EAAE,EAAE,EAAE,GAAG,SAAS,CAAC;YACzB,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC9C,MAAM,IAAI,aAAa,CACrB,iBAAiB,CAAC,aAAa,EAC/B,qFAAqF,CACtF,CAAC;YACJ,CAAC;YAED,IAAI,KAAK,CAAC;YACV,IAAI,CAAC;gBACH,KAAK,GAAG,OAAO,CAAC,aAAa,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC;YACzD,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,IAAI,SAAS,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,SAAS,EAAE,CAAC;oBACvD,MAAM,IAAI,qBAAqB,CAC7B,GAAG,CAAC,QAAQ,EAAE,EACd,oEAAoE,CACrE,CAAC;gBACJ,CAAC;gBACD,MAAM,GAAG,CAAC;YACZ,CAAC;YACD,OAAO;gBACL,QAAQ,EAAE,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC;aAC3E,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC;AAED,kEAAkE;AAClE,mBAAmB;AACnB,kEAAkE;AAElE,MAAM,UAAU,oBAAoB,CAAC,OAAoC;IACvE,OAAO;QACL,0BAA0B,CAAC,OAAO,CAAC;QACnC,oBAAoB,CAAC,OAAO,CAAC;QAC7B,wBAAwB,CAAC,OAAO,CAAC;KAClC,CAAC;AACJ,CAAC;AAED,kEAAkE;AAClE,YAAY;AACZ,kEAAkE;AAElE,SAAS,QAAQ,CAAC,QAA0B;IAC1C,OAAO,KAAK,EAAE,GAAQ,EAAE,SAA4C,EAAE,GAAkB,EAAE,EAAE;QAC1F,IAAI,CAAC;YACH,OAAO,MAAM,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC;QAClD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,2EAA2E;YAC3E,yDAAyD;YACzD,IAAI,KAAK,YAAY,aAAa;gBAAE,MAAM,KAAK,CAAC;YAChD,yEAAyE;YACzE,gEAAgE;YAChE,uEAAuE;YACvE,iDAAiD;YACjD,IAAI,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;gBACzB,MAAM,IAAI,qBAAqB,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;YACjE,CAAC;YACD,+DAA+D;YAC/D,uEAAuE;YACvE,gCAAgC;YAChC,MAAM,GAAG,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,yBAAyB,CAAC,KAAK,CAAC,CAAC;YAChF,MAAM,IAAI,aAAa,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC;QACnD,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,IAAI,CAAC,QAA0B,EAAE,GAAW;IACnD,IAAI,QAAQ,CAAC,GAAG,KAAK,SAAS;QAAE,OAAO,GAAG,KAAK,QAAQ,CAAC,GAAG,CAAC;IAC5D,OAAO,CACL,QAAQ,CAAC,WAAW,KAAK,SAAS,IAAI,IAAI,WAAW,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,IAAI,CAChG,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,IAA2B;IAC3D,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IAC3B,MAAM,OAAO,GAAG,EAAE,GAAG,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,KAAK,EAAE,CAAC;IAC9D,MAAM,IAAI,GAAG,wBAAwB,CAAC,OAAO,CAAC,CAAC;IAC/C,MAAM,iBAAiB,GAAG;QACxB,0BAA0B,CAAC,OAAO,CAAC;QACnC,oBAAoB,CAAC,OAAO,CAAC;QAC7B,IAAI;KACL,CAAC;IAEF,KAAK,MAAM,QAAQ,IAAI,iBAAiB,EAAE,CAAC;QACzC,MAAM,MAAM,GAAG;YACb,GAAG,CAAC,QAAQ,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAClE,GAAG,CAAC,QAAQ,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACpF,GAAG,CAAC,QAAQ,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC3E,GAAG,CAAC,QAAQ,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACpF,GAAG,CAAC,QAAQ,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC/E,CAAC;QAEF,IAAI,QAAQ,CAAC,WAAW,EAAE,CAAC;YACzB,MAAM,QAAQ,GAAG,IAAI,gBAAgB,CAAC,QAAQ,CAAC,WAAW,EAAE;gBAC1D,IAAI,EAAE,QAAQ,CAAC,IAAI;gBACnB,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC9D,CAAC,CAAC;YAEH,MAAM,CAAC,gBAAgB,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC/E,CAAC;aAAM,IAAI,QAAQ,CAAC,GAAG,EAAE,CAAC;YACxB,MAAM,CAAC,gBAAgB,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC,GAAQ,EAAE,GAAkB,EAAE,EAAE,CAC5F,QAAQ,CAAC,QAAQ,CAAC,CAAC,GAAG,EAAE,EAAE,EAAE,GAAG,CAAC,CACjC,CAAC;QACJ,CAAC;IACH,CAAC;IAED,wEAAwE;IACxE,0EAA0E;IAC1E,0EAA0E;IAC1E,6EAA6E;IAC7E,4EAA4E;IAC5E,wEAAwE;IACxE,uEAAuE;IACvE,qEAAqE;IACrE,4EAA4E;IAC5E,qFAAqF;IACrF,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,KAAK,QAAQ,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS,CAAC;IACxE,IAAI,IAAI,CAAC,GAAG,KAAK,QAAQ,IAAI,CAAC,UAAU,EAAE,CAAC;QACzC,MAAM,CAAC,MAAM,CAAC,0BAA0B,CAAC,qBAAqB,CAAC,CAAC;QAChE,MAAM,CAAC,MAAM,CAAC,0BAA0B,CAAC,uBAAuB,CAAC,CAAC;QAElE,0EAA0E;QAC1E,0EAA0E;QAC1E,0EAA0E;QAC1E,4EAA4E;QAC5E,0EAA0E;QAC1E,gEAAgE;QAChE,MAAM,aAAa,GAAG,CAAC,UAAkB,EAAQ,EAAE;YACjD,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAClB,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC;gBAC1C,OAAO;YACT,CAAC;YACD,MAAM,aAAa,GAAsC,EAAE,GAAG,EAAE,UAAU,EAAE,CAAC;YAC7E,sEAAsE;YACtE,KAAK,MAAM,CAAC,MAAM,CAAC,mBAAmB,CAAC,aAAa,CAAC,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;gBAC3E,MAAM,CAAC,KAAK,CAAC,+BAA+B,EAAE;oBAC5C,GAAG,EAAE,UAAU;oBACf,KAAK,EAAE,yBAAyB,CAAC,GAAG,CAAC;iBACtC,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;QACL,CAAC,CAAC;QAEF,MAAM,CAAC,MAAM,CAAC,iBAAiB,CAC7B,qBAAqB,EACrB,KAAK,EAAE,GAAuC,EAAE,EAAE;YAChD,MAAM,EAAE,GAAG,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC;YAC3B,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC;gBACrB,oEAAoE;gBACpE,gEAAgE;gBAChE,uDAAuD;gBACvD,IAAI,iBAAiB,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC;oBAC9D,MAAM,IAAI,aAAa,CACrB,iBAAiB,CAAC,aAAa,EAC/B,YAAY,GAAG,yCAAyC,4BAA4B,gDAAgD,CACrI,CAAC;gBACJ,CAAC;gBACD,MAAM,IAAI,qBAAqB,CAAC,GAAG,EAAE,uBAAuB,GAAG,EAAE,CAAC,CAAC;YACrE,CAAC;YACD,IAAI,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC,KAAK,KAAK,EAAE,CAAC;gBACzD,mEAAmE;gBACnE,8DAA8D;gBAC9D,oBAAoB;gBACpB,MAAM,IAAI,aAAa,CACrB,iBAAiB,CAAC,aAAa,EAC/B,6DAA6D,YAAY,yCAAyC,CACnH,CAAC;YACJ,CAAC;YACD,OAAO,EAAE,CAAC;QACZ,CAAC,CACF,CAAC;QAEF,MAAM,CAAC,MAAM,CAAC,iBAAiB,CAC7B,uBAAuB,EACvB,CAAC,GAAyC,EAAE,EAAE;YAC5C,IAAI,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC;gBAAE,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjE,OAAO,EAAE,CAAC;QACZ,CAAC,CACF,CAAC;IACJ,CAAC;IAED,OAAO;QACL,OAAO;YACL,KAAK,MAAM,QAAQ,IAAI,iBAAiB,EAAE,CAAC;gBACzC,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;oBACrB,QAAQ,CAAC,OAAO,EAAE,CAAC;gBACrB,CAAC;YACH,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import type {\n CacheHint,\n CompleteResourceTemplateCallback,\n McpServer,\n ReadResourceResult,\n Resource,\n ResourceUpdatedNotificationParams,\n Role,\n ServerContext,\n ServerNotifier,\n SubscribeRequestParams,\n UnsubscribeRequestParams,\n} from '@modelcontextprotocol/server';\nimport {\n ProtocolError,\n ProtocolErrorCode,\n ResourceNotFoundError,\n ResourceTemplate,\n UriTemplate,\n} from '@modelcontextprotocol/server';\n\nimport type { FsError } from './core/errors.ts';\nimport { ErrorCode, formatUnknownErrorMessage, fsErrorCode, isFsError } from './core/errors.ts';\nimport {\n decodeFileUriPath,\n encodeFileUriPath,\n extractPath,\n FILESYSTEM_FILE_URI_TEMPLATE,\n} from './core/file-uri.ts';\nimport { GuardedFileSystem } from './core/fs.ts';\nimport { Logger } from './core/observability.ts';\nimport { suggestPaths } from './core/path-completer.ts';\nimport type { PathGuard } from './core/path.ts';\nimport type { ResourceStore } from './core/store.ts';\nimport {\n createWatcherRegistry,\n MAX_WATCHERS,\n type WatcherRegistry,\n} from './core/watcher-registry.ts';\nimport {\n buildSectionsRecord,\n INSTRUCTIONS_SUMMARY,\n INSTRUCTIONS_URI,\n renderSections,\n} from './instructions.ts';\n\n// ═══════════════════════════════════════════════════════════════\n// shared\n// ═══════════════════════════════════════════════════════════════\n\nexport interface ResourceRegistrationOptions {\n resourceStore: ResourceStore;\n pathGuard: PathGuard;\n /** Mirrors the `--read-only` gate so the instructions match the tools actually registered. */\n readOnly: boolean;\n /**\n * Shared file-watcher registry for the modern (per-request) HTTP leg. When set,\n * the resource uses this handler-scoped registry instead of creating its own\n * per-server one, so file-change watchers persist across requests and publish\n * to the shared ServerEventBus. Omitted (per-server registry) on legacy/stdio.\n */\n watcherRegistry?: WatcherRegistry;\n /** Modern-leg typed notification publisher. */\n notifier?: ServerNotifier;\n /** The protocol era this instance serves; omitted where the caller does not know. */\n era?: 'legacy' | 'modern';\n /**\n * Scope for shared-content cache hints. 'private' when the endpoint is\n * API-key gated, mirroring the list hints in server.ts — a shared cache must\n * not store an authenticated response body. Defaults to 'public'.\n */\n cacheScope?: 'public' | 'private';\n}\n\ntype ResourceRegistrarDeps = Omit<ResourceRegistrationOptions, 'pathGuard' | 'readOnly'> & {\n readonly server: McpServer;\n readonly pathGuard: PathGuard;\n readonly readOnly?: boolean;\n};\n\n/**\n * A filesystem failure that reads as not-found on the resource wire, for both\n * `resources/read` and `resources/subscribe`. ACCESS_DENIED folds in\n * deliberately: masking whether an out-of-root or denylisted path exists is the\n * security-correct answer, and the `FsError` message still carries the real\n * cause to the client.\n */\nfunction isNotFoundish(error: unknown): error is FsError {\n return (\n isFsError(error) &&\n (error.code === ErrorCode.NOT_FOUND || error.code === ErrorCode.ACCESS_DENIED)\n );\n}\n\n// ═══════════════════════════════════════════════════════════════\n// contract\n// ═══════════════════════════════════════════════════════════════\n\ninterface ResourceContract {\n name: string;\n title?: string;\n description?: string;\n mimeType?: string;\n cacheHint?: CacheHint;\n annotations?: {\n audience?: Role[];\n priority?: number;\n };\n /** A fixed, enumerable URI (e.g. internal://instructions)... */\n uri?: string;\n /** ...or a URI template, optionally with a completer for its variable. */\n uriTemplate?: string;\n complete?: { path: CompleteResourceTemplateCallback };\n readonly read: (\n uri: URL,\n variables: Record<string, string | string[]>,\n ctx: ServerContext,\n ) => Promise<ReadResourceResult> | ReadResourceResult;\n readonly subscribe?: (\n uri: string,\n notify: (uri: string) => void,\n ) => Promise<boolean | undefined> | boolean | undefined;\n readonly unsubscribe?: (uri: string) => void;\n readonly destroy?: () => void;\n readonly list?: (\n ctx: ServerContext,\n ) => Promise<{ resources: Resource[] }> | { resources: Resource[] };\n}\n\n/** The one contract with a live watcher behind it. */\ntype SubscribableContract = ResourceContract &\n Required<Pick<ResourceContract, 'subscribe' | 'unsubscribe'>>;\n\n// ═══════════════════════════════════════════════════════════════\n// instructions\n// ═══════════════════════════════════════════════════════════════\n\nfunction createInstructionsResource(options: ResourceRegistrationOptions): ResourceContract {\n const text = renderSections(buildSectionsRecord(options.readOnly));\n return {\n name: 'filesystem-mcp-instructions',\n title: 'Server Instructions',\n description: INSTRUCTIONS_SUMMARY,\n mimeType: 'text/markdown',\n uri: INSTRUCTIONS_URI,\n annotations: { audience: ['assistant'], priority: 0.8 },\n cacheHint: { cacheScope: options.cacheScope ?? 'public', ttlMs: 300_000 },\n read(uri) {\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: 'text/markdown',\n text,\n },\n ],\n };\n },\n };\n}\n\n// ═══════════════════════════════════════════════════════════════\n// filesystem\n// ═══════════════════════════════════════════════════════════════\n\nfunction createFilesystemResource(options: ResourceRegistrationOptions): SubscribableContract {\n const registry = options.watcherRegistry ?? createWatcherRegistry();\n // Only the per-server (legacy/stdio) registry is owned by this resource and\n // destroyed on dispose; the shared modern-leg registry is owned by the host\n // and torn down at server shutdown.\n const ownsRegistry = options.watcherRegistry === undefined;\n // The URIs this connection holds a `resources/subscribe` lease for. The wire\n // verb is per-URI and idempotent — a second subscribe is the same\n // subscription, and one unsubscribe ends it — but the registry ref-counts\n // leases (the HTTP listen leg needs that). Without this set a double\n // subscribe took two leases that one unsubscribe could not release, and an\n // unsubscribe for a URI never subscribed released a lease this connection\n // never took, dropping a watcher some other holder still wanted.\n const leasedUris = new Set<string>();\n // Subscribes mid-acquire, keyed by URI. Two concurrent subscribes for the\n // same URI must share one acquire: both passing the `leasedUris` check\n // before either finished took two registry leases that the single set entry\n // could release only once — on the shared modern registry that orphan lease\n // pinned a watcher until process shutdown.\n const inflight = new Map<string, Promise<boolean | undefined>>();\n\n return {\n name: 'filesystem-mcp-file',\n title: 'Workspace File',\n description: 'Read a file from the workspace. Subscribe to get updates when the file changes.',\n uriTemplate: FILESYSTEM_FILE_URI_TEMPLATE,\n annotations: { audience: ['assistant'], priority: 0.8 },\n // Without this the SDK falls back to its conservative `{ ttlMs: 0,\n // cacheScope: 'private' }`, making every file read uncacheable — and this\n // is the one resource with a live invalidation channel, so TTL and the\n // watcher compose exactly as the spec's caching section describes. The TTL\n // stays short because a non-subscriber gets no invalidation signal at all.\n cacheHint: { cacheScope: options.cacheScope ?? 'public', ttlMs: 5_000 },\n\n // No `list`: the template registers with `list: undefined` — readable but\n // not enumerable. Listing each allowed root as a concrete resource\n // duplicated the template and grew resources/list linearly with roots;\n // `list_roots` owns root discovery.\n\n async read(uri, _variables, _ctx: ServerContext) {\n // Decode via extractPath — the same decoder resources/subscribe uses — so\n // both consumers are symmetric with buildFileResourceUri's encoding. The\n // {+path} template variable arrives still percent-encoded, so validating\n // it directly would treat \"c%3A/proj/a.ts\" as a literal relative path;\n // no fallback to it here, or the two branches would validate different\n // strings. extractPath only fails past the template match on undecodable\n // percent-encoding, which is the caller's malformed URI.\n const rawPath = extractPath(uri.href);\n if (rawPath === undefined) {\n throw new ProtocolError(\n ProtocolErrorCode.InvalidParams,\n 'Malformed file URI: path is not valid percent-encoding',\n );\n }\n await options.pathGuard.validateExistingPath(rawPath);\n const fs = new GuardedFileSystem(options.pathGuard);\n const readResult = await fs.readRaw(rawPath);\n\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: readResult.mimeType || 'application/octet-stream',\n ...(readResult.isBinary\n ? { blob: readResult.content.toString('base64') }\n : { text: readResult.content.toString('utf-8') }),\n },\n ],\n };\n },\n\n complete: {\n // Both ends speak the `{+path}` form (see encodeFileUriPath), not raw OS\n // paths: the partial arriving here is whatever this returned last, so the\n // decode mirrors the encode. An undecodable partial is matched as typed.\n path: async (value) => {\n const suggestions = await suggestPaths(\n options.pathGuard,\n decodeFileUriPath(value) ?? value,\n );\n return suggestions.map(encodeFileUriPath);\n },\n },\n\n subscribe(uri, notify) {\n // Already subscribed on this connection: the watcher is live and the sink\n // is registered, so this is a no-op success rather than a second lease.\n if (leasedUris.has(uri)) return undefined;\n // A subscribe for this URI is mid-acquire: share its outcome instead of\n // taking a second lease the lease set cannot represent.\n const pending = inflight.get(uri);\n if (pending) return pending;\n\n const acquire = async (): Promise<boolean | undefined> => {\n const result = await registry.acquire(options.pathGuard, uri, notify, {\n markSubscribe: true,\n });\n if (result.ok) {\n leasedUris.add(uri);\n return undefined;\n }\n\n if (result.reason === 'bad-uri') {\n throw new ResourceNotFoundError(uri, `Cannot subscribe: not a filesystem URI`);\n }\n if (result.reason === 'invalid-path') {\n const err = result.error;\n if (isNotFoundish(err)) {\n throw new ResourceNotFoundError(uri, `Cannot subscribe to ${uri}: ${err.message}`);\n }\n Logger.warn(\n `Unexpected error validating path for watcher ${uri}: ${formatUnknownErrorMessage(err)}`,\n );\n throw err;\n }\n // Unsubscribed (or the registry destroyed) mid-await: the caller already\n // asked for this to stop, so there is nothing to reject.\n if (result.reason === 'stale') return undefined;\n // capped / attach-failed: `false` tells the handler to reject, since\n // undefined would report success with no watcher attached.\n return false;\n };\n\n const promise = acquire().finally(() => inflight.delete(uri));\n inflight.set(uri, promise);\n return promise;\n },\n\n unsubscribe(uri) {\n // Only release a lease this connection actually took.\n if (!leasedUris.delete(uri)) return;\n registry.release(uri);\n },\n\n destroy() {\n // Release what this connection still holds before the registry goes: on\n // the shared (modern) registry nothing else would ever end these leases.\n for (const uri of leasedUris) registry.release(uri);\n leasedUris.clear();\n if (ownsRegistry) registry.destroy();\n },\n };\n}\n\n// ═══════════════════════════════════════════════════════════════\n// result\n// ═══════════════════════════════════════════════════════════════\n\nfunction createResultResource(options: ResourceRegistrationOptions): ResourceContract {\n return {\n name: 'filesystem-mcp-result',\n title: 'Cached Tool Result',\n description:\n 'Ephemeral cached tool output. Listed via resources/list; entries expire after the cache TTL.',\n mimeType: 'application/json',\n uriTemplate: 'filesystem-mcp://result/{id}',\n annotations: { audience: ['assistant'], priority: 0.3 },\n cacheHint: { cacheScope: 'private', ttlMs: 60_000 },\n list() {\n const store = options.resourceStore;\n const uris = store.keys(); // prunes expired first\n const resources: Resource[] = [];\n for (const uri of uris) {\n try {\n const entry = store.getEntry(uri);\n resources.push({\n uri: entry.uri,\n name: entry.name,\n mimeType: entry.mimeType,\n size: entry.size,\n });\n } catch (err) {\n // An entry may expire between keys() and getEntry; skip it.\n if (isFsError(err) && err.code === ErrorCode.NOT_FOUND) continue;\n throw err;\n }\n }\n // Recent-first: keys() returns insertion order, so the newest is last.\n resources.reverse();\n return { resources };\n },\n read(uri, variables) {\n const { id } = variables;\n if (typeof id !== 'string' || id.length === 0) {\n throw new ProtocolError(\n ProtocolErrorCode.InvalidParams,\n 'Malformed result URI: missing {id}. Use the exact resourceUri returned by the tool.',\n );\n }\n\n let entry;\n try {\n entry = options.resourceStore.getEntry(uri.toString());\n } catch (err) {\n if (isFsError(err) && err.code === ErrorCode.NOT_FOUND) {\n throw new ResourceNotFoundError(\n uri.toString(),\n 'Cached result not found or expired. Re-run the tool to regenerate.',\n );\n }\n throw err;\n }\n return {\n contents: [{ uri: entry.uri, mimeType: entry.mimeType, text: entry.text }],\n };\n },\n };\n}\n\n// ═══════════════════════════════════════════════════════════════\n// export contracts\n// ═══════════════════════════════════════════════════════════════\n\nexport function getResourceContracts(options: ResourceRegistrationOptions): ResourceContract[] {\n return [\n createInstructionsResource(options),\n createResultResource(options),\n createFilesystemResource(options),\n ];\n}\n\n// ═══════════════════════════════════════════════════════════════\n// registrar\n// ═══════════════════════════════════════════════════════════════\n\nfunction wrapRead(contract: ResourceContract) {\n return async (uri: URL, variables: Record<string, string | string[]>, ctx: ServerContext) => {\n try {\n return await contract.read(uri, variables, ctx);\n } catch (error) {\n // The SDK brands ProtocolError with `static [Symbol.hasInstance]`, so this\n // holds across separately bundled SDK copies and realms.\n if (error instanceof ProtocolError) throw error;\n // A missing or out-of-root path is a not-found, not a malformed request.\n // The SDK puts ResourceNotFoundError on the wire as -32602 with\n // `data.uri`, which is what clients match on; -32002 is the older code\n // they also accept, and this SDK never emits it.\n if (isNotFoundish(error)) {\n throw new ResourceNotFoundError(uri.toString(), error.message);\n }\n // A remaining FsError (NOT_FILE, TOO_LARGE, ...) traces to the\n // caller-supplied URI; anything else is a server-side failure and must\n // not be blamed on the request.\n const msg = isFsError(error) ? error.message : formatUnknownErrorMessage(error);\n throw new ProtocolError(fsErrorCode(error), msg);\n }\n };\n}\n\n/**\n * Whether `contract` answers for `uri`, decided by the template matcher the SDK\n * dispatches `resources/read` with, so `resources/subscribe` and\n * `resources/read` agree on which resource owns a URI. Not\n * `checkResourceAllowed`: that is an RFC 8707 audience check comparing\n * `URL.origin`, which is the opaque `\"null\"` for every custom scheme, so it\n * matched `filesystem-mcp://result/…` — and any other scheme — to the file\n * template.\n */\nfunction owns(contract: ResourceContract, uri: string): boolean {\n if (contract.uri !== undefined) return uri === contract.uri;\n return (\n contract.uriTemplate !== undefined && new UriTemplate(contract.uriTemplate).match(uri) !== null\n );\n}\n\nexport function registerResources(deps: ResourceRegistrarDeps): { dispose(): void } {\n const server = deps.server;\n const options = { ...deps, readOnly: deps.readOnly ?? false };\n const file = createFilesystemResource(options);\n const resourceContracts = [\n createInstructionsResource(options),\n createResultResource(options),\n file,\n ];\n\n for (const contract of resourceContracts) {\n const config = {\n ...(contract.title !== undefined ? { title: contract.title } : {}),\n ...(contract.description !== undefined ? { description: contract.description } : {}),\n ...(contract.mimeType !== undefined ? { mimeType: contract.mimeType } : {}),\n ...(contract.annotations !== undefined ? { annotations: contract.annotations } : {}),\n ...(contract.cacheHint !== undefined ? { cacheHint: contract.cacheHint } : {}),\n };\n\n if (contract.uriTemplate) {\n const template = new ResourceTemplate(contract.uriTemplate, {\n list: contract.list,\n ...(contract.complete ? { complete: contract.complete } : {}),\n });\n\n server.registerResource(contract.name, template, config, wrapRead(contract));\n } else if (contract.uri) {\n server.registerResource(contract.name, contract.uri, config, (uri: URL, ctx: ServerContext) =>\n wrapRead(contract)(uri, {}, ctx),\n );\n }\n }\n\n // `resources/subscribe`/`unsubscribe` are 2025-era-only verbs; a modern\n // server answers `-32601 Method not found` for them, so registering these\n // handlers on a modern-era instance would dispatch to code no request can\n // reach. A legacy instance on the HTTP leg (the one built with a `notifier`)\n // lives for one request: a subscription accepted there would take a watcher\n // lease for an instance that is gone before the file changes. Leave the\n // handlers off so the SDK refuses the verb, and no lease is taken. The\n // predicate mirrors the capability gate in server.ts exactly, so the\n // handler is registered if and only if `resources.subscribe` is advertised.\n // sunset(SEP-2577): removal trigger in docs/adr/002-legacy-protocol-paths-sunset.md.\n const legacyHttp = deps.era === 'legacy' && deps.notifier !== undefined;\n if (deps.era !== 'modern' && !legacyHttp) {\n server.server.assertCanSetRequestHandler('resources/subscribe');\n server.server.assertCanSetRequestHandler('resources/unsubscribe');\n\n // One stable callback for the whole registrar, NOT one per subscribe. The\n // registry holds these in a Set keyed by identity, so a fresh closure per\n // request made a second `resources/subscribe` for the same URI register a\n // second sink: one file change then sent N notifications, and `unsubscribe`\n // (which only ends a lease) removed none of them. Subscribe is per-URI on\n // the wire, so the sink must be too — this makes it idempotent.\n const notifyUpdated = (updatedUri: string): void => {\n if (deps.notifier) {\n deps.notifier.resourceUpdated(updatedUri);\n return;\n }\n const updatePayload: ResourceUpdatedNotificationParams = { uri: updatedUri };\n // A failed notify means the connection went away; nothing to recover.\n void server.server.sendResourceUpdated(updatePayload).catch((err: unknown) => {\n Logger.debug('resource update not delivered', {\n uri: updatedUri,\n error: formatUnknownErrorMessage(err),\n });\n });\n };\n\n server.server.setRequestHandler(\n 'resources/subscribe',\n async (req: { params: SubscribeRequestParams }) => {\n const { uri } = req.params;\n if (!owns(file, uri)) {\n // A resource that exists but has no watcher (the instructions text,\n // a cached result) is NOT a not-found: reporting it as one told\n // clients a URI they can list and read does not exist.\n if (resourceContracts.some((contract) => owns(contract, uri))) {\n throw new ProtocolError(\n ProtocolErrorCode.InvalidParams,\n `Resource ${uri} does not support subscriptions; only ${FILESYSTEM_FILE_URI_TEMPLATE} does. Read it again for the current contents.`,\n );\n }\n throw new ResourceNotFoundError(uri, `Resource not found: ${uri}`);\n }\n if ((await file.subscribe(uri, notifyUpdated)) === false) {\n // InternalError for want of anything better: ProtocolErrorCode has\n // no resource-limit member, and the message already names the\n // actionable cause.\n throw new ProtocolError(\n ProtocolErrorCode.InternalError,\n `Subscription rejected: no watcher attached (watcher limit ${MAX_WATCHERS} reached, or fs.watch failed to start).`,\n );\n }\n return {};\n },\n );\n\n server.server.setRequestHandler(\n 'resources/unsubscribe',\n (req: { params: UnsubscribeRequestParams }) => {\n if (owns(file, req.params.uri)) file.unsubscribe(req.params.uri);\n return {};\n },\n );\n }\n\n return {\n dispose(): void {\n for (const contract of resourceContracts) {\n if (contract.destroy) {\n contract.destroy();\n }\n }\n },\n };\n}\n"]}
1
+ {"version":3,"file":"resources.js","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n CacheHint,\n CompleteResourceTemplateCallback,\n McpServer,\n ReadResourceResult,\n Resource,\n ResourceUpdatedNotificationParams,\n Role,\n ServerContext,\n ServerNotifier,\n SubscribeRequestParams,\n UnsubscribeRequestParams,\n} from '@modelcontextprotocol/server';\nimport {\n ProtocolError,\n ProtocolErrorCode,\n ResourceNotFoundError,\n ResourceTemplate,\n UriTemplate,\n} from '@modelcontextprotocol/server';\n\nimport type { FsError } from './core/errors.ts';\nimport { ErrorCode, formatUnknownErrorMessage, isFsError } from './core/errors.ts';\nimport {\n decodeFileUriPath,\n encodeFileUriPath,\n extractPath,\n FILESYSTEM_FILE_URI_TEMPLATE,\n} from './core/file-uri.ts';\nimport { GuardedFileSystem } from './core/fs.ts';\nimport { Logger } from './core/observability.ts';\nimport { suggestPaths } from './core/path-completer.ts';\nimport type { PathGuard } from './core/path.ts';\nimport type { ResourceStore } from './core/store.ts';\nimport {\n createWatcherRegistry,\n MAX_WATCHERS,\n type WatcherRegistry,\n} from './core/watcher-registry.ts';\nimport {\n buildSectionsRecord,\n INSTRUCTIONS_SUMMARY,\n INSTRUCTIONS_URI,\n renderSections,\n} from './instructions.ts';\n\n// ═══════════════════════════════════════════════════════════════\n// shared\n// ═══════════════════════════════════════════════════════════════\n\nexport interface ResourceRegistrationOptions {\n resourceStore: ResourceStore;\n pathGuard: PathGuard;\n /** Mirrors the `--read-only` gate so the instructions match the tools actually registered. */\n readOnly: boolean;\n /**\n * Shared file-watcher registry for the modern (per-request) HTTP leg. When set,\n * the resource uses this handler-scoped registry instead of creating its own\n * per-server one, so file-change watchers persist across requests and publish\n * to the shared ServerEventBus. Omitted (per-server registry) on legacy/stdio.\n */\n watcherRegistry?: WatcherRegistry;\n /** Modern-leg typed notification publisher. */\n notifier?: ServerNotifier;\n /** The protocol era this instance serves; omitted where the caller does not know. */\n era?: 'legacy' | 'modern';\n /**\n * Scope for shared-content cache hints. 'private' when the endpoint is\n * API-key gated, mirroring the list hints in server.ts — a shared cache must\n * not store an authenticated response body. Defaults to 'public'.\n */\n cacheScope?: 'public' | 'private';\n}\n\ntype ResourceRegistrarDeps = Omit<ResourceRegistrationOptions, 'pathGuard' | 'readOnly'> & {\n readonly server: McpServer;\n readonly pathGuard: PathGuard;\n readonly readOnly?: boolean;\n};\n\n/**\n * A filesystem failure that reads as not-found on the resource wire, for both\n * `resources/read` and `resources/subscribe`. ACCESS_DENIED folds in\n * deliberately: masking whether an out-of-root or denylisted path exists is the\n * security-correct answer, and the `FsError` message still carries the real\n * cause to the client.\n */\nfunction isNotFoundish(error: unknown): error is FsError {\n return (\n isFsError(error) &&\n (error.code === ErrorCode.NOT_FOUND || error.code === ErrorCode.ACCESS_DENIED)\n );\n}\n\n// ═══════════════════════════════════════════════════════════════\n// contract\n// ═══════════════════════════════════════════════════════════════\n\ninterface ResourceContract {\n name: string;\n title?: string;\n description?: string;\n mimeType?: string;\n cacheHint?: CacheHint;\n annotations?: {\n audience?: Role[];\n priority?: number;\n };\n /** A fixed, enumerable URI (e.g. internal://instructions)... */\n uri?: string;\n /** ...or a URI template, optionally with a completer for its variable. */\n uriTemplate?: string;\n complete?: { path: CompleteResourceTemplateCallback };\n readonly read: (\n uri: URL,\n variables: Record<string, string | string[]>,\n ctx: ServerContext,\n ) => Promise<ReadResourceResult> | ReadResourceResult;\n readonly subscribe?: (\n uri: string,\n notify: (uri: string) => void,\n ) => Promise<boolean | undefined> | boolean | undefined;\n readonly unsubscribe?: (uri: string) => void;\n readonly destroy?: () => void;\n readonly list?: (\n ctx: ServerContext,\n ) => Promise<{ resources: Resource[] }> | { resources: Resource[] };\n}\n\n/** The one contract with a live watcher behind it. */\ntype SubscribableContract = ResourceContract &\n Required<Pick<ResourceContract, 'subscribe' | 'unsubscribe'>>;\n\n// ═══════════════════════════════════════════════════════════════\n// instructions\n// ═══════════════════════════════════════════════════════════════\n\nfunction createInstructionsResource(options: ResourceRegistrationOptions): ResourceContract {\n const text = renderSections(buildSectionsRecord(options.readOnly));\n return {\n name: 'filesystem-mcp-instructions',\n title: 'Server Instructions',\n description: INSTRUCTIONS_SUMMARY,\n mimeType: 'text/markdown',\n uri: INSTRUCTIONS_URI,\n annotations: { audience: ['assistant'], priority: 0.8 },\n cacheHint: { cacheScope: options.cacheScope ?? 'public', ttlMs: 300_000 },\n read(uri) {\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: 'text/markdown',\n text,\n },\n ],\n };\n },\n };\n}\n\n// ═══════════════════════════════════════════════════════════════\n// filesystem\n// ═══════════════════════════════════════════════════════════════\n\nfunction createFilesystemResource(options: ResourceRegistrationOptions): SubscribableContract {\n const registry = options.watcherRegistry ?? createWatcherRegistry();\n // Only the per-server (legacy/stdio) registry is owned by this resource and\n // destroyed on dispose; the shared modern-leg registry is owned by the host\n // and torn down at server shutdown.\n const ownsRegistry = options.watcherRegistry === undefined;\n // The URIs this connection holds a `resources/subscribe` lease for. The wire\n // verb is per-URI and idempotent — a second subscribe is the same\n // subscription, and one unsubscribe ends it — but the registry ref-counts\n // leases (the HTTP listen leg needs that). Without this set a double\n // subscribe took two leases that one unsubscribe could not release, and an\n // unsubscribe for a URI never subscribed released a lease this connection\n // never took, dropping a watcher some other holder still wanted.\n const leasedUris = new Set<string>();\n // Subscribes mid-acquire, keyed by URI. Two concurrent subscribes for the\n // same URI must share one acquire: both passing the `leasedUris` check\n // before either finished took two registry leases that the single set entry\n // could release only once — on the shared modern registry that orphan lease\n // pinned a watcher until process shutdown.\n const inflight = new Map<string, Promise<boolean | undefined>>();\n\n return {\n name: 'filesystem-mcp-file',\n title: 'Workspace File',\n description: 'Read a file from the workspace. Subscribe to get updates when the file changes.',\n uriTemplate: FILESYSTEM_FILE_URI_TEMPLATE,\n annotations: { audience: ['assistant'], priority: 0.8 },\n // Without this the SDK falls back to its conservative `{ ttlMs: 0,\n // cacheScope: 'private' }`, making every file read uncacheable — and this\n // is the one resource with a live invalidation channel, so TTL and the\n // watcher compose exactly as the spec's caching section describes. The TTL\n // stays short because a non-subscriber gets no invalidation signal at all.\n cacheHint: { cacheScope: options.cacheScope ?? 'public', ttlMs: 5_000 },\n\n // No `list`: the template registers with `list: undefined` — readable but\n // not enumerable. Listing each allowed root as a concrete resource\n // duplicated the template and grew resources/list linearly with roots;\n // `list_roots` owns root discovery.\n\n async read(uri, _variables, _ctx: ServerContext) {\n // Decode via extractPath — the same decoder resources/subscribe uses — so\n // both consumers are symmetric with buildFileResourceUri's encoding. The\n // {+path} template variable arrives still percent-encoded, so validating\n // it directly would treat \"c%3A/proj/a.ts\" as a literal relative path;\n // no fallback to it here, or the two branches would validate different\n // strings. extractPath only fails past the template match on undecodable\n // percent-encoding, which is the caller's malformed URI.\n const rawPath = extractPath(uri.href);\n if (rawPath === undefined) {\n throw new ProtocolError(\n ProtocolErrorCode.InvalidParams,\n 'Malformed file URI: path is not valid percent-encoding',\n );\n }\n await options.pathGuard.validateExistingPath(rawPath);\n const fs = new GuardedFileSystem(options.pathGuard);\n const readResult = await fs.readRaw(rawPath);\n\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: readResult.mimeType || 'application/octet-stream',\n ...(readResult.isBinary\n ? { blob: readResult.content.toString('base64') }\n : { text: readResult.content.toString('utf-8') }),\n },\n ],\n };\n },\n\n complete: {\n // Both ends speak the `{+path}` form (see encodeFileUriPath), not raw OS\n // paths: the partial arriving here is whatever this returned last, so the\n // decode mirrors the encode. An undecodable partial is matched as typed.\n path: async (value) => {\n const suggestions = await suggestPaths(\n options.pathGuard,\n decodeFileUriPath(value) ?? value,\n );\n return suggestions.map(encodeFileUriPath);\n },\n },\n\n subscribe(uri, notify) {\n // Already subscribed on this connection: the watcher is live and the sink\n // is registered, so this is a no-op success rather than a second lease.\n if (leasedUris.has(uri)) return undefined;\n // A subscribe for this URI is mid-acquire: share its outcome instead of\n // taking a second lease the lease set cannot represent.\n const pending = inflight.get(uri);\n if (pending) return pending;\n\n const acquire = async (): Promise<boolean | undefined> => {\n const result = await registry.acquire(options.pathGuard, uri, notify, {\n markSubscribe: true,\n });\n if (result.ok) {\n leasedUris.add(uri);\n return undefined;\n }\n\n if (result.reason === 'bad-uri') {\n throw new ResourceNotFoundError(uri, `Cannot subscribe: not a filesystem URI`);\n }\n if (result.reason === 'invalid-path') {\n const err = result.error;\n if (isNotFoundish(err)) {\n throw new ResourceNotFoundError(uri, `Cannot subscribe to ${uri}: ${err.message}`);\n }\n if (isFsError(err)) throw fsErrorToProtocolError(err);\n Logger.warn(\n `Unexpected error validating path for watcher ${uri}: ${formatUnknownErrorMessage(err)}`,\n );\n throw err;\n }\n // Unsubscribed (or the registry destroyed) mid-await: the caller already\n // asked for this to stop, so there is nothing to reject.\n if (result.reason === 'stale') return undefined;\n // capped / attach-failed: `false` tells the handler to reject, since\n // undefined would report success with no watcher attached.\n return false;\n };\n\n const promise = acquire().finally(() => inflight.delete(uri));\n inflight.set(uri, promise);\n return promise;\n },\n\n unsubscribe(uri) {\n // Only release a lease this connection actually took.\n if (!leasedUris.delete(uri)) return;\n registry.release(uri);\n },\n\n destroy() {\n // Release what this connection still holds before the registry goes: on\n // the shared (modern) registry nothing else would ever end these leases.\n for (const uri of leasedUris) registry.release(uri);\n leasedUris.clear();\n if (ownsRegistry) registry.destroy();\n },\n };\n}\n\n// ═══════════════════════════════════════════════════════════════\n// result\n// ═══════════════════════════════════════════════════════════════\n\nfunction createResultResource(options: ResourceRegistrationOptions): ResourceContract {\n return {\n name: 'filesystem-mcp-result',\n title: 'Cached Tool Result',\n description:\n 'Ephemeral cached tool output. Listed via resources/list; entries expire after the cache TTL.',\n mimeType: 'application/json',\n uriTemplate: 'filesystem-mcp://result/{id}',\n annotations: { audience: ['assistant'], priority: 0.3 },\n cacheHint: { cacheScope: 'private', ttlMs: 60_000 },\n list() {\n const store = options.resourceStore;\n const uris = store.keys(); // prunes expired first\n const resources: Resource[] = [];\n for (const uri of uris) {\n try {\n const entry = store.getEntry(uri);\n resources.push({\n uri: entry.uri,\n name: entry.name,\n mimeType: entry.mimeType,\n size: entry.size,\n });\n } catch (err) {\n // An entry may expire between keys() and getEntry; skip it.\n if (isFsError(err) && err.code === ErrorCode.NOT_FOUND) continue;\n throw err;\n }\n }\n // Recent-first: keys() returns insertion order, so the newest is last.\n resources.reverse();\n return { resources };\n },\n read(uri, variables) {\n const { id } = variables;\n if (typeof id !== 'string' || id.length === 0) {\n throw new ProtocolError(\n ProtocolErrorCode.InvalidParams,\n 'Malformed result URI: missing {id}. Use the exact resourceUri returned by the tool.',\n );\n }\n\n let entry;\n try {\n entry = options.resourceStore.getEntry(uri.toString());\n } catch (err) {\n if (isFsError(err) && err.code === ErrorCode.NOT_FOUND) {\n throw new ResourceNotFoundError(\n uri.toString(),\n 'Cached result not found or expired. Re-run the tool to regenerate.',\n );\n }\n throw err;\n }\n return {\n contents: [{ uri: entry.uri, mimeType: entry.mimeType, text: entry.text }],\n };\n },\n };\n}\n\n// ═══════════════════════════════════════════════════════════════\n// export contracts\n// ═══════════════════════════════════════════════════════════════\n\nexport function getResourceContracts(options: ResourceRegistrationOptions): ResourceContract[] {\n return [\n createInstructionsResource(options),\n createResultResource(options),\n createFilesystemResource(options),\n ];\n}\n\n// ═══════════════════════════════════════════════════════════════\n// registrar\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * A caller-traceable FsError as the SDK's InvalidParams error. The Problem's\n * identity fields ride the SDK's error.data channel: `message` stays the\n * wire message, and code/path/suggestion give clients a machine-readable\n * view. Keyed by `code`, so it cannot collide with the\n * ResourceNotFoundError convention (data: { uri } and nothing else).\n */\nfunction fsErrorToProtocolError(error: FsError): ProtocolError {\n const { code, path, suggestion } = error.problem;\n return new ProtocolError(ProtocolErrorCode.InvalidParams, error.message, {\n code,\n ...(path !== undefined ? { path } : {}),\n ...(suggestion !== undefined ? { suggestion } : {}),\n });\n}\n\nfunction wrapRead(contract: ResourceContract) {\n return async (uri: URL, variables: Record<string, string | string[]>, ctx: ServerContext) => {\n try {\n return await contract.read(uri, variables, ctx);\n } catch (error) {\n // The SDK brands ProtocolError with `static [Symbol.hasInstance]`, so this\n // holds across separately bundled SDK copies and realms.\n if (error instanceof ProtocolError) throw error;\n // A missing or out-of-root path is a not-found, not a malformed request.\n // The SDK puts ResourceNotFoundError on the wire as -32602 with\n // `data.uri`, which is what clients match on; -32002 is the older code\n // they also accept, and this SDK never emits it.\n if (isNotFoundish(error)) {\n throw new ResourceNotFoundError(uri.toString(), error.message);\n }\n // A remaining FsError (NOT_FILE, TOO_LARGE, ...) traces to the\n // caller-supplied URI; anything else is a server-side failure and must\n // not be blamed on the request.\n if (isFsError(error)) throw fsErrorToProtocolError(error);\n throw new ProtocolError(ProtocolErrorCode.InternalError, formatUnknownErrorMessage(error));\n }\n };\n}\n\n/**\n * Whether `contract` answers for `uri`, decided by the template matcher the SDK\n * dispatches `resources/read` with, so `resources/subscribe` and\n * `resources/read` agree on which resource owns a URI. Not\n * `checkResourceAllowed`: that is an RFC 8707 audience check comparing\n * `URL.origin`, which is the opaque `\"null\"` for every custom scheme, so it\n * matched `filesystem-mcp://result/…` — and any other scheme — to the file\n * template.\n */\nfunction owns(contract: ResourceContract, uri: string): boolean {\n if (contract.uri !== undefined) return uri === contract.uri;\n return (\n contract.uriTemplate !== undefined && new UriTemplate(contract.uriTemplate).match(uri) !== null\n );\n}\n\nexport function registerResources(deps: ResourceRegistrarDeps): { dispose(): void } {\n const server = deps.server;\n const options = { ...deps, readOnly: deps.readOnly ?? false };\n const file = createFilesystemResource(options);\n const resourceContracts = [\n createInstructionsResource(options),\n createResultResource(options),\n file,\n ];\n\n for (const contract of resourceContracts) {\n const config = {\n ...(contract.title !== undefined ? { title: contract.title } : {}),\n ...(contract.description !== undefined ? { description: contract.description } : {}),\n ...(contract.mimeType !== undefined ? { mimeType: contract.mimeType } : {}),\n ...(contract.annotations !== undefined ? { annotations: contract.annotations } : {}),\n ...(contract.cacheHint !== undefined ? { cacheHint: contract.cacheHint } : {}),\n };\n\n if (contract.uriTemplate) {\n const template = new ResourceTemplate(contract.uriTemplate, {\n list: contract.list,\n ...(contract.complete ? { complete: contract.complete } : {}),\n });\n\n server.registerResource(contract.name, template, config, wrapRead(contract));\n } else if (contract.uri) {\n server.registerResource(contract.name, contract.uri, config, (uri: URL, ctx: ServerContext) =>\n wrapRead(contract)(uri, {}, ctx),\n );\n }\n }\n\n // `resources/subscribe`/`unsubscribe` are 2025-era-only verbs; a modern\n // server answers `-32601 Method not found` for them, so registering these\n // handlers on a modern-era instance would dispatch to code no request can\n // reach. A legacy instance on the HTTP leg (the one built with a `notifier`)\n // lives for one request: a subscription accepted there would take a watcher\n // lease for an instance that is gone before the file changes. Leave the\n // handlers off so the SDK refuses the verb, and no lease is taken. The\n // predicate mirrors the capability gate in server.ts exactly, so the\n // handler is registered if and only if `resources.subscribe` is advertised.\n // sunset(SEP-2577): removal trigger in docs/adr/002-legacy-protocol-paths-sunset.md.\n const legacyHttp = deps.era === 'legacy' && deps.notifier !== undefined;\n if (deps.era !== 'modern' && !legacyHttp) {\n server.server.assertCanSetRequestHandler('resources/subscribe');\n server.server.assertCanSetRequestHandler('resources/unsubscribe');\n\n // One stable callback for the whole registrar, NOT one per subscribe. The\n // registry holds these in a Set keyed by identity, so a fresh closure per\n // request made a second `resources/subscribe` for the same URI register a\n // second sink: one file change then sent N notifications, and `unsubscribe`\n // (which only ends a lease) removed none of them. Subscribe is per-URI on\n // the wire, so the sink must be too — this makes it idempotent.\n const notifyUpdated = (updatedUri: string): void => {\n if (deps.notifier) {\n deps.notifier.resourceUpdated(updatedUri);\n return;\n }\n const updatePayload: ResourceUpdatedNotificationParams = { uri: updatedUri };\n // A failed notify means the connection went away; nothing to recover.\n void server.server.sendResourceUpdated(updatePayload).catch((err: unknown) => {\n Logger.debug('resource update not delivered', {\n uri: updatedUri,\n error: formatUnknownErrorMessage(err),\n });\n });\n };\n\n server.server.setRequestHandler(\n 'resources/subscribe',\n async (req: { params: SubscribeRequestParams }) => {\n const { uri } = req.params;\n if (!owns(file, uri)) {\n // A resource that exists but has no watcher (the instructions text,\n // a cached result) is NOT a not-found: reporting it as one told\n // clients a URI they can list and read does not exist.\n if (resourceContracts.some((contract) => owns(contract, uri))) {\n throw new ProtocolError(\n ProtocolErrorCode.InvalidParams,\n `Resource ${uri} does not support subscriptions; only ${FILESYSTEM_FILE_URI_TEMPLATE} does. Read it again for the current contents.`,\n );\n }\n throw new ResourceNotFoundError(uri, `Resource not found: ${uri}`);\n }\n if ((await file.subscribe(uri, notifyUpdated)) === false) {\n // InternalError for want of anything better: ProtocolErrorCode has\n // no resource-limit member, and the message already names the\n // actionable cause.\n throw new ProtocolError(\n ProtocolErrorCode.InternalError,\n `Subscription rejected: no watcher attached (watcher limit ${MAX_WATCHERS} reached, or fs.watch failed to start).`,\n );\n }\n return {};\n },\n );\n\n server.server.setRequestHandler(\n 'resources/unsubscribe',\n (req: { params: UnsubscribeRequestParams }) => {\n if (owns(file, req.params.uri)) file.unsubscribe(req.params.uri);\n return {};\n },\n );\n }\n\n return {\n dispose(): void {\n for (const contract of resourceContracts) {\n if (contract.destroy) {\n contract.destroy();\n }\n }\n },\n };\n}\n"]}
package/dist/server.d.ts CHANGED
@@ -37,7 +37,7 @@ export declare function createServer(options?: ServerOptions, extraDeps?: {
37
37
  /** The protocol era this instance serves; omitted where the caller does not know. */
38
38
  era?: 'legacy' | 'modern';
39
39
  /**
40
- * The resolved HTTP credential, from `--api-key` or `API_KEY`. Lives here
40
+ * The resolved HTTP credential, from `--api-key` or `FS_API_KEY`. Lives here
41
41
  * rather than on `ServerOptions` because that object is `PathGuard`'s
42
42
  * constructor argument and is exposed on its public `options` field — a
43
43
  * bearer secret has no business being reachable from a tool handler.
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,cAAc,EACf,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAKzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAgBlE,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,mBAAmB,IAAI,IAAI,CAAC;CAC7B;AAED,wBAAsB,YAAY,CAChC,OAAO,GAAE,aAAkB,EAC3B,SAAS,CAAC,EAAE;IACV,0EAA0E;IAC1E,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B;;;OAGG;IACH,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAC9B,qFAAqF;IACrF,GAAG,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC1B;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GACA,OAAO,CAAC,uBAAuB,CAAC,CAmHlC"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,cAAc,EACf,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAKzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAgBlE,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,mBAAmB,IAAI,IAAI,CAAC;CAC7B;AAED,wBAAsB,YAAY,CAChC,OAAO,GAAE,aAAkB,EAC3B,SAAS,CAAC,EAAE;IACV,0EAA0E;IAC1E,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B;;;OAGG;IACH,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAC9B,qFAAqF;IACrF,GAAG,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC1B;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GACA,OAAO,CAAC,uBAAuB,CAAC,CAgIlC"}
package/dist/server.js CHANGED
@@ -18,9 +18,11 @@ export async function createServer(options = {}, extraDeps) {
18
18
  // but never infers `subscribe`.
19
19
  //
20
20
  // `resources.subscribe` stays advertised on both eras: the verb itself is
21
- // 2025-only, but with enforceStrictCapabilities the SDK also gates outbound
22
- // `notifications/resources/updated` — which the modern `subscriptions/listen`
23
- // stream delivers — on this same capability bit. The one exception is a
21
+ // 2025-only, but the SDK's listen router honors a `subscriptions/listen`
22
+ // resource filter only while this bit is advertised (dist: `allow(
23
+ // capabilities?.resources?.subscribe)` on `resourceSubscriptions`). The
24
+ // strict-capability gate on outbound `notifications/resources/updated`
25
+ // checks only that `resources` exists. The one exception is a
24
26
  // legacy instance on the HTTP leg (era 'legacy' with a notifier): it serves
25
27
  // one request, registers no subscribe handler (resources.ts, same
26
28
  // predicate) and sends no notification of any kind — there is no stream to
@@ -55,6 +57,17 @@ export async function createServer(options = {}, extraDeps) {
55
57
  // `ctx.mcpReq.requestState<T>()`.
56
58
  // eslint-disable-next-line @typescript-eslint/unbound-method
57
59
  requestState: { verify: requestStateCodec.verify },
60
+ // The ResourceStore fires list-changed on every externalization and on
61
+ // every expiry prune (store.ts), so parallel tools/call on one
62
+ // connection can send the same notification several times in one tick;
63
+ // each copy makes the client refetch an unchanged list. The SDK
64
+ // coalesces same-tick duplicates for methods listed here.
65
+ // ponytail: same-tick coalescing only — cross-tick bursts (batch
66
+ // externalizations separated by fs awaits) still fan out; a store-level
67
+ // debounce is the fix if clients ever report that. The modern HTTP leg
68
+ // publishes through the ServerEventBus, which this option does not
69
+ // touch, so it protects the stdio/in-memory leg.
70
+ debouncedNotificationMethods: ['notifications/resources/list_changed'],
58
71
  };
59
72
  serverConfig.instructions =
60
73
  `${INSTRUCTIONS_SUMMARY} ` +
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n Implementation,\n ServerCapabilities,\n ServerNotifier,\n} from '@modelcontextprotocol/server';\nimport { McpServer } from '@modelcontextprotocol/server';\n\nimport packageJson from '../package.json' with { type: 'json' };\nimport { requestStateCodec } from './core/input-required.ts';\nimport { Logger } from './core/observability.ts';\nimport type { ServerOptions } from './core/path.ts';\nimport { PathGuard } from './core/path.ts';\nimport { PageSnapshotStore, ResourceStore } from './core/store.ts';\nimport type { WatcherRegistry } from './core/watcher-registry.ts';\nimport { INSTRUCTIONS_SUMMARY, INSTRUCTIONS_URI } from './instructions.ts';\nimport { registerPrompts } from './prompts.ts';\nimport { registerResources } from './resources.ts';\nimport { registerTools } from './tools/index.ts';\n\n// ═══════════════════════════════════════════════════════════════\n// bootstrap\n// ═══════════════════════════════════════════════════════════════\n\nconst {\n version: SERVER_VERSION,\n description: SERVER_DESCRIPTION,\n homepage: SERVER_HOMEPAGE,\n} = packageJson;\n\nexport interface FilesystemServerContext {\n readonly mcp: McpServer;\n readonly pathGuard: PathGuard;\n disposeRuntimeState(): void;\n}\n\nexport async function createServer(\n options: ServerOptions = {},\n extraDeps?: {\n /** Shared file-watcher registry for the modern (per-request) HTTP leg. */\n watcherRegistry?: WatcherRegistry;\n /** Modern-leg typed notification publisher. */\n notifier?: ServerNotifier;\n /**\n * Guard to use instead of constructing one. The HTTP leg builds a single\n * guard for the whole endpoint and passes it to every per-request instance,\n * so an accepted access grant survives the request that accepted it (R8) and\n * the listen-watcher path validates against the same allowed set. Omitted on\n * stdio, where the instance is pinned for the connection and owns its guard.\n */\n pathGuard?: PathGuard;\n /**\n * Store to use instead of constructing one. The HTTP modern leg shares one\n * store across every per-request instance, so a result a tool externalized\n * in one POST survives to the follow-up resources/read. Omitted on stdio,\n * where the pinned instance owns its store for the connection.\n */\n resourceStore?: ResourceStore;\n /**\n * Pagination snapshots shared by every modern HTTP request. Omitted on\n * stdio, where the connection-owned context clears its store on close.\n */\n pageStore?: PageSnapshotStore;\n /** The protocol era this instance serves; omitted where the caller does not know. */\n era?: 'legacy' | 'modern';\n /**\n * The resolved HTTP credential, from `--api-key` or `API_KEY`. Lives here\n * rather than on `ServerOptions` because that object is `PathGuard`'s\n * constructor argument and is exposed on its public `options` field — a\n * bearer secret has no business being reachable from a tool handler.\n */\n apiKey?: string;\n },\n): Promise<FilesystemServerContext> {\n // Only `resources` is declared: `McpServer` advertises `tools` and `prompts`\n // (with `listChanged: true`) and `completions` from the registrations below,\n // but never infers `subscribe`.\n //\n // `resources.subscribe` stays advertised on both eras: the verb itself is\n // 2025-only, but with enforceStrictCapabilities the SDK also gates outbound\n // `notifications/resources/updated` — which the modern `subscriptions/listen`\n // stream delivers — on this same capability bit. The one exception is a\n // legacy instance on the HTTP leg (era 'legacy' with a notifier): it serves\n // one request, registers no subscribe handler (resources.ts, same\n // predicate) and sends no notification of any kind — there is no stream to\n // send it on — so neither `subscribe` nor `listChanged` is advertised.\n const legacyHttp = extraDeps?.era === 'legacy' && extraDeps.notifier !== undefined;\n const capabilities = {\n resources: { subscribe: !legacyHttp, listChanged: !legacyHttp },\n } satisfies ServerCapabilities;\n\n const cacheScope: 'private' | 'public' = extraDeps?.apiKey ? 'private' : 'public';\n const serverConfig: NonNullable<ConstructorParameters<typeof McpServer>[1]> = {\n capabilities,\n enforceStrictCapabilities: true,\n cacheHints: {\n 'tools/list': { ttlMs: 3_600_000, cacheScope },\n 'prompts/list': { ttlMs: 3_600_000, cacheScope },\n // Always private, never `cacheScope`: this list enumerates the\n // ResourceStore, so its entries are one caller's externalized tool output\n // — the same entries whose own read hint is `private` (resources.ts). The\n // other four lists are the same for every caller and follow the key, and\n // they are built from frozen module-level arrays that cannot change at\n // runtime, so an hour is honest: a shorter hint only buys the client a\n // re-fetch of bytes that cannot have differed, at the cost of the prompt\n // cache stability the spec asks deterministic ordering to preserve.\n 'resources/list': { ttlMs: 30_000, cacheScope: 'private' },\n 'resources/templates/list': { ttlMs: 3_600_000, cacheScope },\n 'server/discover': { ttlMs: 3_600_000, cacheScope },\n },\n // Multi-round-trip `requestState` integrity (protocol revision 2026-07-28):\n // the codec verifies the HMAC on every retried round before the handler\n // runs, so a tampered or expired state is rejected as `-32602` rather than\n // trusted. The decoded `PendingState` reaches handlers via\n // `ctx.mcpReq.requestState<T>()`.\n // eslint-disable-next-line @typescript-eslint/unbound-method\n requestState: { verify: requestStateCodec.verify },\n };\n\n serverConfig.instructions =\n `${INSTRUCTIONS_SUMMARY} ` +\n `For full guidance, read ${INSTRUCTIONS_URI} or run the get-help prompt.`;\n\n const implementation: Implementation = {\n name: 'filesystem-mcp',\n title: 'Filesystem MCP',\n version: SERVER_VERSION,\n ...(SERVER_DESCRIPTION ? { description: SERVER_DESCRIPTION } : {}),\n ...(SERVER_HOMEPAGE ? { websiteUrl: SERVER_HOMEPAGE } : {}),\n };\n const server = new McpServer(implementation, serverConfig);\n server.server.fallbackNotificationHandler = (notification) => {\n Logger.debug('Unhandled client notification', { method: notification.method });\n return Promise.resolve();\n };\n\n // A failed send means the connection went away; nothing to recover.\n const resourceStore =\n extraDeps?.resourceStore ??\n new ResourceStore(() => {\n if (extraDeps?.notifier) {\n extraDeps.notifier.resourcesChanged();\n return;\n }\n void server.server.sendResourceListChanged().catch((error: unknown) => {\n Logger.debug('resource list_changed not delivered', { error: String(error) });\n });\n });\n const pageStore = extraDeps?.pageStore ?? new PageSnapshotStore();\n\n const pathGuard = extraDeps?.pathGuard ?? new PathGuard(options);\n // Recompute once per guard, keyed on the guard's own state rather than on\n // whether it was injected — an injected-but-uninitialized guard would\n // otherwise deny every path. Not unconditional: the HTTP leg shares one guard\n // across a fresh instance per request, and a repeat recompute would both redo\n // the root resolution per request and race a concurrent `applyGrant`, which\n // commits under a mutation lock this call does not take.\n if (!pathGuard.isInitialized()) await pathGuard.recomputeAllowedDirectories();\n\n const deps = {\n server,\n pathGuard,\n pageStore,\n resourceStore,\n cacheScope,\n ...(options.readOnly ? { readOnly: true } : {}),\n ...(extraDeps?.watcherRegistry ? { watcherRegistry: extraDeps.watcherRegistry } : {}),\n ...(extraDeps?.notifier ? { notifier: extraDeps.notifier } : {}),\n ...(extraDeps?.era ? { era: extraDeps.era } : {}),\n };\n\n const resourceDisposable = registerResources(deps);\n registerPrompts(deps);\n registerTools(deps);\n\n // False when the store is shared across instances and outlives this one.\n const ownsPages = extraDeps?.pageStore === undefined;\n let cleanedUp = false;\n return {\n mcp: server,\n pathGuard,\n disposeRuntimeState() {\n if (cleanedUp) return;\n cleanedUp = true;\n if (ownsPages) pageStore.clear();\n resourceDisposable.dispose();\n },\n };\n}\n"]}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n Implementation,\n ServerCapabilities,\n ServerNotifier,\n} from '@modelcontextprotocol/server';\nimport { McpServer } from '@modelcontextprotocol/server';\n\nimport packageJson from '../package.json' with { type: 'json' };\nimport { requestStateCodec } from './core/input-required.ts';\nimport { Logger } from './core/observability.ts';\nimport type { ServerOptions } from './core/path.ts';\nimport { PathGuard } from './core/path.ts';\nimport { PageSnapshotStore, ResourceStore } from './core/store.ts';\nimport type { WatcherRegistry } from './core/watcher-registry.ts';\nimport { INSTRUCTIONS_SUMMARY, INSTRUCTIONS_URI } from './instructions.ts';\nimport { registerPrompts } from './prompts.ts';\nimport { registerResources } from './resources.ts';\nimport { registerTools } from './tools/index.ts';\n\n// ═══════════════════════════════════════════════════════════════\n// bootstrap\n// ═══════════════════════════════════════════════════════════════\n\nconst {\n version: SERVER_VERSION,\n description: SERVER_DESCRIPTION,\n homepage: SERVER_HOMEPAGE,\n} = packageJson;\n\nexport interface FilesystemServerContext {\n readonly mcp: McpServer;\n readonly pathGuard: PathGuard;\n disposeRuntimeState(): void;\n}\n\nexport async function createServer(\n options: ServerOptions = {},\n extraDeps?: {\n /** Shared file-watcher registry for the modern (per-request) HTTP leg. */\n watcherRegistry?: WatcherRegistry;\n /** Modern-leg typed notification publisher. */\n notifier?: ServerNotifier;\n /**\n * Guard to use instead of constructing one. The HTTP leg builds a single\n * guard for the whole endpoint and passes it to every per-request instance,\n * so an accepted access grant survives the request that accepted it (R8) and\n * the listen-watcher path validates against the same allowed set. Omitted on\n * stdio, where the instance is pinned for the connection and owns its guard.\n */\n pathGuard?: PathGuard;\n /**\n * Store to use instead of constructing one. The HTTP modern leg shares one\n * store across every per-request instance, so a result a tool externalized\n * in one POST survives to the follow-up resources/read. Omitted on stdio,\n * where the pinned instance owns its store for the connection.\n */\n resourceStore?: ResourceStore;\n /**\n * Pagination snapshots shared by every modern HTTP request. Omitted on\n * stdio, where the connection-owned context clears its store on close.\n */\n pageStore?: PageSnapshotStore;\n /** The protocol era this instance serves; omitted where the caller does not know. */\n era?: 'legacy' | 'modern';\n /**\n * The resolved HTTP credential, from `--api-key` or `FS_API_KEY`. Lives here\n * rather than on `ServerOptions` because that object is `PathGuard`'s\n * constructor argument and is exposed on its public `options` field — a\n * bearer secret has no business being reachable from a tool handler.\n */\n apiKey?: string;\n },\n): Promise<FilesystemServerContext> {\n // Only `resources` is declared: `McpServer` advertises `tools` and `prompts`\n // (with `listChanged: true`) and `completions` from the registrations below,\n // but never infers `subscribe`.\n //\n // `resources.subscribe` stays advertised on both eras: the verb itself is\n // 2025-only, but the SDK's listen router honors a `subscriptions/listen`\n // resource filter only while this bit is advertised (dist: `allow(\n // capabilities?.resources?.subscribe)` on `resourceSubscriptions`). The\n // strict-capability gate on outbound `notifications/resources/updated`\n // checks only that `resources` exists. The one exception is a\n // legacy instance on the HTTP leg (era 'legacy' with a notifier): it serves\n // one request, registers no subscribe handler (resources.ts, same\n // predicate) and sends no notification of any kind — there is no stream to\n // send it on — so neither `subscribe` nor `listChanged` is advertised.\n const legacyHttp = extraDeps?.era === 'legacy' && extraDeps.notifier !== undefined;\n const capabilities = {\n resources: { subscribe: !legacyHttp, listChanged: !legacyHttp },\n } satisfies ServerCapabilities;\n\n const cacheScope: 'private' | 'public' = extraDeps?.apiKey ? 'private' : 'public';\n const serverConfig: NonNullable<ConstructorParameters<typeof McpServer>[1]> = {\n capabilities,\n enforceStrictCapabilities: true,\n cacheHints: {\n 'tools/list': { ttlMs: 3_600_000, cacheScope },\n 'prompts/list': { ttlMs: 3_600_000, cacheScope },\n // Always private, never `cacheScope`: this list enumerates the\n // ResourceStore, so its entries are one caller's externalized tool output\n // — the same entries whose own read hint is `private` (resources.ts). The\n // other four lists are the same for every caller and follow the key, and\n // they are built from frozen module-level arrays that cannot change at\n // runtime, so an hour is honest: a shorter hint only buys the client a\n // re-fetch of bytes that cannot have differed, at the cost of the prompt\n // cache stability the spec asks deterministic ordering to preserve.\n 'resources/list': { ttlMs: 30_000, cacheScope: 'private' },\n 'resources/templates/list': { ttlMs: 3_600_000, cacheScope },\n 'server/discover': { ttlMs: 3_600_000, cacheScope },\n },\n // Multi-round-trip `requestState` integrity (protocol revision 2026-07-28):\n // the codec verifies the HMAC on every retried round before the handler\n // runs, so a tampered or expired state is rejected as `-32602` rather than\n // trusted. The decoded `PendingState` reaches handlers via\n // `ctx.mcpReq.requestState<T>()`.\n // eslint-disable-next-line @typescript-eslint/unbound-method\n requestState: { verify: requestStateCodec.verify },\n // The ResourceStore fires list-changed on every externalization and on\n // every expiry prune (store.ts), so parallel tools/call on one\n // connection can send the same notification several times in one tick;\n // each copy makes the client refetch an unchanged list. The SDK\n // coalesces same-tick duplicates for methods listed here.\n // ponytail: same-tick coalescing only — cross-tick bursts (batch\n // externalizations separated by fs awaits) still fan out; a store-level\n // debounce is the fix if clients ever report that. The modern HTTP leg\n // publishes through the ServerEventBus, which this option does not\n // touch, so it protects the stdio/in-memory leg.\n debouncedNotificationMethods: ['notifications/resources/list_changed'],\n };\n\n serverConfig.instructions =\n `${INSTRUCTIONS_SUMMARY} ` +\n `For full guidance, read ${INSTRUCTIONS_URI} or run the get-help prompt.`;\n\n const implementation: Implementation = {\n name: 'filesystem-mcp',\n title: 'Filesystem MCP',\n version: SERVER_VERSION,\n ...(SERVER_DESCRIPTION ? { description: SERVER_DESCRIPTION } : {}),\n ...(SERVER_HOMEPAGE ? { websiteUrl: SERVER_HOMEPAGE } : {}),\n };\n const server = new McpServer(implementation, serverConfig);\n server.server.fallbackNotificationHandler = (notification) => {\n Logger.debug('Unhandled client notification', { method: notification.method });\n return Promise.resolve();\n };\n\n // A failed send means the connection went away; nothing to recover.\n const resourceStore =\n extraDeps?.resourceStore ??\n new ResourceStore(() => {\n if (extraDeps?.notifier) {\n extraDeps.notifier.resourcesChanged();\n return;\n }\n void server.server.sendResourceListChanged().catch((error: unknown) => {\n Logger.debug('resource list_changed not delivered', { error: String(error) });\n });\n });\n const pageStore = extraDeps?.pageStore ?? new PageSnapshotStore();\n\n const pathGuard = extraDeps?.pathGuard ?? new PathGuard(options);\n // Recompute once per guard, keyed on the guard's own state rather than on\n // whether it was injected — an injected-but-uninitialized guard would\n // otherwise deny every path. Not unconditional: the HTTP leg shares one guard\n // across a fresh instance per request, and a repeat recompute would both redo\n // the root resolution per request and race a concurrent `applyGrant`, which\n // commits under a mutation lock this call does not take.\n if (!pathGuard.isInitialized()) await pathGuard.recomputeAllowedDirectories();\n\n const deps = {\n server,\n pathGuard,\n pageStore,\n resourceStore,\n cacheScope,\n ...(options.readOnly ? { readOnly: true } : {}),\n ...(extraDeps?.watcherRegistry ? { watcherRegistry: extraDeps.watcherRegistry } : {}),\n ...(extraDeps?.notifier ? { notifier: extraDeps.notifier } : {}),\n ...(extraDeps?.era ? { era: extraDeps.era } : {}),\n };\n\n const resourceDisposable = registerResources(deps);\n registerPrompts(deps);\n registerTools(deps);\n\n // False when the store is shared across instances and outlives this one.\n const ownsPages = extraDeps?.pageStore === undefined;\n let cleanedUp = false;\n return {\n mcp: server,\n pathGuard,\n disposeRuntimeState() {\n if (cleanedUp) return;\n cleanedUp = true;\n if (ownsPages) pageStore.clear();\n resourceDisposable.dispose();\n },\n };\n}\n"]}
@@ -24,13 +24,10 @@ type BatchInput<TOverride> = {
24
24
  path: string;
25
25
  } & TOverride)[];
26
26
  };
27
- interface RunOverPathsOptions {
28
- defaultErrorCode?: ErrorCode;
29
- }
30
- export declare function runOverPaths<TOverride, TPerPath>(args: BatchInput<TOverride>, ctx: ToolCtx, perPath: (item: {
27
+ export declare function runOverPaths<TOverride, TPerPath>(args: BatchInput<TOverride>, ctx: ToolCtx, defaultErrorCode: ErrorCode, perPath: (item: {
31
28
  path: string;
32
29
  override?: TOverride;
33
- }, ctx: ToolCtx) => Promise<TPerPath>, options?: RunOverPathsOptions): Promise<BatchResult<TPerPath>>;
30
+ }, ctx: ToolCtx) => Promise<TPerPath>): Promise<BatchResult<TPerPath>>;
34
31
  /**
35
32
  * A batch where every requested item failed produced no work at all, so it is a
36
33
  * failed call - `isError` must say so. Partial failure is deliberately NOT an
@@ -1 +1 @@
1
- {"version":3,"file":"batch.d.ts","sourceRoot":"","sources":["../../src/tools/batch.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAW,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEhE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAE7F,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC;IAC5B,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAC/D;AAED,KAAK,UAAU,CAAC,SAAS,IACvB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC,EAAE,CAAA;CAAE,CAAC;AAEvF,UAAU,mBAAmB;IAC3B,gBAAgB,CAAC,EAAE,SAAS,CAAC;CAC9B;AAeD,wBAAsB,YAAY,CAAC,SAAS,EAAE,QAAQ,EACpD,IAAI,EAAE,UAAU,CAAC,SAAS,CAAC,EAC3B,GAAG,EAAE,OAAO,EACZ,OAAO,EAAE,CAAC,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;CAAE,EAAE,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,EAC1F,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,CAgDhC;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE;IACtC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,GAAG,OAAO,CAEV"}
1
+ {"version":3,"file":"batch.d.ts","sourceRoot":"","sources":["../../src/tools/batch.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAW,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEhE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAE7F,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC;IAC5B,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAC/D;AAED,KAAK,UAAU,CAAC,SAAS,IACvB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC,EAAE,CAAA;CAAE,CAAC;AAevF,wBAAsB,YAAY,CAAC,SAAS,EAAE,QAAQ,EACpD,IAAI,EAAE,UAAU,CAAC,SAAS,CAAC,EAC3B,GAAG,EAAE,OAAO,EACZ,gBAAgB,EAAE,SAAS,EAC3B,OAAO,EAAE,CAAC,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;CAAE,EAAE,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,GACzF,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,CA8ChC;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE;IACtC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,GAAG,OAAO,CAEV"}
@@ -13,12 +13,11 @@ function normalizeBatchItems(args) {
13
13
  }));
14
14
  return [];
15
15
  }
16
- export async function runOverPaths(args, ctx, perPath, options) {
16
+ export async function runOverPaths(args, ctx, defaultErrorCode, perPath) {
17
17
  const items = normalizeBatchItems(args);
18
18
  if (items.length === 0) {
19
19
  throw new FsError(ErrorCode.INVALID_INPUT, "runOverPaths: at least one of 'path', 'paths', or 'files' must be provided");
20
20
  }
21
- const defaultErrorCode = options?.defaultErrorCode ?? ErrorCode.UNKNOWN;
22
21
  const total = items.length;
23
22
  let completed = 0;
24
23
  const results = new Array(total);
@@ -1 +1 @@
1
- {"version":3,"file":"batch.js","sourceRoot":"","sources":["../../src/tools/batch.ts"],"names":[],"mappings":"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","sourcesContent":["import { processInParallel } from '../core/concurrency.ts';\nimport { ErrorCode, FsError, Problem } from '../core/errors.ts';\nimport { PARALLEL_CONCURRENCY } from '../core/util.ts';\nimport type { ToolCtx } from './define.ts';\n\nexport type PerPathResult<T> = { path: string; value: T } | { path: string; error: Problem };\n\nexport interface BatchResult<T> {\n results: PerPathResult<T>[];\n summary: { total: number; succeeded: number; failed: number };\n}\n\ntype BatchInput<TOverride> =\n { path: string } | { paths: string[] } | { files: ({ path: string } & TOverride)[] };\n\ninterface RunOverPathsOptions {\n defaultErrorCode?: ErrorCode;\n}\n\nfunction normalizeBatchItems<TOverride>(\n args: BatchInput<TOverride>,\n): { path: string; override?: TOverride }[] {\n if ('path' in args) return [{ path: args.path }];\n if ('paths' in args) return args.paths.map((path) => ({ path }));\n if ('files' in args)\n return args.files.map(({ path, ...rest }) => ({\n path,\n override: rest as TOverride,\n }));\n return [];\n}\n\nexport async function runOverPaths<TOverride, TPerPath>(\n args: BatchInput<TOverride>,\n ctx: ToolCtx,\n perPath: (item: { path: string; override?: TOverride }, ctx: ToolCtx) => Promise<TPerPath>,\n options?: RunOverPathsOptions,\n): Promise<BatchResult<TPerPath>> {\n const items = normalizeBatchItems(args);\n if (items.length === 0) {\n throw new FsError(\n ErrorCode.INVALID_INPUT,\n \"runOverPaths: at least one of 'path', 'paths', or 'files' must be provided\",\n );\n }\n\n const defaultErrorCode = options?.defaultErrorCode ?? ErrorCode.UNKNOWN;\n\n const total = items.length;\n let completed = 0;\n const results: PerPathResult<TPerPath>[] = new Array<PerPathResult<TPerPath>>(total);\n\n const tick = (): void => {\n completed += 1;\n ctx.onProgress?.({ current: completed, total });\n };\n\n await processInParallel(\n items,\n async (item, index) => {\n try {\n const value = await perPath(item, ctx);\n results[index] = { path: item.path, value };\n } catch (error: unknown) {\n results[index] = {\n path: item.path,\n error: Problem.fromUnknown(error, defaultErrorCode, item.path),\n };\n } finally {\n tick();\n }\n },\n PARALLEL_CONCURRENCY,\n ctx.signal,\n );\n\n let succeeded = 0;\n for (const result of results) {\n if (!('error' in result)) succeeded += 1;\n }\n\n return {\n results,\n summary: { total, succeeded, failed: total - succeeded },\n };\n}\n\n/**\n * A batch where every requested item failed produced no work at all, so it is a\n * failed call - `isError` must say so. Partial failure is deliberately NOT an\n * error: the per-item entries carry which failed, and the succeeded ones really\n * were done. Zero items is not a failure either.\n */\nexport function isTotalFailure(summary: {\n readonly total: number;\n readonly failed: number;\n}): boolean {\n return summary.total > 0 && summary.failed === summary.total;\n}\n"]}
1
+ {"version":3,"file":"batch.js","sourceRoot":"","sources":["../../src/tools/batch.ts"],"names":[],"mappings":"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","sourcesContent":["import { processInParallel } from '../core/concurrency.ts';\nimport { ErrorCode, FsError, Problem } from '../core/errors.ts';\nimport { PARALLEL_CONCURRENCY } from '../core/util.ts';\nimport type { ToolCtx } from './define.ts';\n\nexport type PerPathResult<T> = { path: string; value: T } | { path: string; error: Problem };\n\nexport interface BatchResult<T> {\n results: PerPathResult<T>[];\n summary: { total: number; succeeded: number; failed: number };\n}\n\ntype BatchInput<TOverride> =\n { path: string } | { paths: string[] } | { files: ({ path: string } & TOverride)[] };\n\nfunction normalizeBatchItems<TOverride>(\n args: BatchInput<TOverride>,\n): { path: string; override?: TOverride }[] {\n if ('path' in args) return [{ path: args.path }];\n if ('paths' in args) return args.paths.map((path) => ({ path }));\n if ('files' in args)\n return args.files.map(({ path, ...rest }) => ({\n path,\n override: rest as TOverride,\n }));\n return [];\n}\n\nexport async function runOverPaths<TOverride, TPerPath>(\n args: BatchInput<TOverride>,\n ctx: ToolCtx,\n defaultErrorCode: ErrorCode,\n perPath: (item: { path: string; override?: TOverride }, ctx: ToolCtx) => Promise<TPerPath>,\n): Promise<BatchResult<TPerPath>> {\n const items = normalizeBatchItems(args);\n if (items.length === 0) {\n throw new FsError(\n ErrorCode.INVALID_INPUT,\n \"runOverPaths: at least one of 'path', 'paths', or 'files' must be provided\",\n );\n }\n\n const total = items.length;\n let completed = 0;\n const results: PerPathResult<TPerPath>[] = new Array<PerPathResult<TPerPath>>(total);\n\n const tick = (): void => {\n completed += 1;\n ctx.onProgress?.({ current: completed, total });\n };\n\n await processInParallel(\n items,\n async (item, index) => {\n try {\n const value = await perPath(item, ctx);\n results[index] = { path: item.path, value };\n } catch (error: unknown) {\n results[index] = {\n path: item.path,\n error: Problem.fromUnknown(error, defaultErrorCode, item.path),\n };\n } finally {\n tick();\n }\n },\n PARALLEL_CONCURRENCY,\n ctx.signal,\n );\n\n let succeeded = 0;\n for (const result of results) {\n if (!('error' in result)) succeeded += 1;\n }\n\n return {\n results,\n summary: { total, succeeded, failed: total - succeeded },\n };\n}\n\n/**\n * A batch where every requested item failed produced no work at all, so it is a\n * failed call - `isError` must say so. Partial failure is deliberately NOT an\n * error: the per-item entries carry which failed, and the succeeded ones really\n * were done. Zero items is not a failure either.\n */\nexport function isTotalFailure(summary: {\n readonly total: number;\n readonly failed: number;\n}): boolean {\n return summary.total > 0 && summary.failed === summary.total;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/tools/create.ts"],"names":[],"mappings":"AAiGA,eAAO,MAAM,MAAM,mCAgOjB,CAAC"}
1
+ {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/tools/create.ts"],"names":[],"mappings":"AA8GA,eAAO,MAAM,MAAM,mCAqNjB,CAAC"}
@@ -2,10 +2,11 @@ import { basename, dirname } from 'node:path';
2
2
  import * as z from 'zod/v4';
3
3
  import { processInParallel } from "../core/concurrency.js";
4
4
  import { ErrorCode, formatUnknownErrorMessage, FsError, rethrowIfAborted } from "../core/errors.js";
5
- import { buildFileResourceLink, buildFileResourceUri, buildWrittenFileMeta, } from "../core/file-uri.js";
5
+ import { buildWrittenFileMeta, writtenFileLinks } from "../core/file-uri.js";
6
6
  import { countFileLines, destExists } from "../core/fs.js";
7
7
  import { confirmKey, describeRefusal, pendingRoundTrip, readAcceptedChoice, } from "../core/input-required.js";
8
8
  import { detectMimeFromContent, MIME_SAMPLE_SIZE } from "../core/mime.js";
9
+ import { isSamePath } from "../core/path-utils.js";
9
10
  import { FileKind, IsoDateTime, NonNegInt, PathFailureSchema, RequiredPath, } from "../core/schema.js";
10
11
  import { getMaxTextFileSize, PARALLEL_CONCURRENCY } from "../core/util.js";
11
12
  import { isTotalFailure, runOverPaths } from "./batch.js";
@@ -41,12 +42,29 @@ const CreateFileResultSchema = z.strictObject({
41
42
  created: IsoDateTime.describe('File creation timestamp (ISO 8601 UTC)'),
42
43
  modified: IsoDateTime.describe('File last-modification timestamp (ISO 8601 UTC)'),
43
44
  });
44
- const CreateInputSchema = z.strictObject({
45
+ const CreateInputSchema = z
46
+ .strictObject({
45
47
  files: z
46
48
  .array(CreateFileItemSchema)
47
49
  .min(1)
48
50
  .max(100)
49
51
  .describe('List of files to create (max 100); each entry requires path and content'),
52
+ })
53
+ .superRefine((value, ctx) => {
54
+ // Entries write in parallel and each replaces its file whole, so two
55
+ // entries for one path race: the last rename wins and the other's
56
+ // content is lost while both report success. One entry per file.
57
+ for (const [index, file] of value.files.entries()) {
58
+ const first = value.files.findIndex((other) => isSamePath(other.path, file.path));
59
+ if (first < index) {
60
+ ctx.addIssue({
61
+ code: 'custom',
62
+ path: ['files', index, 'path'],
63
+ message: `duplicate of files[${String(first)}].path; one entry per file`,
64
+ input: value,
65
+ });
66
+ }
67
+ }
50
68
  });
51
69
  const CreateOutputSchema = z.strictObject({
52
70
  files: z.array(CreateFileResultSchema).describe('Successfully created files'),
@@ -116,7 +134,7 @@ export const CREATE = defineTool({
116
134
  if (round !== undefined)
117
135
  return round;
118
136
  }
119
- const batch = await runOverPaths({ files: args.files }, ctx, async ({ path, override }) => {
137
+ const batch = await runOverPaths({ files: args.files }, ctx, ErrorCode.UNKNOWN, async ({ path, override }) => {
120
138
  const content = override?.content ?? '';
121
139
  const pendingPath = pendingByPath.get(path);
122
140
  if (pendingPath !== undefined) {
@@ -142,7 +160,6 @@ export const CREATE = defineTool({
142
160
  let modified;
143
161
  if (override?.append) {
144
162
  const appended = await ctx.fs.appendFile(path, content, {
145
- encoding: 'utf-8',
146
163
  signal: ctx.signal,
147
164
  });
148
165
  validPath = appended.validPath;
@@ -177,35 +194,32 @@ export const CREATE = defineTool({
177
194
  mimeType = mimeInfo.mimeType;
178
195
  kind = mimeInfo.kind;
179
196
  }
180
- // The resource store serves this URI via readRaw, which rejects
181
- // files over the text-size cap with TOO_LARGE — never advertise a
182
- // link the store deterministically cannot serve. A failed stat
183
- // leaves the size unknown, so nothing is advertised either.
184
- const servable = stats !== undefined && stats.size <= getMaxTextFileSize();
185
- const size = stats?.size ?? 0;
197
+ // A failed stat leaves the size unknown, so nothing is advertised.
186
198
  meta = {
187
- size,
199
+ size: stats?.size ?? 0,
188
200
  lineCount,
189
201
  mimeType,
190
202
  kind,
191
- resourceUri: servable ? buildFileResourceUri(appended.validPath) : undefined,
192
- resourceLink: servable && ctx.resourceStore
193
- ? buildFileResourceLink(appended.validPath, mimeType, size)
194
- : undefined,
203
+ ...writtenFileLinks(appended.validPath, mimeType, stats?.size, {
204
+ resourceStore: ctx.resourceStore,
205
+ }),
195
206
  };
196
207
  created = (stats?.birthtime ?? EPOCH).toISOString();
197
208
  modified = (stats?.mtime ?? EPOCH).toISOString();
198
209
  }
199
210
  else {
200
211
  const written = await ctx.fs.writeFile(path, content, {
201
- encoding: 'utf-8',
202
212
  signal: ctx.signal,
203
213
  });
204
214
  validPath = written.validPath;
205
215
  const fileStats = (await ctx.fs.stat(path, { signal: ctx.signal })).stats;
206
216
  created = fileStats.birthtime.toISOString();
207
217
  modified = fileStats.mtime.toISOString();
208
- meta = buildWrittenFileMeta(written.validPath, content, ctx.resourceStore);
218
+ meta = buildWrittenFileMeta({
219
+ validPath: written.validPath,
220
+ content,
221
+ resourceStore: ctx.resourceStore,
222
+ });
209
223
  }
210
224
  const file = {
211
225
  path: validPath,
@@ -218,7 +232,7 @@ export const CREATE = defineTool({
218
232
  modified,
219
233
  };
220
234
  return meta.resourceLink ? { file, resourceLink: meta.resourceLink } : { file };
221
- }, { defaultErrorCode: ErrorCode.UNKNOWN });
235
+ });
222
236
  const results = [];
223
237
  const failures = [];
224
238
  const skipped = [];
@@ -256,6 +270,5 @@ export const CREATE = defineTool({
256
270
  ? basename(args.files[0]?.path ?? '')
257
271
  : `${String(args.files.length)} files`,
258
272
  }),
259
- defaultErrorCode: ErrorCode.UNKNOWN,
260
273
  });
261
274
  //# sourceMappingURL=create.js.map