@j0hanz/filesystem-mcp 1.9.0 → 1.10.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/README.md +15 -15
- package/dist/completions.js +140 -115
- package/dist/lib/constants.d.ts +1 -0
- package/dist/lib/constants.js +2 -0
- package/dist/lib/file-operations/metadata.d.ts +5 -1
- package/dist/lib/file-operations/metadata.js +14 -1
- package/dist/lib/file-operations/search.d.ts +7 -5
- package/dist/lib/file-operations/search.js +64 -32
- package/dist/lib/fs-helpers.d.ts +3 -1
- package/dist/lib/fs-helpers.js +63 -0
- package/dist/lib/paths.d.ts +8 -0
- package/dist/lib/paths.js +119 -64
- package/dist/lib/resource-store.d.ts +2 -0
- package/dist/lib/resource-store.js +58 -17
- package/dist/prompts.d.ts +2 -0
- package/dist/prompts.js +51 -0
- package/dist/resources/generated-instructions.js +36 -9
- package/dist/resources/tool-catalog.js +30 -7
- package/dist/resources/tool-info.d.ts +4 -0
- package/dist/resources/tool-info.js +21 -3
- package/dist/resources/workflows.js +17 -5
- package/dist/schemas.d.ts +47 -12
- package/dist/schemas.js +75 -18
- package/dist/server/bootstrap.js +103 -91
- package/dist/server/roots-manager.d.ts +3 -0
- package/dist/server/roots-manager.js +15 -3
- package/dist/tools/apply-patch.js +135 -31
- package/dist/tools/calculate-hash.js +13 -8
- package/dist/tools/create-directory.js +14 -3
- package/dist/tools/delete-file.js +1 -0
- package/dist/tools/diff-files.js +26 -8
- package/dist/tools/edit-file.js +11 -8
- package/dist/tools/list-directory.js +1 -6
- package/dist/tools/move-file.js +39 -7
- package/dist/tools/read-multiple.js +9 -1
- package/dist/tools/read.js +38 -6
- package/dist/tools/replace-in-files.js +72 -25
- package/dist/tools/roots.js +1 -0
- package/dist/tools/search-content.js +76 -48
- package/dist/tools/search-files.js +6 -7
- package/dist/tools/shared.d.ts +2 -1
- package/dist/tools/shared.js +36 -20
- package/dist/tools/stat-many.js +1 -1
- package/dist/tools/stat.js +4 -0
- package/dist/tools/task-support.js +4 -12
- package/dist/tools/tree.js +4 -0
- package/dist/tools/write-file.js +4 -2
- package/package.json +17 -8
package/dist/tools/shared.js
CHANGED
|
@@ -42,18 +42,16 @@ export function maybeStripStructuredContentFromResult(result) {
|
|
|
42
42
|
return result;
|
|
43
43
|
if (!Object.hasOwn(result, 'structuredContent'))
|
|
44
44
|
return result;
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
return rest;
|
|
45
|
+
const stripped = Object.fromEntries(Object.entries(result).filter(([key]) => key !== 'structuredContent'));
|
|
46
|
+
return stripped;
|
|
48
47
|
}
|
|
49
48
|
function maybeStripOutputSchema(tool) {
|
|
50
49
|
if (!shouldStripStructuredOutput())
|
|
51
50
|
return tool;
|
|
52
51
|
if (!Object.hasOwn(tool, 'outputSchema'))
|
|
53
52
|
return tool;
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
return mutable;
|
|
53
|
+
const stripped = Object.fromEntries(Object.entries(tool).filter(([key]) => key !== 'outputSchema'));
|
|
54
|
+
return stripped;
|
|
57
55
|
}
|
|
58
56
|
function buildTextPreview(text) {
|
|
59
57
|
if (text.length <= MAX_INLINE_PREVIEW_CHARS)
|
|
@@ -83,11 +81,17 @@ export function maybeExternalizeTextContent(resourceStore, content, params) {
|
|
|
83
81
|
};
|
|
84
82
|
}
|
|
85
83
|
export function buildResourceLink(params) {
|
|
84
|
+
const descParts = [];
|
|
85
|
+
if (params.description)
|
|
86
|
+
descParts.push(params.description);
|
|
87
|
+
if (params.expiresAt)
|
|
88
|
+
descParts.push(`Expires: ${params.expiresAt}`);
|
|
89
|
+
const description = descParts.length > 0 ? descParts.join(' · ') : undefined;
|
|
86
90
|
return {
|
|
87
91
|
type: 'resource_link',
|
|
88
92
|
uri: params.uri,
|
|
89
93
|
name: params.name,
|
|
90
|
-
...(
|
|
94
|
+
...(description ? { description } : {}),
|
|
91
95
|
...(params.mimeType ? { mimeType: params.mimeType } : {}),
|
|
92
96
|
};
|
|
93
97
|
}
|
|
@@ -207,6 +211,18 @@ function buildNotInitializedResult() {
|
|
|
207
211
|
return buildToolErrorResponse(NOT_INITIALIZED_ERROR, ErrorCode.E_INVALID_INPUT);
|
|
208
212
|
}
|
|
209
213
|
async function reportProgress(extra, progress) {
|
|
214
|
+
await updateTaskStoreProgress(extra, progress);
|
|
215
|
+
await sendMcpProgressNotification(extra, progress);
|
|
216
|
+
}
|
|
217
|
+
function formatTaskStatusMessage(progress) {
|
|
218
|
+
if (progress.total !== undefined) {
|
|
219
|
+
return progress.message
|
|
220
|
+
? `${progress.message} (${progress.current}/${progress.total})`
|
|
221
|
+
: `${progress.current}/${progress.total}`;
|
|
222
|
+
}
|
|
223
|
+
return progress.message ?? `${progress.current}`;
|
|
224
|
+
}
|
|
225
|
+
async function updateTaskStoreProgress(extra, progress) {
|
|
210
226
|
const taskExtra = extra;
|
|
211
227
|
if (typeof taskExtra.taskId === 'string' &&
|
|
212
228
|
taskExtra.taskStore !== undefined &&
|
|
@@ -214,22 +230,15 @@ async function reportProgress(extra, progress) {
|
|
|
214
230
|
const store = taskExtra.taskStore;
|
|
215
231
|
if (typeof store.updateTaskStatus === 'function') {
|
|
216
232
|
try {
|
|
217
|
-
|
|
218
|
-
if (progress.total !== undefined) {
|
|
219
|
-
statusMessage = statusMessage
|
|
220
|
-
? `${statusMessage} (${progress.current}/${progress.total})`
|
|
221
|
-
: `${progress.current}/${progress.total}`;
|
|
222
|
-
}
|
|
223
|
-
else {
|
|
224
|
-
statusMessage ??= `${progress.current}`;
|
|
225
|
-
}
|
|
226
|
-
await store.updateTaskStatus(taskExtra.taskId, 'working', statusMessage);
|
|
233
|
+
await store.updateTaskStatus(taskExtra.taskId, 'working', formatTaskStatusMessage(progress));
|
|
227
234
|
}
|
|
228
235
|
catch (error) {
|
|
229
236
|
console.error('Failed to update task status message:', error);
|
|
230
237
|
}
|
|
231
238
|
}
|
|
232
239
|
}
|
|
240
|
+
}
|
|
241
|
+
async function sendMcpProgressNotification(extra, progress) {
|
|
233
242
|
if (canSendProgress(extra)) {
|
|
234
243
|
try {
|
|
235
244
|
await extra.sendNotification({
|
|
@@ -282,9 +291,10 @@ export function notifyProgress(extra, progress) {
|
|
|
282
291
|
return;
|
|
283
292
|
void reportProgress(extra, progress);
|
|
284
293
|
}
|
|
285
|
-
export function createToolProgressSession(extra, startMessage) {
|
|
294
|
+
export function createToolProgressSession(extra, startMessage, initialTotal) {
|
|
286
295
|
notifyProgress(extra, {
|
|
287
296
|
current: 0,
|
|
297
|
+
...(initialTotal !== undefined ? { total: initialTotal } : {}),
|
|
288
298
|
message: startMessage,
|
|
289
299
|
});
|
|
290
300
|
let cursor = 0;
|
|
@@ -325,9 +335,15 @@ export function createToolProgressSession(extra, startMessage) {
|
|
|
325
335
|
};
|
|
326
336
|
}
|
|
327
337
|
export function createBatchProgressCallbacks(extra, params) {
|
|
328
|
-
const progress = createToolProgressSession(extra, `${params.toolLabel}: ${params.context}
|
|
338
|
+
const progress = createToolProgressSession(extra, `${params.toolLabel}: ${params.context}`, params.totalItems);
|
|
339
|
+
let itemsDone = 0;
|
|
329
340
|
const onItemComplete = () => {
|
|
330
|
-
|
|
341
|
+
itemsDone++;
|
|
342
|
+
progress.update({
|
|
343
|
+
current: itemsDone,
|
|
344
|
+
total: params.totalItems,
|
|
345
|
+
message: `${params.toolLabel}: ${params.context} [${itemsDone}/${params.totalItems} ${params.itemVerb}]`,
|
|
346
|
+
});
|
|
331
347
|
};
|
|
332
348
|
return { progress, onItemComplete };
|
|
333
349
|
}
|
package/dist/tools/stat-many.js
CHANGED
|
@@ -79,7 +79,7 @@ export function registerGetMultipleFileInfoTool(server, options = {}) {
|
|
|
79
79
|
toolLabel: '🕮 stat_many',
|
|
80
80
|
context,
|
|
81
81
|
totalItems: args.paths.length,
|
|
82
|
-
itemVerb: '
|
|
82
|
+
itemVerb: 'done',
|
|
83
83
|
});
|
|
84
84
|
try {
|
|
85
85
|
const result = await handleGetMultipleFileInfo(args, signal, onItemComplete);
|
package/dist/tools/stat.js
CHANGED
|
@@ -5,6 +5,7 @@ import { getFileInfo } from '../lib/file-operations/metadata.js';
|
|
|
5
5
|
import { formatBytes, joinLines } from '../config.js';
|
|
6
6
|
import { GetFileInfoInputSchema, GetFileInfoOutputSchema } from '../schemas.js';
|
|
7
7
|
import { buildFileInfoPayload, buildToolErrorResponse, buildToolResponse, executeToolWithDiagnostics, READ_ONLY_TOOL_ANNOTATIONS, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
8
|
+
import { registerToolTaskIfAvailable } from './task-support.js';
|
|
8
9
|
export const GET_FILE_INFO_TOOL = {
|
|
9
10
|
name: 'stat',
|
|
10
11
|
title: 'Get File Info',
|
|
@@ -13,6 +14,7 @@ export const GET_FILE_INFO_TOOL = {
|
|
|
13
14
|
inputSchema: GetFileInfoInputSchema,
|
|
14
15
|
outputSchema: GetFileInfoOutputSchema,
|
|
15
16
|
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
|
17
|
+
taskSupport: 'optional',
|
|
16
18
|
nuances: ['Use before read/search when file size/type uncertainty exists.'],
|
|
17
19
|
};
|
|
18
20
|
function formatFileInfoDetails(info) {
|
|
@@ -62,5 +64,7 @@ export function registerGetFileInfoTool(server, options = {}) {
|
|
|
62
64
|
},
|
|
63
65
|
});
|
|
64
66
|
const validatedHandler = withValidatedArgs(GetFileInfoInputSchema, wrappedHandler);
|
|
67
|
+
if (registerToolTaskIfAvailable(server, 'stat', GET_FILE_INFO_TOOL, validatedHandler, options.iconInfo, options.isInitialized))
|
|
68
|
+
return;
|
|
65
69
|
server.registerTool('stat', withDefaultIcons({ ...GET_FILE_INFO_TOOL }, options.iconInfo), validatedHandler);
|
|
66
70
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { channel } from 'node:diagnostics_channel';
|
|
2
2
|
import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
+
import { DEFAULT_TASK_TTL_MS } from '../lib/constants.js';
|
|
3
4
|
import { ErrorCode, McpError } from '../lib/errors.js';
|
|
4
5
|
import { isRecord } from '../lib/utils.js';
|
|
5
6
|
import { buildToolErrorResponse, maybeStripStructuredContentFromResult, withDefaultIcons, } from './shared.js';
|
|
@@ -44,7 +45,6 @@ function hasTaskToolCapability(server) {
|
|
|
44
45
|
const { call } = tools;
|
|
45
46
|
return isRecord(call);
|
|
46
47
|
}
|
|
47
|
-
const RELATED_TASK_META_KEY = 'io.modelcontextprotocol/related-task';
|
|
48
48
|
const TASK_STATUS_NOTIFICATION_METHOD = 'notifications/tasks/status';
|
|
49
49
|
const TASK_DIAGNOSTICS_CHANNEL = channel('filesystem-mcp:tasks');
|
|
50
50
|
function publishTaskDiagnostics(event) {
|
|
@@ -169,13 +169,6 @@ async function projectCancelledTaskStatus(taskStore, task) {
|
|
|
169
169
|
}
|
|
170
170
|
return task;
|
|
171
171
|
}
|
|
172
|
-
function withRelatedTaskMeta(result, taskId) {
|
|
173
|
-
const existingMeta = isRecord(result['_meta']) ? result['_meta'] : {};
|
|
174
|
-
return {
|
|
175
|
-
...result,
|
|
176
|
-
_meta: { ...existingMeta, [RELATED_TASK_META_KEY]: { taskId } },
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
172
|
function buildTaskStatusNotificationParams(task) {
|
|
180
173
|
const params = {
|
|
181
174
|
taskId: task.taskId,
|
|
@@ -259,9 +252,8 @@ async function isTaskAlreadyTerminal(taskStore, taskId) {
|
|
|
259
252
|
}
|
|
260
253
|
}
|
|
261
254
|
async function tryStoreTaskResult(taskStore, taskId, status, result) {
|
|
262
|
-
const resultWithTaskMeta = withRelatedTaskMeta(result, taskId);
|
|
263
255
|
try {
|
|
264
|
-
await taskStore.storeTaskResult(taskId, status,
|
|
256
|
+
await taskStore.storeTaskResult(taskId, status, result);
|
|
265
257
|
}
|
|
266
258
|
catch (error) {
|
|
267
259
|
if (await isTaskAlreadyTerminal(taskStore, taskId))
|
|
@@ -352,7 +344,7 @@ export function createToolTaskHandler(run, options) {
|
|
|
352
344
|
}
|
|
353
345
|
const taskStore = getTaskStore(extra);
|
|
354
346
|
const task = await taskStore.createTask({
|
|
355
|
-
ttl: extra.taskRequestedTtl ??
|
|
347
|
+
ttl: extra.taskRequestedTtl ?? DEFAULT_TASK_TTL_MS,
|
|
356
348
|
});
|
|
357
349
|
publishTaskDiagnostics({
|
|
358
350
|
phase: 'task_created',
|
|
@@ -383,7 +375,7 @@ export function createToolTaskHandler(run, options) {
|
|
|
383
375
|
const taskStore = getTaskStore(extra);
|
|
384
376
|
const taskId = getTaskId(extra);
|
|
385
377
|
const result = await taskStore.getTaskResult(taskId);
|
|
386
|
-
return normalizeCallToolResult(
|
|
378
|
+
return normalizeCallToolResult(result);
|
|
387
379
|
});
|
|
388
380
|
return {
|
|
389
381
|
createTask,
|
package/dist/tools/tree.js
CHANGED
|
@@ -23,6 +23,7 @@ async function handleTree(args, signal, onProgress) {
|
|
|
23
23
|
maxEntries: args.maxEntries,
|
|
24
24
|
includeHidden: args.includeHidden,
|
|
25
25
|
includeIgnored: args.includeIgnored,
|
|
26
|
+
includeSizes: args.includeSizes,
|
|
26
27
|
...(signal ? { signal } : {}),
|
|
27
28
|
...(onProgress ? { onProgress } : {}),
|
|
28
29
|
});
|
|
@@ -49,8 +50,10 @@ export function registerTreeTool(server, options = {}) {
|
|
|
49
50
|
run: async (signal) => {
|
|
50
51
|
const context = args.path ? path.basename(args.path) : '.';
|
|
51
52
|
let progressCursor = 0;
|
|
53
|
+
const knownTotal = args.maxEntries;
|
|
52
54
|
notifyProgress(extra, {
|
|
53
55
|
current: 0,
|
|
56
|
+
total: knownTotal,
|
|
54
57
|
message: `≣ tree: ${context}`,
|
|
55
58
|
});
|
|
56
59
|
const baseReporter = createProgressReporter(extra);
|
|
@@ -60,6 +63,7 @@ export function registerTreeTool(server, options = {}) {
|
|
|
60
63
|
progressCursor = current;
|
|
61
64
|
baseReporter({
|
|
62
65
|
current,
|
|
66
|
+
total: knownTotal,
|
|
63
67
|
message: `≣ tree: ${context} [${current} entries]`,
|
|
64
68
|
});
|
|
65
69
|
};
|
package/dist/tools/write-file.js
CHANGED
|
@@ -3,6 +3,7 @@ import * as path from 'node:path';
|
|
|
3
3
|
import { ErrorCode } from '../lib/errors.js';
|
|
4
4
|
import { atomicWriteFile, withAbort } from '../lib/fs-helpers.js';
|
|
5
5
|
import { validatePathForWrite } from '../lib/paths.js';
|
|
6
|
+
import { formatBytes } from '../config.js';
|
|
6
7
|
import { WriteFileInputSchema, WriteFileOutputSchema } from '../schemas.js';
|
|
7
8
|
import { buildToolErrorResponse, buildToolResponse, DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS, executeToolWithDiagnostics, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
8
9
|
import { registerToolTaskIfAvailable } from './task-support.js';
|
|
@@ -16,6 +17,7 @@ export const WRITE_FILE_TOOL = {
|
|
|
16
17
|
gotchas: [
|
|
17
18
|
'`write` replaces ALL existing content — use `edit` for partial updates.',
|
|
18
19
|
],
|
|
20
|
+
taskSupport: 'optional',
|
|
19
21
|
};
|
|
20
22
|
async function handleWriteFile(args, signal) {
|
|
21
23
|
const validPath = await validatePathForWrite(args.path, signal);
|
|
@@ -40,7 +42,7 @@ export function registerWriteFileTool(server, options = {}) {
|
|
|
40
42
|
});
|
|
41
43
|
const wrappedHandler = wrapToolHandler(handler, {
|
|
42
44
|
guard: options.isInitialized,
|
|
43
|
-
progressMessage: (args) => `🛠 write: ${path.basename(args.path)}
|
|
45
|
+
progressMessage: (args) => `🛠 write: ${path.basename(args.path)}`,
|
|
44
46
|
completionMessage: (args, result) => {
|
|
45
47
|
const name = path.basename(args.path);
|
|
46
48
|
if (result.isError)
|
|
@@ -48,7 +50,7 @@ export function registerWriteFileTool(server, options = {}) {
|
|
|
48
50
|
const sc = result.structuredContent;
|
|
49
51
|
if (!sc.ok)
|
|
50
52
|
return `🛠 write: ${name} • failed`;
|
|
51
|
-
return `🛠 write: ${name} • ${sc.bytesWritten ?? 0}
|
|
53
|
+
return `🛠 write: ${name} • ${formatBytes(sc.bytesWritten ?? 0)} written`;
|
|
52
54
|
},
|
|
53
55
|
});
|
|
54
56
|
const validatedHandler = withValidatedArgs(WriteFileInputSchema, wrappedHandler);
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@j0hanz/filesystem-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"mcpName": "io.github.j0hanz/filesystem-mcp",
|
|
5
|
-
"description": "MCP
|
|
5
|
+
"description": "A local filesystem MCP server that lets LLMs and AI agents read, write, search, diff, patch, and manage files safely and efficiently. Built for reliable, structured, and controlled filesystem interaction.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"types": "dist/index.d.ts",
|
|
@@ -48,13 +48,23 @@
|
|
|
48
48
|
"keywords": [
|
|
49
49
|
"mcp",
|
|
50
50
|
"model-context-protocol",
|
|
51
|
+
"mcp-server",
|
|
51
52
|
"filesystem",
|
|
52
|
-
"
|
|
53
|
+
"file-management",
|
|
54
|
+
"file-operations",
|
|
55
|
+
"grep",
|
|
56
|
+
"glob",
|
|
57
|
+
"diff",
|
|
58
|
+
"patch",
|
|
53
59
|
"search",
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
60
|
+
"security",
|
|
61
|
+
"server",
|
|
62
|
+
"stdio",
|
|
63
|
+
"sse",
|
|
64
|
+
"typescript",
|
|
65
|
+
"nodejs",
|
|
66
|
+
"llm",
|
|
67
|
+
"cli"
|
|
58
68
|
],
|
|
59
69
|
"author": "j0hanz",
|
|
60
70
|
"license": "MIT",
|
|
@@ -80,7 +90,6 @@
|
|
|
80
90
|
"eslint-config-prettier": "^10.1.8",
|
|
81
91
|
"eslint-plugin-de-morgan": "^2.1.1",
|
|
82
92
|
"eslint-plugin-depend": "^1.5.0",
|
|
83
|
-
"eslint-plugin-sonarjs": "^4.0.0",
|
|
84
93
|
"eslint-plugin-unused-imports": "^4.4.1",
|
|
85
94
|
"jscpd": "^4.0.8",
|
|
86
95
|
"knip": "^5.85.0",
|