@j0hanz/filesystem-mcp 1.19.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (301) hide show
  1. package/README.md +333 -838
  2. package/dist/cli-help.d.ts +3 -0
  3. package/dist/cli-help.d.ts.map +1 -0
  4. package/dist/cli-help.js +169 -0
  5. package/dist/cli-help.js.map +1 -0
  6. package/dist/cli.d.ts +28 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +168 -123
  9. package/dist/cli.js.map +1 -0
  10. package/dist/core/concurrency.d.ts +70 -0
  11. package/dist/core/concurrency.d.ts.map +1 -0
  12. package/dist/core/concurrency.js +173 -0
  13. package/dist/core/concurrency.js.map +1 -0
  14. package/dist/core/config.d.ts +19 -0
  15. package/dist/core/config.d.ts.map +1 -0
  16. package/dist/core/config.js +11 -0
  17. package/dist/core/config.js.map +1 -0
  18. package/dist/core/cursor.d.ts +23 -0
  19. package/dist/core/cursor.d.ts.map +1 -0
  20. package/dist/core/cursor.js +62 -0
  21. package/dist/core/cursor.js.map +1 -0
  22. package/dist/core/errors.d.ts +96 -0
  23. package/dist/core/errors.d.ts.map +1 -0
  24. package/dist/core/errors.js +356 -0
  25. package/dist/core/errors.js.map +1 -0
  26. package/dist/core/file-uri.d.ts +24 -0
  27. package/dist/core/file-uri.d.ts.map +1 -0
  28. package/dist/core/file-uri.js +52 -0
  29. package/dist/core/file-uri.js.map +1 -0
  30. package/dist/core/fmt.d.ts +38 -0
  31. package/dist/core/fmt.d.ts.map +1 -0
  32. package/dist/core/fmt.js +109 -0
  33. package/dist/core/fmt.js.map +1 -0
  34. package/dist/core/fs.d.ts +108 -0
  35. package/dist/core/fs.d.ts.map +1 -0
  36. package/dist/core/fs.js +243 -0
  37. package/dist/core/fs.js.map +1 -0
  38. package/dist/core/glob.d.ts +41 -0
  39. package/dist/core/glob.d.ts.map +1 -0
  40. package/dist/core/glob.js +399 -0
  41. package/dist/core/glob.js.map +1 -0
  42. package/dist/core/input-required.d.ts +147 -0
  43. package/dist/core/input-required.d.ts.map +1 -0
  44. package/dist/core/input-required.js +261 -0
  45. package/dist/core/input-required.js.map +1 -0
  46. package/dist/core/mime.d.ts +15 -0
  47. package/dist/core/mime.d.ts.map +1 -0
  48. package/dist/core/mime.js +188 -0
  49. package/dist/core/mime.js.map +1 -0
  50. package/dist/core/observability.d.ts +10 -0
  51. package/dist/core/observability.d.ts.map +1 -0
  52. package/dist/core/observability.js +76 -0
  53. package/dist/core/observability.js.map +1 -0
  54. package/dist/core/page-store.d.ts +41 -0
  55. package/dist/core/page-store.d.ts.map +1 -0
  56. package/dist/core/page-store.js +74 -0
  57. package/dist/core/page-store.js.map +1 -0
  58. package/dist/core/path-completer.d.ts +10 -0
  59. package/dist/core/path-completer.d.ts.map +1 -0
  60. package/dist/core/path-completer.js +227 -0
  61. package/dist/core/path-completer.js.map +1 -0
  62. package/dist/core/path-discovery.d.ts +8 -0
  63. package/dist/core/path-discovery.d.ts.map +1 -0
  64. package/dist/core/path-discovery.js +105 -0
  65. package/dist/core/path-discovery.js.map +1 -0
  66. package/dist/core/path-utils.d.ts +9 -0
  67. package/dist/core/path-utils.d.ts.map +1 -0
  68. package/dist/core/path-utils.js +121 -0
  69. package/dist/core/path-utils.js.map +1 -0
  70. package/dist/core/path.d.ts +148 -0
  71. package/dist/core/path.d.ts.map +1 -0
  72. package/dist/core/path.js +674 -0
  73. package/dist/core/path.js.map +1 -0
  74. package/dist/core/primitives.d.ts +23 -0
  75. package/dist/core/primitives.d.ts.map +1 -0
  76. package/dist/core/primitives.js +54 -0
  77. package/dist/core/primitives.js.map +1 -0
  78. package/dist/core/read.d.ts +74 -0
  79. package/dist/core/read.d.ts.map +1 -0
  80. package/dist/core/read.js +562 -0
  81. package/dist/core/read.js.map +1 -0
  82. package/dist/core/schema.d.ts +139 -0
  83. package/dist/core/schema.d.ts.map +1 -0
  84. package/dist/core/schema.js +345 -0
  85. package/dist/core/schema.js.map +1 -0
  86. package/dist/core/search.d.ts +100 -0
  87. package/dist/core/search.d.ts.map +1 -0
  88. package/dist/core/search.js +248 -0
  89. package/dist/core/search.js.map +1 -0
  90. package/dist/core/sensitive.d.ts +6 -0
  91. package/dist/core/sensitive.d.ts.map +1 -0
  92. package/dist/core/sensitive.js +142 -0
  93. package/dist/core/sensitive.js.map +1 -0
  94. package/dist/core/store.d.ts +57 -0
  95. package/dist/core/store.d.ts.map +1 -0
  96. package/dist/core/store.js +162 -0
  97. package/dist/core/store.js.map +1 -0
  98. package/dist/core/util.d.ts +26 -0
  99. package/dist/core/util.d.ts.map +1 -0
  100. package/dist/core/util.js +66 -0
  101. package/dist/core/util.js.map +1 -0
  102. package/dist/core/watcher-registry.d.ts +68 -0
  103. package/dist/core/watcher-registry.d.ts.map +1 -0
  104. package/dist/core/watcher-registry.js +312 -0
  105. package/dist/core/watcher-registry.js.map +1 -0
  106. package/dist/http-policy.d.ts +121 -0
  107. package/dist/http-policy.d.ts.map +1 -0
  108. package/dist/http-policy.js +340 -0
  109. package/dist/http-policy.js.map +1 -0
  110. package/dist/index.d.ts +1 -0
  111. package/dist/index.d.ts.map +1 -0
  112. package/dist/index.js +77 -41
  113. package/dist/index.js.map +1 -0
  114. package/dist/instructions.d.ts +12 -0
  115. package/dist/instructions.d.ts.map +1 -0
  116. package/dist/instructions.js +71 -0
  117. package/dist/instructions.js.map +1 -0
  118. package/dist/prompts.d.ts +8 -6
  119. package/dist/prompts.d.ts.map +1 -0
  120. package/dist/prompts.js +74 -168
  121. package/dist/prompts.js.map +1 -0
  122. package/dist/resources.d.ts +73 -9
  123. package/dist/resources.d.ts.map +1 -0
  124. package/dist/resources.js +392 -163
  125. package/dist/resources.js.map +1 -0
  126. package/dist/server.d.ts +62 -1
  127. package/dist/server.d.ts.map +1 -0
  128. package/dist/server.js +139 -1
  129. package/dist/server.js.map +1 -0
  130. package/dist/tools/batch.d.ts +89 -0
  131. package/dist/tools/batch.d.ts.map +1 -0
  132. package/dist/tools/batch.js +157 -0
  133. package/dist/tools/batch.js.map +1 -0
  134. package/dist/tools/create.d.ts +2 -0
  135. package/dist/tools/create.d.ts.map +1 -0
  136. package/dist/tools/create.js +142 -0
  137. package/dist/tools/create.js.map +1 -0
  138. package/dist/tools/define.d.ts +110 -0
  139. package/dist/tools/define.d.ts.map +1 -0
  140. package/dist/tools/define.js +394 -0
  141. package/dist/tools/define.js.map +1 -0
  142. package/dist/tools/delete-file.d.ts +2 -4
  143. package/dist/tools/delete-file.d.ts.map +1 -0
  144. package/dist/tools/delete-file.js +330 -87
  145. package/dist/tools/delete-file.js.map +1 -0
  146. package/dist/tools/diff.d.ts +2 -0
  147. package/dist/tools/diff.d.ts.map +1 -0
  148. package/dist/tools/diff.js +94 -0
  149. package/dist/tools/diff.js.map +1 -0
  150. package/dist/tools/edit.d.ts +2 -0
  151. package/dist/tools/edit.d.ts.map +1 -0
  152. package/dist/tools/edit.js +442 -0
  153. package/dist/tools/edit.js.map +1 -0
  154. package/dist/tools/index.d.ts +26 -0
  155. package/dist/tools/index.d.ts.map +1 -0
  156. package/dist/tools/index.js +52 -0
  157. package/dist/tools/index.js.map +1 -0
  158. package/dist/tools/list.d.ts +2 -0
  159. package/dist/tools/list.d.ts.map +1 -0
  160. package/dist/tools/list.js +291 -0
  161. package/dist/tools/list.js.map +1 -0
  162. package/dist/tools/move.d.ts +2 -0
  163. package/dist/tools/move.d.ts.map +1 -0
  164. package/dist/tools/move.js +321 -0
  165. package/dist/tools/move.js.map +1 -0
  166. package/dist/tools/patch.d.ts +2 -0
  167. package/dist/tools/patch.d.ts.map +1 -0
  168. package/dist/tools/patch.js +155 -0
  169. package/dist/tools/patch.js.map +1 -0
  170. package/dist/tools/progress.d.ts +52 -0
  171. package/dist/tools/progress.d.ts.map +1 -0
  172. package/dist/tools/progress.js +172 -0
  173. package/dist/tools/progress.js.map +1 -0
  174. package/dist/tools/read.d.ts +2 -4
  175. package/dist/tools/read.d.ts.map +1 -0
  176. package/dist/tools/read.js +398 -152
  177. package/dist/tools/read.js.map +1 -0
  178. package/dist/tools/replace-in-files.d.ts +2 -4
  179. package/dist/tools/replace-in-files.d.ts.map +1 -0
  180. package/dist/tools/replace-in-files.js +435 -264
  181. package/dist/tools/replace-in-files.js.map +1 -0
  182. package/dist/tools/roots.d.ts +2 -4
  183. package/dist/tools/roots.d.ts.map +1 -0
  184. package/dist/tools/roots.js +29 -52
  185. package/dist/tools/roots.js.map +1 -0
  186. package/dist/tools/search-content.d.ts +2 -4
  187. package/dist/tools/search-content.d.ts.map +1 -0
  188. package/dist/tools/search-content.js +262 -228
  189. package/dist/tools/search-content.js.map +1 -0
  190. package/dist/tools/search-files.d.ts +2 -4
  191. package/dist/tools/search-files.d.ts.map +1 -0
  192. package/dist/tools/search-files.js +162 -161
  193. package/dist/tools/search-files.js.map +1 -0
  194. package/dist/tools/stat.d.ts +2 -4
  195. package/dist/tools/stat.d.ts.map +1 -0
  196. package/dist/tools/stat.js +202 -62
  197. package/dist/tools/stat.js.map +1 -0
  198. package/dist/transport/http.d.ts +5 -0
  199. package/dist/transport/http.d.ts.map +1 -0
  200. package/dist/transport/http.js +276 -0
  201. package/dist/transport/http.js.map +1 -0
  202. package/dist/transport/shared.d.ts +45 -0
  203. package/dist/transport/shared.d.ts.map +1 -0
  204. package/dist/transport/shared.js +65 -0
  205. package/dist/transport/shared.js.map +1 -0
  206. package/dist/transport/stdio.d.ts +32 -0
  207. package/dist/transport/stdio.d.ts.map +1 -0
  208. package/dist/transport/stdio.js +279 -0
  209. package/dist/transport/stdio.js.map +1 -0
  210. package/dist/transport.d.ts +5 -0
  211. package/dist/transport.d.ts.map +1 -0
  212. package/dist/transport.js +4 -0
  213. package/dist/transport.js.map +1 -0
  214. package/package.json +37 -46
  215. package/dist/assets/logo.svg +0 -35
  216. package/dist/completions.d.ts +0 -2
  217. package/dist/completions.js +0 -572
  218. package/dist/config.d.ts +0 -119
  219. package/dist/config.js +0 -31
  220. package/dist/lib/abort.d.ts +0 -7
  221. package/dist/lib/abort.js +0 -81
  222. package/dist/lib/constants.d.ts +0 -42
  223. package/dist/lib/constants.js +0 -313
  224. package/dist/lib/errors.d.ts +0 -28
  225. package/dist/lib/errors.js +0 -301
  226. package/dist/lib/file-operations/core.d.ts +0 -53
  227. package/dist/lib/file-operations/core.js +0 -144
  228. package/dist/lib/file-operations/metadata.d.ts +0 -77
  229. package/dist/lib/file-operations/metadata.js +0 -878
  230. package/dist/lib/file-operations/search.d.ts +0 -46
  231. package/dist/lib/file-operations/search.js +0 -1194
  232. package/dist/lib/file-operations/traversal.d.ts +0 -40
  233. package/dist/lib/file-operations/traversal.js +0 -310
  234. package/dist/lib/fs-helpers.d.ts +0 -46
  235. package/dist/lib/fs-helpers.js +0 -606
  236. package/dist/lib/globs.d.ts +0 -2
  237. package/dist/lib/globs.js +0 -19
  238. package/dist/lib/logger.d.ts +0 -27
  239. package/dist/lib/logger.js +0 -91
  240. package/dist/lib/observability.d.ts +0 -33
  241. package/dist/lib/observability.js +0 -373
  242. package/dist/lib/paths.d.ts +0 -35
  243. package/dist/lib/paths.js +0 -634
  244. package/dist/lib/resource-store.d.ts +0 -28
  245. package/dist/lib/resource-store.js +0 -195
  246. package/dist/lib/utils.d.ts +0 -7
  247. package/dist/lib/utils.js +0 -37
  248. package/dist/lib/zod-codecs.d.ts +0 -2
  249. package/dist/lib/zod-codecs.js +0 -18
  250. package/dist/pkg-info.d.ts +0 -7
  251. package/dist/pkg-info.js +0 -9
  252. package/dist/resources/generated-instructions.d.ts +0 -1
  253. package/dist/resources/generated-instructions.js +0 -105
  254. package/dist/resources/tool-catalog.d.ts +0 -2
  255. package/dist/resources/tool-catalog.js +0 -78
  256. package/dist/resources/tool-info.d.ts +0 -9
  257. package/dist/resources/tool-info.js +0 -206
  258. package/dist/resources/workflows.d.ts +0 -1
  259. package/dist/resources/workflows.js +0 -72
  260. package/dist/schemas.d.ts +0 -505
  261. package/dist/schemas.js +0 -676
  262. package/dist/server/bootstrap.d.ts +0 -6
  263. package/dist/server/bootstrap.js +0 -563
  264. package/dist/server/event-store.d.ts +0 -18
  265. package/dist/server/event-store.js +0 -71
  266. package/dist/server/roots-manager.d.ts +0 -28
  267. package/dist/server/roots-manager.js +0 -227
  268. package/dist/server/task-store.d.ts +0 -10
  269. package/dist/server/task-store.js +0 -91
  270. package/dist/tools/apply-patch.d.ts +0 -4
  271. package/dist/tools/apply-patch.js +0 -235
  272. package/dist/tools/calculate-hash.d.ts +0 -4
  273. package/dist/tools/calculate-hash.js +0 -187
  274. package/dist/tools/contract.d.ts +0 -50
  275. package/dist/tools/contract.js +0 -1
  276. package/dist/tools/create-directory.d.ts +0 -4
  277. package/dist/tools/create-directory.js +0 -68
  278. package/dist/tools/diff-files.d.ts +0 -4
  279. package/dist/tools/diff-files.js +0 -136
  280. package/dist/tools/edit-file.d.ts +0 -4
  281. package/dist/tools/edit-file.js +0 -256
  282. package/dist/tools/icons.d.ts +0 -15
  283. package/dist/tools/icons.js +0 -24
  284. package/dist/tools/list-directory.d.ts +0 -4
  285. package/dist/tools/list-directory.js +0 -218
  286. package/dist/tools/move-file.d.ts +0 -4
  287. package/dist/tools/move-file.js +0 -178
  288. package/dist/tools/read-multiple.d.ts +0 -4
  289. package/dist/tools/read-multiple.js +0 -207
  290. package/dist/tools/shared.d.ts +0 -172
  291. package/dist/tools/shared.js +0 -599
  292. package/dist/tools/stat-many.d.ts +0 -4
  293. package/dist/tools/stat-many.js +0 -104
  294. package/dist/tools/task-support.d.ts +0 -29
  295. package/dist/tools/task-support.js +0 -472
  296. package/dist/tools/tree.d.ts +0 -4
  297. package/dist/tools/tree.js +0 -102
  298. package/dist/tools/write-file.d.ts +0 -4
  299. package/dist/tools/write-file.js +0 -60
  300. package/dist/tools.d.ts +0 -5
  301. package/dist/tools.js +0 -54
@@ -1,104 +0,0 @@
1
- import { DEFAULT_SEARCH_TIMEOUT_MS } from '../lib/constants.js';
2
- import { classifyError, ErrorCode } from '../lib/errors.js';
3
- import { getMultipleFileInfo } from '../lib/file-operations/metadata.js';
4
- import { formatBytes, joinLines } from '../config.js';
5
- import { GetMultipleFileInfoInputSchema, GetMultipleFileInfoOutputSchema, } from '../schemas.js';
6
- import { FILE_READ_ICONS } from './icons.js';
7
- import { buildBatchPathContext, buildFileInfoPayload, buildStructuredError, buildToolErrorResponse, buildToolResponse, createBatchProgressCallbacks, executeToolWithDiagnostics, READ_ONLY_TOOL_ANNOTATIONS, resolveFinalProgressCurrent, } from './shared.js';
8
- import { registerStandardTool } from './task-support.js';
9
- export const GET_MULTIPLE_FILE_INFO_TOOL = {
10
- name: 'stat_many',
11
- title: 'Get Multiple File Info',
12
- description: 'Get metadata for multiple files/directories in one request. ' +
13
- 'Use `tokenEstimate` (size\u00f74) to pre-screen token cost before reading.',
14
- inputSchema: GetMultipleFileInfoInputSchema,
15
- outputSchema: GetMultipleFileInfoOutputSchema,
16
- annotations: READ_ONLY_TOOL_ANNOTATIONS,
17
- icons: FILE_READ_ICONS,
18
- taskSupport: 'optional',
19
- };
20
- function formatFileInfoDetail(info) {
21
- const lines = [
22
- `${info.name} (${info.type})`,
23
- ` Path: ${info.path}`,
24
- ` Size: ${formatBytes(info.size)}`,
25
- ` Modified: ${info.modified.toISOString()}`,
26
- ];
27
- if (info.mimeType)
28
- lines.push(` Type: ${info.mimeType}`);
29
- if (info.symlinkTarget)
30
- lines.push(` Target: ${info.symlinkTarget}`);
31
- return joinLines(lines);
32
- }
33
- async function handleGetMultipleFileInfo(args, signal, onProgress) {
34
- const result = await getMultipleFileInfo(args.paths, {
35
- includeMimeType: true,
36
- ...(signal ? { signal } : {}),
37
- ...(onProgress ? { onProgress } : {}),
38
- });
39
- const structuredResults = result.results.map((entry) => ({
40
- path: entry.path,
41
- info: entry.info ? buildFileInfoPayload(entry.info) : undefined,
42
- error: entry.error
43
- ? buildStructuredError(entry.error, ErrorCode.NOT_FOUND, entry.path)
44
- : undefined,
45
- }));
46
- const text = result.results
47
- .map((entry) => {
48
- if (entry.error) {
49
- return `${entry.path}: ${buildStructuredError(entry.error, ErrorCode.NOT_FOUND, entry.path).message}`;
50
- }
51
- if (entry.info) {
52
- return formatFileInfoDetail(entry.info);
53
- }
54
- return entry.path;
55
- })
56
- .join('\n\n');
57
- const structured = {
58
- ok: true,
59
- results: structuredResults,
60
- summary: {
61
- total: result.summary.total,
62
- succeeded: result.summary.succeeded,
63
- failed: result.summary.failed,
64
- },
65
- };
66
- return buildToolResponse(text, structured);
67
- }
68
- export function registerGetMultipleFileInfoTool(server, options = {}) {
69
- const handler = (args, ctx) => {
70
- const primaryPath = args.paths[0] ?? '';
71
- return executeToolWithDiagnostics({
72
- toolName: 'stat_many',
73
- ctx,
74
- outputSchema: GetMultipleFileInfoOutputSchema,
75
- timedSignal: { timeoutMs: DEFAULT_SEARCH_TIMEOUT_MS },
76
- context: { path: primaryPath },
77
- run: async (signal) => {
78
- const context = buildBatchPathContext(args.paths);
79
- const { progress, onItemComplete } = createBatchProgressCallbacks(ctx, {
80
- toolLabel: GET_MULTIPLE_FILE_INFO_TOOL.title,
81
- context,
82
- totalItems: args.paths.length,
83
- itemVerb: 'done',
84
- });
85
- try {
86
- const result = await handleGetMultipleFileInfo(args, signal, onItemComplete);
87
- const sc = result.structuredContent;
88
- const total = sc.summary?.total ?? 0;
89
- const failed = sc.summary?.failed ?? 0;
90
- const suffix = failed ? `${failed} failed` : 'done';
91
- const finalCurrent = resolveFinalProgressCurrent(progress, total);
92
- progress.complete(`${GET_MULTIPLE_FILE_INFO_TOOL.title}: ${context} • ${suffix}`, finalCurrent);
93
- return result;
94
- }
95
- catch (error) {
96
- progress.fail(`${GET_MULTIPLE_FILE_INFO_TOOL.title}: ${context} • ${classifyError(error)}`);
97
- throw error;
98
- }
99
- },
100
- onError: (error) => buildToolErrorResponse(error, ErrorCode.NOT_FOUND, primaryPath),
101
- });
102
- };
103
- registerStandardTool(server, GET_MULTIPLE_FILE_INFO_TOOL, handler, options);
104
- }
@@ -1,29 +0,0 @@
1
- import { type McpServer, type RequestTaskStore, type StandardSchemaWithJSON, type ToolTaskHandler } from '@modelcontextprotocol/server';
2
- import { type IconInfo, type ToolContext, type ToolContract, type ToolRegistrationOptions, type ToolResult } from './shared.js';
3
- /**
4
- * Report an intermediate 'working' status update for the current task.
5
- * No-op when called outside of a task context.
6
- */
7
- export declare function reportTaskStatus(statusMessage: string): Promise<void>;
8
- type TaskToolContext = ToolContext & {
9
- taskId?: string;
10
- taskStore?: RequestTaskStore;
11
- taskRequestedTtl?: number;
12
- };
13
- type ToolSchema = StandardSchemaWithJSON | undefined;
14
- type ToolArgs<Args extends ToolSchema> = Args extends StandardSchemaWithJSON ? StandardSchemaWithJSON.InferOutput<Args> : undefined;
15
- export declare function registerToolTaskIfAvailable<Args extends ToolSchema, Result>(server: McpServer, toolName: string, toolDef: object, run: (args: ToolArgs<Args>, ctx: TaskToolContext) => Promise<ToolResult<Result>>, iconInfo: IconInfo | undefined, guard?: () => boolean): boolean;
16
- export declare function registerStandardTool<Args, Result extends Record<string, unknown>>(server: McpServer, toolDef: ToolContract, handler: (args: Args, ctx: ToolContext) => Promise<ToolResult<Result>>, options?: ToolRegistrationOptions, wrapOptions?: {
17
- guard?: (() => boolean) | undefined;
18
- progressMessage?: (args: Args) => string;
19
- completionMessage?: (args: Args, result: ToolResult<Result>) => string | undefined;
20
- }): void;
21
- interface TaskHandlerOptions {
22
- guard?: () => boolean;
23
- toolName?: string;
24
- cancelPollMs?: number;
25
- pollIntervalMs?: number;
26
- }
27
- export declare function createToolTaskHandler<Result>(run: (args: undefined, ctx: TaskToolContext) => Promise<ToolResult<Result>>, options?: TaskHandlerOptions): ToolTaskHandler;
28
- export declare function createToolTaskHandler<Args extends StandardSchemaWithJSON, Result>(run: (args: ToolArgs<Args>, ctx: TaskToolContext) => Promise<ToolResult<Result>>, options?: TaskHandlerOptions): ToolTaskHandler<Args>;
29
- export {};
@@ -1,472 +0,0 @@
1
- import { isTerminal, RELATED_TASK_META_KEY, } from '@modelcontextprotocol/server';
2
- import { AsyncLocalStorage } from 'node:async_hooks';
3
- import { channel } from 'node:diagnostics_channel';
4
- import { performance } from 'node:perf_hooks';
5
- import { format } from 'node:util';
6
- import { DEFAULT_TASK_TTL_MS, MAX_CONCURRENT_TASKS, MAX_TASK_TTL_MS, TASK_CANCEL_POLL_MS, TASK_POLL_INTERVAL_MS, } from '../lib/constants.js';
7
- import { ErrorCode, McpError } from '../lib/errors.js';
8
- import { Logger } from '../lib/logger.js';
9
- import { isRecord } from '../lib/utils.js';
10
- import { buildToolErrorResponse, maybeStripStructuredContentFromResult, resolveToolTaskSupportLevel, toToolContext, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
11
- const taskContext = new AsyncLocalStorage();
12
- /**
13
- * Report an intermediate 'working' status update for the current task.
14
- * No-op when called outside of a task context.
15
- */
16
- export async function reportTaskStatus(statusMessage) {
17
- const store = taskContext.getStore();
18
- if (!store)
19
- return;
20
- const { taskId, taskStore, ctx, toolName } = store;
21
- try {
22
- await taskStore.updateTaskStatus(taskId, 'working', statusMessage);
23
- await notifyTaskStatusIfPossible(ctx, taskStore, taskId, toolName);
24
- }
25
- catch {
26
- // Best-effort: never fail tool execution for status updates.
27
- }
28
- }
29
- const TASK_DIAGNOSTICS_CHANNEL = channel('filesystem-mcp:tasks');
30
- function publishTaskDiagnostics(event) {
31
- if (TASK_DIAGNOSTICS_CHANNEL.hasSubscribers) {
32
- TASK_DIAGNOSTICS_CHANNEL.publish(event);
33
- }
34
- }
35
- // --- Type Guards & Helpers ---
36
- function hasTaskToolCapability(server) {
37
- try {
38
- const capabilities = server.server.getCapabilities();
39
- return capabilities.tasks?.requests?.tools?.call !== undefined;
40
- }
41
- catch {
42
- return false;
43
- }
44
- }
45
- const TASK_STATUS_NOTIFICATION_METHOD = 'notifications/tasks/status';
46
- const TASK_CREATED_NOTIFICATION_METHOD = 'notifications/tasks/created';
47
- function assertCreateTaskContext(value) {
48
- if (!isRecord(value.task) || typeof value.task.store !== 'object') {
49
- throw new McpError(ErrorCode.INVALID_INPUT, 'Task store not configured.');
50
- }
51
- }
52
- function assertTaskRequestContext(value) {
53
- if (!isRecord(value.task) ||
54
- typeof value.task.store !== 'object' ||
55
- typeof value.task.id !== 'string' ||
56
- value.task.id.length === 0) {
57
- throw new McpError(ErrorCode.INVALID_INPUT, 'Task id or store missing.');
58
- }
59
- }
60
- function toTaskToolContext(ctx) {
61
- return {
62
- ...toToolContext(ctx),
63
- taskStore: ctx.task.store,
64
- ...(typeof ctx.task.id === 'string' && ctx.task.id.length > 0
65
- ? { taskId: ctx.task.id }
66
- : {}),
67
- ...(ctx.task.requestedTtl !== undefined
68
- ? { taskRequestedTtl: ctx.task.requestedTtl }
69
- : {}),
70
- };
71
- }
72
- function toGetTaskResult(task) {
73
- return task;
74
- }
75
- function toCallToolResult(value) {
76
- return value;
77
- }
78
- function getToolResultErrorCode(result) {
79
- if (!isRecord(result) || result.isError !== true)
80
- return undefined;
81
- if (typeof result.errorCode === 'string')
82
- return result.errorCode;
83
- const { content } = result;
84
- if (!Array.isArray(content) || content.length === 0)
85
- return undefined;
86
- const first = content[0];
87
- if (!isRecord(first) || first.type !== 'text')
88
- return undefined;
89
- const { text } = first;
90
- if (typeof text !== 'string')
91
- return undefined;
92
- const match = /^Error \[([A-Z0-9_]+)\]:/.exec(text);
93
- return match ? match[1] : undefined;
94
- }
95
- function isCancelledToolResult(result) {
96
- return getToolResultErrorCode(result) === ErrorCode.CANCELLED;
97
- }
98
- function resolveTaskResultStatuses(result) {
99
- if (isCancelledToolResult(result)) {
100
- return { storedStatus: 'failed', reportedStatus: 'cancelled' };
101
- }
102
- if (isRecord(result) && result.isError === true) {
103
- return { storedStatus: 'failed', reportedStatus: 'failed' };
104
- }
105
- return { storedStatus: 'completed', reportedStatus: 'completed' };
106
- }
107
- function attachRelatedTaskMeta(result, taskId) {
108
- const existingMeta = isRecord(result._meta) ? result._meta : {};
109
- return {
110
- ...result,
111
- _meta: {
112
- ...existingMeta,
113
- [RELATED_TASK_META_KEY]: { taskId },
114
- },
115
- };
116
- }
117
- async function projectCancelledTaskStatus(taskStore, task) {
118
- if (task.status !== 'failed')
119
- return task;
120
- try {
121
- const result = await taskStore.getTaskResult(task.taskId);
122
- if (isCancelledToolResult(result)) {
123
- return { ...task, status: 'cancelled' };
124
- }
125
- }
126
- catch {
127
- // Best effort only: task result may not be available yet.
128
- }
129
- return task;
130
- }
131
- function buildTaskStatusNotificationParams(task) {
132
- const params = {
133
- taskId: task.taskId,
134
- status: task.status,
135
- ttl: task.ttl,
136
- createdAt: task.createdAt,
137
- lastUpdatedAt: task.lastUpdatedAt,
138
- };
139
- if (task.pollInterval !== undefined)
140
- params.pollInterval = task.pollInterval;
141
- if (task.statusMessage !== undefined)
142
- params.statusMessage = task.statusMessage;
143
- return params;
144
- }
145
- async function notifyTaskCreatedIfPossible(ctx, taskId, toolName) {
146
- if (!ctx.sendNotification)
147
- return;
148
- try {
149
- await ctx.sendNotification({
150
- method: TASK_CREATED_NOTIFICATION_METHOD,
151
- params: {
152
- _meta: {
153
- [RELATED_TASK_META_KEY]: {
154
- taskId,
155
- },
156
- },
157
- },
158
- });
159
- }
160
- catch {
161
- publishTaskDiagnostics({
162
- phase: 'task_status_notify_failed',
163
- taskId,
164
- ...(toolName ? { toolName } : {}),
165
- });
166
- }
167
- }
168
- async function notifyTaskStatusIfPossible(ctx, taskStore, taskId, toolName) {
169
- if (!ctx.sendNotification)
170
- return;
171
- try {
172
- const task = await taskStore.getTask(taskId);
173
- const normalized = await projectCancelledTaskStatus(taskStore, toGetTaskResult(task));
174
- await ctx.sendNotification({
175
- method: TASK_STATUS_NOTIFICATION_METHOD,
176
- params: buildTaskStatusNotificationParams(normalized),
177
- });
178
- publishTaskDiagnostics({
179
- phase: 'task_status_notified',
180
- taskId,
181
- status: normalized.status,
182
- ...(toolName ? { toolName } : {}),
183
- });
184
- }
185
- catch {
186
- publishTaskDiagnostics({
187
- phase: 'task_status_notify_failed',
188
- taskId,
189
- ...(toolName ? { toolName } : {}),
190
- });
191
- // Never fail task execution because status notifications are optional.
192
- }
193
- }
194
- function getTaskStore(ctx) {
195
- if (!ctx.taskStore) {
196
- throw new McpError(ErrorCode.INVALID_INPUT, 'Task store not configured.');
197
- }
198
- return ctx.taskStore;
199
- }
200
- function getTaskId(ctx) {
201
- if (!ctx.taskId) {
202
- throw new McpError(ErrorCode.INVALID_INPUT, 'Task id missing.');
203
- }
204
- return ctx.taskId;
205
- }
206
- function isErrorResult(result) {
207
- return 'isError' in result && result.isError;
208
- }
209
- // Strips structuredContent from a tool result if present, without modifying the original object.
210
- function withoutStructuredContent(result) {
211
- if (!Object.hasOwn(result, 'structuredContent'))
212
- return result;
213
- const stripped = { ...result };
214
- delete stripped['structuredContent'];
215
- return stripped;
216
- }
217
- const taskCreationLocks = new WeakMap();
218
- async function acquireTaskCreationLock(taskStore) {
219
- const previous = taskCreationLocks.get(taskStore) ?? Promise.resolve();
220
- let release;
221
- const next = new Promise((resolve) => {
222
- release = resolve;
223
- });
224
- taskCreationLocks.set(taskStore, previous.catch(() => { }).then(() => next));
225
- await previous.catch(() => { });
226
- return () => {
227
- release();
228
- };
229
- }
230
- async function isTaskAlreadyTerminal(taskStore, taskId) {
231
- try {
232
- const task = await taskStore.getTask(taskId);
233
- if (!isRecord(task))
234
- return false;
235
- const { status } = task;
236
- return typeof status === 'string' && isTerminal(status);
237
- }
238
- catch {
239
- return false;
240
- }
241
- }
242
- function isMissingTaskStoreError(error) {
243
- return (error instanceof Error &&
244
- /Task .*not found|not found after cancellation/iu.test(error.message));
245
- }
246
- async function countActiveTasks(taskStore) {
247
- if (typeof taskStore.listTasks !== 'function')
248
- return 0;
249
- const listed = await taskStore.listTasks();
250
- const tasks = Array.isArray(listed.tasks) ? listed.tasks : [];
251
- let active = 0;
252
- for (const task of tasks) {
253
- if (!isRecord(task) || typeof task.status !== 'string')
254
- continue;
255
- if (!isTerminal(task.status)) {
256
- active += 1;
257
- }
258
- }
259
- return active;
260
- }
261
- function resolveRequestedTaskTtl(requestedTtl) {
262
- if (requestedTtl === undefined)
263
- return DEFAULT_TASK_TTL_MS;
264
- if (!Number.isFinite(requestedTtl)) {
265
- throw new McpError(ErrorCode.INVALID_INPUT, 'Task TTL must be finite.');
266
- }
267
- const normalized = Math.trunc(requestedTtl);
268
- if (normalized <= 0) {
269
- throw new McpError(ErrorCode.INVALID_INPUT, 'Task TTL must be > 0.');
270
- }
271
- return Math.min(normalized, MAX_TASK_TTL_MS);
272
- }
273
- async function safelyStoreTaskResult(taskStore, taskId, storedStatus, result) {
274
- try {
275
- await taskStore.storeTaskResult(taskId, storedStatus, result);
276
- }
277
- catch (error) {
278
- if (isMissingTaskStoreError(error))
279
- return;
280
- // If task was already cancelled/failed by another process, ignore the write error
281
- if (await isTaskAlreadyTerminal(taskStore, taskId))
282
- return;
283
- throw error;
284
- }
285
- }
286
- async function isTaskCancelled(taskStore, taskId) {
287
- try {
288
- const task = await taskStore.getTask(taskId);
289
- return isRecord(task) && task.status === 'cancelled';
290
- }
291
- catch {
292
- return false;
293
- }
294
- }
295
- async function runTaskInBackground(run, args, ctx, taskStore, taskId, toolName, cancelPollMs) {
296
- // Create a dedicated AbortController for background execution.
297
- // The original request signal is stale once createTask returns.
298
- const taskAbort = new AbortController();
299
- const taskExtra = { ...ctx, signal: taskAbort.signal };
300
- // Poll the task store for client-initiated cancellation.
301
- const cancelPoller = setInterval(() => {
302
- void isTaskCancelled(taskStore, taskId).then((cancelled) => {
303
- if (cancelled)
304
- taskAbort.abort(new Error('Task cancelled by client'));
305
- });
306
- }, cancelPollMs ?? TASK_CANCEL_POLL_MS);
307
- const start = performance.now();
308
- let taskStatuses;
309
- let result;
310
- try {
311
- const rawResult = await taskContext.run({ taskId, toolName, startTime: start, taskStore, ctx: taskExtra }, () => run(args, taskExtra));
312
- taskStatuses = resolveTaskResultStatuses(rawResult);
313
- result = isErrorResult(rawResult)
314
- ? withoutStructuredContent(rawResult)
315
- : maybeStripStructuredContentFromResult(rawResult);
316
- }
317
- catch (error) {
318
- taskStatuses = { storedStatus: 'failed', reportedStatus: 'failed' };
319
- result = maybeStripStructuredContentFromResult(buildToolErrorResponse(error, ErrorCode.UNKNOWN));
320
- }
321
- finally {
322
- clearInterval(cancelPoller);
323
- }
324
- const durationMs = performance.now() - start;
325
- try {
326
- await safelyStoreTaskResult(taskStore, taskId, taskStatuses.storedStatus, result);
327
- publishTaskDiagnostics({
328
- phase: 'task_result_stored',
329
- taskId,
330
- status: taskStatuses.reportedStatus,
331
- toolName,
332
- durationMs,
333
- });
334
- await notifyTaskStatusIfPossible(ctx, taskStore, taskId, toolName);
335
- }
336
- catch (innerError) {
337
- Logger.error(format('Failed to store task result for task %s:', taskId), innerError);
338
- const syntheticTask = {
339
- taskId,
340
- status: taskStatuses.reportedStatus === 'cancelled' ? 'cancelled' : 'failed',
341
- ttl: null,
342
- createdAt: new Date().toISOString(),
343
- lastUpdatedAt: new Date().toISOString(),
344
- statusMessage: 'Internal system error while storing result',
345
- };
346
- if (ctx.sendNotification) {
347
- try {
348
- await ctx.sendNotification({
349
- method: TASK_STATUS_NOTIFICATION_METHOD,
350
- params: buildTaskStatusNotificationParams(syntheticTask),
351
- });
352
- }
353
- catch {
354
- // Best effort only: transport may already be closed while a cancelled
355
- // task is still unwinding in the background.
356
- }
357
- }
358
- }
359
- }
360
- /**
361
- * Registers a tool preferring task-capable registration when available, and
362
- * returns `true`. Returns `false` so the caller can fall through to standard
363
- * `server.registerTool`.
364
- */
365
- function tryRegisterToolTask(server, toolName, toolDef, taskHandler, iconInfo) {
366
- if (!hasTaskToolCapability(server))
367
- return false;
368
- const def = toolDef;
369
- const existingExecution = def.execution ?? {};
370
- const taskSupport = resolveToolTaskSupportLevel(def.taskSupport, existingExecution.taskSupport);
371
- if (!taskSupport || taskSupport === 'forbidden')
372
- return false;
373
- server.experimental.tasks.registerToolTask(toolName, withDefaultIcons({ ...toolDef, execution: { ...existingExecution, taskSupport } }, iconInfo), taskHandler);
374
- return true;
375
- }
376
- export function registerToolTaskIfAvailable(server, toolName, toolDef, run, iconInfo, guard) {
377
- const taskOptions = {
378
- ...(guard ? { guard } : {}),
379
- toolName,
380
- };
381
- return tryRegisterToolTask(server, toolName, toolDef, createToolTaskHandler(run, taskOptions), iconInfo);
382
- }
383
- export function registerStandardTool(server, toolDef, handler, options = {}, wrapOptions = {}) {
384
- const wrappedHandler = wrapToolHandler(handler, {
385
- guard: options.isInitialized,
386
- ...wrapOptions,
387
- });
388
- const validatedHandler = withValidatedArgs(toolDef.inputSchema, wrappedHandler);
389
- if (registerToolTaskIfAvailable(server, toolDef.name, toolDef, validatedHandler, options.iconInfo, options.isInitialized)) {
390
- return;
391
- }
392
- server.registerTool(toolDef.name, withDefaultIcons({ ...toolDef }, options.iconInfo), validatedHandler);
393
- }
394
- export function createToolTaskHandler(run, options) {
395
- const createTask = (async (...params) => {
396
- let args;
397
- let serverCtx;
398
- if (params.length === 1) {
399
- serverCtx = params[0];
400
- }
401
- else {
402
- [args, serverCtx] = params;
403
- }
404
- assertCreateTaskContext(serverCtx);
405
- const ctx = toTaskToolContext(serverCtx);
406
- if (options?.guard && !options.guard()) {
407
- throw new McpError(ErrorCode.INVALID_INPUT, 'Client not initialized; wait for notifications/initialized');
408
- }
409
- const taskStore = getTaskStore(ctx);
410
- const releaseCreationLock = await acquireTaskCreationLock(taskStore);
411
- let task;
412
- try {
413
- if ((await countActiveTasks(taskStore)) >= MAX_CONCURRENT_TASKS) {
414
- throw new McpError(ErrorCode.INVALID_INPUT, `Too many active tasks (limit: ${String(MAX_CONCURRENT_TASKS)}).`);
415
- }
416
- task = await taskStore.createTask({
417
- ttl: resolveRequestedTaskTtl(ctx.taskRequestedTtl),
418
- pollInterval: options?.pollIntervalMs ?? TASK_POLL_INTERVAL_MS,
419
- });
420
- }
421
- finally {
422
- releaseCreationLock();
423
- }
424
- const toolLabel = options?.toolName ?? 'tool';
425
- try {
426
- await taskStore.updateTaskStatus(task.taskId, 'working', `${toolLabel}: starting`);
427
- }
428
- catch {
429
- // Best effort — status message is informational.
430
- }
431
- publishTaskDiagnostics({
432
- phase: 'task_created',
433
- taskId: task.taskId,
434
- status: task.status,
435
- ...(options?.toolName ? { toolName: options.toolName } : {}),
436
- });
437
- const taskExtra = {
438
- ...ctx,
439
- taskStore,
440
- taskId: task.taskId,
441
- };
442
- void notifyTaskCreatedIfPossible(taskExtra, task.taskId, options?.toolName);
443
- void notifyTaskStatusIfPossible(taskExtra, taskStore, task.taskId, options?.toolName);
444
- void runTaskInBackground(run, args, taskExtra, taskStore, task.taskId, options?.toolName, options?.cancelPollMs);
445
- return {
446
- task,
447
- };
448
- });
449
- const getTask = (async (...params) => {
450
- const serverCtx = params.length === 1 ? params[0] : params[1];
451
- assertTaskRequestContext(serverCtx);
452
- const ctx = toTaskToolContext(serverCtx);
453
- const taskStore = getTaskStore(ctx);
454
- const taskId = getTaskId(ctx);
455
- const task = await taskStore.getTask(taskId);
456
- return projectCancelledTaskStatus(taskStore, toGetTaskResult(task));
457
- });
458
- const getTaskResult = (async (...params) => {
459
- const serverCtx = params.length === 1 ? params[0] : params[1];
460
- assertTaskRequestContext(serverCtx);
461
- const ctx = toTaskToolContext(serverCtx);
462
- const taskStore = getTaskStore(ctx);
463
- const taskId = getTaskId(ctx);
464
- const result = await taskStore.getTaskResult(taskId);
465
- return attachRelatedTaskMeta(toCallToolResult(result), taskId);
466
- });
467
- return {
468
- createTask,
469
- getTask,
470
- getTaskResult,
471
- };
472
- }
@@ -1,4 +0,0 @@
1
- import type { McpServer } from '@modelcontextprotocol/server';
2
- import { type ToolContract, type ToolRegistrationOptions } from './shared.js';
3
- export declare const TREE_TOOL: ToolContract;
4
- export declare function registerTreeTool(server: McpServer, options?: ToolRegistrationOptions): void;