@j0hanz/filesystem-mcp 1.17.1 → 1.19.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.
- package/dist/lib/constants.d.ts +3 -1
- package/dist/lib/constants.js +3 -1
- package/dist/lib/errors.d.ts +1 -0
- package/dist/lib/errors.js +1 -1
- package/dist/prompts.js +20 -0
- package/dist/resources.js +4 -5
- package/dist/server/bootstrap.js +11 -37
- package/dist/server/event-store.d.ts +18 -0
- package/dist/server/event-store.js +71 -0
- package/dist/server/task-store.d.ts +1 -0
- package/dist/server/task-store.js +23 -5
- package/dist/tools/apply-patch.js +7 -5
- package/dist/tools/calculate-hash.js +5 -5
- package/dist/tools/create-directory.js +5 -6
- package/dist/tools/delete-file.js +3 -3
- package/dist/tools/diff-files.js +5 -5
- package/dist/tools/edit-file.js +5 -5
- package/dist/tools/list-directory.js +3 -3
- package/dist/tools/move-file.js +5 -6
- package/dist/tools/read-multiple.js +14 -7
- package/dist/tools/read.js +2 -2
- package/dist/tools/replace-in-files.js +5 -5
- package/dist/tools/roots.js +3 -3
- package/dist/tools/search-content.js +5 -4
- package/dist/tools/search-files.js +5 -5
- package/dist/tools/shared.d.ts +15 -18
- package/dist/tools/shared.js +5 -17
- package/dist/tools/stat-many.js +7 -6
- package/dist/tools/stat.js +4 -4
- package/dist/tools/task-support.d.ts +5 -0
- package/dist/tools/task-support.js +57 -107
- package/dist/tools/tree.js +5 -5
- package/dist/tools/write-file.js +3 -3
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {} from '@modelcontextprotocol/server';
|
|
1
|
+
import { isTerminal, RELATED_TASK_META_KEY, } from '@modelcontextprotocol/server';
|
|
2
2
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
3
3
|
import { channel } from 'node:diagnostics_channel';
|
|
4
4
|
import { performance } from 'node:perf_hooks';
|
|
@@ -7,8 +7,25 @@ import { DEFAULT_TASK_TTL_MS, MAX_CONCURRENT_TASKS, MAX_TASK_TTL_MS, TASK_CANCEL
|
|
|
7
7
|
import { ErrorCode, McpError } from '../lib/errors.js';
|
|
8
8
|
import { Logger } from '../lib/logger.js';
|
|
9
9
|
import { isRecord } from '../lib/utils.js';
|
|
10
|
-
import { buildToolErrorResponse, maybeStripStructuredContentFromResult, resolveToolTaskSupportLevel, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
10
|
+
import { buildToolErrorResponse, maybeStripStructuredContentFromResult, resolveToolTaskSupportLevel, toToolContext, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
11
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
|
+
}
|
|
12
29
|
const TASK_DIAGNOSTICS_CHANNEL = channel('filesystem-mcp:tasks');
|
|
13
30
|
function publishTaskDiagnostics(event) {
|
|
14
31
|
if (TASK_DIAGNOSTICS_CHANNEL.hasSubscribers) {
|
|
@@ -17,44 +34,33 @@ function publishTaskDiagnostics(event) {
|
|
|
17
34
|
}
|
|
18
35
|
// --- Type Guards & Helpers ---
|
|
19
36
|
function hasTaskToolCapability(server) {
|
|
20
|
-
|
|
21
|
-
|
|
37
|
+
try {
|
|
38
|
+
const capabilities = server.server.getCapabilities();
|
|
39
|
+
return capabilities.tasks?.requests?.tools?.call !== undefined;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
22
44
|
}
|
|
23
45
|
const TASK_STATUS_NOTIFICATION_METHOD = 'notifications/tasks/status';
|
|
24
46
|
const TASK_CREATED_NOTIFICATION_METHOD = 'notifications/tasks/created';
|
|
25
|
-
function
|
|
26
|
-
|
|
27
|
-
typeof value.createTask === 'function' &&
|
|
28
|
-
typeof value.getTask === 'function' &&
|
|
29
|
-
typeof value.storeTaskResult === 'function' &&
|
|
30
|
-
typeof value.getTaskResult === 'function');
|
|
31
|
-
}
|
|
32
|
-
function hasTaskStoreContext(value) {
|
|
33
|
-
return isRecord(value.task) && isRequestTaskStore(value.task.store);
|
|
34
|
-
}
|
|
35
|
-
function asCreateTaskContext(value) {
|
|
36
|
-
if (!hasTaskStoreContext(value)) {
|
|
47
|
+
function assertCreateTaskContext(value) {
|
|
48
|
+
if (!isRecord(value.task) || typeof value.task.store !== 'object') {
|
|
37
49
|
throw new McpError(ErrorCode.INVALID_INPUT, 'Task store not configured.');
|
|
38
50
|
}
|
|
39
|
-
return value;
|
|
40
51
|
}
|
|
41
|
-
function
|
|
42
|
-
if (!
|
|
43
|
-
|
|
52
|
+
function assertTaskRequestContext(value) {
|
|
53
|
+
if (!isRecord(value.task) ||
|
|
54
|
+
typeof value.task.store !== 'object' ||
|
|
44
55
|
typeof value.task.id !== 'string' ||
|
|
45
56
|
value.task.id.length === 0) {
|
|
46
57
|
throw new McpError(ErrorCode.INVALID_INPUT, 'Task id or store missing.');
|
|
47
58
|
}
|
|
48
|
-
return value;
|
|
49
59
|
}
|
|
50
60
|
function toTaskToolContext(ctx) {
|
|
51
61
|
return {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
? { _meta: ctx.mcpReq._meta }
|
|
55
|
-
: {}),
|
|
56
|
-
sendNotification: async (notification) => ctx.mcpReq.notify(notification),
|
|
57
|
-
...(hasTaskStoreContext(ctx) ? { taskStore: ctx.task.store } : {}),
|
|
62
|
+
...toToolContext(ctx),
|
|
63
|
+
taskStore: ctx.task.store,
|
|
58
64
|
...(typeof ctx.task.id === 'string' && ctx.task.id.length > 0
|
|
59
65
|
? { taskId: ctx.task.id }
|
|
60
66
|
: {}),
|
|
@@ -63,56 +69,11 @@ function toTaskToolContext(ctx) {
|
|
|
63
69
|
: {}),
|
|
64
70
|
};
|
|
65
71
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
'working',
|
|
69
|
-
'input_required',
|
|
70
|
-
'completed',
|
|
71
|
-
'failed',
|
|
72
|
-
'cancelled',
|
|
73
|
-
'unknown',
|
|
74
|
-
]);
|
|
75
|
-
function isTaskStatus(value) {
|
|
76
|
-
return typeof value === 'string' && TASK_STATUSES.has(value);
|
|
77
|
-
}
|
|
78
|
-
function normalizeGetTaskResult(value) {
|
|
79
|
-
if (!isRecord(value) || typeof value.taskId !== 'string') {
|
|
80
|
-
throw new McpError(ErrorCode.INVALID_INPUT, 'Invalid task object.');
|
|
81
|
-
}
|
|
82
|
-
const status = isTaskStatus(value.status) ? value.status : undefined;
|
|
83
|
-
if (!status) {
|
|
84
|
-
throw new McpError(ErrorCode.INVALID_INPUT, 'Invalid task status.');
|
|
85
|
-
}
|
|
86
|
-
const createdAt = typeof value.createdAt === 'string'
|
|
87
|
-
? value.createdAt
|
|
88
|
-
: new Date().toISOString();
|
|
89
|
-
const lastUpdatedAt = typeof value.lastUpdatedAt === 'string' ? value.lastUpdatedAt : createdAt;
|
|
90
|
-
const ttl = typeof value.ttl === 'number' ? value.ttl : null;
|
|
91
|
-
const normalized = {
|
|
92
|
-
taskId: value.taskId,
|
|
93
|
-
status,
|
|
94
|
-
ttl,
|
|
95
|
-
createdAt,
|
|
96
|
-
lastUpdatedAt,
|
|
97
|
-
};
|
|
98
|
-
if (typeof value.pollInterval === 'number') {
|
|
99
|
-
normalized.pollInterval = value.pollInterval;
|
|
100
|
-
}
|
|
101
|
-
if (typeof value.statusMessage === 'string') {
|
|
102
|
-
normalized.statusMessage = value.statusMessage;
|
|
103
|
-
}
|
|
104
|
-
if (isRecord(value._meta)) {
|
|
105
|
-
normalized._meta = value._meta;
|
|
106
|
-
}
|
|
107
|
-
return normalized;
|
|
72
|
+
function toGetTaskResult(task) {
|
|
73
|
+
return task;
|
|
108
74
|
}
|
|
109
|
-
function
|
|
110
|
-
|
|
111
|
-
Array.isArray(value.content) &&
|
|
112
|
-
value.content.every((entry) => isRecord(entry) && typeof entry.type === 'string')) {
|
|
113
|
-
return value;
|
|
114
|
-
}
|
|
115
|
-
throw new McpError(ErrorCode.INVALID_INPUT, 'Invalid stored task result.');
|
|
75
|
+
function toCallToolResult(value) {
|
|
76
|
+
return value;
|
|
116
77
|
}
|
|
117
78
|
function getToolResultErrorCode(result) {
|
|
118
79
|
if (!isRecord(result) || result.isError !== true)
|
|
@@ -149,7 +110,7 @@ function attachRelatedTaskMeta(result, taskId) {
|
|
|
149
110
|
...result,
|
|
150
111
|
_meta: {
|
|
151
112
|
...existingMeta,
|
|
152
|
-
|
|
113
|
+
[RELATED_TASK_META_KEY]: { taskId },
|
|
153
114
|
},
|
|
154
115
|
};
|
|
155
116
|
}
|
|
@@ -182,16 +143,14 @@ function buildTaskStatusNotificationParams(task) {
|
|
|
182
143
|
return params;
|
|
183
144
|
}
|
|
184
145
|
async function notifyTaskCreatedIfPossible(ctx, taskId, toolName) {
|
|
185
|
-
|
|
186
|
-
if (typeof sendNotification !== 'function')
|
|
146
|
+
if (!ctx.sendNotification)
|
|
187
147
|
return;
|
|
188
|
-
const notify = sendNotification;
|
|
189
148
|
try {
|
|
190
|
-
await
|
|
149
|
+
await ctx.sendNotification({
|
|
191
150
|
method: TASK_CREATED_NOTIFICATION_METHOD,
|
|
192
151
|
params: {
|
|
193
152
|
_meta: {
|
|
194
|
-
|
|
153
|
+
[RELATED_TASK_META_KEY]: {
|
|
195
154
|
taskId,
|
|
196
155
|
},
|
|
197
156
|
},
|
|
@@ -207,14 +166,12 @@ async function notifyTaskCreatedIfPossible(ctx, taskId, toolName) {
|
|
|
207
166
|
}
|
|
208
167
|
}
|
|
209
168
|
async function notifyTaskStatusIfPossible(ctx, taskStore, taskId, toolName) {
|
|
210
|
-
|
|
211
|
-
if (typeof sendNotification !== 'function')
|
|
169
|
+
if (!ctx.sendNotification)
|
|
212
170
|
return;
|
|
213
|
-
const notify = sendNotification;
|
|
214
171
|
try {
|
|
215
172
|
const task = await taskStore.getTask(taskId);
|
|
216
|
-
const normalized = await projectCancelledTaskStatus(taskStore,
|
|
217
|
-
await
|
|
173
|
+
const normalized = await projectCancelledTaskStatus(taskStore, toGetTaskResult(task));
|
|
174
|
+
await ctx.sendNotification({
|
|
218
175
|
method: TASK_STATUS_NOTIFICATION_METHOD,
|
|
219
176
|
params: buildTaskStatusNotificationParams(normalized),
|
|
220
177
|
});
|
|
@@ -257,12 +214,6 @@ function withoutStructuredContent(result) {
|
|
|
257
214
|
delete stripped['structuredContent'];
|
|
258
215
|
return stripped;
|
|
259
216
|
}
|
|
260
|
-
const TERMINAL_TASK_STATUSES = new Set([
|
|
261
|
-
'completed',
|
|
262
|
-
'failed',
|
|
263
|
-
'cancelled',
|
|
264
|
-
'unknown',
|
|
265
|
-
]);
|
|
266
217
|
const taskCreationLocks = new WeakMap();
|
|
267
218
|
async function acquireTaskCreationLock(taskStore) {
|
|
268
219
|
const previous = taskCreationLocks.get(taskStore) ?? Promise.resolve();
|
|
@@ -282,7 +233,7 @@ async function isTaskAlreadyTerminal(taskStore, taskId) {
|
|
|
282
233
|
if (!isRecord(task))
|
|
283
234
|
return false;
|
|
284
235
|
const { status } = task;
|
|
285
|
-
return typeof status === 'string' &&
|
|
236
|
+
return typeof status === 'string' && isTerminal(status);
|
|
286
237
|
}
|
|
287
238
|
catch {
|
|
288
239
|
return false;
|
|
@@ -301,7 +252,7 @@ async function countActiveTasks(taskStore) {
|
|
|
301
252
|
for (const task of tasks) {
|
|
302
253
|
if (!isRecord(task) || typeof task.status !== 'string')
|
|
303
254
|
continue;
|
|
304
|
-
if (!
|
|
255
|
+
if (!isTerminal(task.status)) {
|
|
305
256
|
active += 1;
|
|
306
257
|
}
|
|
307
258
|
}
|
|
@@ -357,7 +308,7 @@ async function runTaskInBackground(run, args, ctx, taskStore, taskId, toolName,
|
|
|
357
308
|
let taskStatuses;
|
|
358
309
|
let result;
|
|
359
310
|
try {
|
|
360
|
-
const rawResult = await taskContext.run({ taskId, toolName, startTime: start }, () => run(args, taskExtra));
|
|
311
|
+
const rawResult = await taskContext.run({ taskId, toolName, startTime: start, taskStore, ctx: taskExtra }, () => run(args, taskExtra));
|
|
361
312
|
taskStatuses = resolveTaskResultStatuses(rawResult);
|
|
362
313
|
result = isErrorResult(rawResult)
|
|
363
314
|
? withoutStructuredContent(rawResult)
|
|
@@ -392,10 +343,9 @@ async function runTaskInBackground(run, args, ctx, taskStore, taskId, toolName,
|
|
|
392
343
|
lastUpdatedAt: new Date().toISOString(),
|
|
393
344
|
statusMessage: 'Internal system error while storing result',
|
|
394
345
|
};
|
|
395
|
-
|
|
396
|
-
if (typeof sendNotification === 'function') {
|
|
346
|
+
if (ctx.sendNotification) {
|
|
397
347
|
try {
|
|
398
|
-
await sendNotification({
|
|
348
|
+
await ctx.sendNotification({
|
|
399
349
|
method: TASK_STATUS_NOTIFICATION_METHOD,
|
|
400
350
|
params: buildTaskStatusNotificationParams(syntheticTask),
|
|
401
351
|
});
|
|
@@ -451,7 +401,8 @@ export function createToolTaskHandler(run, options) {
|
|
|
451
401
|
else {
|
|
452
402
|
[args, serverCtx] = params;
|
|
453
403
|
}
|
|
454
|
-
|
|
404
|
+
assertCreateTaskContext(serverCtx);
|
|
405
|
+
const ctx = toTaskToolContext(serverCtx);
|
|
455
406
|
if (options?.guard && !options.guard()) {
|
|
456
407
|
throw new McpError(ErrorCode.INVALID_INPUT, 'Client not initialized; wait for notifications/initialized');
|
|
457
408
|
}
|
|
@@ -493,26 +444,25 @@ export function createToolTaskHandler(run, options) {
|
|
|
493
444
|
void runTaskInBackground(run, args, taskExtra, taskStore, task.taskId, options?.toolName, options?.cancelPollMs);
|
|
494
445
|
return {
|
|
495
446
|
task,
|
|
496
|
-
_meta: {
|
|
497
|
-
'io.modelcontextprotocol/model-immediate-response': `${toolLabel} task created — poll tasks/get for progress.`,
|
|
498
|
-
},
|
|
499
447
|
};
|
|
500
448
|
});
|
|
501
449
|
const getTask = (async (...params) => {
|
|
502
450
|
const serverCtx = params.length === 1 ? params[0] : params[1];
|
|
503
|
-
|
|
451
|
+
assertTaskRequestContext(serverCtx);
|
|
452
|
+
const ctx = toTaskToolContext(serverCtx);
|
|
504
453
|
const taskStore = getTaskStore(ctx);
|
|
505
454
|
const taskId = getTaskId(ctx);
|
|
506
455
|
const task = await taskStore.getTask(taskId);
|
|
507
|
-
return projectCancelledTaskStatus(taskStore,
|
|
456
|
+
return projectCancelledTaskStatus(taskStore, toGetTaskResult(task));
|
|
508
457
|
});
|
|
509
458
|
const getTaskResult = (async (...params) => {
|
|
510
459
|
const serverCtx = params.length === 1 ? params[0] : params[1];
|
|
511
|
-
|
|
460
|
+
assertTaskRequestContext(serverCtx);
|
|
461
|
+
const ctx = toTaskToolContext(serverCtx);
|
|
512
462
|
const taskStore = getTaskStore(ctx);
|
|
513
463
|
const taskId = getTaskId(ctx);
|
|
514
464
|
const result = await taskStore.getTaskResult(taskId);
|
|
515
|
-
return attachRelatedTaskMeta(
|
|
465
|
+
return attachRelatedTaskMeta(toCallToolResult(result), taskId);
|
|
516
466
|
});
|
|
517
467
|
return {
|
|
518
468
|
createTask,
|
package/dist/tools/tree.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { basename } from 'node:path';
|
|
2
2
|
import { DEFAULT_SEARCH_TIMEOUT_MS } from '../lib/constants.js';
|
|
3
|
-
import { ErrorCode } from '../lib/errors.js';
|
|
3
|
+
import { classifyError, ErrorCode } from '../lib/errors.js';
|
|
4
4
|
import { formatTreeAscii, treeDirectory, } from '../lib/file-operations/metadata.js';
|
|
5
5
|
import { TreeInputSchema, TreeOutputSchema } from '../schemas.js';
|
|
6
6
|
import { DIRECTORY_ICONS } from './icons.js';
|
|
@@ -56,7 +56,7 @@ export function registerTreeTool(server, options = {}) {
|
|
|
56
56
|
notifyProgress(ctx, {
|
|
57
57
|
current: 0,
|
|
58
58
|
total: knownTotal,
|
|
59
|
-
message:
|
|
59
|
+
message: `${TREE_TOOL.title}: ${context}`,
|
|
60
60
|
});
|
|
61
61
|
const baseReporter = createProgressReporter(ctx);
|
|
62
62
|
const onProgress = (progress) => {
|
|
@@ -66,7 +66,7 @@ export function registerTreeTool(server, options = {}) {
|
|
|
66
66
|
baseReporter({
|
|
67
67
|
current,
|
|
68
68
|
total: knownTotal,
|
|
69
|
-
message:
|
|
69
|
+
message: `${TREE_TOOL.title}: ${context} [${current} entries]`,
|
|
70
70
|
});
|
|
71
71
|
};
|
|
72
72
|
try {
|
|
@@ -81,7 +81,7 @@ export function registerTreeTool(server, options = {}) {
|
|
|
81
81
|
notifyProgress(ctx, {
|
|
82
82
|
current: finalCurrent,
|
|
83
83
|
total: finalCurrent,
|
|
84
|
-
message:
|
|
84
|
+
message: `${TREE_TOOL.title}: ${context} • ${suffix}`,
|
|
85
85
|
});
|
|
86
86
|
return result;
|
|
87
87
|
}
|
|
@@ -90,7 +90,7 @@ export function registerTreeTool(server, options = {}) {
|
|
|
90
90
|
notifyProgress(ctx, {
|
|
91
91
|
current: finalCurrent,
|
|
92
92
|
total: finalCurrent,
|
|
93
|
-
message:
|
|
93
|
+
message: `${TREE_TOOL.title}: ${context} • ${classifyError(error)}`,
|
|
94
94
|
});
|
|
95
95
|
throw error;
|
|
96
96
|
}
|
package/dist/tools/write-file.js
CHANGED
|
@@ -48,13 +48,13 @@ export function registerWriteFileTool(server, options = {}) {
|
|
|
48
48
|
onError: (error) => buildToolErrorResponse(error, ErrorCode.UNKNOWN, args.path),
|
|
49
49
|
});
|
|
50
50
|
registerStandardTool(server, WRITE_FILE_TOOL, handler, options, {
|
|
51
|
-
progressMessage: (args) =>
|
|
51
|
+
progressMessage: (args) => `${WRITE_FILE_TOOL.title}: ${basename(args.path)}`,
|
|
52
52
|
completionMessage: (args, result) => {
|
|
53
53
|
const name = basename(args.path);
|
|
54
54
|
if (result.isError)
|
|
55
|
-
return
|
|
55
|
+
return `${WRITE_FILE_TOOL.title}: ${name} • ${result.errorCode}`;
|
|
56
56
|
const sc = result.structuredContent;
|
|
57
|
-
return
|
|
57
|
+
return `${WRITE_FILE_TOOL.title}: ${name} • ${formatBytes(sc.bytesWritten ?? 0)}`;
|
|
58
58
|
},
|
|
59
59
|
});
|
|
60
60
|
}
|
package/package.json
CHANGED