@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.
- package/README.md +33 -27
- package/dist/cli-help.d.ts.map +1 -1
- package/dist/cli-help.js +10 -1
- package/dist/cli-help.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +10 -3
- package/dist/cli.js.map +1 -1
- package/dist/core/concurrency.d.ts +4 -4
- package/dist/core/concurrency.d.ts.map +1 -1
- package/dist/core/concurrency.js +1 -1
- package/dist/core/concurrency.js.map +1 -1
- package/dist/core/config.d.ts +2 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js.map +1 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +7 -1
- package/dist/core/errors.js.map +1 -1
- package/dist/core/file-uri.d.ts +12 -3
- package/dist/core/file-uri.d.ts.map +1 -1
- package/dist/core/file-uri.js +35 -7
- package/dist/core/file-uri.js.map +1 -1
- package/dist/core/fmt.d.ts +2 -0
- package/dist/core/fmt.d.ts.map +1 -1
- package/dist/core/fmt.js +16 -0
- package/dist/core/fmt.js.map +1 -1
- package/dist/core/fs.d.ts +16 -1
- package/dist/core/fs.d.ts.map +1 -1
- package/dist/core/fs.js +127 -32
- package/dist/core/fs.js.map +1 -1
- package/dist/core/glob.d.ts +15 -0
- package/dist/core/glob.d.ts.map +1 -1
- package/dist/core/glob.js +119 -14
- package/dist/core/glob.js.map +1 -1
- package/dist/core/path-completer.d.ts.map +1 -1
- package/dist/core/path-completer.js +5 -3
- package/dist/core/path-completer.js.map +1 -1
- package/dist/core/path-utils.d.ts +42 -6
- package/dist/core/path-utils.d.ts.map +1 -1
- package/dist/core/path-utils.js +56 -10
- package/dist/core/path-utils.js.map +1 -1
- package/dist/core/path.d.ts +111 -7
- package/dist/core/path.d.ts.map +1 -1
- package/dist/core/path.js +453 -65
- package/dist/core/path.js.map +1 -1
- package/dist/core/read.d.ts +6 -0
- package/dist/core/read.d.ts.map +1 -1
- package/dist/core/read.js +26 -3
- package/dist/core/read.js.map +1 -1
- package/dist/core/schema.d.ts +9 -6
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/schema.js +22 -19
- package/dist/core/schema.js.map +1 -1
- package/dist/core/search.d.ts +6 -0
- package/dist/core/search.d.ts.map +1 -1
- package/dist/core/search.js +161 -33
- package/dist/core/search.js.map +1 -1
- package/dist/core/sensitive.d.ts.map +1 -1
- package/dist/core/sensitive.js +64 -15
- package/dist/core/sensitive.js.map +1 -1
- package/dist/core/store.d.ts +1 -0
- package/dist/core/store.d.ts.map +1 -1
- package/dist/core/store.js +3 -0
- package/dist/core/store.js.map +1 -1
- package/dist/core/util.d.ts +12 -1
- package/dist/core/util.d.ts.map +1 -1
- package/dist/core/util.js +16 -1
- package/dist/core/util.js.map +1 -1
- package/dist/resources.d.ts.map +1 -1
- package/dist/resources.js +20 -11
- package/dist/resources.js.map +1 -1
- package/dist/server.js +1 -1
- package/dist/server.js.map +1 -1
- package/dist/tools/create.d.ts.map +1 -1
- package/dist/tools/create.js +121 -43
- package/dist/tools/create.js.map +1 -1
- package/dist/tools/define.d.ts +8 -4
- package/dist/tools/define.d.ts.map +1 -1
- package/dist/tools/define.js +36 -9
- package/dist/tools/define.js.map +1 -1
- package/dist/tools/delete.d.ts.map +1 -1
- package/dist/tools/delete.js +50 -11
- package/dist/tools/delete.js.map +1 -1
- package/dist/tools/diff.js +1 -1
- package/dist/tools/diff.js.map +1 -1
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +14 -12
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/find-files.d.ts.map +1 -1
- package/dist/tools/find-files.js +15 -9
- package/dist/tools/find-files.js.map +1 -1
- package/dist/tools/list.d.ts.map +1 -1
- package/dist/tools/list.js +141 -86
- package/dist/tools/list.js.map +1 -1
- package/dist/tools/move.d.ts.map +1 -1
- package/dist/tools/move.js +72 -26
- package/dist/tools/move.js.map +1 -1
- package/dist/tools/patch.js +13 -6
- package/dist/tools/patch.js.map +1 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +113 -48
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/replace-text.d.ts.map +1 -1
- package/dist/tools/replace-text.js +50 -35
- package/dist/tools/replace-text.js.map +1 -1
- package/dist/tools/search-text.d.ts.map +1 -1
- package/dist/tools/search-text.js +15 -33
- package/dist/tools/search-text.js.map +1 -1
- package/dist/tools/stat.d.ts.map +1 -1
- package/dist/tools/stat.js +26 -8
- package/dist/tools/stat.js.map +1 -1
- package/dist/transport/http.d.ts.map +1 -1
- package/dist/transport/http.js +1 -0
- package/dist/transport/http.js.map +1 -1
- package/dist/transport/stdio.js +1 -1
- package/dist/transport/stdio.js.map +1 -1
- package/package.json +5 -5
package/dist/server.js.map
CHANGED
|
@@ -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":"
|
|
1
|
+
{"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/tools/create.ts"],"names":[],"mappings":"AAmIA,eAAO,MAAM,MAAM,mCA6KjB,CAAC"}
|
package/dist/tools/create.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
45
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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(
|
|
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
|
|
243
|
-
|
|
244
|
-
|
|
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;
|
package/dist/tools/create.js.map
CHANGED
|
@@ -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"]}
|
package/dist/tools/define.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
93
|
-
*
|
|
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
|
|
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":"AAAA,OAAO,KAAK,EAEV,kBAAkB,EAClB,YAAY,EACZ,mBAAmB,EACnB,SAAS,EAET,cAAc,EACd,SAAS,EACT,oBAAoB,EACpB,aAAa,EAGd,MAAM,8BAA8B,CAAC;AAOtC,OAAO,KAAK,KAAK,CAAC,MAAM,QAAQ,CAAC;AAEjC,OAAO,EAAE,SAAS,EAA+C,MAAM,mBAAmB,CAAC;AAC3F,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAQlD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAE7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAIzE,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,wFAAwF;IACxF,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,EAAE,EAAE,iBAAiB,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,iBAAiB,CAAC;IACtC,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC,6EAA6E;IAC7E,QAAQ,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5E,QAAQ,CAAC,UAAU,EAAE,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAC3E;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAC9D;;;;;OAKG;IACH,QAAQ,CAAC,wBAAwB,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IAClE;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,oBAAoB,GAAG,SAAS,CAAC;IACzD;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAC;IAC7D,iGAAiG;IACjG,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC;IAClC,gGAAgG;IAChG,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;CACnD;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,iBAAiB,CAAC;IACtC,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IACnC,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CACvC;AAED,UAAU,SAAS,CAAC,CAAC;IACnB,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,CAAC,EAAE,YAAY,EAAE,CAAC;IACpC;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC,SAAS,MAAM;IAC5D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;IAClB,4EAA4E;IAC5E,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,
|
|
1
|
+
{"version":3,"file":"define.d.ts","sourceRoot":"","sources":["../../src/tools/define.ts"],"names":[],"mappings":"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"}
|
package/dist/tools/define.js
CHANGED
|
@@ -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.
|
|
81
|
-
? AbortSignal.any([ctx.signal, AbortSignal.timeout(
|
|
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
|
|
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;
|