@j0hanz/filesystem-mcp 1.16.2 → 1.16.3
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 +18 -14
- package/dist/assets/logo.svg +26 -11
- package/dist/cli.js +1 -1
- package/dist/completions.d.ts +1 -1
- package/dist/completions.js +1 -2
- package/dist/lib/constants.d.ts +2 -0
- package/dist/lib/constants.js +2 -3
- package/dist/lib/fs-helpers.d.ts +1 -1
- package/dist/lib/fs-helpers.js +0 -1
- package/dist/lib/logger.d.ts +3 -4
- package/dist/lib/logger.js +1 -1
- package/dist/lib/paths.d.ts +1 -1
- package/dist/prompts.d.ts +1 -1
- package/dist/prompts.js +11 -11
- package/dist/resources/workflows.js +12 -0
- package/dist/resources.d.ts +1 -1
- package/dist/resources.js +1 -1
- package/dist/schemas.d.ts +4 -4
- package/dist/server/bootstrap.d.ts +1 -10
- package/dist/server/bootstrap.js +165 -95
- package/dist/server/roots-manager.d.ts +3 -2
- package/dist/server/roots-manager.js +30 -4
- package/dist/server/task-store.d.ts +2 -3
- package/dist/server/task-store.js +1 -1
- package/dist/tools/apply-patch.d.ts +1 -1
- package/dist/tools/apply-patch.js +16 -12
- package/dist/tools/calculate-hash.d.ts +1 -1
- package/dist/tools/calculate-hash.js +8 -12
- package/dist/tools/contract.d.ts +5 -0
- package/dist/tools/create-directory.d.ts +1 -1
- package/dist/tools/create-directory.js +7 -10
- package/dist/tools/delete-file.d.ts +1 -1
- package/dist/tools/delete-file.js +12 -11
- package/dist/tools/diff-files.d.ts +1 -1
- package/dist/tools/diff-files.js +8 -11
- package/dist/tools/edit-file.d.ts +1 -1
- package/dist/tools/edit-file.js +12 -11
- package/dist/tools/icons.d.ts +15 -0
- package/dist/tools/icons.js +24 -0
- package/dist/tools/list-directory.d.ts +1 -1
- package/dist/tools/list-directory.js +7 -10
- package/dist/tools/move-file.d.ts +1 -1
- package/dist/tools/move-file.js +12 -11
- package/dist/tools/read-multiple.d.ts +1 -1
- package/dist/tools/read-multiple.js +8 -12
- package/dist/tools/read.d.ts +1 -1
- package/dist/tools/read.js +7 -10
- package/dist/tools/replace-in-files.d.ts +1 -1
- package/dist/tools/replace-in-files.js +11 -13
- package/dist/tools/roots.d.ts +1 -1
- package/dist/tools/roots.js +7 -10
- package/dist/tools/search-content.d.ts +1 -1
- package/dist/tools/search-content.js +8 -13
- package/dist/tools/search-files.d.ts +1 -1
- package/dist/tools/search-files.js +11 -16
- package/dist/tools/shared.d.ts +15 -12
- package/dist/tools/shared.js +84 -56
- package/dist/tools/stat-many.d.ts +1 -1
- package/dist/tools/stat-many.js +8 -12
- package/dist/tools/stat.d.ts +1 -1
- package/dist/tools/stat.js +7 -10
- package/dist/tools/task-support.d.ts +14 -12
- package/dist/tools/task-support.js +98 -92
- package/dist/tools/tree.d.ts +1 -1
- package/dist/tools/tree.js +11 -15
- package/dist/tools/write-file.d.ts +1 -1
- package/dist/tools/write-file.js +12 -11
- package/dist/tools.d.ts +1 -1
- package/package.json +6 -4
package/dist/tools/stat.js
CHANGED
|
@@ -4,8 +4,9 @@ import { ErrorCode } from '../lib/errors.js';
|
|
|
4
4
|
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
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { FILE_READ_ICONS } from './icons.js';
|
|
8
|
+
import { buildFileInfoPayload, buildToolErrorResponse, buildToolResponse, executeToolWithDiagnostics, READ_ONLY_TOOL_ANNOTATIONS, } from './shared.js';
|
|
9
|
+
import { registerStandardTool } from './task-support.js';
|
|
9
10
|
export const GET_FILE_INFO_TOOL = {
|
|
10
11
|
name: 'stat',
|
|
11
12
|
title: 'Get File Info',
|
|
@@ -14,6 +15,7 @@ export const GET_FILE_INFO_TOOL = {
|
|
|
14
15
|
inputSchema: GetFileInfoInputSchema,
|
|
15
16
|
outputSchema: GetFileInfoOutputSchema,
|
|
16
17
|
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
|
18
|
+
icons: FILE_READ_ICONS,
|
|
17
19
|
taskSupport: 'forbidden',
|
|
18
20
|
};
|
|
19
21
|
function formatFileInfoDetails(info) {
|
|
@@ -41,17 +43,16 @@ async function handleGetFileInfo(args, signal) {
|
|
|
41
43
|
return buildToolResponse(formatFileInfoDetails(info), structured);
|
|
42
44
|
}
|
|
43
45
|
export function registerGetFileInfoTool(server, options = {}) {
|
|
44
|
-
const handler = (args,
|
|
46
|
+
const handler = (args, ctx) => executeToolWithDiagnostics({
|
|
45
47
|
toolName: 'stat',
|
|
46
|
-
|
|
48
|
+
ctx,
|
|
47
49
|
outputSchema: GetFileInfoOutputSchema,
|
|
48
50
|
timedSignal: { timeoutMs: DEFAULT_SEARCH_TIMEOUT_MS },
|
|
49
51
|
context: { path: args.path },
|
|
50
52
|
run: (signal) => handleGetFileInfo(args, signal),
|
|
51
53
|
onError: (error) => buildToolErrorResponse(error, ErrorCode.NOT_FOUND, args.path),
|
|
52
54
|
});
|
|
53
|
-
|
|
54
|
-
guard: options.isInitialized,
|
|
55
|
+
registerStandardTool(server, GET_FILE_INFO_TOOL, handler, options, {
|
|
55
56
|
progressMessage: (args) => `🕮 stat: ${basename(args.path)}`,
|
|
56
57
|
completionMessage: (args, result) => {
|
|
57
58
|
const name = basename(args.path);
|
|
@@ -63,8 +64,4 @@ export function registerGetFileInfoTool(server, options = {}) {
|
|
|
63
64
|
return `🕮 stat: ${sc.info.name} • ${sc.info.type}, ${formatBytes(sc.info.size)}`;
|
|
64
65
|
},
|
|
65
66
|
});
|
|
66
|
-
const validatedHandler = withValidatedArgs(GetFileInfoInputSchema, wrappedHandler);
|
|
67
|
-
if (registerToolTaskIfAvailable(server, 'stat', GET_FILE_INFO_TOOL, validatedHandler, options.iconInfo, options.isInitialized))
|
|
68
|
-
return;
|
|
69
|
-
server.registerTool('stat', withDefaultIcons({ ...GET_FILE_INFO_TOOL }, options.iconInfo), validatedHandler);
|
|
70
67
|
}
|
|
@@ -1,22 +1,24 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import type
|
|
3
|
-
|
|
4
|
-
import type { RequestTaskStore } from '@modelcontextprotocol/sdk/shared/protocol.js';
|
|
5
|
-
import { type IconInfo, type ToolExtra, type ToolResult } from './shared.js';
|
|
6
|
-
type TaskToolExtra = ToolExtra & {
|
|
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
|
+
type TaskToolContext = ToolContext & {
|
|
7
4
|
taskId?: string;
|
|
8
5
|
taskStore?: RequestTaskStore;
|
|
9
|
-
taskRequestedTtl?: number
|
|
6
|
+
taskRequestedTtl?: number;
|
|
10
7
|
};
|
|
11
|
-
|
|
12
|
-
type ToolArgs<Args extends ToolSchema> = Args extends
|
|
13
|
-
export declare function registerToolTaskIfAvailable<Args extends ToolSchema, Result>(server: McpServer, toolName: string, toolDef: object, run: (args: ToolArgs<Args>,
|
|
8
|
+
type ToolSchema = StandardSchemaWithJSON | undefined;
|
|
9
|
+
type ToolArgs<Args extends ToolSchema> = Args extends StandardSchemaWithJSON ? StandardSchemaWithJSON.InferOutput<Args> : undefined;
|
|
10
|
+
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;
|
|
11
|
+
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?: {
|
|
12
|
+
guard?: (() => boolean) | undefined;
|
|
13
|
+
progressMessage?: (args: Args) => string;
|
|
14
|
+
completionMessage?: (args: Args, result: ToolResult<Result>) => string | undefined;
|
|
15
|
+
}): void;
|
|
14
16
|
interface TaskHandlerOptions {
|
|
15
17
|
guard?: () => boolean;
|
|
16
18
|
toolName?: string;
|
|
17
19
|
cancelPollMs?: number;
|
|
18
20
|
pollIntervalMs?: number;
|
|
19
21
|
}
|
|
20
|
-
export declare function createToolTaskHandler<Result>(run: (args: undefined,
|
|
21
|
-
export declare function createToolTaskHandler<Args extends
|
|
22
|
+
export declare function createToolTaskHandler<Result>(run: (args: undefined, ctx: TaskToolContext) => Promise<ToolResult<Result>>, options?: TaskHandlerOptions): ToolTaskHandler;
|
|
23
|
+
export declare function createToolTaskHandler<Args extends StandardSchemaWithJSON, Result>(run: (args: ToolArgs<Args>, ctx: TaskToolContext) => Promise<ToolResult<Result>>, options?: TaskHandlerOptions): ToolTaskHandler<Args>;
|
|
22
24
|
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {} 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,7 +7,7 @@ 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, withDefaultIcons, } from './shared.js';
|
|
10
|
+
import { buildToolErrorResponse, maybeStripStructuredContentFromResult, resolveToolTaskSupportLevel, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
11
11
|
const taskContext = new AsyncLocalStorage();
|
|
12
12
|
const TASK_DIAGNOSTICS_CHANNEL = channel('filesystem-mcp:tasks');
|
|
13
13
|
function publishTaskDiagnostics(event) {
|
|
@@ -15,40 +15,10 @@ function publishTaskDiagnostics(event) {
|
|
|
15
15
|
TASK_DIAGNOSTICS_CHANNEL.publish(event);
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
|
-
function getDynamicProperty(target, key) {
|
|
19
|
-
return Reflect.get(target, key);
|
|
20
|
-
}
|
|
21
18
|
// --- Type Guards & Helpers ---
|
|
22
|
-
function isExperimentalTaskRegistration(value) {
|
|
23
|
-
if (!isRecord(value))
|
|
24
|
-
return false;
|
|
25
|
-
const { registerToolTask } = value;
|
|
26
|
-
return (registerToolTask === undefined || typeof registerToolTask === 'function');
|
|
27
|
-
}
|
|
28
|
-
function getExperimentalTaskRegistration(server) {
|
|
29
|
-
const experimental = getDynamicProperty(server, 'experimental');
|
|
30
|
-
if (!isRecord(experimental))
|
|
31
|
-
return undefined;
|
|
32
|
-
const { tasks } = experimental;
|
|
33
|
-
if (!isExperimentalTaskRegistration(tasks))
|
|
34
|
-
return undefined;
|
|
35
|
-
return tasks;
|
|
36
|
-
}
|
|
37
19
|
function hasTaskToolCapability(server) {
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
return true; // Assume capability if runtime structure is opaque
|
|
41
|
-
const getCapabilities = getDynamicProperty(serverRuntime, 'getCapabilities');
|
|
42
|
-
if (typeof getCapabilities !== 'function')
|
|
43
|
-
return true;
|
|
44
|
-
const capabilities = getCapabilities.call(serverRuntime);
|
|
45
|
-
if (!isRecord(capabilities))
|
|
46
|
-
return false;
|
|
47
|
-
const { tasks } = capabilities;
|
|
48
|
-
return (isRecord(tasks) &&
|
|
49
|
-
isRecord(tasks.requests) &&
|
|
50
|
-
isRecord(tasks.requests.tools) &&
|
|
51
|
-
isRecord(tasks.requests.tools.call));
|
|
20
|
+
const capabilities = server.server.getCapabilities();
|
|
21
|
+
return capabilities.tasks?.requests?.tools?.call !== undefined;
|
|
52
22
|
}
|
|
53
23
|
const TASK_STATUS_NOTIFICATION_METHOD = 'notifications/tasks/status';
|
|
54
24
|
const TASK_CREATED_NOTIFICATION_METHOD = 'notifications/tasks/created';
|
|
@@ -59,36 +29,51 @@ function isRequestTaskStore(value) {
|
|
|
59
29
|
typeof value.storeTaskResult === 'function' &&
|
|
60
30
|
typeof value.getTaskResult === 'function');
|
|
61
31
|
}
|
|
62
|
-
function
|
|
63
|
-
return isRecord(value) && isRequestTaskStore(value.
|
|
64
|
-
}
|
|
65
|
-
function isTaskExtra(value) {
|
|
66
|
-
return (isCreateTaskExtra(value) &&
|
|
67
|
-
typeof value.taskId === 'string' &&
|
|
68
|
-
value.taskId.length > 0);
|
|
32
|
+
function hasTaskStoreContext(value) {
|
|
33
|
+
return isRecord(value.task) && isRequestTaskStore(value.task.store);
|
|
69
34
|
}
|
|
70
|
-
function
|
|
71
|
-
if (!
|
|
35
|
+
function asCreateTaskContext(value) {
|
|
36
|
+
if (!hasTaskStoreContext(value)) {
|
|
72
37
|
throw new McpError(ErrorCode.INVALID_INPUT, 'Task store not configured.');
|
|
73
38
|
}
|
|
74
39
|
return value;
|
|
75
40
|
}
|
|
76
|
-
function
|
|
77
|
-
if (!
|
|
41
|
+
function asTaskRequestContext(value) {
|
|
42
|
+
if (!hasTaskStoreContext(value) ||
|
|
43
|
+
!isRecord(value.task) ||
|
|
44
|
+
typeof value.task.id !== 'string' ||
|
|
45
|
+
value.task.id.length === 0) {
|
|
78
46
|
throw new McpError(ErrorCode.INVALID_INPUT, 'Task id or store missing.');
|
|
79
47
|
}
|
|
80
48
|
return value;
|
|
81
49
|
}
|
|
50
|
+
function toTaskToolContext(ctx) {
|
|
51
|
+
return {
|
|
52
|
+
signal: ctx.mcpReq.signal,
|
|
53
|
+
...(ctx.mcpReq._meta
|
|
54
|
+
? { _meta: ctx.mcpReq._meta }
|
|
55
|
+
: {}),
|
|
56
|
+
sendNotification: async (notification) => ctx.mcpReq.notify(notification),
|
|
57
|
+
...(hasTaskStoreContext(ctx) ? { taskStore: ctx.task.store } : {}),
|
|
58
|
+
...(typeof ctx.task.id === 'string' && ctx.task.id.length > 0
|
|
59
|
+
? { taskId: ctx.task.id }
|
|
60
|
+
: {}),
|
|
61
|
+
...(ctx.task.requestedTtl !== undefined
|
|
62
|
+
? { taskRequestedTtl: ctx.task.requestedTtl }
|
|
63
|
+
: {}),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
82
66
|
const TASK_STATUSES = new Set([
|
|
67
|
+
'submitted',
|
|
83
68
|
'working',
|
|
84
69
|
'input_required',
|
|
85
70
|
'completed',
|
|
86
71
|
'failed',
|
|
87
72
|
'cancelled',
|
|
73
|
+
'unknown',
|
|
88
74
|
]);
|
|
89
75
|
function isTaskStatus(value) {
|
|
90
|
-
return
|
|
91
|
-
TASK_STATUSES.has(value));
|
|
76
|
+
return typeof value === 'string' && TASK_STATUSES.has(value);
|
|
92
77
|
}
|
|
93
78
|
function normalizeGetTaskResult(value) {
|
|
94
79
|
if (!isRecord(value) || typeof value.taskId !== 'string') {
|
|
@@ -122,9 +107,11 @@ function normalizeGetTaskResult(value) {
|
|
|
122
107
|
return normalized;
|
|
123
108
|
}
|
|
124
109
|
function normalizeCallToolResult(value) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
110
|
+
if (isRecord(value) &&
|
|
111
|
+
Array.isArray(value.content) &&
|
|
112
|
+
value.content.every((entry) => isRecord(entry) && typeof entry.type === 'string')) {
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
128
115
|
throw new McpError(ErrorCode.INVALID_INPUT, 'Invalid stored task result.');
|
|
129
116
|
}
|
|
130
117
|
function getToolResultErrorCode(result) {
|
|
@@ -194,8 +181,8 @@ function buildTaskStatusNotificationParams(task) {
|
|
|
194
181
|
params.statusMessage = task.statusMessage;
|
|
195
182
|
return params;
|
|
196
183
|
}
|
|
197
|
-
async function notifyTaskCreatedIfPossible(
|
|
198
|
-
const { sendNotification } =
|
|
184
|
+
async function notifyTaskCreatedIfPossible(ctx, taskId, toolName) {
|
|
185
|
+
const { sendNotification } = ctx;
|
|
199
186
|
if (typeof sendNotification !== 'function')
|
|
200
187
|
return;
|
|
201
188
|
const notify = sendNotification;
|
|
@@ -204,7 +191,7 @@ async function notifyTaskCreatedIfPossible(extra, taskId, toolName) {
|
|
|
204
191
|
method: TASK_CREATED_NOTIFICATION_METHOD,
|
|
205
192
|
params: {
|
|
206
193
|
_meta: {
|
|
207
|
-
'modelcontextprotocol
|
|
194
|
+
'io.modelcontextprotocol/related-task': {
|
|
208
195
|
taskId,
|
|
209
196
|
},
|
|
210
197
|
},
|
|
@@ -219,8 +206,8 @@ async function notifyTaskCreatedIfPossible(extra, taskId, toolName) {
|
|
|
219
206
|
});
|
|
220
207
|
}
|
|
221
208
|
}
|
|
222
|
-
async function notifyTaskStatusIfPossible(
|
|
223
|
-
const { sendNotification } =
|
|
209
|
+
async function notifyTaskStatusIfPossible(ctx, taskStore, taskId, toolName) {
|
|
210
|
+
const { sendNotification } = ctx;
|
|
224
211
|
if (typeof sendNotification !== 'function')
|
|
225
212
|
return;
|
|
226
213
|
const notify = sendNotification;
|
|
@@ -247,17 +234,17 @@ async function notifyTaskStatusIfPossible(extra, taskStore, taskId, toolName) {
|
|
|
247
234
|
// Never fail task execution because status notifications are optional.
|
|
248
235
|
}
|
|
249
236
|
}
|
|
250
|
-
function getTaskStore(
|
|
251
|
-
if (!
|
|
237
|
+
function getTaskStore(ctx) {
|
|
238
|
+
if (!ctx.taskStore) {
|
|
252
239
|
throw new McpError(ErrorCode.INVALID_INPUT, 'Task store not configured.');
|
|
253
240
|
}
|
|
254
|
-
return
|
|
241
|
+
return ctx.taskStore;
|
|
255
242
|
}
|
|
256
|
-
function getTaskId(
|
|
257
|
-
if (!
|
|
243
|
+
function getTaskId(ctx) {
|
|
244
|
+
if (!ctx.taskId) {
|
|
258
245
|
throw new McpError(ErrorCode.INVALID_INPUT, 'Task id missing.');
|
|
259
246
|
}
|
|
260
|
-
return
|
|
247
|
+
return ctx.taskId;
|
|
261
248
|
}
|
|
262
249
|
function isErrorResult(result) {
|
|
263
250
|
return 'isError' in result && result.isError;
|
|
@@ -274,6 +261,7 @@ const TERMINAL_TASK_STATUSES = new Set([
|
|
|
274
261
|
'completed',
|
|
275
262
|
'failed',
|
|
276
263
|
'cancelled',
|
|
264
|
+
'unknown',
|
|
277
265
|
]);
|
|
278
266
|
async function isTaskAlreadyTerminal(taskStore, taskId) {
|
|
279
267
|
try {
|
|
@@ -300,16 +288,14 @@ async function countActiveTasks(taskStore) {
|
|
|
300
288
|
for (const task of tasks) {
|
|
301
289
|
if (!isRecord(task) || typeof task.status !== 'string')
|
|
302
290
|
continue;
|
|
303
|
-
if (task.status
|
|
304
|
-
task.status !== 'failed' &&
|
|
305
|
-
task.status !== 'cancelled') {
|
|
291
|
+
if (!TERMINAL_TASK_STATUSES.has(task.status)) {
|
|
306
292
|
active += 1;
|
|
307
293
|
}
|
|
308
294
|
}
|
|
309
295
|
return active;
|
|
310
296
|
}
|
|
311
297
|
function resolveRequestedTaskTtl(requestedTtl) {
|
|
312
|
-
if (requestedTtl
|
|
298
|
+
if (requestedTtl === undefined)
|
|
313
299
|
return DEFAULT_TASK_TTL_MS;
|
|
314
300
|
if (!Number.isFinite(requestedTtl)) {
|
|
315
301
|
throw new McpError(ErrorCode.INVALID_INPUT, 'Task TTL must be finite.');
|
|
@@ -342,11 +328,11 @@ async function isTaskCancelled(taskStore, taskId) {
|
|
|
342
328
|
return false;
|
|
343
329
|
}
|
|
344
330
|
}
|
|
345
|
-
async function runTaskInBackground(run, args,
|
|
331
|
+
async function runTaskInBackground(run, args, ctx, taskStore, taskId, toolName, cancelPollMs) {
|
|
346
332
|
// Create a dedicated AbortController for background execution.
|
|
347
333
|
// The original request signal is stale once createTask returns.
|
|
348
334
|
const taskAbort = new AbortController();
|
|
349
|
-
const taskExtra = { ...
|
|
335
|
+
const taskExtra = { ...ctx, signal: taskAbort.signal };
|
|
350
336
|
// Poll the task store for client-initiated cancellation.
|
|
351
337
|
const cancelPoller = setInterval(() => {
|
|
352
338
|
void isTaskCancelled(taskStore, taskId).then((cancelled) => {
|
|
@@ -381,7 +367,7 @@ async function runTaskInBackground(run, args, extra, taskStore, taskId, toolName
|
|
|
381
367
|
toolName,
|
|
382
368
|
durationMs,
|
|
383
369
|
});
|
|
384
|
-
await notifyTaskStatusIfPossible(
|
|
370
|
+
await notifyTaskStatusIfPossible(ctx, taskStore, taskId, toolName);
|
|
385
371
|
}
|
|
386
372
|
catch (innerError) {
|
|
387
373
|
Logger.error(format('Failed to store task result for task %s:', taskId), innerError);
|
|
@@ -393,7 +379,7 @@ async function runTaskInBackground(run, args, extra, taskStore, taskId, toolName
|
|
|
393
379
|
lastUpdatedAt: new Date().toISOString(),
|
|
394
380
|
statusMessage: 'Internal system error while storing result',
|
|
395
381
|
};
|
|
396
|
-
const { sendNotification } =
|
|
382
|
+
const { sendNotification } = ctx;
|
|
397
383
|
if (typeof sendNotification === 'function') {
|
|
398
384
|
try {
|
|
399
385
|
await sendNotification({
|
|
@@ -416,17 +402,12 @@ async function runTaskInBackground(run, args, extra, taskStore, taskId, toolName
|
|
|
416
402
|
function tryRegisterToolTask(server, toolName, toolDef, taskHandler, iconInfo) {
|
|
417
403
|
if (!hasTaskToolCapability(server))
|
|
418
404
|
return false;
|
|
419
|
-
const tasks = getExperimentalTaskRegistration(server);
|
|
420
|
-
if (!tasks?.registerToolTask)
|
|
421
|
-
return false;
|
|
422
405
|
const def = toolDef;
|
|
423
406
|
const existingExecution = def.execution ?? {};
|
|
424
|
-
const taskSupport = def.taskSupport
|
|
425
|
-
|
|
426
|
-
'forbidden';
|
|
427
|
-
if (taskSupport === 'forbidden')
|
|
407
|
+
const taskSupport = resolveToolTaskSupportLevel(def.taskSupport, existingExecution.taskSupport);
|
|
408
|
+
if (!taskSupport || taskSupport === 'forbidden')
|
|
428
409
|
return false;
|
|
429
|
-
tasks.registerToolTask(toolName, withDefaultIcons({ ...toolDef, execution: { ...existingExecution, taskSupport } }, iconInfo), taskHandler);
|
|
410
|
+
server.experimental.tasks.registerToolTask(toolName, withDefaultIcons({ ...toolDef, execution: { ...existingExecution, taskSupport } }, iconInfo), taskHandler);
|
|
430
411
|
return true;
|
|
431
412
|
}
|
|
432
413
|
export function registerToolTaskIfAvailable(server, toolName, toolDef, run, iconInfo, guard) {
|
|
@@ -436,19 +417,37 @@ export function registerToolTaskIfAvailable(server, toolName, toolDef, run, icon
|
|
|
436
417
|
};
|
|
437
418
|
return tryRegisterToolTask(server, toolName, toolDef, createToolTaskHandler(run, taskOptions), iconInfo);
|
|
438
419
|
}
|
|
420
|
+
export function registerStandardTool(server, toolDef, handler, options = {}, wrapOptions = {}) {
|
|
421
|
+
const wrappedHandler = wrapToolHandler(handler, {
|
|
422
|
+
guard: options.isInitialized,
|
|
423
|
+
...wrapOptions,
|
|
424
|
+
});
|
|
425
|
+
const validatedHandler = withValidatedArgs(toolDef.inputSchema, wrappedHandler);
|
|
426
|
+
if (registerToolTaskIfAvailable(server, toolDef.name, toolDef, validatedHandler, options.iconInfo, options.isInitialized)) {
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
server.registerTool(toolDef.name, withDefaultIcons({ ...toolDef }, options.iconInfo), validatedHandler);
|
|
430
|
+
}
|
|
439
431
|
export function createToolTaskHandler(run, options) {
|
|
440
|
-
const createTask = (async (
|
|
441
|
-
|
|
442
|
-
|
|
432
|
+
const createTask = (async (...params) => {
|
|
433
|
+
let args;
|
|
434
|
+
let serverCtx;
|
|
435
|
+
if (params.length === 1) {
|
|
436
|
+
serverCtx = params[0];
|
|
437
|
+
}
|
|
438
|
+
else {
|
|
439
|
+
[args, serverCtx] = params;
|
|
440
|
+
}
|
|
441
|
+
const ctx = toTaskToolContext(asCreateTaskContext(serverCtx));
|
|
443
442
|
if (options?.guard && !options.guard()) {
|
|
444
443
|
throw new McpError(ErrorCode.INVALID_INPUT, 'Client not initialized; wait for notifications/initialized');
|
|
445
444
|
}
|
|
446
|
-
const taskStore = getTaskStore(
|
|
445
|
+
const taskStore = getTaskStore(ctx);
|
|
447
446
|
if ((await countActiveTasks(taskStore)) >= MAX_CONCURRENT_TASKS) {
|
|
448
447
|
throw new McpError(ErrorCode.INVALID_INPUT, `Too many active tasks (limit: ${String(MAX_CONCURRENT_TASKS)}).`);
|
|
449
448
|
}
|
|
450
449
|
const task = await taskStore.createTask({
|
|
451
|
-
ttl: resolveRequestedTaskTtl(
|
|
450
|
+
ttl: resolveRequestedTaskTtl(ctx.taskRequestedTtl),
|
|
452
451
|
pollInterval: options?.pollIntervalMs ?? TASK_POLL_INTERVAL_MS,
|
|
453
452
|
});
|
|
454
453
|
const toolLabel = options?.toolName ?? 'tool';
|
|
@@ -465,26 +464,33 @@ export function createToolTaskHandler(run, options) {
|
|
|
465
464
|
...(options?.toolName ? { toolName: options.toolName } : {}),
|
|
466
465
|
});
|
|
467
466
|
const taskExtra = {
|
|
468
|
-
...
|
|
467
|
+
...ctx,
|
|
469
468
|
taskStore,
|
|
470
469
|
taskId: task.taskId,
|
|
471
470
|
};
|
|
472
471
|
void notifyTaskCreatedIfPossible(taskExtra, task.taskId, options?.toolName);
|
|
473
472
|
void notifyTaskStatusIfPossible(taskExtra, taskStore, task.taskId, options?.toolName);
|
|
474
473
|
void runTaskInBackground(run, args, taskExtra, taskStore, task.taskId, options?.toolName, options?.cancelPollMs);
|
|
475
|
-
return {
|
|
474
|
+
return {
|
|
475
|
+
task,
|
|
476
|
+
_meta: {
|
|
477
|
+
'io.modelcontextprotocol/model-immediate-response': `${toolLabel} task created — poll tasks/get for progress.`,
|
|
478
|
+
},
|
|
479
|
+
};
|
|
476
480
|
});
|
|
477
|
-
const getTask = (async (
|
|
478
|
-
const
|
|
479
|
-
const
|
|
480
|
-
const
|
|
481
|
+
const getTask = (async (...params) => {
|
|
482
|
+
const serverCtx = params.length === 1 ? params[0] : params[1];
|
|
483
|
+
const ctx = toTaskToolContext(asTaskRequestContext(serverCtx));
|
|
484
|
+
const taskStore = getTaskStore(ctx);
|
|
485
|
+
const taskId = getTaskId(ctx);
|
|
481
486
|
const task = await taskStore.getTask(taskId);
|
|
482
487
|
return projectCancelledTaskStatus(taskStore, normalizeGetTaskResult(task));
|
|
483
488
|
});
|
|
484
|
-
const getTaskResult = (async (
|
|
485
|
-
const
|
|
486
|
-
const
|
|
487
|
-
const
|
|
489
|
+
const getTaskResult = (async (...params) => {
|
|
490
|
+
const serverCtx = params.length === 1 ? params[0] : params[1];
|
|
491
|
+
const ctx = toTaskToolContext(asTaskRequestContext(serverCtx));
|
|
492
|
+
const taskStore = getTaskStore(ctx);
|
|
493
|
+
const taskId = getTaskId(ctx);
|
|
488
494
|
const result = await taskStore.getTaskResult(taskId);
|
|
489
495
|
return attachRelatedTaskMeta(normalizeCallToolResult(result), taskId);
|
|
490
496
|
});
|
package/dist/tools/tree.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { McpServer } from '@modelcontextprotocol/
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/server';
|
|
2
2
|
import { type ToolContract, type ToolRegistrationOptions } from './shared.js';
|
|
3
3
|
export declare const TREE_TOOL: ToolContract;
|
|
4
4
|
export declare function registerTreeTool(server: McpServer, options?: ToolRegistrationOptions): void;
|
package/dist/tools/tree.js
CHANGED
|
@@ -3,8 +3,9 @@ import { DEFAULT_SEARCH_TIMEOUT_MS } from '../lib/constants.js';
|
|
|
3
3
|
import { 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
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { DIRECTORY_ICONS } from './icons.js';
|
|
7
|
+
import { buildToolErrorResponse, buildToolResponse, createProgressReporter, executeToolWithDiagnostics, notifyProgress, READ_ONLY_TOOL_ANNOTATIONS, resolvePathOrRoot, } from './shared.js';
|
|
8
|
+
import { registerStandardTool } from './task-support.js';
|
|
8
9
|
export const TREE_TOOL = {
|
|
9
10
|
name: 'tree',
|
|
10
11
|
title: 'Tree',
|
|
@@ -13,6 +14,7 @@ export const TREE_TOOL = {
|
|
|
13
14
|
inputSchema: TreeInputSchema,
|
|
14
15
|
outputSchema: TreeOutputSchema,
|
|
15
16
|
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
|
17
|
+
icons: DIRECTORY_ICONS,
|
|
16
18
|
taskSupport: 'optional',
|
|
17
19
|
};
|
|
18
20
|
async function handleTree(args, signal, onProgress) {
|
|
@@ -39,11 +41,11 @@ async function handleTree(args, signal, onProgress) {
|
|
|
39
41
|
return buildToolResponse(text, structured);
|
|
40
42
|
}
|
|
41
43
|
export function registerTreeTool(server, options = {}) {
|
|
42
|
-
const handler = (args,
|
|
44
|
+
const handler = (args, ctx) => {
|
|
43
45
|
const targetPath = args.path ?? '.';
|
|
44
46
|
return executeToolWithDiagnostics({
|
|
45
47
|
toolName: 'tree',
|
|
46
|
-
|
|
48
|
+
ctx,
|
|
47
49
|
outputSchema: TreeOutputSchema,
|
|
48
50
|
timedSignal: { timeoutMs: DEFAULT_SEARCH_TIMEOUT_MS },
|
|
49
51
|
context: { path: targetPath },
|
|
@@ -51,12 +53,12 @@ export function registerTreeTool(server, options = {}) {
|
|
|
51
53
|
const context = args.path ? basename(args.path) : '.';
|
|
52
54
|
let progressCursor = 0;
|
|
53
55
|
const knownTotal = args.maxEntries;
|
|
54
|
-
notifyProgress(
|
|
56
|
+
notifyProgress(ctx, {
|
|
55
57
|
current: 0,
|
|
56
58
|
total: knownTotal,
|
|
57
59
|
message: `≣ tree: ${context}`,
|
|
58
60
|
});
|
|
59
|
-
const baseReporter = createProgressReporter(
|
|
61
|
+
const baseReporter = createProgressReporter(ctx);
|
|
60
62
|
const onProgress = (progress) => {
|
|
61
63
|
const { current } = progress;
|
|
62
64
|
if (current > progressCursor)
|
|
@@ -76,7 +78,7 @@ export function registerTreeTool(server, options = {}) {
|
|
|
76
78
|
if (truncated)
|
|
77
79
|
suffix += ' [truncated]';
|
|
78
80
|
const finalCurrent = Math.max(count, progressCursor + 1);
|
|
79
|
-
notifyProgress(
|
|
81
|
+
notifyProgress(ctx, {
|
|
80
82
|
current: finalCurrent,
|
|
81
83
|
total: finalCurrent,
|
|
82
84
|
message: `≣ tree: ${context} • ${suffix}`,
|
|
@@ -85,7 +87,7 @@ export function registerTreeTool(server, options = {}) {
|
|
|
85
87
|
}
|
|
86
88
|
catch (error) {
|
|
87
89
|
const finalCurrent = Math.max(progressCursor + 1, 1);
|
|
88
|
-
notifyProgress(
|
|
90
|
+
notifyProgress(ctx, {
|
|
89
91
|
current: finalCurrent,
|
|
90
92
|
total: finalCurrent,
|
|
91
93
|
message: `≣ tree: ${context} • failed`,
|
|
@@ -96,11 +98,5 @@ export function registerTreeTool(server, options = {}) {
|
|
|
96
98
|
onError: (error) => buildToolErrorResponse(error, ErrorCode.NOT_DIRECTORY, targetPath),
|
|
97
99
|
});
|
|
98
100
|
};
|
|
99
|
-
|
|
100
|
-
guard: options.isInitialized,
|
|
101
|
-
});
|
|
102
|
-
const validatedHandler = withValidatedArgs(TreeInputSchema, wrappedHandler);
|
|
103
|
-
if (registerToolTaskIfAvailable(server, 'tree', TREE_TOOL, validatedHandler, options.iconInfo, options.isInitialized))
|
|
104
|
-
return;
|
|
105
|
-
server.registerTool('tree', withDefaultIcons({ ...TREE_TOOL }, options.iconInfo), validatedHandler);
|
|
101
|
+
registerStandardTool(server, TREE_TOOL, handler, options);
|
|
106
102
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { McpServer } from '@modelcontextprotocol/
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/server';
|
|
2
2
|
import { type ToolContract, type ToolRegistrationOptions } from './shared.js';
|
|
3
3
|
export declare const WRITE_FILE_TOOL: ToolContract;
|
|
4
4
|
export declare function registerWriteFileTool(server: McpServer, options?: ToolRegistrationOptions): void;
|
package/dist/tools/write-file.js
CHANGED
|
@@ -7,8 +7,9 @@ import { Logger } from '../lib/logger.js';
|
|
|
7
7
|
import { validatePathForWrite } from '../lib/paths.js';
|
|
8
8
|
import { formatBytes } from '../config.js';
|
|
9
9
|
import { WriteFileInputSchema, WriteFileOutputSchema } from '../schemas.js';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
10
|
+
import { FILE_EDIT_ICONS } from './icons.js';
|
|
11
|
+
import { buildToolErrorResponse, buildToolResponse, DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS, executeToolWithDiagnostics, } from './shared.js';
|
|
12
|
+
import { registerStandardTool } from './task-support.js';
|
|
12
13
|
export const WRITE_FILE_TOOL = {
|
|
13
14
|
name: 'write',
|
|
14
15
|
title: 'Write File',
|
|
@@ -16,6 +17,7 @@ export const WRITE_FILE_TOOL = {
|
|
|
16
17
|
inputSchema: WriteFileInputSchema,
|
|
17
18
|
outputSchema: WriteFileOutputSchema,
|
|
18
19
|
annotations: DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS,
|
|
20
|
+
icons: FILE_EDIT_ICONS,
|
|
19
21
|
taskSupport: 'forbidden',
|
|
20
22
|
};
|
|
21
23
|
async function handleWriteFile(args, signal) {
|
|
@@ -32,17 +34,20 @@ async function handleWriteFile(args, signal) {
|
|
|
32
34
|
});
|
|
33
35
|
}
|
|
34
36
|
export function registerWriteFileTool(server, options = {}) {
|
|
35
|
-
const handler = (args,
|
|
37
|
+
const handler = (args, ctx) => executeToolWithDiagnostics({
|
|
36
38
|
toolName: 'write',
|
|
37
|
-
|
|
39
|
+
ctx,
|
|
38
40
|
outputSchema: WriteFileOutputSchema,
|
|
39
41
|
timedSignal: {},
|
|
40
42
|
context: { path: args.path },
|
|
41
|
-
run: (signal) =>
|
|
43
|
+
run: async (signal) => {
|
|
44
|
+
const result = await handleWriteFile(args, signal);
|
|
45
|
+
void ctx.log?.('info', `write: ${args.path} (${String(result.structuredContent.bytesWritten ?? 0)} bytes)`);
|
|
46
|
+
return result;
|
|
47
|
+
},
|
|
42
48
|
onError: (error) => buildToolErrorResponse(error, ErrorCode.UNKNOWN, args.path),
|
|
43
49
|
});
|
|
44
|
-
|
|
45
|
-
guard: options.isInitialized,
|
|
50
|
+
registerStandardTool(server, WRITE_FILE_TOOL, handler, options, {
|
|
46
51
|
progressMessage: (args) => `🛠 write: ${basename(args.path)}`,
|
|
47
52
|
completionMessage: (args, result) => {
|
|
48
53
|
const name = basename(args.path);
|
|
@@ -52,8 +57,4 @@ export function registerWriteFileTool(server, options = {}) {
|
|
|
52
57
|
return `🛠 write: ${name} • ${formatBytes(sc.bytesWritten ?? 0)}`;
|
|
53
58
|
},
|
|
54
59
|
});
|
|
55
|
-
const validatedHandler = withValidatedArgs(WriteFileInputSchema, wrappedHandler);
|
|
56
|
-
if (registerToolTaskIfAvailable(server, 'write', WRITE_FILE_TOOL, validatedHandler, options.iconInfo, options.isInitialized))
|
|
57
|
-
return;
|
|
58
|
-
server.registerTool('write', withDefaultIcons({ ...WRITE_FILE_TOOL }, options.iconInfo), validatedHandler);
|
|
59
60
|
}
|
package/dist/tools.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { McpServer } from '@modelcontextprotocol/
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/server';
|
|
2
2
|
import { type ToolContract } from './tools/contract.js';
|
|
3
3
|
import type { ToolRegistrationOptions } from './tools/shared.js';
|
|
4
4
|
export declare const ALL_TOOLS: ToolContract[];
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@j0hanz/filesystem-mcp",
|
|
3
|
-
"version": "1.16.
|
|
3
|
+
"version": "1.16.3",
|
|
4
4
|
"mcpName": "io.github.j0hanz/filesystem-mcp",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Secure filesystem MCP server for reading, writing, searching, diffing, and patching files.",
|
|
6
6
|
"author": "j0hanz",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"repository": {
|
|
@@ -68,14 +68,16 @@
|
|
|
68
68
|
"security",
|
|
69
69
|
"server",
|
|
70
70
|
"stdio",
|
|
71
|
-
"sse",
|
|
72
71
|
"typescript",
|
|
73
72
|
"nodejs",
|
|
74
73
|
"llm",
|
|
75
74
|
"cli"
|
|
76
75
|
],
|
|
77
76
|
"dependencies": {
|
|
78
|
-
"@
|
|
77
|
+
"@cfworker/json-schema": "^4.1.1",
|
|
78
|
+
"@modelcontextprotocol/client": "^2.0.0-alpha.2",
|
|
79
|
+
"@modelcontextprotocol/node": "^2.0.0-alpha.2",
|
|
80
|
+
"@modelcontextprotocol/server": "^2.0.0-alpha.2",
|
|
79
81
|
"commander": "^14.0.3",
|
|
80
82
|
"diff": "^8.0.4",
|
|
81
83
|
"ignore": "^7.0.5",
|