@j0hanz/filesystem-mcp 1.2.3 → 1.3.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 +8 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +13 -1
- package/dist/completions.d.ts +1 -1
- package/dist/completions.js +36 -1
- package/dist/index.js +26 -8
- package/dist/lib/observability.d.ts +6 -0
- package/dist/lib/observability.js +1 -1
- package/dist/lib/resource-store.js +53 -0
- package/dist/prompts.js +34 -14
- package/dist/resources/generated-instructions.d.ts +1 -0
- package/dist/resources/generated-instructions.js +100 -0
- package/dist/resources.d.ts +1 -0
- package/dist/resources.js +36 -1
- package/dist/schemas.d.ts +6 -0
- package/dist/schemas.js +24 -0
- package/dist/server/bootstrap.d.ts +2 -0
- package/dist/server/bootstrap.js +226 -20
- package/dist/server.d.ts +1 -1
- package/dist/server.js +1 -1
- package/dist/tools/apply-patch.d.ts +2 -1
- package/dist/tools/apply-patch.js +7 -5
- package/dist/tools/calculate-hash.d.ts +2 -1
- package/dist/tools/calculate-hash.js +9 -5
- package/dist/tools/contract.d.ts +41 -0
- package/dist/tools/contract.js +1 -0
- package/dist/tools/create-directory.d.ts +2 -1
- package/dist/tools/create-directory.js +6 -5
- package/dist/tools/delete-file.d.ts +2 -1
- package/dist/tools/delete-file.js +9 -5
- package/dist/tools/diff-files.d.ts +2 -1
- package/dist/tools/diff-files.js +7 -4
- package/dist/tools/edit-file.d.ts +2 -1
- package/dist/tools/edit-file.js +19 -6
- package/dist/tools/list-directory.d.ts +2 -1
- package/dist/tools/list-directory.js +36 -7
- package/dist/tools/move-file.d.ts +2 -1
- package/dist/tools/move-file.js +7 -5
- package/dist/tools/read-multiple.d.ts +2 -1
- package/dist/tools/read-multiple.js +10 -5
- package/dist/tools/read.d.ts +2 -1
- package/dist/tools/read.js +9 -5
- package/dist/tools/replace-in-files.d.ts +2 -1
- package/dist/tools/replace-in-files.js +14 -7
- package/dist/tools/roots.d.ts +2 -1
- package/dist/tools/roots.js +7 -4
- package/dist/tools/search-content.d.ts +2 -1
- package/dist/tools/search-content.js +14 -6
- package/dist/tools/search-files.d.ts +2 -1
- package/dist/tools/search-files.js +39 -7
- package/dist/tools/shared.d.ts +2 -2
- package/dist/tools/shared.js +16 -1
- package/dist/tools/stat-many.d.ts +2 -1
- package/dist/tools/stat-many.js +7 -5
- package/dist/tools/stat.d.ts +2 -1
- package/dist/tools/stat.js +7 -4
- package/dist/tools/task-support.d.ts +2 -0
- package/dist/tools/task-support.js +48 -7
- package/dist/tools/tree.d.ts +2 -1
- package/dist/tools/tree.js +7 -5
- package/dist/tools/write-file.d.ts +2 -1
- package/dist/tools/write-file.js +12 -5
- package/dist/tools.d.ts +2 -0
- package/dist/tools.js +39 -18
- package/package.json +1 -2
- package/dist/instructions.md +0 -200
|
@@ -18,8 +18,10 @@ export declare function tryRegisterToolTask<Args extends ZodRawShapeCompat | Any
|
|
|
18
18
|
export declare function registerToolTaskIfAvailable<Args extends ZodRawShapeCompat | AnySchema | undefined, Result>(server: McpServer, toolName: string, toolDef: object, run: (args: ToolArgs<Args>, extra: TaskToolExtra) => Promise<ToolResult<Result>>, iconInfo: IconInfo | undefined, guard?: () => boolean): boolean;
|
|
19
19
|
export declare function createToolTaskHandler<Result>(run: (args: undefined, extra: TaskToolExtra) => Promise<ToolResult<Result>>, options?: {
|
|
20
20
|
guard?: () => boolean;
|
|
21
|
+
toolName?: string;
|
|
21
22
|
}): ToolTaskHandler;
|
|
22
23
|
export declare function createToolTaskHandler<Args extends ZodRawShapeCompat | AnySchema, Result>(run: (args: ToolArgs<Args>, extra: TaskToolExtra) => Promise<ToolResult<Result>>, options?: {
|
|
23
24
|
guard?: () => boolean;
|
|
25
|
+
toolName?: string;
|
|
24
26
|
}): ToolTaskHandler<Args>;
|
|
25
27
|
export {};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { channel } from 'node:diagnostics_channel';
|
|
1
2
|
import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
2
3
|
import { ErrorCode, McpError } from '../lib/errors.js';
|
|
3
4
|
import { isRecord } from '../lib/type-guards.js';
|
|
@@ -43,6 +44,12 @@ function hasTaskToolCapability(server) {
|
|
|
43
44
|
}
|
|
44
45
|
const RELATED_TASK_META_KEY = 'io.modelcontextprotocol/related-task';
|
|
45
46
|
const TASK_STATUS_NOTIFICATION_METHOD = 'notifications/tasks/status';
|
|
47
|
+
const TASK_DIAGNOSTICS_CHANNEL = channel('filesystem-mcp:tasks');
|
|
48
|
+
function publishTaskDiagnostics(event) {
|
|
49
|
+
if (!TASK_DIAGNOSTICS_CHANNEL.hasSubscribers)
|
|
50
|
+
return;
|
|
51
|
+
TASK_DIAGNOSTICS_CHANNEL.publish(event);
|
|
52
|
+
}
|
|
46
53
|
function isRequestTaskStore(value) {
|
|
47
54
|
if (!isRecord(value))
|
|
48
55
|
return false;
|
|
@@ -174,7 +181,7 @@ function buildTaskStatusNotificationParams(task) {
|
|
|
174
181
|
params.statusMessage = task.statusMessage;
|
|
175
182
|
return params;
|
|
176
183
|
}
|
|
177
|
-
async function notifyTaskStatusIfPossible(extra, taskStore, taskId) {
|
|
184
|
+
async function notifyTaskStatusIfPossible(extra, taskStore, taskId, toolName) {
|
|
178
185
|
const { sendNotification } = extra;
|
|
179
186
|
if (typeof sendNotification !== 'function')
|
|
180
187
|
return;
|
|
@@ -186,8 +193,19 @@ async function notifyTaskStatusIfPossible(extra, taskStore, taskId) {
|
|
|
186
193
|
method: TASK_STATUS_NOTIFICATION_METHOD,
|
|
187
194
|
params: buildTaskStatusNotificationParams(normalized),
|
|
188
195
|
});
|
|
196
|
+
publishTaskDiagnostics({
|
|
197
|
+
phase: 'task_status_notified',
|
|
198
|
+
taskId,
|
|
199
|
+
status: normalized.status,
|
|
200
|
+
...(toolName !== undefined ? { toolName } : {}),
|
|
201
|
+
});
|
|
189
202
|
}
|
|
190
203
|
catch {
|
|
204
|
+
publishTaskDiagnostics({
|
|
205
|
+
phase: 'task_status_notify_failed',
|
|
206
|
+
taskId,
|
|
207
|
+
...(toolName !== undefined ? { toolName } : {}),
|
|
208
|
+
});
|
|
191
209
|
// Never fail task execution because status notifications are optional.
|
|
192
210
|
}
|
|
193
211
|
}
|
|
@@ -234,18 +252,30 @@ async function tryStoreTaskResult(taskStore, taskId, status, result) {
|
|
|
234
252
|
throw error;
|
|
235
253
|
}
|
|
236
254
|
}
|
|
237
|
-
async function runTaskInBackground(run, args, extra, taskStore, taskId) {
|
|
255
|
+
async function runTaskInBackground(run, args, extra, taskStore, taskId, toolName) {
|
|
238
256
|
try {
|
|
239
257
|
const result = maybeStripStructuredContentFromResult(await run(args, extra));
|
|
240
258
|
const status = isErrorResult(result) ? 'failed' : 'completed';
|
|
241
259
|
await tryStoreTaskResult(taskStore, taskId, status, result);
|
|
242
|
-
|
|
260
|
+
publishTaskDiagnostics({
|
|
261
|
+
phase: 'task_result_stored',
|
|
262
|
+
taskId,
|
|
263
|
+
status,
|
|
264
|
+
...(toolName !== undefined ? { toolName } : {}),
|
|
265
|
+
});
|
|
266
|
+
await notifyTaskStatusIfPossible(extra, taskStore, taskId, toolName);
|
|
243
267
|
}
|
|
244
268
|
catch (error) {
|
|
245
269
|
const fallback = maybeStripStructuredContentFromResult(buildToolErrorResponse(error, ErrorCode.E_UNKNOWN));
|
|
246
270
|
try {
|
|
247
271
|
await tryStoreTaskResult(taskStore, taskId, 'failed', fallback);
|
|
248
|
-
|
|
272
|
+
publishTaskDiagnostics({
|
|
273
|
+
phase: 'task_result_stored',
|
|
274
|
+
taskId,
|
|
275
|
+
status: 'failed',
|
|
276
|
+
...(toolName !== undefined ? { toolName } : {}),
|
|
277
|
+
});
|
|
278
|
+
await notifyTaskStatusIfPossible(extra, taskStore, taskId, toolName);
|
|
249
279
|
}
|
|
250
280
|
catch (innerError) {
|
|
251
281
|
console.error(`Failed to store task failure result for task ${taskId}:`, innerError);
|
|
@@ -267,7 +297,10 @@ export function tryRegisterToolTask(server, toolName, toolDef, taskHandler, icon
|
|
|
267
297
|
return true;
|
|
268
298
|
}
|
|
269
299
|
export function registerToolTaskIfAvailable(server, toolName, toolDef, run, iconInfo, guard) {
|
|
270
|
-
const taskOptions =
|
|
300
|
+
const taskOptions = {
|
|
301
|
+
...(guard ? { guard } : {}),
|
|
302
|
+
toolName,
|
|
303
|
+
};
|
|
271
304
|
return tryRegisterToolTask(server, toolName, toolDef, createToolTaskHandler(run, taskOptions), iconInfo);
|
|
272
305
|
}
|
|
273
306
|
export function createToolTaskHandler(run, options) {
|
|
@@ -281,13 +314,21 @@ export function createToolTaskHandler(run, options) {
|
|
|
281
314
|
const task = await taskStore.createTask({
|
|
282
315
|
ttl: extra.taskRequestedTtl ?? null,
|
|
283
316
|
});
|
|
317
|
+
publishTaskDiagnostics({
|
|
318
|
+
phase: 'task_created',
|
|
319
|
+
taskId: task.taskId,
|
|
320
|
+
status: task.status,
|
|
321
|
+
...(options?.toolName !== undefined
|
|
322
|
+
? { toolName: options.toolName }
|
|
323
|
+
: {}),
|
|
324
|
+
});
|
|
284
325
|
const taskExtra = {
|
|
285
326
|
...extra,
|
|
286
327
|
taskStore,
|
|
287
328
|
taskId: task.taskId,
|
|
288
329
|
};
|
|
289
|
-
void notifyTaskStatusIfPossible(taskExtra, taskStore, task.taskId);
|
|
290
|
-
void runTaskInBackground(run, args, taskExtra, taskStore, task.taskId);
|
|
330
|
+
void notifyTaskStatusIfPossible(taskExtra, taskStore, task.taskId, options?.toolName);
|
|
331
|
+
void runTaskInBackground(run, args, taskExtra, taskStore, task.taskId, options?.toolName);
|
|
291
332
|
return { task };
|
|
292
333
|
});
|
|
293
334
|
const getTask = (async (argsOrExtra, maybeExtra) => {
|
package/dist/tools/tree.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { type ToolRegistrationOptions } from './shared.js';
|
|
2
|
+
import { type ToolContract, type ToolRegistrationOptions } from './shared.js';
|
|
3
|
+
export declare const TREE_TOOL: ToolContract;
|
|
3
4
|
export declare function registerTreeTool(server: McpServer, options?: ToolRegistrationOptions): void;
|
package/dist/tools/tree.js
CHANGED
|
@@ -5,7 +5,8 @@ import { formatTreeAscii, treeDirectory } from '../lib/file-operations/tree.js';
|
|
|
5
5
|
import { TreeInputSchema, TreeOutputSchema } from '../schemas.js';
|
|
6
6
|
import { buildToolErrorResponse, buildToolResponse, executeToolWithDiagnostics, READ_ONLY_TOOL_ANNOTATIONS, resolvePathOrRoot, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
7
7
|
import { registerToolTaskIfAvailable } from './task-support.js';
|
|
8
|
-
const TREE_TOOL = {
|
|
8
|
+
export const TREE_TOOL = {
|
|
9
|
+
name: 'tree',
|
|
9
10
|
title: 'Tree',
|
|
10
11
|
description: 'Render a directory tree (bounded recursion). ' +
|
|
11
12
|
'Returns an ASCII tree for quick scanning and a structured JSON tree for programmatic use. ' +
|
|
@@ -13,6 +14,7 @@ const TREE_TOOL = {
|
|
|
13
14
|
inputSchema: TreeInputSchema,
|
|
14
15
|
outputSchema: TreeOutputSchema,
|
|
15
16
|
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
|
17
|
+
gotchas: ['`maxDepth=0` returns only the root node.'],
|
|
16
18
|
};
|
|
17
19
|
async function handleTree(args, signal) {
|
|
18
20
|
const basePath = resolvePathOrRoot(args.path);
|
|
@@ -47,8 +49,7 @@ export function registerTreeTool(server, options = {}) {
|
|
|
47
49
|
onError: (error) => buildToolErrorResponse(error, ErrorCode.E_NOT_DIRECTORY, targetPath),
|
|
48
50
|
});
|
|
49
51
|
};
|
|
50
|
-
const
|
|
51
|
-
const wrappedHandler = wrapToolHandler(validatedHandler, {
|
|
52
|
+
const wrappedHandler = wrapToolHandler(handler, {
|
|
52
53
|
guard: options.isInitialized,
|
|
53
54
|
progressMessage: (args) => {
|
|
54
55
|
if (args.path) {
|
|
@@ -69,7 +70,8 @@ export function registerTreeTool(server, options = {}) {
|
|
|
69
70
|
return `≣ tree: ${base} • ${count} ${count === 1 ? 'entry' : 'entries'}`;
|
|
70
71
|
},
|
|
71
72
|
});
|
|
72
|
-
|
|
73
|
+
const validatedHandler = withValidatedArgs(TreeInputSchema, wrappedHandler);
|
|
74
|
+
if (registerToolTaskIfAvailable(server, 'tree', TREE_TOOL, validatedHandler, options.iconInfo, options.isInitialized))
|
|
73
75
|
return;
|
|
74
|
-
server.registerTool('tree', withDefaultIcons({ ...TREE_TOOL }, options.iconInfo),
|
|
76
|
+
server.registerTool('tree', withDefaultIcons({ ...TREE_TOOL }, options.iconInfo), validatedHandler);
|
|
75
77
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { type ToolRegistrationOptions } from './shared.js';
|
|
2
|
+
import { type ToolContract, type ToolRegistrationOptions } from './shared.js';
|
|
3
|
+
export declare const WRITE_FILE_TOOL: ToolContract;
|
|
3
4
|
export declare function registerWriteFileTool(server: McpServer, options?: ToolRegistrationOptions): void;
|
package/dist/tools/write-file.js
CHANGED
|
@@ -6,12 +6,19 @@ import { validatePathForWrite } from '../lib/path-validation.js';
|
|
|
6
6
|
import { WriteFileInputSchema, WriteFileOutputSchema } from '../schemas.js';
|
|
7
7
|
import { buildToolErrorResponse, buildToolResponse, DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS, executeToolWithDiagnostics, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
8
8
|
import { registerToolTaskIfAvailable } from './task-support.js';
|
|
9
|
-
const WRITE_FILE_TOOL = {
|
|
9
|
+
export const WRITE_FILE_TOOL = {
|
|
10
|
+
name: 'write',
|
|
10
11
|
title: 'Write File',
|
|
11
12
|
description: 'Write content to a file. Creates the file if it does not exist.',
|
|
12
13
|
inputSchema: WriteFileInputSchema,
|
|
13
14
|
outputSchema: WriteFileOutputSchema,
|
|
14
15
|
annotations: DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS,
|
|
16
|
+
nuances: [
|
|
17
|
+
'Creates parent directories automatically; overwrites existing content.',
|
|
18
|
+
],
|
|
19
|
+
gotchas: [
|
|
20
|
+
'Creates parent directories automatically; overwrites existing content.',
|
|
21
|
+
],
|
|
15
22
|
};
|
|
16
23
|
async function handleWriteFile(args, signal) {
|
|
17
24
|
const validPath = await validatePathForWrite(args.path, signal);
|
|
@@ -34,8 +41,7 @@ export function registerWriteFileTool(server, options = {}) {
|
|
|
34
41
|
run: (signal) => handleWriteFile(args, signal),
|
|
35
42
|
onError: (error) => buildToolErrorResponse(error, ErrorCode.E_UNKNOWN, args.path),
|
|
36
43
|
});
|
|
37
|
-
const
|
|
38
|
-
const wrappedHandler = wrapToolHandler(validatedHandler, {
|
|
44
|
+
const wrappedHandler = wrapToolHandler(handler, {
|
|
39
45
|
guard: options.isInitialized,
|
|
40
46
|
progressMessage: (args) => `🛠 write: ${path.basename(args.path)} [${args.content.length} chars]`,
|
|
41
47
|
completionMessage: (args, result) => {
|
|
@@ -48,7 +54,8 @@ export function registerWriteFileTool(server, options = {}) {
|
|
|
48
54
|
return `🛠 write: ${name} • ${sc.bytesWritten ?? 0} bytes`;
|
|
49
55
|
},
|
|
50
56
|
});
|
|
51
|
-
|
|
57
|
+
const validatedHandler = withValidatedArgs(WriteFileInputSchema, wrappedHandler);
|
|
58
|
+
if (registerToolTaskIfAvailable(server, 'write', WRITE_FILE_TOOL, validatedHandler, options.iconInfo, options.isInitialized))
|
|
52
59
|
return;
|
|
53
|
-
server.registerTool('write', withDefaultIcons({ ...WRITE_FILE_TOOL }, options.iconInfo),
|
|
60
|
+
server.registerTool('write', withDefaultIcons({ ...WRITE_FILE_TOOL }, options.iconInfo), validatedHandler);
|
|
54
61
|
}
|
package/dist/tools.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { type ToolContract } from './tools/contract.js';
|
|
2
3
|
import type { ToolRegistrationOptions } from './tools/shared.js';
|
|
3
4
|
export { buildToolErrorResponse, buildToolResponse } from './tools/shared.js';
|
|
5
|
+
export declare const ALL_TOOLS: ToolContract[];
|
|
4
6
|
export declare function registerAllTools(server: McpServer, options?: ToolRegistrationOptions): void;
|
package/dist/tools.js
CHANGED
|
@@ -1,22 +1,43 @@
|
|
|
1
|
-
import { registerApplyPatchTool } from './tools/apply-patch.js';
|
|
2
|
-
import { registerCalculateHashTool } from './tools/calculate-hash.js';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
1
|
+
import { APPLY_PATCH_TOOL, registerApplyPatchTool, } from './tools/apply-patch.js';
|
|
2
|
+
import { CALCULATE_HASH_TOOL, registerCalculateHashTool, } from './tools/calculate-hash.js';
|
|
3
|
+
import {} from './tools/contract.js';
|
|
4
|
+
import { CREATE_DIRECTORY_TOOL, registerCreateDirectoryTool, } from './tools/create-directory.js';
|
|
5
|
+
import { DELETE_FILE_TOOL, registerDeleteFileTool, } from './tools/delete-file.js';
|
|
6
|
+
import { DIFF_FILES_TOOL, registerDiffFilesTool } from './tools/diff-files.js';
|
|
7
|
+
import { EDIT_FILE_TOOL, registerEditFileTool } from './tools/edit-file.js';
|
|
8
|
+
import { LIST_DIRECTORY_TOOL, registerListDirectoryTool, } from './tools/list-directory.js';
|
|
9
|
+
import { MOVE_FILE_TOOL, registerMoveFileTool } from './tools/move-file.js';
|
|
10
|
+
import { READ_MULTIPLE_FILES_TOOL, registerReadMultipleFilesTool, } from './tools/read-multiple.js';
|
|
11
|
+
import { READ_FILE_TOOL, registerReadFileTool } from './tools/read.js';
|
|
12
|
+
import { registerSearchAndReplaceTool, SEARCH_AND_REPLACE_TOOL, } from './tools/replace-in-files.js';
|
|
13
|
+
import { LIST_ALLOWED_DIRECTORIES_TOOL, registerListAllowedDirectoriesTool, } from './tools/roots.js';
|
|
14
|
+
import { registerSearchContentTool, SEARCH_CONTENT_TOOL, } from './tools/search-content.js';
|
|
15
|
+
import { registerSearchFilesTool, SEARCH_FILES_TOOL, } from './tools/search-files.js';
|
|
16
|
+
import { GET_MULTIPLE_FILE_INFO_TOOL, registerGetMultipleFileInfoTool, } from './tools/stat-many.js';
|
|
17
|
+
import { GET_FILE_INFO_TOOL, registerGetFileInfoTool } from './tools/stat.js';
|
|
18
|
+
import { registerTreeTool, TREE_TOOL } from './tools/tree.js';
|
|
19
|
+
import { registerWriteFileTool, WRITE_FILE_TOOL } from './tools/write-file.js';
|
|
19
20
|
export { buildToolErrorResponse, buildToolResponse } from './tools/shared.js';
|
|
21
|
+
export const ALL_TOOLS = [
|
|
22
|
+
LIST_ALLOWED_DIRECTORIES_TOOL,
|
|
23
|
+
LIST_DIRECTORY_TOOL,
|
|
24
|
+
SEARCH_FILES_TOOL,
|
|
25
|
+
TREE_TOOL,
|
|
26
|
+
READ_FILE_TOOL,
|
|
27
|
+
READ_MULTIPLE_FILES_TOOL,
|
|
28
|
+
GET_FILE_INFO_TOOL,
|
|
29
|
+
GET_MULTIPLE_FILE_INFO_TOOL,
|
|
30
|
+
SEARCH_CONTENT_TOOL,
|
|
31
|
+
CREATE_DIRECTORY_TOOL,
|
|
32
|
+
WRITE_FILE_TOOL,
|
|
33
|
+
EDIT_FILE_TOOL,
|
|
34
|
+
MOVE_FILE_TOOL,
|
|
35
|
+
DELETE_FILE_TOOL,
|
|
36
|
+
CALCULATE_HASH_TOOL,
|
|
37
|
+
DIFF_FILES_TOOL,
|
|
38
|
+
APPLY_PATCH_TOOL,
|
|
39
|
+
SEARCH_AND_REPLACE_TOOL,
|
|
40
|
+
];
|
|
20
41
|
const TOOL_REGISTRARS = [
|
|
21
42
|
registerListAllowedDirectoriesTool,
|
|
22
43
|
registerListDirectoryTool,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@j0hanz/filesystem-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"mcpName": "io.github.j0hanz/filesystem-mcp",
|
|
5
5
|
"description": "MCP Server that enables LLMs to interact with the local filesystem.",
|
|
6
6
|
"type": "module",
|
|
@@ -22,7 +22,6 @@
|
|
|
22
22
|
],
|
|
23
23
|
"scripts": {
|
|
24
24
|
"clean": "node scripts/tasks.mjs clean",
|
|
25
|
-
"validate:instructions": "node scripts/tasks.mjs validate:instructions",
|
|
26
25
|
"build": "node scripts/tasks.mjs build",
|
|
27
26
|
"copy:assets": "node scripts/tasks.mjs copy:assets",
|
|
28
27
|
"prepare": "npm run build",
|
package/dist/instructions.md
DELETED
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
# FILESYSTEM-MCP INSTRUCTIONS
|
|
2
|
-
|
|
3
|
-
These instructions are available as a resource (internal://instructions) or prompt (get-help). Load them when unsure about tool usage.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## CORE CAPABILITY
|
|
8
|
-
|
|
9
|
-
- Domain: Filesystem operations via an MCP server for LLM agents that need safe read/search/edit/diff/patch workflows within allowed roots.
|
|
10
|
-
- Primary Resources: Files, directories, metadata, search matches, and ephemeral cached result resources.
|
|
11
|
-
- Tools: READ: `roots`, `ls`, `find`, `tree`, `read`, `read_many`, `stat`, `stat_many`, `grep`, `calculate_hash`, `diff_files`. WRITE: `mkdir`, `write`, `edit`, `mv`, `rm`, `apply_patch`, `search_and_replace`.
|
|
12
|
-
|
|
13
|
-
---
|
|
14
|
-
|
|
15
|
-
## PROMPTS
|
|
16
|
-
|
|
17
|
-
- `get-help`: Returns these instructions for quick recall.
|
|
18
|
-
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
## RESOURCES & RESOURCE LINKS
|
|
22
|
-
|
|
23
|
-
- `internal://instructions`: This document.
|
|
24
|
-
- `filesystem-mcp://result/{id}`: Ephemeral cached tool output (in-memory); used when payloads are externalized.
|
|
25
|
-
- If a tool response includes a `resourceUri` or `resource_link`, call `resources/read` with that URI to fetch full content.
|
|
26
|
-
|
|
27
|
-
---
|
|
28
|
-
|
|
29
|
-
## PROGRESS & TASKS
|
|
30
|
-
|
|
31
|
-
- Include `_meta.progressToken` in requests to receive `notifications/progress` updates for long-running tools.
|
|
32
|
-
- Task-augmented tool calls are supported for `find`, `tree`, `read`, `read_many`, `stat_many`, `grep`, `mkdir`, `write`, `mv`, `rm`, `calculate_hash`, `apply_patch`, and `search_and_replace`:
|
|
33
|
-
- Send `tools/call` with `task` to create a task.
|
|
34
|
-
- Poll `tasks/get` and fetch final output with `tasks/result`.
|
|
35
|
-
- Use `tasks/cancel` to abort.
|
|
36
|
-
- Task status notifications are emitted via `notifications/tasks/status` when supported.
|
|
37
|
-
|
|
38
|
-
---
|
|
39
|
-
|
|
40
|
-
## THE "GOLDEN PATH" WORKFLOWS (CRITICAL)
|
|
41
|
-
|
|
42
|
-
### WORKFLOW A: DISCOVER AND INSPECT
|
|
43
|
-
|
|
44
|
-
- Call `roots` first to get allowed workspace roots.
|
|
45
|
-
- Call `ls` for non-recursive listing, or `tree` for bounded recursive overview.
|
|
46
|
-
- Call `stat` or `stat_many` to confirm path types/sizes before reading.
|
|
47
|
-
- Call `read` for one file or `read_many` for batches.
|
|
48
|
-
NOTE: Never guess paths. Resolve from `roots`/`ls`/`find` first.
|
|
49
|
-
|
|
50
|
-
### WORKFLOW B: SEARCH CONTENT SAFELY
|
|
51
|
-
|
|
52
|
-
- Call `find` to locate candidate files by glob.
|
|
53
|
-
- Call `grep` with `filePattern` to search content only in relevant file types.
|
|
54
|
-
- If output is truncated or externalized, call `resources/read` on returned `resourceUri`.
|
|
55
|
-
- Call `read` on exact hits to inspect surrounding context.
|
|
56
|
-
NOTE: `grep` regex uses RE2; do not rely on lookbehind/lookahead/backreferences.
|
|
57
|
-
|
|
58
|
-
### WORKFLOW C: MODIFY FILES WITH LOW RISK
|
|
59
|
-
|
|
60
|
-
- Call `mkdir` to prepare directories if needed.
|
|
61
|
-
- Use `edit` for precise first-occurrence replacements in one file.
|
|
62
|
-
- Use `search_and_replace` for bulk replacements across globs.
|
|
63
|
-
- Use `mv` to rename/move paths and `rm` to delete paths.
|
|
64
|
-
NOTE: Confirm destructive operations (`write`, `mv`, `rm`, bulk replace) with the user before execution.
|
|
65
|
-
|
|
66
|
-
### WORKFLOW D: DIFF/PATCH LOOP
|
|
67
|
-
|
|
68
|
-
- Call `diff_files` to generate a unified diff.
|
|
69
|
-
- Call `apply_patch` with `dryRun: true` first.
|
|
70
|
-
- If dry run succeeds, call `apply_patch` again with `dryRun: false`.
|
|
71
|
-
- Call `diff_files` again to verify `isIdentical: true` when expected.
|
|
72
|
-
NOTE: If patch apply fails, regenerate patch against current file content and retry.
|
|
73
|
-
|
|
74
|
-
---
|
|
75
|
-
|
|
76
|
-
## TOOL NUANCES & GOTCHAS
|
|
77
|
-
|
|
78
|
-
`roots`
|
|
79
|
-
|
|
80
|
-
- Purpose: Enumerate allowed workspace roots.
|
|
81
|
-
- Gotcha: Other tools are constrained to these roots.
|
|
82
|
-
|
|
83
|
-
`ls`
|
|
84
|
-
|
|
85
|
-
- Purpose: List directory contents (non-recursive by default).
|
|
86
|
-
- Nuance: `pattern` enables filtered recursive traversal up to `maxDepth`.
|
|
87
|
-
|
|
88
|
-
`find`
|
|
89
|
-
|
|
90
|
-
- Purpose: Find files by glob.
|
|
91
|
-
- Output: Returns relative paths plus metadata; may truncate based on limits.
|
|
92
|
-
- Nuance: Respects `.gitignore` unless `includeIgnored=true`.
|
|
93
|
-
|
|
94
|
-
`tree`
|
|
95
|
-
|
|
96
|
-
- Purpose: Return both ASCII and JSON tree views.
|
|
97
|
-
- Gotcha: `maxDepth=0` returns only the root node.
|
|
98
|
-
|
|
99
|
-
`read`
|
|
100
|
-
|
|
101
|
-
- Purpose: Read a single text file with optional head/range.
|
|
102
|
-
- Gotcha: Large content is externalized to `filesystem-mcp://result/{id}` and preview is returned inline.
|
|
103
|
-
|
|
104
|
-
`read_many`
|
|
105
|
-
|
|
106
|
-
- Purpose: Batch read multiple files.
|
|
107
|
-
- Gotcha: Per-file `truncationReason` can be `head`, `range`, or `externalized`.
|
|
108
|
-
- Limits: Total read budget is capped by `MAX_READ_MANY_TOTAL_SIZE`.
|
|
109
|
-
|
|
110
|
-
`stat` / `stat_many`
|
|
111
|
-
|
|
112
|
-
- Purpose: Return metadata including token estimate, MIME type, and timestamps.
|
|
113
|
-
- Nuance: Use before read/search when file size/type uncertainty exists.
|
|
114
|
-
|
|
115
|
-
`grep`
|
|
116
|
-
|
|
117
|
-
- Purpose: Search file contents by literal or RE2 regex.
|
|
118
|
-
- Gotcha: Inline match rows are capped (first 50); full structured results are externalized via `resourceUri`.
|
|
119
|
-
- Limits: Skips binary and oversized files; reports skips in structured output.
|
|
120
|
-
|
|
121
|
-
`write`
|
|
122
|
-
|
|
123
|
-
- Purpose: Create or overwrite a file atomically.
|
|
124
|
-
- Side effects: Creates parent directories automatically; overwrites existing content.
|
|
125
|
-
|
|
126
|
-
`edit`
|
|
127
|
-
|
|
128
|
-
- Purpose: Apply sequential literal replacements (first occurrence per edit).
|
|
129
|
-
- Gotcha: `oldText` must match exactly; unmatched items are reported in `unmatchedEdits`.
|
|
130
|
-
|
|
131
|
-
`mv`
|
|
132
|
-
|
|
133
|
-
- Purpose: Move or rename file/directory paths.
|
|
134
|
-
- Nuance: Cross-device moves fall back to copy+delete.
|
|
135
|
-
|
|
136
|
-
`rm`
|
|
137
|
-
|
|
138
|
-
- Purpose: Delete file/directory paths.
|
|
139
|
-
- Gotcha: Non-empty directory delete requires `recursive=true`; else returns actionable input error.
|
|
140
|
-
|
|
141
|
-
`calculate_hash`
|
|
142
|
-
|
|
143
|
-
- Purpose: SHA-256 for files or deterministic composite hash for directories.
|
|
144
|
-
- Nuance: Directory hashing respects root `.gitignore` and sorts paths for stable output.
|
|
145
|
-
|
|
146
|
-
`diff_files`
|
|
147
|
-
|
|
148
|
-
- Purpose: Generate unified diff between two files.
|
|
149
|
-
- Gotcha: `isIdentical=true` means no hunks (`@@`) and empty diff.
|
|
150
|
-
|
|
151
|
-
`apply_patch`
|
|
152
|
-
|
|
153
|
-
- Purpose: Apply unified diff text to a file.
|
|
154
|
-
- Gotcha: Patch must include valid hunk headers; use `dryRun=true` first.
|
|
155
|
-
|
|
156
|
-
`search_and_replace`
|
|
157
|
-
|
|
158
|
-
- Purpose: Replace all matches across files selected by `filePattern`.
|
|
159
|
-
- Gotcha: Literal mode is default; `isRegex=true` enables RE2 + capture replacements (`$1`, `$2`).
|
|
160
|
-
- Limits: Changed-file sample and failure sample are capped/truncated in output.
|
|
161
|
-
|
|
162
|
-
---
|
|
163
|
-
|
|
164
|
-
## CROSS-FEATURE RELATIONSHIPS
|
|
165
|
-
|
|
166
|
-
- Use `roots` output to scope all other tool calls.
|
|
167
|
-
- Use `find` → `grep` → `read` as the default search triad.
|
|
168
|
-
- Use `diff_files` output as input to `apply_patch`.
|
|
169
|
-
- Use `resourceUri` from `read`, `read_many`, `grep`, and `diff_files` with `resources/read` for full payload retrieval.
|
|
170
|
-
- Use `stat`/`stat_many` before `read`/`read_many` when size/type may violate limits.
|
|
171
|
-
|
|
172
|
-
---
|
|
173
|
-
|
|
174
|
-
## CONSTRAINTS & LIMITATIONS
|
|
175
|
-
|
|
176
|
-
- Access is restricted to allowed roots negotiated from CLI and MCP Roots.
|
|
177
|
-
- If multiple roots are configured and no path is provided, tools requiring base path fail with disambiguation error.
|
|
178
|
-
- Default timeouts and size caps are enforced (`DEFAULT_SEARCH_TIMEOUT`, `MAX_FILE_SIZE`, `MAX_SEARCH_SIZE`, `MAX_READ_MANY_TOTAL_SIZE`).
|
|
179
|
-
- Sensitive files are denylisted by default unless explicitly allowed via environment settings.
|
|
180
|
-
- Binary files are skipped for content search/read workflows where text is required.
|
|
181
|
-
- Externalized resource cache is in-memory, bounded (entry size/count/total bytes), and ephemeral.
|
|
182
|
-
- Regex engine is RE2-based; advanced PCRE features are unsupported.
|
|
183
|
-
|
|
184
|
-
---
|
|
185
|
-
|
|
186
|
-
## ERROR HANDLING STRATEGY
|
|
187
|
-
|
|
188
|
-
- `E_ACCESS_DENIED`: Path is outside allowed roots or roots are not configured. → Call `roots`, then retry with an allowed path.
|
|
189
|
-
- `E_NOT_FOUND`: Path or resource does not exist. → Call `ls`/`find` to verify existence and exact spelling.
|
|
190
|
-
- `E_NOT_FILE`: Path points to a directory/non-file for file-only operation. → Call `ls` or switch to directory tool.
|
|
191
|
-
- `E_NOT_DIRECTORY`: Path points to a file for directory operation. → Call `read` for file content or choose a directory path.
|
|
192
|
-
- `E_TOO_LARGE`: File/content exceeds limits. → Narrow scope, use range/head reads, or reduce candidate files.
|
|
193
|
-
- `E_TIMEOUT`: Operation exceeded timeout. → Reduce path scope, lower result limits, or simplify pattern.
|
|
194
|
-
- `E_INVALID_PATTERN`: Glob/regex invalid. → Fix syntax (RE2 for regex) and retry.
|
|
195
|
-
- `E_INVALID_INPUT`: Arguments are invalid for current context (e.g., ambiguous roots, bad patch, missing flags). → Correct parameters and retry.
|
|
196
|
-
- `E_PERMISSION_DENIED`: OS-level permission denied. → Adjust file permissions or choose accessible paths.
|
|
197
|
-
- `E_SYMLINK_NOT_ALLOWED`: Symlink traversal escapes allowed roots. → Use paths within allowed directories.
|
|
198
|
-
- `E_UNKNOWN`: Unclassified failure. → Inspect message details and retry with narrower, validated inputs.
|
|
199
|
-
|
|
200
|
-
---
|