@j0hanz/filesystem-mcp 1.14.0 → 1.14.1
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 +47 -47
- package/dist/cli.js +2 -2
- package/dist/completions.js +54 -51
- package/dist/config.d.ts +13 -13
- package/dist/config.js +12 -12
- package/dist/index.js +1 -1
- package/dist/lib/abort.d.ts +7 -0
- package/dist/lib/abort.js +81 -0
- package/dist/lib/constants.d.ts +3 -1
- package/dist/lib/constants.js +8 -2
- package/dist/lib/errors.d.ts +7 -3
- package/dist/lib/errors.js +59 -39
- package/dist/lib/file-operations/core.d.ts +3 -3
- package/dist/lib/file-operations/core.js +23 -20
- package/dist/lib/file-operations/metadata.d.ts +2 -2
- package/dist/lib/file-operations/metadata.js +60 -19
- package/dist/lib/file-operations/search.js +83 -84
- package/dist/lib/file-operations/traversal.js +13 -15
- package/dist/lib/fs-helpers.d.ts +3 -10
- package/dist/lib/fs-helpers.js +20 -98
- package/dist/lib/globs.js +1 -1
- package/dist/lib/logger.d.ts +28 -0
- package/dist/lib/logger.js +91 -0
- package/dist/lib/observability.d.ts +7 -0
- package/dist/lib/observability.js +19 -9
- package/dist/lib/paths.js +55 -55
- package/dist/lib/resource-store.js +4 -4
- package/dist/lib/utils.d.ts +0 -12
- package/dist/lib/utils.js +0 -13
- package/dist/resources/generated-instructions.js +40 -31
- package/dist/resources/tool-catalog.js +32 -26
- package/dist/resources/tool-info.js +34 -29
- package/dist/resources/workflows.js +39 -18
- package/dist/resources.d.ts +1 -1
- package/dist/resources.js +4 -4
- package/dist/schemas.d.ts +66 -66
- package/dist/schemas.js +21 -44
- package/dist/server/bootstrap.d.ts +12 -11
- package/dist/server/bootstrap.js +95 -86
- package/dist/server/roots-manager.d.ts +5 -2
- package/dist/server/roots-manager.js +8 -6
- package/dist/server/task-store.d.ts +10 -0
- package/dist/server/task-store.js +73 -0
- package/dist/tools/apply-patch.js +26 -18
- package/dist/tools/calculate-hash.js +11 -22
- package/dist/tools/create-directory.js +10 -8
- package/dist/tools/delete-file.js +17 -15
- package/dist/tools/diff-files.js +15 -15
- package/dist/tools/edit-file.js +7 -4
- package/dist/tools/list-directory.js +6 -6
- package/dist/tools/move-file.js +98 -81
- package/dist/tools/read-multiple.js +4 -4
- package/dist/tools/read.js +5 -5
- package/dist/tools/replace-in-files.js +20 -23
- package/dist/tools/roots.js +1 -1
- package/dist/tools/search-content.js +32 -41
- package/dist/tools/search-files.js +54 -41
- package/dist/tools/shared.d.ts +3 -0
- package/dist/tools/shared.js +70 -28
- package/dist/tools/stat-many.js +11 -7
- package/dist/tools/stat.js +4 -4
- package/dist/tools/task-support.d.ts +10 -9
- package/dist/tools/task-support.js +94 -23
- package/dist/tools/tree.js +3 -3
- package/dist/tools/write-file.js +10 -7
- package/package.json +9 -9
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { basename, relative } from 'node:path';
|
|
2
2
|
import { DEFAULT_EXCLUDE_PATTERNS, DEFAULT_SEARCH_TIMEOUT_MS, } from '../lib/constants.js';
|
|
3
3
|
import { ErrorCode } from '../lib/errors.js';
|
|
4
4
|
import { searchFiles } from '../lib/file-operations/search.js';
|
|
@@ -17,6 +17,42 @@ export const SEARCH_FILES_TOOL = {
|
|
|
17
17
|
nuances: ['Respects `.gitignore` unless `includeIgnored=true`.'],
|
|
18
18
|
taskSupport: 'optional',
|
|
19
19
|
};
|
|
20
|
+
function buildTruncatedReason(summary) {
|
|
21
|
+
if (!summary.truncated)
|
|
22
|
+
return undefined;
|
|
23
|
+
if (summary.stoppedReason === 'timeout')
|
|
24
|
+
return 'timeout';
|
|
25
|
+
if (summary.stoppedReason === 'maxFiles')
|
|
26
|
+
return `max files (${summary.filesScanned})`;
|
|
27
|
+
return `max results (${summary.matched})`;
|
|
28
|
+
}
|
|
29
|
+
function buildRelativeResults(basePath, displayResults) {
|
|
30
|
+
const relativeResults = [];
|
|
31
|
+
for (const entry of displayResults) {
|
|
32
|
+
relativeResults.push({
|
|
33
|
+
path: relative(basePath, entry.path),
|
|
34
|
+
size: entry.size,
|
|
35
|
+
modified: entry.modified?.toISOString(),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return relativeResults;
|
|
39
|
+
}
|
|
40
|
+
function computeNextCursor(summary, displayResultsCount, cursorOffset) {
|
|
41
|
+
if (summary.truncated && displayResultsCount > 0) {
|
|
42
|
+
return encodeOffsetCursor(cursorOffset + displayResultsCount);
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
function applySummaryFields(structured, summary, nextCursor) {
|
|
47
|
+
if (summary.truncated)
|
|
48
|
+
structured.truncated = true;
|
|
49
|
+
if (summary.skippedInaccessible)
|
|
50
|
+
structured.skippedInaccessible = summary.skippedInaccessible;
|
|
51
|
+
if (summary.stoppedReason)
|
|
52
|
+
structured.stoppedReason = summary.stoppedReason;
|
|
53
|
+
if (nextCursor !== undefined)
|
|
54
|
+
structured.nextCursor = nextCursor;
|
|
55
|
+
}
|
|
20
56
|
async function handleSearchFiles(args, signal, onProgress) {
|
|
21
57
|
const basePath = resolvePathOrRoot(args.path);
|
|
22
58
|
const excludePatterns = args.includeIgnored ? [] : DEFAULT_EXCLUDE_PATTERNS;
|
|
@@ -28,57 +64,34 @@ async function handleSearchFiles(args, signal, onProgress) {
|
|
|
28
64
|
includeHidden: args.includeHidden,
|
|
29
65
|
sortBy: args.sortBy,
|
|
30
66
|
respectGitignore: !args.includeIgnored,
|
|
31
|
-
...(args.maxDepth !== undefined ? { maxDepth: args.maxDepth } : {}),
|
|
32
|
-
...(onProgress ? { onProgress } : {}),
|
|
33
|
-
...(signal ? { signal } : {}),
|
|
34
67
|
};
|
|
68
|
+
if (args.maxDepth !== undefined)
|
|
69
|
+
searchOptions.maxDepth = args.maxDepth;
|
|
70
|
+
if (onProgress)
|
|
71
|
+
searchOptions.onProgress = onProgress;
|
|
72
|
+
if (signal)
|
|
73
|
+
searchOptions.signal = signal;
|
|
35
74
|
const result = await searchFiles(basePath, args.pattern, excludePatterns, searchOptions);
|
|
36
75
|
const allResults = result.results;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const relativeResults =
|
|
42
|
-
for (const entry of displayResults) {
|
|
43
|
-
relativeResults.push({
|
|
44
|
-
path: path.relative(result.basePath, entry.path),
|
|
45
|
-
size: entry.size,
|
|
46
|
-
modified: entry.modified?.toISOString(),
|
|
47
|
-
});
|
|
48
|
-
}
|
|
76
|
+
let displayResults = allResults;
|
|
77
|
+
if (cursorOffset > 0)
|
|
78
|
+
displayResults = allResults.slice(cursorOffset);
|
|
79
|
+
const nextCursor = computeNextCursor(result.summary, displayResults.length, cursorOffset);
|
|
80
|
+
const relativeResults = buildRelativeResults(result.basePath, displayResults);
|
|
49
81
|
const structured = {
|
|
50
82
|
ok: true,
|
|
51
83
|
root: basePath,
|
|
52
84
|
results: relativeResults,
|
|
53
85
|
totalMatches: result.summary.matched,
|
|
54
86
|
filesScanned: result.summary.filesScanned,
|
|
55
|
-
...(result.summary.truncated
|
|
56
|
-
? { truncated: result.summary.truncated }
|
|
57
|
-
: {}),
|
|
58
|
-
...(result.summary.skippedInaccessible
|
|
59
|
-
? { skippedInaccessible: result.summary.skippedInaccessible }
|
|
60
|
-
: {}),
|
|
61
|
-
...(result.summary.stoppedReason
|
|
62
|
-
? { stoppedReason: result.summary.stoppedReason }
|
|
63
|
-
: {}),
|
|
64
|
-
...(nextCursor !== undefined ? { nextCursor } : {}),
|
|
65
87
|
};
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (result.summary.stoppedReason === 'timeout') {
|
|
69
|
-
truncatedReason = 'timeout';
|
|
70
|
-
}
|
|
71
|
-
else if (result.summary.stoppedReason === 'maxFiles') {
|
|
72
|
-
truncatedReason = `max files (${result.summary.filesScanned})`;
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
75
|
-
truncatedReason = `max results (${result.summary.matched})`;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
88
|
+
applySummaryFields(structured, result.summary, nextCursor);
|
|
89
|
+
const truncatedReason = buildTruncatedReason(result.summary);
|
|
78
90
|
const summaryOptions = {
|
|
79
91
|
truncated: result.summary.truncated,
|
|
80
|
-
...(truncatedReason ? { truncatedReason } : {}),
|
|
81
92
|
};
|
|
93
|
+
if (truncatedReason)
|
|
94
|
+
summaryOptions.truncatedReason = truncatedReason;
|
|
82
95
|
const textLines = [];
|
|
83
96
|
if (relativeResults.length === 0) {
|
|
84
97
|
textLines.push('No matches');
|
|
@@ -103,7 +116,7 @@ export function registerSearchFilesTool(server, options = {}) {
|
|
|
103
116
|
timedSignal: { timeoutMs: DEFAULT_SEARCH_TIMEOUT_MS },
|
|
104
117
|
context: { path: args.path ?? '.' },
|
|
105
118
|
run: async (signal) => {
|
|
106
|
-
const rawScopeLabel = args.path ?
|
|
119
|
+
const rawScopeLabel = args.path ? basename(args.path) : '.';
|
|
107
120
|
const scopeLabel = rawScopeLabel || '.';
|
|
108
121
|
const { pattern } = args;
|
|
109
122
|
const truncatedPattern = truncateProgressPattern(pattern);
|
|
@@ -161,7 +174,7 @@ export function registerSearchFilesTool(server, options = {}) {
|
|
|
161
174
|
throw error;
|
|
162
175
|
}
|
|
163
176
|
},
|
|
164
|
-
onError: (error) => buildToolErrorResponse(error, ErrorCode.
|
|
177
|
+
onError: (error) => buildToolErrorResponse(error, ErrorCode.UNKNOWN, args.path),
|
|
165
178
|
});
|
|
166
179
|
const { isInitialized } = options;
|
|
167
180
|
const wrappedHandler = wrapToolHandler(handler, {
|
package/dist/tools/shared.d.ts
CHANGED
|
@@ -63,6 +63,9 @@ export interface ToolExtra {
|
|
|
63
63
|
signal?: AbortSignal;
|
|
64
64
|
_meta?: {
|
|
65
65
|
progressToken?: ProgressToken | undefined;
|
|
66
|
+
traceparent?: string | undefined;
|
|
67
|
+
tracestate?: string | undefined;
|
|
68
|
+
baggage?: string | undefined;
|
|
66
69
|
};
|
|
67
70
|
sendNotification?: (notification: {
|
|
68
71
|
method: 'notifications/progress';
|
package/dist/tools/shared.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import * as path from 'node:path';
|
|
2
1
|
import { channel } from 'node:diagnostics_channel';
|
|
2
|
+
import { basename } from 'node:path';
|
|
3
3
|
import { z } from 'zod';
|
|
4
|
+
import { createTimedAbortSignal } from '../lib/abort.js';
|
|
4
5
|
import { parseTrueEnvFlag } from '../lib/constants.js';
|
|
5
6
|
import { createDetailedError, ErrorCode, formatDetailedError, getSuggestion, McpError, } from '../lib/errors.js';
|
|
6
|
-
import {
|
|
7
|
-
import { withToolDiagnostics } from '../lib/observability.js';
|
|
7
|
+
import { Logger } from '../lib/logger.js';
|
|
8
|
+
import { withToolDiagnostics, } from '../lib/observability.js';
|
|
8
9
|
import { getAllowedDirectories } from '../lib/paths.js';
|
|
9
10
|
import { createBase64JsonCodec } from '../lib/zod-codecs.js';
|
|
10
11
|
export {} from './contract.js';
|
|
@@ -12,6 +13,20 @@ const MAX_INLINE_CONTENT_CHARS = parseInt(process.env['FS_CONTEXT_MAX_INLINE_CHA
|
|
|
12
13
|
const MAX_INLINE_PREVIEW_CHARS = 4_000;
|
|
13
14
|
const PROGRESS_RATE_LIMIT_MS = 50;
|
|
14
15
|
const CONTEXT_DIAGNOSTICS_CHANNEL = channel('filesystem-mcp:context');
|
|
16
|
+
// W3C Trace Context: version-traceid-parentid-traceflags
|
|
17
|
+
const TRACEPARENT_RE = /^[\da-f]{2}-[\da-f]{32}-[\da-f]{16}-[\da-f]{2}$/i;
|
|
18
|
+
function extractTraceContext(meta) {
|
|
19
|
+
const tp = meta?.traceparent;
|
|
20
|
+
if (typeof tp !== 'string' || !TRACEPARENT_RE.test(tp))
|
|
21
|
+
return undefined;
|
|
22
|
+
return {
|
|
23
|
+
traceparent: tp,
|
|
24
|
+
...(typeof meta?.tracestate === 'string'
|
|
25
|
+
? { tracestate: meta.tracestate }
|
|
26
|
+
: {}),
|
|
27
|
+
...(typeof meta?.baggage === 'string' ? { baggage: meta.baggage } : {}),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
15
30
|
function publishContextDiagnostics(event) {
|
|
16
31
|
if (!CONTEXT_DIAGNOSTICS_CHANNEL.hasSubscribers)
|
|
17
32
|
return;
|
|
@@ -45,11 +60,13 @@ function normalizeToolExecution(tool) {
|
|
|
45
60
|
? candidate['execution']
|
|
46
61
|
: undefined;
|
|
47
62
|
const executionTaskSupport = existingExecution?.['taskSupport'];
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
63
|
+
let resolvedTaskSupport;
|
|
64
|
+
if (isTaskSupportLevel(topLevelTaskSupport)) {
|
|
65
|
+
resolvedTaskSupport = topLevelTaskSupport;
|
|
66
|
+
}
|
|
67
|
+
else if (isTaskSupportLevel(executionTaskSupport)) {
|
|
68
|
+
resolvedTaskSupport = executionTaskSupport;
|
|
69
|
+
}
|
|
53
70
|
if (resolvedTaskSupport === undefined && topLevelTaskSupport === undefined) {
|
|
54
71
|
return tool;
|
|
55
72
|
}
|
|
@@ -106,6 +123,7 @@ export function maybeExternalizeTextContent(resourceStore, content, params) {
|
|
|
106
123
|
chars: content.length,
|
|
107
124
|
uri: entry.uri,
|
|
108
125
|
});
|
|
126
|
+
Logger.debug(`Content externalized: ${params.name} (${content.length} chars) → ${entry.uri}`);
|
|
109
127
|
return {
|
|
110
128
|
entry,
|
|
111
129
|
preview: buildTextPreview(content),
|
|
@@ -128,9 +146,12 @@ export function buildResourceLink(params) {
|
|
|
128
146
|
}
|
|
129
147
|
function resolveDetailedError(error, defaultCode, path) {
|
|
130
148
|
const detailed = createDetailedError(error, path);
|
|
131
|
-
if (detailed.code === ErrorCode.
|
|
149
|
+
if (detailed.code === ErrorCode.UNKNOWN) {
|
|
132
150
|
detailed.code = defaultCode;
|
|
133
|
-
|
|
151
|
+
const suggestion = getSuggestion(defaultCode);
|
|
152
|
+
if (suggestion) {
|
|
153
|
+
detailed.suggestion = suggestion;
|
|
154
|
+
}
|
|
134
155
|
}
|
|
135
156
|
return detailed;
|
|
136
157
|
}
|
|
@@ -156,13 +177,13 @@ function validateStructuredContent(toolName, outputSchema, structuredContent) {
|
|
|
156
177
|
if (parsed.success) {
|
|
157
178
|
return parsed.data;
|
|
158
179
|
}
|
|
159
|
-
throw new McpError(ErrorCode.
|
|
180
|
+
throw new McpError(ErrorCode.UNKNOWN, `Tool "${toolName}" returned invalid structuredContent.`, undefined, { errors: z.treeifyError(parsed.error) });
|
|
160
181
|
}
|
|
161
182
|
function validateToolResponse(toolName, result, outputSchema) {
|
|
162
183
|
if (!outputSchema)
|
|
163
184
|
return result;
|
|
164
185
|
if (!Object.hasOwn(result, 'structuredContent')) {
|
|
165
|
-
throw new McpError(ErrorCode.
|
|
186
|
+
throw new McpError(ErrorCode.UNKNOWN, `Tool "${toolName}" returned success without structuredContent.`);
|
|
166
187
|
}
|
|
167
188
|
return {
|
|
168
189
|
...result,
|
|
@@ -175,12 +196,20 @@ function parseToolArgs(schema, args) {
|
|
|
175
196
|
if (parsed.success) {
|
|
176
197
|
return parsed.data;
|
|
177
198
|
}
|
|
178
|
-
throw new McpError(ErrorCode.
|
|
199
|
+
throw new McpError(ErrorCode.INVALID_INPUT, `Invalid tool arguments:\n${z.prettifyError(parsed.error)}`);
|
|
179
200
|
}
|
|
180
201
|
export function withValidatedArgs(schema, handler) {
|
|
181
202
|
return async (args, extra) => {
|
|
182
|
-
|
|
183
|
-
|
|
203
|
+
try {
|
|
204
|
+
const normalizedArgs = parseToolArgs(schema, args);
|
|
205
|
+
return await handler(normalizedArgs, extra);
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
if (error instanceof McpError && error.code === ErrorCode.INVALID_INPUT) {
|
|
209
|
+
return buildToolErrorResponse(error, ErrorCode.INVALID_INPUT);
|
|
210
|
+
}
|
|
211
|
+
throw error;
|
|
212
|
+
}
|
|
184
213
|
};
|
|
185
214
|
}
|
|
186
215
|
function canSendProgress(extra) {
|
|
@@ -232,7 +261,7 @@ export function buildFileInfoPayload(info) {
|
|
|
232
261
|
: {}),
|
|
233
262
|
};
|
|
234
263
|
}
|
|
235
|
-
const NOT_INITIALIZED_ERROR = new McpError(ErrorCode.
|
|
264
|
+
const NOT_INITIALIZED_ERROR = new McpError(ErrorCode.INVALID_INPUT, 'Client not initialized; wait for notifications/initialized');
|
|
236
265
|
async function withToolErrorHandling(run, onError) {
|
|
237
266
|
try {
|
|
238
267
|
return await run();
|
|
@@ -249,6 +278,7 @@ function getToolSignal(extraSignal, timedSignal) {
|
|
|
249
278
|
return { signal, cleanup };
|
|
250
279
|
}
|
|
251
280
|
export async function executeToolWithDiagnostics(options) {
|
|
281
|
+
const traceContext = extractTraceContext(options.extra._meta);
|
|
252
282
|
return withToolDiagnostics(options.toolName, () => withToolErrorHandling(async () => {
|
|
253
283
|
const { signal, cleanup } = getToolSignal(options.extra.signal, options.timedSignal);
|
|
254
284
|
try {
|
|
@@ -257,7 +287,10 @@ export async function executeToolWithDiagnostics(options) {
|
|
|
257
287
|
finally {
|
|
258
288
|
cleanup();
|
|
259
289
|
}
|
|
260
|
-
}, options.onError),
|
|
290
|
+
}, options.onError), {
|
|
291
|
+
...options.context,
|
|
292
|
+
...(traceContext ? { traceContext } : {}),
|
|
293
|
+
});
|
|
261
294
|
}
|
|
262
295
|
export function buildToolErrorResponse(error, defaultCode, path) {
|
|
263
296
|
const detailed = resolveDetailedError(error, defaultCode, path);
|
|
@@ -269,7 +302,7 @@ export function buildToolErrorResponse(error, defaultCode, path) {
|
|
|
269
302
|
};
|
|
270
303
|
}
|
|
271
304
|
function buildNotInitializedResult() {
|
|
272
|
-
return buildToolErrorResponse(NOT_INITIALIZED_ERROR, ErrorCode.
|
|
305
|
+
return buildToolErrorResponse(NOT_INITIALIZED_ERROR, ErrorCode.INVALID_INPUT);
|
|
273
306
|
}
|
|
274
307
|
async function reportProgress(extra, progress) {
|
|
275
308
|
await updateTaskStoreProgress(extra, progress);
|
|
@@ -283,6 +316,10 @@ function formatTaskStatusMessage(progress) {
|
|
|
283
316
|
}
|
|
284
317
|
return progress.message ?? `${progress.current}`;
|
|
285
318
|
}
|
|
319
|
+
function isBenignTaskStatusUpdateError(error) {
|
|
320
|
+
return (error instanceof Error &&
|
|
321
|
+
/Task .*not found|terminal status/iu.test(error.message));
|
|
322
|
+
}
|
|
286
323
|
async function updateTaskStoreProgress(extra, progress) {
|
|
287
324
|
const taskExtra = extra;
|
|
288
325
|
if (typeof taskExtra.taskId === 'string' &&
|
|
@@ -294,7 +331,9 @@ async function updateTaskStoreProgress(extra, progress) {
|
|
|
294
331
|
await store.updateTaskStatus(taskExtra.taskId, 'working', formatTaskStatusMessage(progress));
|
|
295
332
|
}
|
|
296
333
|
catch (error) {
|
|
297
|
-
|
|
334
|
+
if (isBenignTaskStatusUpdateError(error))
|
|
335
|
+
return;
|
|
336
|
+
Logger.error('Failed to update task status message:', error);
|
|
298
337
|
}
|
|
299
338
|
}
|
|
300
339
|
}
|
|
@@ -315,7 +354,7 @@ async function sendMcpProgressNotification(extra, progress) {
|
|
|
315
354
|
});
|
|
316
355
|
}
|
|
317
356
|
catch (error) {
|
|
318
|
-
|
|
357
|
+
Logger.error('Failed to send progress notification:', error);
|
|
319
358
|
}
|
|
320
359
|
}
|
|
321
360
|
}
|
|
@@ -480,14 +519,14 @@ export function resolvePathOrRoot(pathValue) {
|
|
|
480
519
|
return pathValue;
|
|
481
520
|
const roots = getAllowedDirectories();
|
|
482
521
|
if (roots.length === 0) {
|
|
483
|
-
throw new McpError(ErrorCode.
|
|
522
|
+
throw new McpError(ErrorCode.ACCESS_DENIED, 'No roots configured. Use roots tool, --allow-cwd, or MCP Roots protocol.');
|
|
484
523
|
}
|
|
485
524
|
if (roots.length > 1) {
|
|
486
|
-
throw new McpError(ErrorCode.
|
|
525
|
+
throw new McpError(ErrorCode.INVALID_INPUT, 'Multiple roots configured. Provide an explicit path.');
|
|
487
526
|
}
|
|
488
527
|
const root = roots[0];
|
|
489
528
|
if (!root) {
|
|
490
|
-
throw new McpError(ErrorCode.
|
|
529
|
+
throw new McpError(ErrorCode.ACCESS_DENIED, 'Workspace root is unexpectedly undefined');
|
|
491
530
|
}
|
|
492
531
|
return root;
|
|
493
532
|
}
|
|
@@ -505,14 +544,17 @@ export function decodeOffsetCursor(cursor) {
|
|
|
505
544
|
catch {
|
|
506
545
|
// fall through to throw
|
|
507
546
|
}
|
|
508
|
-
throw new McpError(ErrorCode.
|
|
547
|
+
throw new McpError(ErrorCode.INVALID_INPUT, `Invalid cursor. Request the first page without a cursor.`);
|
|
509
548
|
}
|
|
510
549
|
export function buildBatchPathContext(paths, unitLabel = 'paths') {
|
|
511
550
|
const normalizedLabel = paths.length === 1 ? unitLabel.replace(/s$/i, '') : unitLabel;
|
|
512
|
-
const first =
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
551
|
+
const first = basename(paths[0] ?? '');
|
|
552
|
+
let extraPaths = '';
|
|
553
|
+
if (paths.length > 1) {
|
|
554
|
+
const secondPath = basename(paths[1] ?? '');
|
|
555
|
+
const ellipsis = paths.length > 2 ? '…' : '';
|
|
556
|
+
extraPaths = `, ${secondPath}${ellipsis}`;
|
|
557
|
+
}
|
|
516
558
|
return `${paths.length} ${normalizedLabel} [${first}${extraPaths}]`;
|
|
517
559
|
}
|
|
518
560
|
export function truncateProgressPattern(pattern, maxLength = 40) {
|
package/dist/tools/stat-many.js
CHANGED
|
@@ -38,15 +38,19 @@ async function handleGetMultipleFileInfo(args, signal, onProgress) {
|
|
|
38
38
|
path: entry.path,
|
|
39
39
|
info: entry.info ? buildFileInfoPayload(entry.info) : undefined,
|
|
40
40
|
error: entry.error
|
|
41
|
-
? buildStructuredError(entry.error, ErrorCode.
|
|
41
|
+
? buildStructuredError(entry.error, ErrorCode.NOT_FOUND, entry.path)
|
|
42
42
|
: undefined,
|
|
43
43
|
}));
|
|
44
44
|
const text = result.results
|
|
45
|
-
.map((entry) =>
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
.map((entry) => {
|
|
46
|
+
if (entry.error) {
|
|
47
|
+
return `${entry.path}: ${buildStructuredError(entry.error, ErrorCode.NOT_FOUND, entry.path).message}`;
|
|
48
|
+
}
|
|
49
|
+
if (entry.info) {
|
|
50
|
+
return formatFileInfoDetail(entry.info);
|
|
51
|
+
}
|
|
52
|
+
return entry.path;
|
|
53
|
+
})
|
|
50
54
|
.join('\n\n');
|
|
51
55
|
const structured = {
|
|
52
56
|
ok: true,
|
|
@@ -90,7 +94,7 @@ export function registerGetMultipleFileInfoTool(server, options = {}) {
|
|
|
90
94
|
throw error;
|
|
91
95
|
}
|
|
92
96
|
},
|
|
93
|
-
onError: (error) => buildToolErrorResponse(error, ErrorCode.
|
|
97
|
+
onError: (error) => buildToolErrorResponse(error, ErrorCode.NOT_FOUND, primaryPath),
|
|
94
98
|
});
|
|
95
99
|
};
|
|
96
100
|
const wrappedHandler = wrapToolHandler(handler, {
|
package/dist/tools/stat.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { basename } from 'node:path';
|
|
2
2
|
import { DEFAULT_SEARCH_TIMEOUT_MS } from '../lib/constants.js';
|
|
3
3
|
import { ErrorCode } from '../lib/errors.js';
|
|
4
4
|
import { getFileInfo } from '../lib/file-operations/metadata.js';
|
|
@@ -48,13 +48,13 @@ export function registerGetFileInfoTool(server, options = {}) {
|
|
|
48
48
|
timedSignal: { timeoutMs: DEFAULT_SEARCH_TIMEOUT_MS },
|
|
49
49
|
context: { path: args.path },
|
|
50
50
|
run: (signal) => handleGetFileInfo(args, signal),
|
|
51
|
-
onError: (error) => buildToolErrorResponse(error, ErrorCode.
|
|
51
|
+
onError: (error) => buildToolErrorResponse(error, ErrorCode.NOT_FOUND, args.path),
|
|
52
52
|
});
|
|
53
53
|
const wrappedHandler = wrapToolHandler(handler, {
|
|
54
54
|
guard: options.isInitialized,
|
|
55
|
-
progressMessage: (args) => `🕮 stat: ${
|
|
55
|
+
progressMessage: (args) => `🕮 stat: ${basename(args.path)}`,
|
|
56
56
|
completionMessage: (args, result) => {
|
|
57
|
-
const name =
|
|
57
|
+
const name = basename(args.path);
|
|
58
58
|
if (result.isError)
|
|
59
59
|
return `🕮 stat: ${name} • failed`;
|
|
60
60
|
const sc = result.structuredContent;
|
|
@@ -2,20 +2,21 @@ import type { ToolTaskHandler } from '@modelcontextprotocol/sdk/experimental/tas
|
|
|
2
2
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
3
|
import type { AnySchema, SchemaOutput, ShapeOutput, ZodRawShapeCompat } from '@modelcontextprotocol/sdk/server/zod-compat.js';
|
|
4
4
|
import type { RequestTaskStore } from '@modelcontextprotocol/sdk/shared/protocol.js';
|
|
5
|
-
import type
|
|
5
|
+
import { type IconInfo, type ToolExtra, type ToolResult } from './shared.js';
|
|
6
6
|
type TaskToolExtra = ToolExtra & {
|
|
7
7
|
taskId?: string;
|
|
8
8
|
taskStore?: RequestTaskStore;
|
|
9
9
|
taskRequestedTtl?: number | null;
|
|
10
10
|
};
|
|
11
|
-
type
|
|
12
|
-
|
|
13
|
-
export declare function
|
|
11
|
+
export type ToolSchema = ZodRawShapeCompat | AnySchema | undefined;
|
|
12
|
+
type ToolArgs<Args extends ToolSchema> = Args extends ZodRawShapeCompat ? ShapeOutput<Args> : Args extends AnySchema ? SchemaOutput<Args> : undefined;
|
|
13
|
+
export declare function registerToolTaskIfAvailable<Args extends ToolSchema, Result>(server: McpServer, toolName: string, toolDef: object, run: (args: ToolArgs<Args>, extra: TaskToolExtra) => Promise<ToolResult<Result>>, iconInfo: IconInfo | undefined, guard?: () => boolean): boolean;
|
|
14
|
+
interface TaskHandlerOptions {
|
|
14
15
|
guard?: () => boolean;
|
|
15
16
|
toolName?: string;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
cancelPollMs?: number;
|
|
18
|
+
pollIntervalMs?: number;
|
|
19
|
+
}
|
|
20
|
+
export declare function createToolTaskHandler<Result>(run: (args: undefined, extra: TaskToolExtra) => Promise<ToolResult<Result>>, options?: TaskHandlerOptions): ToolTaskHandler;
|
|
21
|
+
export declare function createToolTaskHandler<Args extends ZodRawShapeCompat | AnySchema, Result>(run: (args: ToolArgs<Args>, extra: TaskToolExtra) => Promise<ToolResult<Result>>, options?: TaskHandlerOptions): ToolTaskHandler<Args>;
|
|
21
22
|
export {};
|