@j0hanz/filesystem-mcp 1.17.0 → 1.18.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/completions.js +83 -16
- package/dist/lib/constants.d.ts +4 -2
- package/dist/lib/constants.js +6 -2
- package/dist/lib/errors.d.ts +1 -0
- package/dist/lib/errors.js +1 -1
- package/dist/lib/fs-helpers.d.ts +2 -1
- package/dist/lib/fs-helpers.js +39 -22
- package/dist/prompts.js +20 -0
- package/dist/server/bootstrap.js +24 -40
- package/dist/server/event-store.d.ts +18 -0
- package/dist/server/event-store.js +71 -0
- package/dist/server/roots-manager.js +5 -4
- package/dist/server/task-store.d.ts +1 -0
- package/dist/server/task-store.js +23 -5
- package/dist/tools/apply-patch.js +15 -8
- 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 +13 -8
- 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 +4 -7
- 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 +83 -113
- package/dist/tools/tree.js +5 -5
- package/dist/tools/write-file.js +3 -3
- package/package.json +1 -1
package/dist/tools/shared.js
CHANGED
|
@@ -3,7 +3,7 @@ import { basename } from 'node:path';
|
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import { createTimedAbortSignal } from '../lib/abort.js';
|
|
5
5
|
import { parseTrueEnvFlag } from '../lib/constants.js';
|
|
6
|
-
import { createDetailedError, ErrorCode, formatDetailedError, getSuggestion, McpError, } from '../lib/errors.js';
|
|
6
|
+
import { classifyError, createDetailedError, ErrorCode, formatDetailedError, getSuggestion, McpError, } from '../lib/errors.js';
|
|
7
7
|
import { Logger } from '../lib/logger.js';
|
|
8
8
|
import { withToolDiagnostics, } from '../lib/observability.js';
|
|
9
9
|
import { getAllowedDirectories } from '../lib/paths.js';
|
|
@@ -223,20 +223,18 @@ export function withValidatedArgs(schema, handler) {
|
|
|
223
223
|
}
|
|
224
224
|
};
|
|
225
225
|
}
|
|
226
|
-
function toToolContext(ctx) {
|
|
226
|
+
export function toToolContext(ctx) {
|
|
227
227
|
if (!ctx)
|
|
228
228
|
return {};
|
|
229
229
|
if ('mcpReq' in ctx) {
|
|
230
230
|
return {
|
|
231
231
|
signal: ctx.mcpReq.signal,
|
|
232
|
+
...(ctx.sessionId ? { sessionId: ctx.sessionId } : {}),
|
|
232
233
|
...(ctx.mcpReq._meta
|
|
233
234
|
? { _meta: ctx.mcpReq._meta }
|
|
234
235
|
: {}),
|
|
235
236
|
sendNotification: async (notification) => ctx.mcpReq.notify(notification),
|
|
236
|
-
log: async (level, data, logger) => ctx.mcpReq.
|
|
237
|
-
method: 'notifications/message',
|
|
238
|
-
params: { level, data, ...(logger ? { logger } : {}) },
|
|
239
|
-
}),
|
|
237
|
+
log: async (level, data, logger) => ctx.mcpReq.log(level, data, logger),
|
|
240
238
|
};
|
|
241
239
|
}
|
|
242
240
|
return ctx;
|
|
@@ -509,7 +507,7 @@ async function withProgress(message, ctx, run, getCompletionMessage) {
|
|
|
509
507
|
void reportProgress(ctx, {
|
|
510
508
|
current: total,
|
|
511
509
|
total,
|
|
512
|
-
message: `${message} •
|
|
510
|
+
message: `${message} • ${classifyError(error)}`,
|
|
513
511
|
});
|
|
514
512
|
throw error;
|
|
515
513
|
}
|
|
@@ -599,13 +597,3 @@ export function truncateProgressPattern(pattern, maxLength = 40) {
|
|
|
599
597
|
}
|
|
600
598
|
return `${pattern.slice(0, maxLength)}…`;
|
|
601
599
|
}
|
|
602
|
-
export function buildBatchCompletionSuffix(summary, successWord, singularWord) {
|
|
603
|
-
const total = summary?.total ?? 0;
|
|
604
|
-
const failed = summary?.failed ?? 0;
|
|
605
|
-
const succeeded = summary?.succeeded ?? 0;
|
|
606
|
-
if (failed) {
|
|
607
|
-
return `${succeeded}/${total} ${successWord}, ${failed} failed`;
|
|
608
|
-
}
|
|
609
|
-
const word = total === 1 && singularWord ? singularWord : successWord;
|
|
610
|
-
return `${total} ${word}`;
|
|
611
|
-
}
|
package/dist/tools/stat-many.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { DEFAULT_SEARCH_TIMEOUT_MS } from '../lib/constants.js';
|
|
2
|
-
import { ErrorCode } from '../lib/errors.js';
|
|
2
|
+
import { classifyError, ErrorCode } from '../lib/errors.js';
|
|
3
3
|
import { getMultipleFileInfo } from '../lib/file-operations/metadata.js';
|
|
4
4
|
import { formatBytes, joinLines } from '../config.js';
|
|
5
5
|
import { GetMultipleFileInfoInputSchema, GetMultipleFileInfoOutputSchema, } from '../schemas.js';
|
|
6
6
|
import { FILE_READ_ICONS } from './icons.js';
|
|
7
|
-
import {
|
|
7
|
+
import { buildBatchPathContext, buildFileInfoPayload, buildStructuredError, buildToolErrorResponse, buildToolResponse, createBatchProgressCallbacks, executeToolWithDiagnostics, READ_ONLY_TOOL_ANNOTATIONS, resolveFinalProgressCurrent, } from './shared.js';
|
|
8
8
|
import { registerStandardTool } from './task-support.js';
|
|
9
9
|
export const GET_MULTIPLE_FILE_INFO_TOOL = {
|
|
10
10
|
name: 'stat_many',
|
|
@@ -77,7 +77,7 @@ export function registerGetMultipleFileInfoTool(server, options = {}) {
|
|
|
77
77
|
run: async (signal) => {
|
|
78
78
|
const context = buildBatchPathContext(args.paths);
|
|
79
79
|
const { progress, onItemComplete } = createBatchProgressCallbacks(ctx, {
|
|
80
|
-
toolLabel:
|
|
80
|
+
toolLabel: GET_MULTIPLE_FILE_INFO_TOOL.title,
|
|
81
81
|
context,
|
|
82
82
|
totalItems: args.paths.length,
|
|
83
83
|
itemVerb: 'done',
|
|
@@ -85,14 +85,15 @@ export function registerGetMultipleFileInfoTool(server, options = {}) {
|
|
|
85
85
|
try {
|
|
86
86
|
const result = await handleGetMultipleFileInfo(args, signal, onItemComplete);
|
|
87
87
|
const sc = result.structuredContent;
|
|
88
|
-
const suffix = buildBatchCompletionSuffix(sc.summary, 'OK');
|
|
89
88
|
const total = sc.summary?.total ?? 0;
|
|
89
|
+
const failed = sc.summary?.failed ?? 0;
|
|
90
|
+
const suffix = failed ? `${failed} failed` : 'done';
|
|
90
91
|
const finalCurrent = resolveFinalProgressCurrent(progress, total);
|
|
91
|
-
progress.complete(
|
|
92
|
+
progress.complete(`${GET_MULTIPLE_FILE_INFO_TOOL.title}: ${context} • ${suffix}`, finalCurrent);
|
|
92
93
|
return result;
|
|
93
94
|
}
|
|
94
95
|
catch (error) {
|
|
95
|
-
progress.fail(
|
|
96
|
+
progress.fail(`${GET_MULTIPLE_FILE_INFO_TOOL.title}: ${context} • ${classifyError(error)}`);
|
|
96
97
|
throw error;
|
|
97
98
|
}
|
|
98
99
|
},
|
package/dist/tools/stat.js
CHANGED
|
@@ -53,15 +53,15 @@ export function registerGetFileInfoTool(server, options = {}) {
|
|
|
53
53
|
onError: (error) => buildToolErrorResponse(error, ErrorCode.NOT_FOUND, args.path),
|
|
54
54
|
});
|
|
55
55
|
registerStandardTool(server, GET_FILE_INFO_TOOL, handler, options, {
|
|
56
|
-
progressMessage: (args) =>
|
|
56
|
+
progressMessage: (args) => `${GET_FILE_INFO_TOOL.title}: ${basename(args.path)}`,
|
|
57
57
|
completionMessage: (args, result) => {
|
|
58
58
|
const name = basename(args.path);
|
|
59
59
|
if (result.isError)
|
|
60
|
-
return
|
|
60
|
+
return `${GET_FILE_INFO_TOOL.title}: ${name} • ${result.errorCode}`;
|
|
61
61
|
const sc = result.structuredContent;
|
|
62
62
|
if (!sc.info)
|
|
63
|
-
return
|
|
64
|
-
return
|
|
63
|
+
return `${GET_FILE_INFO_TOOL.title}: ${name} • failed`;
|
|
64
|
+
return `${GET_FILE_INFO_TOOL.title}: ${sc.info.name} • ${formatBytes(sc.info.size)}`;
|
|
65
65
|
},
|
|
66
66
|
});
|
|
67
67
|
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { type McpServer, type RequestTaskStore, type StandardSchemaWithJSON, type ToolTaskHandler } from '@modelcontextprotocol/server';
|
|
2
2
|
import { type IconInfo, type ToolContext, type ToolContract, type ToolRegistrationOptions, type ToolResult } from './shared.js';
|
|
3
|
+
/**
|
|
4
|
+
* Report an intermediate 'working' status update for the current task.
|
|
5
|
+
* No-op when called outside of a task context.
|
|
6
|
+
*/
|
|
7
|
+
export declare function reportTaskStatus(statusMessage: string): Promise<void>;
|
|
3
8
|
type TaskToolContext = ToolContext & {
|
|
4
9
|
taskId?: string;
|
|
5
10
|
taskStore?: RequestTaskStore;
|
|
@@ -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,19 +214,26 @@ function withoutStructuredContent(result) {
|
|
|
257
214
|
delete stripped['structuredContent'];
|
|
258
215
|
return stripped;
|
|
259
216
|
}
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
217
|
+
const taskCreationLocks = new WeakMap();
|
|
218
|
+
async function acquireTaskCreationLock(taskStore) {
|
|
219
|
+
const previous = taskCreationLocks.get(taskStore) ?? Promise.resolve();
|
|
220
|
+
let release;
|
|
221
|
+
const next = new Promise((resolve) => {
|
|
222
|
+
release = resolve;
|
|
223
|
+
});
|
|
224
|
+
taskCreationLocks.set(taskStore, previous.catch(() => { }).then(() => next));
|
|
225
|
+
await previous.catch(() => { });
|
|
226
|
+
return () => {
|
|
227
|
+
release();
|
|
228
|
+
};
|
|
229
|
+
}
|
|
266
230
|
async function isTaskAlreadyTerminal(taskStore, taskId) {
|
|
267
231
|
try {
|
|
268
232
|
const task = await taskStore.getTask(taskId);
|
|
269
233
|
if (!isRecord(task))
|
|
270
234
|
return false;
|
|
271
235
|
const { status } = task;
|
|
272
|
-
return typeof status === 'string' &&
|
|
236
|
+
return typeof status === 'string' && isTerminal(status);
|
|
273
237
|
}
|
|
274
238
|
catch {
|
|
275
239
|
return false;
|
|
@@ -288,7 +252,7 @@ async function countActiveTasks(taskStore) {
|
|
|
288
252
|
for (const task of tasks) {
|
|
289
253
|
if (!isRecord(task) || typeof task.status !== 'string')
|
|
290
254
|
continue;
|
|
291
|
-
if (!
|
|
255
|
+
if (!isTerminal(task.status)) {
|
|
292
256
|
active += 1;
|
|
293
257
|
}
|
|
294
258
|
}
|
|
@@ -344,7 +308,7 @@ async function runTaskInBackground(run, args, ctx, taskStore, taskId, toolName,
|
|
|
344
308
|
let taskStatuses;
|
|
345
309
|
let result;
|
|
346
310
|
try {
|
|
347
|
-
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));
|
|
348
312
|
taskStatuses = resolveTaskResultStatuses(rawResult);
|
|
349
313
|
result = isErrorResult(rawResult)
|
|
350
314
|
? withoutStructuredContent(rawResult)
|
|
@@ -379,10 +343,9 @@ async function runTaskInBackground(run, args, ctx, taskStore, taskId, toolName,
|
|
|
379
343
|
lastUpdatedAt: new Date().toISOString(),
|
|
380
344
|
statusMessage: 'Internal system error while storing result',
|
|
381
345
|
};
|
|
382
|
-
|
|
383
|
-
if (typeof sendNotification === 'function') {
|
|
346
|
+
if (ctx.sendNotification) {
|
|
384
347
|
try {
|
|
385
|
-
await sendNotification({
|
|
348
|
+
await ctx.sendNotification({
|
|
386
349
|
method: TASK_STATUS_NOTIFICATION_METHOD,
|
|
387
350
|
params: buildTaskStatusNotificationParams(syntheticTask),
|
|
388
351
|
});
|
|
@@ -438,18 +401,26 @@ export function createToolTaskHandler(run, options) {
|
|
|
438
401
|
else {
|
|
439
402
|
[args, serverCtx] = params;
|
|
440
403
|
}
|
|
441
|
-
|
|
404
|
+
assertCreateTaskContext(serverCtx);
|
|
405
|
+
const ctx = toTaskToolContext(serverCtx);
|
|
442
406
|
if (options?.guard && !options.guard()) {
|
|
443
407
|
throw new McpError(ErrorCode.INVALID_INPUT, 'Client not initialized; wait for notifications/initialized');
|
|
444
408
|
}
|
|
445
409
|
const taskStore = getTaskStore(ctx);
|
|
446
|
-
|
|
447
|
-
|
|
410
|
+
const releaseCreationLock = await acquireTaskCreationLock(taskStore);
|
|
411
|
+
let task;
|
|
412
|
+
try {
|
|
413
|
+
if ((await countActiveTasks(taskStore)) >= MAX_CONCURRENT_TASKS) {
|
|
414
|
+
throw new McpError(ErrorCode.INVALID_INPUT, `Too many active tasks (limit: ${String(MAX_CONCURRENT_TASKS)}).`);
|
|
415
|
+
}
|
|
416
|
+
task = await taskStore.createTask({
|
|
417
|
+
ttl: resolveRequestedTaskTtl(ctx.taskRequestedTtl),
|
|
418
|
+
pollInterval: options?.pollIntervalMs ?? TASK_POLL_INTERVAL_MS,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
finally {
|
|
422
|
+
releaseCreationLock();
|
|
448
423
|
}
|
|
449
|
-
const task = await taskStore.createTask({
|
|
450
|
-
ttl: resolveRequestedTaskTtl(ctx.taskRequestedTtl),
|
|
451
|
-
pollInterval: options?.pollIntervalMs ?? TASK_POLL_INTERVAL_MS,
|
|
452
|
-
});
|
|
453
424
|
const toolLabel = options?.toolName ?? 'tool';
|
|
454
425
|
try {
|
|
455
426
|
await taskStore.updateTaskStatus(task.taskId, 'working', `${toolLabel}: starting`);
|
|
@@ -473,26 +444,25 @@ export function createToolTaskHandler(run, options) {
|
|
|
473
444
|
void runTaskInBackground(run, args, taskExtra, taskStore, task.taskId, options?.toolName, options?.cancelPollMs);
|
|
474
445
|
return {
|
|
475
446
|
task,
|
|
476
|
-
_meta: {
|
|
477
|
-
'io.modelcontextprotocol/model-immediate-response': `${toolLabel} task created — poll tasks/get for progress.`,
|
|
478
|
-
},
|
|
479
447
|
};
|
|
480
448
|
});
|
|
481
449
|
const getTask = (async (...params) => {
|
|
482
450
|
const serverCtx = params.length === 1 ? params[0] : params[1];
|
|
483
|
-
|
|
451
|
+
assertTaskRequestContext(serverCtx);
|
|
452
|
+
const ctx = toTaskToolContext(serverCtx);
|
|
484
453
|
const taskStore = getTaskStore(ctx);
|
|
485
454
|
const taskId = getTaskId(ctx);
|
|
486
455
|
const task = await taskStore.getTask(taskId);
|
|
487
|
-
return projectCancelledTaskStatus(taskStore,
|
|
456
|
+
return projectCancelledTaskStatus(taskStore, toGetTaskResult(task));
|
|
488
457
|
});
|
|
489
458
|
const getTaskResult = (async (...params) => {
|
|
490
459
|
const serverCtx = params.length === 1 ? params[0] : params[1];
|
|
491
|
-
|
|
460
|
+
assertTaskRequestContext(serverCtx);
|
|
461
|
+
const ctx = toTaskToolContext(serverCtx);
|
|
492
462
|
const taskStore = getTaskStore(ctx);
|
|
493
463
|
const taskId = getTaskId(ctx);
|
|
494
464
|
const result = await taskStore.getTaskResult(taskId);
|
|
495
|
-
return attachRelatedTaskMeta(
|
|
465
|
+
return attachRelatedTaskMeta(toCallToolResult(result), taskId);
|
|
496
466
|
});
|
|
497
467
|
return {
|
|
498
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