@j0hanz/filesystem-mcp 2.8.1 → 2.9.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 (116) hide show
  1. package/README.md +33 -27
  2. package/dist/cli-help.d.ts.map +1 -1
  3. package/dist/cli-help.js +10 -1
  4. package/dist/cli-help.js.map +1 -1
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +10 -3
  7. package/dist/cli.js.map +1 -1
  8. package/dist/core/concurrency.d.ts +4 -4
  9. package/dist/core/concurrency.d.ts.map +1 -1
  10. package/dist/core/concurrency.js +1 -1
  11. package/dist/core/concurrency.js.map +1 -1
  12. package/dist/core/config.d.ts +2 -0
  13. package/dist/core/config.d.ts.map +1 -1
  14. package/dist/core/config.js.map +1 -1
  15. package/dist/core/errors.d.ts.map +1 -1
  16. package/dist/core/errors.js +7 -1
  17. package/dist/core/errors.js.map +1 -1
  18. package/dist/core/file-uri.d.ts +12 -3
  19. package/dist/core/file-uri.d.ts.map +1 -1
  20. package/dist/core/file-uri.js +35 -7
  21. package/dist/core/file-uri.js.map +1 -1
  22. package/dist/core/fmt.d.ts +2 -0
  23. package/dist/core/fmt.d.ts.map +1 -1
  24. package/dist/core/fmt.js +16 -0
  25. package/dist/core/fmt.js.map +1 -1
  26. package/dist/core/fs.d.ts +16 -1
  27. package/dist/core/fs.d.ts.map +1 -1
  28. package/dist/core/fs.js +127 -32
  29. package/dist/core/fs.js.map +1 -1
  30. package/dist/core/glob.d.ts +15 -0
  31. package/dist/core/glob.d.ts.map +1 -1
  32. package/dist/core/glob.js +119 -14
  33. package/dist/core/glob.js.map +1 -1
  34. package/dist/core/path-completer.d.ts.map +1 -1
  35. package/dist/core/path-completer.js +5 -3
  36. package/dist/core/path-completer.js.map +1 -1
  37. package/dist/core/path-utils.d.ts +42 -6
  38. package/dist/core/path-utils.d.ts.map +1 -1
  39. package/dist/core/path-utils.js +56 -10
  40. package/dist/core/path-utils.js.map +1 -1
  41. package/dist/core/path.d.ts +111 -7
  42. package/dist/core/path.d.ts.map +1 -1
  43. package/dist/core/path.js +453 -65
  44. package/dist/core/path.js.map +1 -1
  45. package/dist/core/read.d.ts +6 -0
  46. package/dist/core/read.d.ts.map +1 -1
  47. package/dist/core/read.js +26 -3
  48. package/dist/core/read.js.map +1 -1
  49. package/dist/core/schema.d.ts +9 -6
  50. package/dist/core/schema.d.ts.map +1 -1
  51. package/dist/core/schema.js +22 -19
  52. package/dist/core/schema.js.map +1 -1
  53. package/dist/core/search.d.ts +6 -0
  54. package/dist/core/search.d.ts.map +1 -1
  55. package/dist/core/search.js +161 -33
  56. package/dist/core/search.js.map +1 -1
  57. package/dist/core/sensitive.d.ts.map +1 -1
  58. package/dist/core/sensitive.js +64 -15
  59. package/dist/core/sensitive.js.map +1 -1
  60. package/dist/core/store.d.ts +1 -0
  61. package/dist/core/store.d.ts.map +1 -1
  62. package/dist/core/store.js +3 -0
  63. package/dist/core/store.js.map +1 -1
  64. package/dist/core/util.d.ts +12 -1
  65. package/dist/core/util.d.ts.map +1 -1
  66. package/dist/core/util.js +16 -1
  67. package/dist/core/util.js.map +1 -1
  68. package/dist/resources.d.ts.map +1 -1
  69. package/dist/resources.js +20 -11
  70. package/dist/resources.js.map +1 -1
  71. package/dist/server.js +1 -1
  72. package/dist/server.js.map +1 -1
  73. package/dist/tools/create.d.ts.map +1 -1
  74. package/dist/tools/create.js +121 -43
  75. package/dist/tools/create.js.map +1 -1
  76. package/dist/tools/define.d.ts +8 -4
  77. package/dist/tools/define.d.ts.map +1 -1
  78. package/dist/tools/define.js +36 -9
  79. package/dist/tools/define.js.map +1 -1
  80. package/dist/tools/delete.d.ts.map +1 -1
  81. package/dist/tools/delete.js +50 -11
  82. package/dist/tools/delete.js.map +1 -1
  83. package/dist/tools/diff.js +1 -1
  84. package/dist/tools/diff.js.map +1 -1
  85. package/dist/tools/edit.d.ts.map +1 -1
  86. package/dist/tools/edit.js +14 -12
  87. package/dist/tools/edit.js.map +1 -1
  88. package/dist/tools/find-files.d.ts.map +1 -1
  89. package/dist/tools/find-files.js +15 -9
  90. package/dist/tools/find-files.js.map +1 -1
  91. package/dist/tools/list.d.ts.map +1 -1
  92. package/dist/tools/list.js +141 -86
  93. package/dist/tools/list.js.map +1 -1
  94. package/dist/tools/move.d.ts.map +1 -1
  95. package/dist/tools/move.js +72 -26
  96. package/dist/tools/move.js.map +1 -1
  97. package/dist/tools/patch.js +13 -6
  98. package/dist/tools/patch.js.map +1 -1
  99. package/dist/tools/read.d.ts.map +1 -1
  100. package/dist/tools/read.js +113 -48
  101. package/dist/tools/read.js.map +1 -1
  102. package/dist/tools/replace-text.d.ts.map +1 -1
  103. package/dist/tools/replace-text.js +50 -35
  104. package/dist/tools/replace-text.js.map +1 -1
  105. package/dist/tools/search-text.d.ts.map +1 -1
  106. package/dist/tools/search-text.js +15 -33
  107. package/dist/tools/search-text.js.map +1 -1
  108. package/dist/tools/stat.d.ts.map +1 -1
  109. package/dist/tools/stat.js +26 -8
  110. package/dist/tools/stat.js.map +1 -1
  111. package/dist/transport/http.d.ts.map +1 -1
  112. package/dist/transport/http.js +1 -0
  113. package/dist/transport/http.js.map +1 -1
  114. package/dist/transport/stdio.js +1 -1
  115. package/dist/transport/stdio.js.map +1 -1
  116. package/package.json +5 -5
@@ -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, voidSend } 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 { MAX_OUTBOUND_RESULT_BYTES, MAX_TOOL_INPUT_ELEMENTS } from './core/util.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 * Only `stdio` has the SDK's fixed 10 MiB ReadBuffer, so only there are\n * results capped: any tool call that changed nothing, any resource read,\n * and a result-store entry by the size of its read (define.ts,\n * resources.ts, store.ts).\n */\n transport?: 'stdio' | 'http';\n /**\n * `private` when the HTTP host requires a bearer key, so a shared cache\n * never serves one tenant's listing to another. Default `public`.\n */\n cacheScope?: 'private' | 'public';\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 // sunset(SEP-2577): removal trigger in docs/adr/002-legacy-protocol-paths-sunset.md.\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 = extraDeps?.cacheScope ?? 'public';\n const serverConfig: NonNullable<ConstructorParameters<typeof McpServer>[1]> = {\n capabilities,\n enforceStrictCapabilities: true,\n // Structural bound on tools/call arguments, checked before Zod walks the\n // value: the body limit is bytes (31 MiB by default), and every per-tool\n // `.max()` runs only after a full traversal. No schema-legal call comes\n // near this (see MAX_TOOL_INPUT_ELEMENTS); a payload over it is answered\n // with a tool error after counting at most 4097 nodes.\n maxToolInputElements: MAX_TOOL_INPUT_ELEMENTS,\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 const resourceStore =\n extraDeps?.resourceStore ??\n new ResourceStore(\n () => {\n if (extraDeps?.notifier) {\n extraDeps.notifier.resourcesChanged();\n return;\n }\n voidSend(server.server.sendResourceListChanged(), 'resource list_changed');\n },\n // A tool hands out no link that this connection's resources/read refuses.\n extraDeps?.transport === 'stdio' ? { maxReadResultBytes: MAX_OUTBOUND_RESULT_BYTES } : {},\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 readOnly: options.readOnly ?? false,\n ...(extraDeps?.watcherRegistry ? { watcherRegistry: extraDeps.watcherRegistry } : {}),\n ...(extraDeps?.notifier ? { notifier: extraDeps.notifier } : {}),\n ...(extraDeps?.era ? { era: extraDeps.era } : {}),\n ...(extraDeps?.transport ? { transport: extraDeps.transport } : {}),\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 const context: FilesystemServerContext = {\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 const previousOnClose = server.server.onclose;\n server.server.onclose = () => {\n previousOnClose?.();\n context.disposeRuntimeState();\n };\n return context;\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, voidSend } 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 { MAX_OUTBOUND_RESULT_BYTES, MAX_TOOL_INPUT_ELEMENTS } from './core/util.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 * Only `stdio` has the SDK's fixed 10 MiB ReadBuffer, so only there are\n * results capped: any tool call that changed nothing, any resource read,\n * and a result-store entry by the size of its read (define.ts,\n * resources.ts, store.ts).\n */\n transport?: 'stdio' | 'http';\n /**\n * `private` when the HTTP host requires a bearer key, so a shared cache\n * never serves one tenant's listing to another. Default `public`.\n */\n cacheScope?: 'private' | 'public';\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 // sunset(SEP-2577): removal trigger in docs/adr/002-legacy-protocol-paths-sunset.md.\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 = extraDeps?.cacheScope ?? 'public';\n const serverConfig: NonNullable<ConstructorParameters<typeof McpServer>[1]> = {\n capabilities,\n enforceStrictCapabilities: true,\n // Structural bound on tools/call arguments, checked before Zod walks the\n // value: the body limit is bytes (31 MiB by default), and every per-tool\n // `.max()` runs only after a full traversal. No schema-legal call comes\n // near this (see MAX_TOOL_INPUT_ELEMENTS); a payload over it is answered\n // with a tool error after counting at most 4097 nodes.\n maxToolInputElements: MAX_TOOL_INPUT_ELEMENTS,\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 const resourceStore =\n extraDeps?.resourceStore ??\n new ResourceStore(\n () => {\n if (extraDeps?.notifier) {\n extraDeps.notifier.resourcesChanged();\n return;\n }\n voidSend(server.server.sendResourceListChanged(), 'resource list_changed');\n },\n // A tool hands out no result-store link that this connection's resources/read refuses; file links are gated in writtenFileLinks.\n extraDeps?.transport === 'stdio' ? { maxReadResultBytes: MAX_OUTBOUND_RESULT_BYTES } : {},\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 readOnly: options.readOnly ?? false,\n ...(extraDeps?.watcherRegistry ? { watcherRegistry: extraDeps.watcherRegistry } : {}),\n ...(extraDeps?.notifier ? { notifier: extraDeps.notifier } : {}),\n ...(extraDeps?.era ? { era: extraDeps.era } : {}),\n ...(extraDeps?.transport ? { transport: extraDeps.transport } : {}),\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 const context: FilesystemServerContext = {\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 const previousOnClose = server.server.onclose;\n server.server.onclose = () => {\n previousOnClose?.();\n context.disposeRuntimeState();\n };\n return context;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/tools/create.ts"],"names":[],"mappings":"AA0GA,eAAO,MAAM,MAAM,mCA6HjB,CAAC"}
1
+ {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/tools/create.ts"],"names":[],"mappings":"AAmIA,eAAO,MAAM,MAAM,mCA6KjB,CAAC"}
@@ -6,12 +6,17 @@ import { buildWrittenFileMeta, writtenFileLinks } from "../core/file-uri.js";
6
6
  import { destExists } from "../core/fs.js";
7
7
  import { confirmKey, pendingRoundTrip, readPendingVerdict } from "../core/input-required.js";
8
8
  import { detectMimeFromContent } from "../core/mime.js";
9
- import { isSamePath } from "../core/path-utils.js";
9
+ import { isEquivalentPath } from "../core/path-utils.js";
10
10
  import { RequiredPath } from "../core/schema.js";
11
11
  import { getMaxTextFileSize, PARALLEL_CONCURRENCY } from "../core/util.js";
12
12
  import { isTotalFailure, runOverPaths } from "./batch.js";
13
13
  import { defineTool } from "./define.js";
14
14
  const EPOCH = new Date(0);
15
+ // runOverPaths rejects an empty list, and a call may carry only one of the two lists.
16
+ const NO_ENTRIES = {
17
+ results: [],
18
+ summary: { total: 0, succeeded: 0, failed: 0 },
19
+ };
15
20
  const CreateFileItemSchema = z.strictObject({
16
21
  path: RequiredPath.describe('Absolute path where the file will be created'),
17
22
  content: z
@@ -35,14 +40,31 @@ const CreateInputSchema = z
35
40
  .array(CreateFileItemSchema)
36
41
  .min(1)
37
42
  .max(100)
43
+ .optional()
38
44
  .describe('List of files to create (max 100); each entry requires path and content; combined content is capped at the file-size limit'),
45
+ directories: z
46
+ .array(RequiredPath)
47
+ .min(1)
48
+ .max(100)
49
+ .optional()
50
+ .describe('Empty directories to create, like mkdir -p; one that already exists is left as it is'),
39
51
  })
40
52
  .superRefine((value, ctx) => {
53
+ if (value.files === undefined && value.directories === undefined) {
54
+ ctx.addIssue({
55
+ code: 'custom',
56
+ path: ['files'],
57
+ message: 'Provide files, directories, or both',
58
+ input: value,
59
+ });
60
+ }
61
+ const files = value.files ?? [];
41
62
  // Entries write in parallel and each replaces its file whole, so two
42
63
  // entries for one path race: the last rename wins and the other's
43
- // content is lost while both report success. One entry per file.
44
- for (const [index, file] of value.files.entries()) {
45
- const first = value.files.findIndex((other) => isSamePath(other.path, file.path));
64
+ // content is lost while both report success. One entry per file, counting
65
+ // the Unicode spellings the path guard resolves to one name (#101).
66
+ for (const [index, file] of files.entries()) {
67
+ const first = files.findIndex((other) => isEquivalentPath(other.path, file.path));
46
68
  if (first < index) {
47
69
  ctx.addIssue({
48
70
  code: 'custom',
@@ -56,7 +78,7 @@ const CreateInputSchema = z
56
78
  // size their message bound from this same limit (getMaxInboundMessageBytes),
57
79
  // so a batch that passes here always fits on the wire. Counted in the same
58
80
  // UTF-16 units as the per-file refine above.
59
- const total = value.files.reduce((sum, file) => sum + file.content.length, 0);
81
+ const total = files.reduce((sum, file) => sum + file.content.length, 0);
60
82
  const limit = getMaxTextFileSize();
61
83
  if (total > limit) {
62
84
  ctx.addIssue({
@@ -70,19 +92,25 @@ const CreateInputSchema = z
70
92
  export const CREATE = defineTool({
71
93
  name: 'create',
72
94
  title: 'Create Files',
73
- description: 'Write whole text files: create new ones, overwrite, or append. Missing parent directories are created. ' +
74
- 'edit or patch changes part of an existing file.',
95
+ description: 'Write whole text files (create new ones, overwrite, or append) or make empty directories. ' +
96
+ 'Missing parent directories are created. edit or patch changes part of an existing file.',
75
97
  input: CreateInputSchema,
76
98
  readOnlyHint: false,
77
- accessPaths: (args) => args.files.map((f) => f.path),
99
+ mapPaths: (args, fn) => ({
100
+ ...args,
101
+ ...(args.files ? { files: args.files.map((file) => ({ ...file, path: fn(file.path) })) } : {}),
102
+ ...(args.directories ? { directories: args.directories.map((dir) => fn(dir)) } : {}),
103
+ }),
78
104
  run: async (args, ctx) => {
105
+ const files = args.files ?? [];
106
+ const dirs = args.directories ?? [];
79
107
  // Phase 1 (no mutation): which entries would replace a file that exists
80
108
  // right now? Those need the user's word before anything is written (R14).
81
109
  // append never destroys, and overwrite: true is that word given up front.
82
110
  // A path that fails validation here is left for runOverPaths to report.
83
111
  // A file that appears between rounds needs no re-stat before the write:
84
112
  // the retry re-plans, and the grown pending set fails the R9 check.
85
- const { results: planned } = await processInParallel(args.files, async (entry) => {
113
+ const { results: planned } = await processInParallel(files, async (entry) => {
86
114
  if (entry.append || entry.overwrite)
87
115
  return undefined;
88
116
  try {
@@ -96,7 +124,7 @@ export const CREATE = defineTool({
96
124
  }, PARALLEL_CONCURRENCY, ctx.signal);
97
125
  const pendingByPath = new Map();
98
126
  for (const { index, value } of planned) {
99
- const requested = args.files[index]?.path;
127
+ const requested = files[index]?.path;
100
128
  if (value && requested)
101
129
  pendingByPath.set(requested, value);
102
130
  }
@@ -116,33 +144,61 @@ export const CREATE = defineTool({
116
144
  if (round !== undefined)
117
145
  return round;
118
146
  }
119
- const batch = await runOverPaths(args.files, ctx, ErrorCode.UNKNOWN, async ({ path, content, append, overwrite }) => {
120
- const pendingPath = pendingByPath.get(path);
121
- if (pendingPath !== undefined) {
122
- const verdict = readPendingVerdict(ctx, pendingSorted, pendingPath, 'overwrite');
123
- if (verdict === 'skipped')
124
- return { skipped: path };
125
- if (verdict !== 'accepted') {
126
- throw new FsError(ErrorCode.CANCELLED, `create cancelled: overwrite of "${path}" was ${verdict.refused}`, path);
127
- }
128
- }
129
- // Lock per resolved file so two entries naming one file (e.g. through a
130
- // symlinked parent) cannot clobber each other.
131
- return ctx.fs.lockForWrite(path, async (resolvedTarget) => {
132
- // The file may have appeared since the overwrite set was planned.
133
- if (!append &&
134
- !overwrite &&
135
- pendingPath === undefined &&
136
- (await destExists(ctx.fs, resolvedTarget, 'create'))) {
137
- throw new FsError(ErrorCode.INVALID_INPUT, `create refused: "${path}" appeared after overwrite confirmation was checked`, path);
138
- }
139
- return createOne(path, content, append, ctx);
140
- });
147
+ // One counter across both batches: ProgressSession drops a value at or below its last one.
148
+ const progressTotal = dirs.length + files.length;
149
+ const progressFrom = (offset) => ({
150
+ ...ctx,
151
+ onProgress: ({ current }) => {
152
+ ctx.onProgress({ current: offset + current, total: progressTotal });
153
+ },
141
154
  });
155
+ // Directories wait for the overwrite question above, so nothing is written
156
+ // before the user answers.
157
+ const dirBatch = dirs.length > 0
158
+ ? await runOverPaths(dirs, progressFrom(0), ErrorCode.UNKNOWN, async (dir) => {
159
+ await ctx.fs.mkdir(dir, { recursive: true });
160
+ return dir;
161
+ })
162
+ : NO_ENTRIES;
163
+ const filesCtx = progressFrom(dirs.length);
164
+ const batch = files.length > 0
165
+ ? await runOverPaths(files, filesCtx, ErrorCode.UNKNOWN, async ({ path, content, append, overwrite }) => {
166
+ const pendingPath = pendingByPath.get(path);
167
+ if (pendingPath !== undefined) {
168
+ const verdict = readPendingVerdict(ctx, pendingSorted, pendingPath, 'overwrite');
169
+ if (verdict === 'skipped')
170
+ return { skipped: path };
171
+ if (verdict !== 'accepted') {
172
+ throw new FsError(ErrorCode.CANCELLED, `create cancelled: overwrite of "${path}" was ${verdict.refused}`, path);
173
+ }
174
+ }
175
+ // Lock per resolved file so two entries naming one file (e.g. through a
176
+ // symlinked parent) cannot clobber each other.
177
+ return ctx.fs.lockForWrite(path, async (resolvedTarget) => {
178
+ // The file may have appeared since the overwrite set was planned.
179
+ if (!append &&
180
+ !overwrite &&
181
+ pendingPath === undefined &&
182
+ (await destExists(ctx.fs, resolvedTarget, 'create'))) {
183
+ throw new FsError(ErrorCode.INVALID_INPUT, `create refused: "${path}" appeared after overwrite confirmation was checked`, path);
184
+ }
185
+ return createOne(path, content, append, ctx);
186
+ });
187
+ })
188
+ : NO_ENTRIES;
142
189
  const results = [];
190
+ const directories = [];
143
191
  const failures = [];
144
192
  const skipped = [];
145
193
  const links = [];
194
+ for (const r of dirBatch.results) {
195
+ if ('error' in r) {
196
+ failures.push({ path: r.path, error: r.error });
197
+ }
198
+ else {
199
+ directories.push(r.value);
200
+ }
201
+ }
146
202
  for (const r of batch.results) {
147
203
  if ('error' in r) {
148
204
  failures.push({ path: r.path, error: r.error });
@@ -158,21 +214,35 @@ export const CREATE = defineTool({
158
214
  }
159
215
  const structured = {
160
216
  files: results,
217
+ ...(directories.length > 0 ? { directories } : {}),
161
218
  ...(failures.length > 0 ? { failures } : {}),
162
219
  ...(skipped.length > 0 ? { skipped } : {}),
163
220
  };
221
+ // Failed only when every directory and every file failed, across both lists.
222
+ const summary = {
223
+ total: dirBatch.summary.total + batch.summary.total,
224
+ succeeded: dirBatch.summary.succeeded + batch.summary.succeeded,
225
+ failed: dirBatch.summary.failed + batch.summary.failed,
226
+ };
164
227
  // No `text` on purpose. The one-line roster this used to build named the
165
228
  // paths and dropped every per-file size, line count and error the caller
166
229
  // asked `create` for. Supplying no text makes this a data tool, so
167
230
  // `define.ts` renders the JSON and keeps it in `structuredContent`.
168
- return { structured, resources: links, isError: isTotalFailure(batch.summary) };
231
+ return { structured, resources: links, isError: isTotalFailure(summary) };
232
+ },
233
+ progress: (args) => {
234
+ const files = args.files ?? [];
235
+ const dirs = args.directories ?? [];
236
+ if (files.length + dirs.length === 1) {
237
+ return { label: 'Create', subject: basename(files[0]?.path ?? dirs[0] ?? '') };
238
+ }
239
+ const counts = [];
240
+ if (files.length > 0)
241
+ counts.push(`${String(files.length)} files`);
242
+ if (dirs.length > 0)
243
+ counts.push(`${String(dirs.length)} directories`);
244
+ return { label: 'Create', subject: counts.join(', ') };
169
245
  },
170
- progress: (args) => ({
171
- label: 'Create',
172
- subject: args.files.length === 1
173
- ? basename(args.files[0]?.path ?? '')
174
- : `${String(args.files.length)} files`,
175
- }),
176
246
  });
177
247
  async function createOne(path, content, append, ctx) {
178
248
  await ctx.fs.mkdir(dirname(path), { recursive: true });
@@ -229,7 +299,7 @@ async function createOne(path, content, append, ctx) {
229
299
  lineCount,
230
300
  mimeType,
231
301
  kind,
232
- ...writtenFileLinks(appended.validPath, mimeType, stats?.size, false, stats?.mtime.toISOString()),
302
+ ...writtenFileLinks(appended.validPath, mimeType, stats?.size, false, stats?.mtime.toISOString(), ctx.transport),
233
303
  };
234
304
  created = (stats?.birthtime ?? EPOCH).toISOString();
235
305
  modified = (stats?.mtime ?? EPOCH).toISOString();
@@ -239,13 +309,21 @@ async function createOne(path, content, append, ctx) {
239
309
  signal: ctx.signal,
240
310
  });
241
311
  validPath = written.validPath;
242
- const fileStats = (await ctx.fs.stat(path, { signal: ctx.signal })).stats;
243
- created = fileStats.birthtime.toISOString();
244
- modified = fileStats.mtime.toISOString();
312
+ const nowIso = new Date().toISOString();
313
+ let fileStats;
314
+ try {
315
+ fileStats = (await ctx.fs.stat(written.validPath)).stats;
316
+ }
317
+ catch {
318
+ /* post-commit metadata only */
319
+ }
320
+ created = fileStats?.birthtime.toISOString() ?? nowIso;
321
+ modified = fileStats?.mtime.toISOString() ?? nowIso;
245
322
  meta = buildWrittenFileMeta({
246
323
  validPath: written.validPath,
247
324
  content,
248
325
  lastModified: modified,
326
+ transport: ctx.transport,
249
327
  });
250
328
  }
251
329
  const { resourceLink, ...fileMeta } = meta;
@@ -1 +1 @@
1
- {"version":3,"file":"create.js","sourceRoot":"","sources":["../../src/tools/create.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ContentBlock } from '@modelcontextprotocol/server';\n\nimport { basename, dirname } from 'node:path';\n\nimport * as z from 'zod/v4';\n\nimport { processInParallel } from '../core/concurrency.ts';\nimport {\n ErrorCode,\n formatUnknownErrorMessage,\n FsError,\n type Problem,\n rethrowIfAborted,\n} from '../core/errors.ts';\nimport { buildWrittenFileMeta, writtenFileLinks, type WrittenFileMeta } from '../core/file-uri.ts';\nimport { destExists, type Stats } from '../core/fs.ts';\nimport { confirmKey, pendingRoundTrip, readPendingVerdict } from '../core/input-required.ts';\nimport { detectMimeFromContent, type FileKind } from '../core/mime.ts';\nimport { isSamePath } from '../core/path-utils.ts';\nimport { RequiredPath } from '../core/schema.ts';\nimport { getMaxTextFileSize, PARALLEL_CONCURRENCY } from '../core/util.ts';\nimport { isTotalFailure, runOverPaths } from './batch.ts';\nimport { defineTool, type ToolCtx } from './define.ts';\n\nconst EPOCH = new Date(0);\n\nconst CreateFileItemSchema = z.strictObject({\n path: RequiredPath.describe('Absolute path where the file will be created'),\n content: z\n .string()\n .refine((val) => val.length <= getMaxTextFileSize(), {\n message: 'Content exceeds maximum allowed text file size',\n })\n .describe('Text content to write, verbatim.'),\n append: z\n .boolean()\n .optional()\n .describe(\n 'Append the content to the end of the file instead of overwriting; creates the file if it does not exist',\n ),\n overwrite: z\n .boolean()\n .optional()\n .describe(\n 'Replace an existing file without asking the user; without it an existing file prompts for confirmation',\n ),\n});\n\ntype CreateFileResult = Omit<WrittenFileMeta, 'resourceLink'> & {\n path: string;\n created: string;\n modified: string;\n};\n\nconst CreateInputSchema = z\n .strictObject({\n files: z\n .array(CreateFileItemSchema)\n .min(1)\n .max(100)\n .describe(\n 'List of files to create (max 100); each entry requires path and content; combined content is capped at the file-size limit',\n ),\n })\n .superRefine((value, ctx) => {\n // Entries write in parallel and each replaces its file whole, so two\n // entries for one path race: the last rename wins and the other's\n // content is lost while both report success. One entry per file.\n for (const [index, file] of value.files.entries()) {\n const first = value.files.findIndex((other) => isSamePath(other.path, file.path));\n if (first < index) {\n ctx.addIssue({\n code: 'custom',\n path: ['files', index, 'path'],\n message: `duplicate of files[${String(first)}].path; one entry per file`,\n input: value,\n });\n }\n }\n // One call's content is what one inbound message carries; the transports\n // size their message bound from this same limit (getMaxInboundMessageBytes),\n // so a batch that passes here always fits on the wire. Counted in the same\n // UTF-16 units as the per-file refine above.\n const total = value.files.reduce((sum, file) => sum + file.content.length, 0);\n const limit = getMaxTextFileSize();\n if (total > limit) {\n ctx.addIssue({\n code: 'custom',\n path: ['files'],\n message: `combined content is ${String(total)} characters; one create call carries at most ${String(limit)} (FS_MAX_FILE_SIZE). Split the batch.`,\n input: value,\n });\n }\n });\n\ninterface CreateFailureItem {\n path: string;\n error: Problem;\n}\n\ninterface CreateOutput {\n files: CreateFileResult[];\n failures?: CreateFailureItem[];\n skipped?: string[];\n}\n\nexport const CREATE = defineTool<typeof CreateInputSchema, CreateOutput>({\n name: 'create',\n title: 'Create Files',\n description:\n 'Write whole text files: create new ones, overwrite, or append. Missing parent directories are created. ' +\n 'edit or patch changes part of an existing file.',\n input: CreateInputSchema,\n readOnlyHint: false,\n accessPaths: (args) => args.files.map((f) => f.path),\n run: async (args, ctx) => {\n // Phase 1 (no mutation): which entries would replace a file that exists\n // right now? Those need the user's word before anything is written (R14).\n // append never destroys, and overwrite: true is that word given up front.\n // A path that fails validation here is left for runOverPaths to report.\n // A file that appears between rounds needs no re-stat before the write:\n // the retry re-plans, and the grown pending set fails the R9 check.\n const { results: planned } = await processInParallel(\n args.files,\n async (entry) => {\n if (entry.append || entry.overwrite) return undefined;\n try {\n const validPath = await ctx.fs.pathGuard.validatePathForWrite(entry.path);\n return (await destExists(ctx.fs, validPath, 'create')) ? validPath : undefined;\n } catch (error) {\n rethrowIfAborted(error);\n return undefined;\n }\n },\n PARALLEL_CONCURRENCY,\n ctx.signal,\n );\n const pendingByPath = new Map<string, string>();\n for (const { index, value } of planned) {\n const requested = args.files[index]?.path;\n if (value && requested) pendingByPath.set(requested, value);\n }\n const pendingSorted = [...new Set(pendingByPath.values())].sort();\n if (pendingSorted.length > 0) {\n // Round 1 returns input_required; a retry whose verified state does not\n // bind this overwrite set throws (R9) via `pendingRoundTrip`.\n const round = await pendingRoundTrip(ctx, {\n op: 'create',\n pending: pendingSorted,\n buildInputs: (paths) =>\n paths.map((target, i) => ({\n key: confirmKey(i),\n message: `\"${target}\" already exists. Overwrite it?`,\n choices: ['overwrite', 'skip'],\n })),\n });\n if (round !== undefined) return round;\n }\n\n const batch = await runOverPaths<\n z.infer<typeof CreateFileItemSchema>,\n { file: CreateFileResult; resourceLink?: ContentBlock } | { skipped: string }\n >(args.files, ctx, ErrorCode.UNKNOWN, async ({ path, content, append, overwrite }) => {\n const pendingPath = pendingByPath.get(path);\n if (pendingPath !== undefined) {\n const verdict = readPendingVerdict(ctx, pendingSorted, pendingPath, 'overwrite');\n if (verdict === 'skipped') return { skipped: path };\n if (verdict !== 'accepted') {\n throw new FsError(\n ErrorCode.CANCELLED,\n `create cancelled: overwrite of \"${path}\" was ${verdict.refused}`,\n path,\n );\n }\n }\n\n // Lock per resolved file so two entries naming one file (e.g. through a\n // symlinked parent) cannot clobber each other.\n return ctx.fs.lockForWrite(path, async (resolvedTarget) => {\n // The file may have appeared since the overwrite set was planned.\n if (\n !append &&\n !overwrite &&\n pendingPath === undefined &&\n (await destExists(ctx.fs, resolvedTarget, 'create'))\n ) {\n throw new FsError(\n ErrorCode.INVALID_INPUT,\n `create refused: \"${path}\" appeared after overwrite confirmation was checked`,\n path,\n );\n }\n return createOne(path, content, append, ctx);\n });\n });\n\n const results: CreateFileResult[] = [];\n const failures: CreateFailureItem[] = [];\n const skipped: string[] = [];\n const links: ContentBlock[] = [];\n for (const r of batch.results) {\n if ('error' in r) {\n failures.push({ path: r.path, error: r.error });\n continue;\n }\n if ('skipped' in r.value) {\n skipped.push(r.value.skipped);\n continue;\n }\n results.push(r.value.file);\n if (r.value.resourceLink) links.push(r.value.resourceLink);\n }\n\n const structured = {\n files: results,\n ...(failures.length > 0 ? { failures } : {}),\n ...(skipped.length > 0 ? { skipped } : {}),\n };\n // No `text` on purpose. The one-line roster this used to build named the\n // paths and dropped every per-file size, line count and error the caller\n // asked `create` for. Supplying no text makes this a data tool, so\n // `define.ts` renders the JSON and keeps it in `structuredContent`.\n return { structured, resources: links, isError: isTotalFailure(batch.summary) };\n },\n progress: (args) => ({\n label: 'Create',\n subject:\n args.files.length === 1\n ? basename(args.files[0]?.path ?? '')\n : `${String(args.files.length)} files`,\n }),\n});\n\nasync function createOne(\n path: string,\n content: string,\n append: boolean | undefined,\n ctx: ToolCtx,\n): Promise<{ file: CreateFileResult; resourceLink?: ContentBlock }> {\n await ctx.fs.mkdir(dirname(path), { recursive: true });\n\n // Overwrite: file content == `content`, so content-derived meta is\n // exact and the atomic temp+rename write protects the existing mode.\n // Append: the resulting file is everything that was there plus\n // `content`, so size/created/modified come from a real post-append\n // stat, MIME from the resulting file's leading bytes, and the line\n // count streams the file — reading a multi-GB log back whole is the\n // round-trip append exists to avoid.\n let validPath: string;\n let meta: WrittenFileMeta;\n let created: string;\n let modified: string;\n if (append) {\n const appended = await ctx.fs.appendFile(path, content, {\n signal: ctx.signal,\n });\n validPath = appended.validPath;\n // The append is the commit point: the bytes are on disk, so\n // everything below is best-effort RESULT METADATA, not part of the\n // write. It runs unwired to ctx.signal, and a metadata failure\n // degrades the result instead of failing it — either would report\n // the append as failed and a client retry would append twice. The\n // stat covers the committed validPath, never the input path: a\n // symlink there could have been swapped between write and stat,\n // and the result must describe the file the bytes landed in.\n let stats: Stats | undefined;\n let mimeType: string;\n let kind: FileKind;\n let lineCount = 0;\n try {\n stats = (await ctx.fs.stat(appended.validPath)).stats;\n // Sniff the resulting file's leading bytes, not the appended\n // chunk: text appended to a binary file is still a binary file.\n const sample = await ctx.fs.readLeadingSample(appended.validPath);\n const mimeInfo = detectMimeFromContent(appended.validPath, sample);\n mimeType = mimeInfo.mimeType;\n kind = mimeInfo.kind;\n lineCount = await ctx.fs.countFileLines(appended.validPath);\n } catch (error) {\n ctx.log(\n 'warning',\n `create append: result metadata degraded for ${appended.validPath}: ${formatUnknownErrorMessage(error)}`,\n 'create',\n );\n // A POSIX mode-0222 file appends fine but cannot be opened 'r';\n // fall back to the chunk for MIME (an approximation) and report\n // the line count as unknown-as-zero rather than fail the append.\n const mimeInfo = detectMimeFromContent(appended.validPath, content);\n mimeType = mimeInfo.mimeType;\n kind = mimeInfo.kind;\n }\n // A failed stat leaves the size unknown, so nothing is advertised.\n meta = {\n size: stats?.size ?? 0,\n lineCount,\n mimeType,\n kind,\n ...writtenFileLinks(\n appended.validPath,\n mimeType,\n stats?.size,\n false,\n stats?.mtime.toISOString(),\n ),\n };\n created = (stats?.birthtime ?? EPOCH).toISOString();\n modified = (stats?.mtime ?? EPOCH).toISOString();\n } else {\n const written = await ctx.fs.writeFile(path, content, {\n signal: ctx.signal,\n });\n validPath = written.validPath;\n const fileStats = (await ctx.fs.stat(path, { signal: ctx.signal })).stats;\n created = fileStats.birthtime.toISOString();\n modified = fileStats.mtime.toISOString();\n meta = buildWrittenFileMeta({\n validPath: written.validPath,\n content,\n lastModified: modified,\n });\n }\n\n const { resourceLink, ...fileMeta } = meta;\n const file: CreateFileResult = { path: validPath, ...fileMeta, created, modified };\n\n return resourceLink ? { file, resourceLink } : { file };\n}\n"]}
1
+ {"version":3,"file":"create.js","sourceRoot":"","sources":["../../src/tools/create.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ContentBlock } from '@modelcontextprotocol/server';\n\nimport { basename, dirname } from 'node:path';\n\nimport * as z from 'zod/v4';\n\nimport { processInParallel } from '../core/concurrency.ts';\nimport {\n ErrorCode,\n formatUnknownErrorMessage,\n FsError,\n type Problem,\n rethrowIfAborted,\n} from '../core/errors.ts';\nimport { buildWrittenFileMeta, writtenFileLinks, type WrittenFileMeta } from '../core/file-uri.ts';\nimport { destExists, type Stats } from '../core/fs.ts';\nimport { confirmKey, pendingRoundTrip, readPendingVerdict } from '../core/input-required.ts';\nimport { detectMimeFromContent, type FileKind } from '../core/mime.ts';\nimport { isEquivalentPath } from '../core/path-utils.ts';\nimport { RequiredPath } from '../core/schema.ts';\nimport { getMaxTextFileSize, PARALLEL_CONCURRENCY } from '../core/util.ts';\nimport { type BatchResult, isTotalFailure, runOverPaths } from './batch.ts';\nimport { defineTool, type ToolCtx } from './define.ts';\n\nconst EPOCH = new Date(0);\n// runOverPaths rejects an empty list, and a call may carry only one of the two lists.\nconst NO_ENTRIES: BatchResult<never> = {\n results: [],\n summary: { total: 0, succeeded: 0, failed: 0 },\n};\n\nconst CreateFileItemSchema = z.strictObject({\n path: RequiredPath.describe('Absolute path where the file will be created'),\n content: z\n .string()\n .refine((val) => val.length <= getMaxTextFileSize(), {\n message: 'Content exceeds maximum allowed text file size',\n })\n .describe('Text content to write, verbatim.'),\n append: z\n .boolean()\n .optional()\n .describe(\n 'Append the content to the end of the file instead of overwriting; creates the file if it does not exist',\n ),\n overwrite: z\n .boolean()\n .optional()\n .describe(\n 'Replace an existing file without asking the user; without it an existing file prompts for confirmation',\n ),\n});\n\ntype CreateFileResult = Omit<WrittenFileMeta, 'resourceLink'> & {\n path: string;\n created: string;\n modified: string;\n};\n\nconst CreateInputSchema = z\n .strictObject({\n files: z\n .array(CreateFileItemSchema)\n .min(1)\n .max(100)\n .optional()\n .describe(\n 'List of files to create (max 100); each entry requires path and content; combined content is capped at the file-size limit',\n ),\n directories: z\n .array(RequiredPath)\n .min(1)\n .max(100)\n .optional()\n .describe(\n 'Empty directories to create, like mkdir -p; one that already exists is left as it is',\n ),\n })\n .superRefine((value, ctx) => {\n if (value.files === undefined && value.directories === undefined) {\n ctx.addIssue({\n code: 'custom',\n path: ['files'],\n message: 'Provide files, directories, or both',\n input: value,\n });\n }\n const files = value.files ?? [];\n // Entries write in parallel and each replaces its file whole, so two\n // entries for one path race: the last rename wins and the other's\n // content is lost while both report success. One entry per file, counting\n // the Unicode spellings the path guard resolves to one name (#101).\n for (const [index, file] of files.entries()) {\n const first = files.findIndex((other) => isEquivalentPath(other.path, file.path));\n if (first < index) {\n ctx.addIssue({\n code: 'custom',\n path: ['files', index, 'path'],\n message: `duplicate of files[${String(first)}].path; one entry per file`,\n input: value,\n });\n }\n }\n // One call's content is what one inbound message carries; the transports\n // size their message bound from this same limit (getMaxInboundMessageBytes),\n // so a batch that passes here always fits on the wire. Counted in the same\n // UTF-16 units as the per-file refine above.\n const total = files.reduce((sum, file) => sum + file.content.length, 0);\n const limit = getMaxTextFileSize();\n if (total > limit) {\n ctx.addIssue({\n code: 'custom',\n path: ['files'],\n message: `combined content is ${String(total)} characters; one create call carries at most ${String(limit)} (FS_MAX_FILE_SIZE). Split the batch.`,\n input: value,\n });\n }\n });\n\ninterface CreateFailureItem {\n path: string;\n error: Problem;\n}\n\ninterface CreateOutput {\n files: CreateFileResult[];\n directories?: string[];\n failures?: CreateFailureItem[];\n skipped?: string[];\n}\n\nexport const CREATE = defineTool<typeof CreateInputSchema, CreateOutput>({\n name: 'create',\n title: 'Create Files',\n description:\n 'Write whole text files (create new ones, overwrite, or append) or make empty directories. ' +\n 'Missing parent directories are created. edit or patch changes part of an existing file.',\n input: CreateInputSchema,\n readOnlyHint: false,\n mapPaths: (args, fn) => ({\n ...args,\n ...(args.files ? { files: args.files.map((file) => ({ ...file, path: fn(file.path) })) } : {}),\n ...(args.directories ? { directories: args.directories.map((dir) => fn(dir)) } : {}),\n }),\n run: async (args, ctx) => {\n const files = args.files ?? [];\n const dirs = args.directories ?? [];\n // Phase 1 (no mutation): which entries would replace a file that exists\n // right now? Those need the user's word before anything is written (R14).\n // append never destroys, and overwrite: true is that word given up front.\n // A path that fails validation here is left for runOverPaths to report.\n // A file that appears between rounds needs no re-stat before the write:\n // the retry re-plans, and the grown pending set fails the R9 check.\n const { results: planned } = await processInParallel(\n files,\n async (entry) => {\n if (entry.append || entry.overwrite) return undefined;\n try {\n const validPath = await ctx.fs.pathGuard.validatePathForWrite(entry.path);\n return (await destExists(ctx.fs, validPath, 'create')) ? validPath : undefined;\n } catch (error) {\n rethrowIfAborted(error);\n return undefined;\n }\n },\n PARALLEL_CONCURRENCY,\n ctx.signal,\n );\n const pendingByPath = new Map<string, string>();\n for (const { index, value } of planned) {\n const requested = files[index]?.path;\n if (value && requested) pendingByPath.set(requested, value);\n }\n const pendingSorted = [...new Set(pendingByPath.values())].sort();\n if (pendingSorted.length > 0) {\n // Round 1 returns input_required; a retry whose verified state does not\n // bind this overwrite set throws (R9) via `pendingRoundTrip`.\n const round = await pendingRoundTrip(ctx, {\n op: 'create',\n pending: pendingSorted,\n buildInputs: (paths) =>\n paths.map((target, i) => ({\n key: confirmKey(i),\n message: `\"${target}\" already exists. Overwrite it?`,\n choices: ['overwrite', 'skip'],\n })),\n });\n if (round !== undefined) return round;\n }\n\n // One counter across both batches: ProgressSession drops a value at or below its last one.\n const progressTotal = dirs.length + files.length;\n const progressFrom = (offset: number): ToolCtx => ({\n ...ctx,\n onProgress: ({ current }) => {\n ctx.onProgress({ current: offset + current, total: progressTotal });\n },\n });\n\n // Directories wait for the overwrite question above, so nothing is written\n // before the user answers.\n const dirBatch =\n dirs.length > 0\n ? await runOverPaths(dirs, progressFrom(0), ErrorCode.UNKNOWN, async (dir) => {\n await ctx.fs.mkdir(dir, { recursive: true });\n return dir;\n })\n : NO_ENTRIES;\n\n const filesCtx = progressFrom(dirs.length);\n const batch =\n files.length > 0\n ? await runOverPaths<\n z.infer<typeof CreateFileItemSchema>,\n { file: CreateFileResult; resourceLink?: ContentBlock } | { skipped: string }\n >(files, filesCtx, ErrorCode.UNKNOWN, async ({ path, content, append, overwrite }) => {\n const pendingPath = pendingByPath.get(path);\n if (pendingPath !== undefined) {\n const verdict = readPendingVerdict(ctx, pendingSorted, pendingPath, 'overwrite');\n if (verdict === 'skipped') return { skipped: path };\n if (verdict !== 'accepted') {\n throw new FsError(\n ErrorCode.CANCELLED,\n `create cancelled: overwrite of \"${path}\" was ${verdict.refused}`,\n path,\n );\n }\n }\n\n // Lock per resolved file so two entries naming one file (e.g. through a\n // symlinked parent) cannot clobber each other.\n return ctx.fs.lockForWrite(path, async (resolvedTarget) => {\n // The file may have appeared since the overwrite set was planned.\n if (\n !append &&\n !overwrite &&\n pendingPath === undefined &&\n (await destExists(ctx.fs, resolvedTarget, 'create'))\n ) {\n throw new FsError(\n ErrorCode.INVALID_INPUT,\n `create refused: \"${path}\" appeared after overwrite confirmation was checked`,\n path,\n );\n }\n return createOne(path, content, append, ctx);\n });\n })\n : NO_ENTRIES;\n\n const results: CreateFileResult[] = [];\n const directories: string[] = [];\n const failures: CreateFailureItem[] = [];\n const skipped: string[] = [];\n const links: ContentBlock[] = [];\n for (const r of dirBatch.results) {\n if ('error' in r) {\n failures.push({ path: r.path, error: r.error });\n } else {\n directories.push(r.value);\n }\n }\n for (const r of batch.results) {\n if ('error' in r) {\n failures.push({ path: r.path, error: r.error });\n continue;\n }\n if ('skipped' in r.value) {\n skipped.push(r.value.skipped);\n continue;\n }\n results.push(r.value.file);\n if (r.value.resourceLink) links.push(r.value.resourceLink);\n }\n\n const structured = {\n files: results,\n ...(directories.length > 0 ? { directories } : {}),\n ...(failures.length > 0 ? { failures } : {}),\n ...(skipped.length > 0 ? { skipped } : {}),\n };\n // Failed only when every directory and every file failed, across both lists.\n const summary = {\n total: dirBatch.summary.total + batch.summary.total,\n succeeded: dirBatch.summary.succeeded + batch.summary.succeeded,\n failed: dirBatch.summary.failed + batch.summary.failed,\n };\n // No `text` on purpose. The one-line roster this used to build named the\n // paths and dropped every per-file size, line count and error the caller\n // asked `create` for. Supplying no text makes this a data tool, so\n // `define.ts` renders the JSON and keeps it in `structuredContent`.\n return { structured, resources: links, isError: isTotalFailure(summary) };\n },\n progress: (args) => {\n const files = args.files ?? [];\n const dirs = args.directories ?? [];\n if (files.length + dirs.length === 1) {\n return { label: 'Create', subject: basename(files[0]?.path ?? dirs[0] ?? '') };\n }\n const counts: string[] = [];\n if (files.length > 0) counts.push(`${String(files.length)} files`);\n if (dirs.length > 0) counts.push(`${String(dirs.length)} directories`);\n return { label: 'Create', subject: counts.join(', ') };\n },\n});\n\nasync function createOne(\n path: string,\n content: string,\n append: boolean | undefined,\n ctx: ToolCtx,\n): Promise<{ file: CreateFileResult; resourceLink?: ContentBlock }> {\n await ctx.fs.mkdir(dirname(path), { recursive: true });\n\n // Overwrite: file content == `content`, so content-derived meta is\n // exact and the atomic temp+rename write protects the existing mode.\n // Append: the resulting file is everything that was there plus\n // `content`, so size/created/modified come from a real post-append\n // stat, MIME from the resulting file's leading bytes, and the line\n // count streams the file — reading a multi-GB log back whole is the\n // round-trip append exists to avoid.\n let validPath: string;\n let meta: WrittenFileMeta;\n let created: string;\n let modified: string;\n if (append) {\n const appended = await ctx.fs.appendFile(path, content, {\n signal: ctx.signal,\n });\n validPath = appended.validPath;\n // The append is the commit point: the bytes are on disk, so\n // everything below is best-effort RESULT METADATA, not part of the\n // write. It runs unwired to ctx.signal, and a metadata failure\n // degrades the result instead of failing it — either would report\n // the append as failed and a client retry would append twice. The\n // stat covers the committed validPath, never the input path: a\n // symlink there could have been swapped between write and stat,\n // and the result must describe the file the bytes landed in.\n let stats: Stats | undefined;\n let mimeType: string;\n let kind: FileKind;\n let lineCount = 0;\n try {\n stats = (await ctx.fs.stat(appended.validPath)).stats;\n // Sniff the resulting file's leading bytes, not the appended\n // chunk: text appended to a binary file is still a binary file.\n const sample = await ctx.fs.readLeadingSample(appended.validPath);\n const mimeInfo = detectMimeFromContent(appended.validPath, sample);\n mimeType = mimeInfo.mimeType;\n kind = mimeInfo.kind;\n lineCount = await ctx.fs.countFileLines(appended.validPath);\n } catch (error) {\n ctx.log(\n 'warning',\n `create append: result metadata degraded for ${appended.validPath}: ${formatUnknownErrorMessage(error)}`,\n 'create',\n );\n // A POSIX mode-0222 file appends fine but cannot be opened 'r';\n // fall back to the chunk for MIME (an approximation) and report\n // the line count as unknown-as-zero rather than fail the append.\n const mimeInfo = detectMimeFromContent(appended.validPath, content);\n mimeType = mimeInfo.mimeType;\n kind = mimeInfo.kind;\n }\n // A failed stat leaves the size unknown, so nothing is advertised.\n meta = {\n size: stats?.size ?? 0,\n lineCount,\n mimeType,\n kind,\n ...writtenFileLinks(\n appended.validPath,\n mimeType,\n stats?.size,\n false,\n stats?.mtime.toISOString(),\n ctx.transport,\n ),\n };\n created = (stats?.birthtime ?? EPOCH).toISOString();\n modified = (stats?.mtime ?? EPOCH).toISOString();\n } else {\n const written = await ctx.fs.writeFile(path, content, {\n signal: ctx.signal,\n });\n validPath = written.validPath;\n const nowIso = new Date().toISOString();\n let fileStats: Stats | undefined;\n try {\n fileStats = (await ctx.fs.stat(written.validPath)).stats;\n } catch {\n /* post-commit metadata only */\n }\n created = fileStats?.birthtime.toISOString() ?? nowIso;\n modified = fileStats?.mtime.toISOString() ?? nowIso;\n meta = buildWrittenFileMeta({\n validPath: written.validPath,\n content,\n lastModified: modified,\n transport: ctx.transport,\n });\n }\n\n const { resourceLink, ...fileMeta } = meta;\n const file: CreateFileResult = { path: validPath, ...fileMeta, created, modified };\n\n return resourceLink ? { file, resourceLink } : { file };\n}\n"]}
@@ -84,19 +84,23 @@ export interface ToolDef<I extends z.ZodType, O extends object> {
84
84
  readonly input: I;
85
85
  /** Required: drives both the --read-only gate and published annotations. */
86
86
  readonly readOnlyHint: boolean;
87
- readonly timeoutMs?: number;
87
+ /** Abort the call after getToolTimeoutMs() (`--tool-timeout`, FS_TOOL_TIMEOUT_MS). */
88
+ readonly deadline?: boolean;
88
89
  readonly progress?: (args: z.infer<I>) => ProgressCtx;
89
90
  readonly progressDone?: (args: z.infer<I>, result: O) => Partial<ProgressCtx>;
90
91
  readonly defaultErrorCode?: ErrorCode;
91
92
  /**
92
- * The filesystem paths this tool will operate on, extracted from its parsed
93
- * args. The executor runs an access-grant pre-check over these BEFORE `run`
93
+ * The parsed args with every caller-supplied filesystem path passed through
94
+ * `fn`. Before `run`, the executor reads the paths through it to resolve
95
+ * relative ones (`PathGuard.resolveRelativePaths`) and writes the results
96
+ * back, so every check `run` makes — duplicates and overlaps included — sees
97
+ * one spelling. It then runs the access-grant pre-check over the same paths
94
98
  * (R7): any path outside the granted roots yields an `input_required`
95
99
  * round-trip requesting a grant, and nothing is touched until the client
96
100
  * retries with an accepted grant (R8). Omit for tools that do not operate on
97
101
  * caller-supplied filesystem paths.
98
102
  */
99
- readonly accessPaths?: (args: z.infer<I>) => readonly string[];
103
+ readonly mapPaths?: (args: z.infer<I>, fn: (path: string) => string) => z.infer<I>;
100
104
  readonly run: (args: z.infer<I>, ctx: ToolCtx) => Promise<RunResult<O> | InputRequiredResult>;
101
105
  /**
102
106
  * What the stdio size refusal tells the caller to do instead, for a tool
@@ -1 +1 @@
1
- {"version":3,"file":"define.d.ts","sourceRoot":"","sources":["../../src/tools/define.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"define.d.ts","sourceRoot":"","sources":["../../src/tools/define.ts"],"names":[],"mappings":"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"}
@@ -6,7 +6,7 @@ import { confirmKey, pendingRoundTrip, readAcceptedConfirm, readAcceptedMultiCho
6
6
  import { Logger, sanitizeLogField } from "../core/observability.js";
7
7
  import { isSamePath } from "../core/path-utils.js";
8
8
  import { withSplitArgsRecovery } from "../core/schema.js";
9
- import { jsonBytesOver, MAX_OUTBOUND_RESULT_BYTES } from "../core/util.js";
9
+ import { getToolTimeoutMs, jsonBytesOver, MAX_OUTBOUND_RESULT_BYTES } from "../core/util.js";
10
10
  import { ProgressSession } from "./progress.js";
11
11
  function toToolCtx(ctx, deps) {
12
12
  // Envelope first, accessor second — the two eras carry this differently.
@@ -70,6 +70,7 @@ function buildSuccessResponse(result) {
70
70
  class ToolExecutor {
71
71
  signal;
72
72
  def;
73
+ /** Rewritten once, by `resolveRelativePaths`, before anything reads a path. */
73
74
  parsedArgs;
74
75
  toolCtx;
75
76
  #progressCtx;
@@ -77,8 +78,8 @@ class ToolExecutor {
77
78
  constructor(toolName, ctx, def, parsedArgs) {
78
79
  this.def = def;
79
80
  this.parsedArgs = parsedArgs;
80
- this.signal = def.timeoutMs
81
- ? AbortSignal.any([ctx.signal, AbortSignal.timeout(def.timeoutMs)])
81
+ this.signal = def.deadline
82
+ ? AbortSignal.any([ctx.signal, AbortSignal.timeout(getToolTimeoutMs())])
82
83
  : ctx.signal;
83
84
  this.#progressCtx = def.progress ? def.progress(parsedArgs) : { label: def.title };
84
85
  const { mcpReq } = ctx.serverCtx;
@@ -138,6 +139,32 @@ class ToolExecutor {
138
139
  isError: true,
139
140
  };
140
141
  }
142
+ collectPaths() {
143
+ const paths = [];
144
+ this.def.mapPaths?.(this.parsedArgs, (path) => {
145
+ paths.push(path);
146
+ return path;
147
+ });
148
+ return paths;
149
+ }
150
+ async resolveRelativePaths() {
151
+ const { mapPaths } = this.def;
152
+ if (!mapPaths)
153
+ return;
154
+ const resolved = await this.toolCtx.fs.pathGuard.resolveRelativePaths(this.collectPaths());
155
+ if (resolved.size === 0)
156
+ return;
157
+ const rewritten = mapPaths(this.parsedArgs, (path) => resolved.get(path) ?? path);
158
+ // The schema compared the spellings the caller sent, so `a.txt` and
159
+ // `<root>/a.txt` passed create's and edit's one-entry-per-file rule as two
160
+ // files. Its checks judge the resolved paths too.
161
+ const check = this.def.input.safeParse(rewritten);
162
+ if (!check.success) {
163
+ const issues = check.error.issues.map((issue) => `${issue.path.map(String).join('.')}: ${issue.message}`);
164
+ throw new FsError(ErrorCode.INVALID_INPUT, issues.join('; '));
165
+ }
166
+ this.parsedArgs = rewritten;
167
+ }
141
168
  /**
142
169
  * Access-grant pre-check (R7/R8/R9). Before `run` touches the filesystem,
143
170
  * ask PathGuard which of the tool's declared paths are out-of-root and
@@ -149,10 +176,7 @@ class ToolExecutor {
149
176
  * when no round-trip is needed and any applicable grants have been applied.
150
177
  */
151
178
  async precheckGrant() {
152
- const extract = this.def.accessPaths;
153
- if (!extract)
154
- return undefined;
155
- const paths = extract(this.parsedArgs);
179
+ const paths = this.collectPaths();
156
180
  if (paths.length === 0)
157
181
  return undefined;
158
182
  const grantDirs = await this.toolCtx.fs.pathGuard.precheckAccess(paths);
@@ -203,7 +227,7 @@ class ToolExecutor {
203
227
  for (const dir of accepted) {
204
228
  if (!grantDirs.some((g) => isSamePath(g, dir)))
205
229
  continue;
206
- await this.toolCtx.fs.pathGuard.applyGrant(dir);
230
+ await this.toolCtx.fs.pathGuard.applyGrant(dir, 'access');
207
231
  }
208
232
  return undefined;
209
233
  }
@@ -242,7 +266,10 @@ class ToolExecutor {
242
266
  // grant input_required short-circuits here (progress paused, not
243
267
  // finished); an R9 mismatch throws, which this catch surfaces as an
244
268
  // isError tool result like every other handler failure — not a raw
245
- // JSON-RPC error (GRANT-1 impact #2).
269
+ // JSON-RPC error (GRANT-1 impact #2). Relative paths resolve first, so
270
+ // the pre-check judges the path the call will use; with several roots
271
+ // that probes each candidate through validation, and reads nothing.
272
+ await this.resolveRelativePaths();
246
273
  const grantRequired = await this.precheckGrant();
247
274
  if (grantRequired !== undefined)
248
275
  return grantRequired;