@j0hanz/filesystem-mcp 1.13.1 → 1.14.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 +117 -100
- package/dist/completions.d.ts +0 -11
- package/dist/completions.js +1 -1
- package/dist/config.d.ts +0 -1
- package/dist/lib/constants.d.ts +0 -1
- package/dist/lib/constants.js +0 -1
- package/dist/lib/errors.d.ts +0 -1
- package/dist/lib/errors.js +5 -9
- package/dist/lib/file-operations/core.d.ts +3 -2
- package/dist/lib/file-operations/metadata.js +9 -3
- package/dist/lib/file-operations/search.d.ts +1 -59
- package/dist/lib/file-operations/search.js +9 -22
- package/dist/lib/file-operations/traversal.d.ts +2 -2
- package/dist/lib/fs-helpers.d.ts +1 -2
- package/dist/lib/fs-helpers.js +10 -12
- package/dist/lib/globs.d.ts +2 -0
- package/dist/lib/globs.js +19 -0
- package/dist/lib/paths.d.ts +0 -3
- package/dist/lib/paths.js +1 -4
- package/dist/lib/utils.d.ts +4 -4
- package/dist/lib/utils.js +1 -10
- package/dist/lib/zod-codecs.d.ts +2 -0
- package/dist/lib/zod-codecs.js +18 -0
- package/dist/pkg-info.d.ts +1 -0
- package/dist/pkg-info.js +2 -2
- package/dist/prompts.js +3 -3
- package/dist/resources/generated-instructions.js +3 -12
- package/dist/resources/tool-catalog.js +10 -41
- package/dist/resources/tool-info.d.ts +0 -1
- package/dist/resources/tool-info.js +24 -39
- package/dist/resources/workflows.js +8 -1
- package/dist/schemas.d.ts +179 -481
- package/dist/schemas.js +156 -169
- package/dist/server/bootstrap.d.ts +0 -10
- package/dist/server/bootstrap.js +3 -3
- package/dist/server/roots-manager.js +1 -1
- package/dist/tools/apply-patch.js +19 -8
- package/dist/tools/calculate-hash.js +3 -5
- package/dist/tools/create-directory.d.ts +1 -4
- package/dist/tools/create-directory.js +2 -2
- package/dist/tools/delete-file.js +2 -4
- package/dist/tools/diff-files.js +1 -3
- package/dist/tools/edit-file.d.ts +1 -7
- package/dist/tools/edit-file.js +6 -3
- package/dist/tools/list-directory.js +10 -15
- package/dist/tools/move-file.d.ts +1 -4
- package/dist/tools/move-file.js +15 -27
- package/dist/tools/read-multiple.js +12 -7
- package/dist/tools/read.js +1 -2
- package/dist/tools/replace-in-files.d.ts +1 -10
- package/dist/tools/replace-in-files.js +62 -97
- package/dist/tools/roots.js +2 -6
- package/dist/tools/search-content.js +152 -188
- package/dist/tools/search-files.js +10 -13
- package/dist/tools/shared.d.ts +10 -3
- package/dist/tools/shared.js +53 -12
- package/dist/tools/stat-many.js +6 -4
- package/dist/tools/stat.js +2 -2
- package/dist/tools/task-support.d.ts +0 -7
- package/dist/tools/task-support.js +7 -6
- package/dist/tools/tree.js +1 -1
- package/dist/tools/write-file.js +1 -5
- package/dist/tools.d.ts +0 -1
- package/dist/tools.js +0 -1
- package/package.json +5 -5
|
@@ -3,6 +3,7 @@ import * as path from 'node:path';
|
|
|
3
3
|
import { DEFAULT_EXCLUDE_PATTERNS, DEFAULT_LIST_MAX_ENTRIES, DEFAULT_MAX_DEPTH, DEFAULT_READ_MANY_MAX_TOTAL_SIZE, DEFAULT_SEARCH_TIMEOUT_MS, getMimeType, MAX_TEXT_FILE_SIZE, PARALLEL_CONCURRENCY, } from '../constants.js';
|
|
4
4
|
import { isAbortError } from '../errors.js';
|
|
5
5
|
import { assertNotAborted, getFileType, isHidden, processInParallel, readFile, readFileWithStats, withAbort, withTimedAbortSignal, } from '../fs-helpers.js';
|
|
6
|
+
import { assertSafeGlobPattern } from '../globs.js';
|
|
6
7
|
import { assertAllowedFileAccess, isPathWithinDirectories, isSensitivePath, normalizePath, toPosixPath, validateExistingDirectory, validateExistingPath, validateExistingPathDetailed, } from '../paths.js';
|
|
7
8
|
import { applyIndexedErrors, applyIndexedValues, isEntryAccessibleByType, isIgnoredByGitignore, loadRootGitignore, needsStatsForSort, resolveEntryType, resolveStopReason, withOptionalStoppedReason, } from './core.js';
|
|
8
9
|
import { globEntries } from './traversal.js';
|
|
@@ -149,6 +150,7 @@ function normalizeListOptions(options) {
|
|
|
149
150
|
timeoutMs: options.timeoutMs ?? DEFAULT_SEARCH_TIMEOUT_MS,
|
|
150
151
|
};
|
|
151
152
|
if (options.pattern && options.pattern.length > 0) {
|
|
153
|
+
assertSafeGlobPattern(options.pattern);
|
|
152
154
|
normalized.pattern = options.pattern;
|
|
153
155
|
}
|
|
154
156
|
return normalized;
|
|
@@ -590,6 +592,7 @@ function buildReadOptions(options) {
|
|
|
590
592
|
const readOptions = {
|
|
591
593
|
encoding: options.encoding,
|
|
592
594
|
maxSize: options.maxSize,
|
|
595
|
+
skipBinary: true,
|
|
593
596
|
};
|
|
594
597
|
applyLineSelection(readOptions, options);
|
|
595
598
|
return readOptions;
|
|
@@ -653,10 +656,13 @@ function applyLineSelection(target, source) {
|
|
|
653
656
|
target.head = source.head;
|
|
654
657
|
if (source.tail !== undefined)
|
|
655
658
|
target.tail = source.tail;
|
|
656
|
-
if (source.
|
|
657
|
-
target.startLine = source.startLine;
|
|
658
|
-
if (source.endLine !== undefined)
|
|
659
|
+
if (source.endLine !== undefined) {
|
|
660
|
+
target.startLine = source.startLine ?? 1;
|
|
659
661
|
target.endLine = source.endLine;
|
|
662
|
+
}
|
|
663
|
+
else if (source.startLine !== undefined) {
|
|
664
|
+
target.startLine = source.startLine;
|
|
665
|
+
}
|
|
660
666
|
}
|
|
661
667
|
function resolveNormalizedReadOptions(options) {
|
|
662
668
|
const { signal, ...rest } = options;
|
|
@@ -1,21 +1,5 @@
|
|
|
1
|
-
import * as fsp from 'node:fs/promises';
|
|
2
1
|
import { z } from 'zod';
|
|
3
|
-
import type {
|
|
4
|
-
export declare const MatcherOptionsSchema: z.ZodObject<{
|
|
5
|
-
caseSensitive: z.ZodBoolean;
|
|
6
|
-
wholeWord: z.ZodBoolean;
|
|
7
|
-
isLiteral: z.ZodBoolean;
|
|
8
|
-
multiline: z.ZodBoolean;
|
|
9
|
-
}, z.core.$strict>;
|
|
10
|
-
export type MatcherOptions = z.infer<typeof MatcherOptionsSchema>;
|
|
11
|
-
export type Matcher = (line: string) => number;
|
|
12
|
-
export declare function validatePattern(pattern: string, options: MatcherOptions): void;
|
|
13
|
-
export declare function buildMatcher(pattern: string, options: MatcherOptions): Matcher;
|
|
14
|
-
export interface ScanFileOptions {
|
|
15
|
-
maxFileSize: number;
|
|
16
|
-
skipBinary: boolean;
|
|
17
|
-
contextLines: number;
|
|
18
|
-
}
|
|
2
|
+
import type { SearchContentResult, SearchFilesResult } from '../../config.js';
|
|
19
3
|
declare const SearchOptionsSchema: z.ZodObject<{
|
|
20
4
|
filePattern: z.ZodString;
|
|
21
5
|
excludePatterns: z.ZodArray<z.ZodString>;
|
|
@@ -28,7 +12,6 @@ declare const SearchOptionsSchema: z.ZodObject<{
|
|
|
28
12
|
contextLines: z.ZodInt;
|
|
29
13
|
wholeWord: z.ZodBoolean;
|
|
30
14
|
isLiteral: z.ZodBoolean;
|
|
31
|
-
multiline: z.ZodBoolean;
|
|
32
15
|
includeHidden: z.ZodBoolean;
|
|
33
16
|
baseNameMatch: z.ZodBoolean;
|
|
34
17
|
caseSensitiveFileMatch: z.ZodBoolean;
|
|
@@ -41,40 +24,6 @@ export interface SearchContentOptions extends Partial<ResolvedOptions> {
|
|
|
41
24
|
current: number;
|
|
42
25
|
}) => void;
|
|
43
26
|
}
|
|
44
|
-
type BinaryDetector = (resolvedPath: string, handle: fsp.FileHandle, signal?: AbortSignal) => Promise<boolean>;
|
|
45
|
-
export interface ScanRequest {
|
|
46
|
-
type: 'scan';
|
|
47
|
-
id: number;
|
|
48
|
-
resolvedPath: string;
|
|
49
|
-
requestedPath: string;
|
|
50
|
-
pattern: string;
|
|
51
|
-
matcherOptions: MatcherOptions;
|
|
52
|
-
scanOptions: ScanFileOptions;
|
|
53
|
-
maxMatches: number;
|
|
54
|
-
}
|
|
55
|
-
export interface ScanResult {
|
|
56
|
-
type: 'result';
|
|
57
|
-
id: number;
|
|
58
|
-
result: {
|
|
59
|
-
matches: readonly ContentMatch[];
|
|
60
|
-
matched: boolean;
|
|
61
|
-
skippedTooLarge: boolean;
|
|
62
|
-
skippedBinary: boolean;
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
export interface ScanError {
|
|
66
|
-
type: 'error';
|
|
67
|
-
id: number;
|
|
68
|
-
error: string;
|
|
69
|
-
}
|
|
70
|
-
export type WorkerResponse = ScanResult | ScanError;
|
|
71
|
-
interface WorkerScanResult {
|
|
72
|
-
matches: readonly ContentMatch[];
|
|
73
|
-
matched: boolean;
|
|
74
|
-
skippedTooLarge: boolean;
|
|
75
|
-
skippedBinary: boolean;
|
|
76
|
-
}
|
|
77
|
-
export declare function scanFileInWorker(resolvedPath: string, requestedPath: string, matcher: Matcher, options: ScanFileOptions, maxMatches: number, isCancelled: () => boolean, isBinaryDetector: BinaryDetector): Promise<WorkerScanResult>;
|
|
78
27
|
export declare function searchContent(basePath: string, pattern: string, options?: SearchContentOptions): Promise<SearchContentResult>;
|
|
79
28
|
type SortBy = 'name' | 'size' | 'modified' | 'path';
|
|
80
29
|
interface SearchFilesOptions {
|
|
@@ -93,12 +42,5 @@ interface SearchFilesOptions {
|
|
|
93
42
|
current: number;
|
|
94
43
|
}) => void;
|
|
95
44
|
}
|
|
96
|
-
interface Sortable {
|
|
97
|
-
name?: string;
|
|
98
|
-
size?: number;
|
|
99
|
-
modified?: Date;
|
|
100
|
-
path?: string;
|
|
101
|
-
}
|
|
102
|
-
export declare function sortSearchResults(results: Sortable[], sortBy: SortBy): void;
|
|
103
45
|
export declare function searchFiles(basePath: string, pattern: string, excludePatterns?: readonly string[], options?: SearchFilesOptions): Promise<SearchFilesResult>;
|
|
104
46
|
export {};
|
|
@@ -68,12 +68,6 @@ import { assertAllowedFileAccess, isPathWithinDirectories, isSensitivePath, norm
|
|
|
68
68
|
import { mergeOptions, omitOptionKeys, reportPeriodicProgress, } from '../utils.js';
|
|
69
69
|
import { compareOptionalNumberDesc, compareStringValues, isEntryAccessibleByType, isIgnoredByGitignore, loadRootGitignore, needsStatsForSort, resolveEntryType, resolveStopReason, stableSortByDerivedString, withOptionalStoppedReason, } from './core.js';
|
|
70
70
|
import { buildGlobOptions, globEntries } from './traversal.js';
|
|
71
|
-
export const MatcherOptionsSchema = z.strictObject({
|
|
72
|
-
caseSensitive: z.boolean(),
|
|
73
|
-
wholeWord: z.boolean(),
|
|
74
|
-
isLiteral: z.boolean(),
|
|
75
|
-
multiline: z.boolean(),
|
|
76
|
-
});
|
|
77
71
|
function countRegexLineMatches(regex, line) {
|
|
78
72
|
regex.lastIndex = 0;
|
|
79
73
|
let count = 0;
|
|
@@ -91,7 +85,7 @@ function buildRegexPattern(pattern, options) {
|
|
|
91
85
|
const escaped = options.isLiteral ? escapeLiteral(pattern) : pattern;
|
|
92
86
|
return options.wholeWord ? `\\b${escaped}\\b` : escaped;
|
|
93
87
|
}
|
|
94
|
-
|
|
88
|
+
function validatePattern(pattern, options) {
|
|
95
89
|
if (options.isLiteral && pattern.length === 0)
|
|
96
90
|
return;
|
|
97
91
|
if (options.isLiteral && !options.wholeWord)
|
|
@@ -123,14 +117,12 @@ function buildLiteralMatcher(pattern, options) {
|
|
|
123
117
|
return count;
|
|
124
118
|
};
|
|
125
119
|
}
|
|
126
|
-
function buildRegexMatcher(final, caseSensitive
|
|
127
|
-
|
|
128
|
-
if (multiline)
|
|
129
|
-
flags += 'm';
|
|
120
|
+
function buildRegexMatcher(final, caseSensitive) {
|
|
121
|
+
const flags = caseSensitive ? 'g' : 'gi';
|
|
130
122
|
const regex = new RE2(final, flags);
|
|
131
123
|
return (line) => countRegexLineMatches(regex, line);
|
|
132
124
|
}
|
|
133
|
-
|
|
125
|
+
function buildMatcher(pattern, options) {
|
|
134
126
|
if (options.isLiteral && pattern.length === 0)
|
|
135
127
|
return () => 0;
|
|
136
128
|
if (options.isLiteral && !options.wholeWord) {
|
|
@@ -139,7 +131,7 @@ export function buildMatcher(pattern, options) {
|
|
|
139
131
|
}
|
|
140
132
|
const final = buildRegexPattern(pattern, options);
|
|
141
133
|
validatePattern(pattern, options); // Re-validate to be safe
|
|
142
|
-
return buildRegexMatcher(final, options.caseSensitive
|
|
134
|
+
return buildRegexMatcher(final, options.caseSensitive);
|
|
143
135
|
}
|
|
144
136
|
// --- Configuration & Schemas ---
|
|
145
137
|
const SEARCH_CONTENT_MAX_RESULTS = 500;
|
|
@@ -155,7 +147,6 @@ const SearchOptionsSchema = z.strictObject({
|
|
|
155
147
|
contextLines: z.int().min(0),
|
|
156
148
|
wholeWord: z.boolean(),
|
|
157
149
|
isLiteral: z.boolean(),
|
|
158
|
-
multiline: z.boolean(),
|
|
159
150
|
includeHidden: z.boolean(),
|
|
160
151
|
baseNameMatch: z.boolean(),
|
|
161
152
|
caseSensitiveFileMatch: z.boolean(),
|
|
@@ -172,7 +163,6 @@ const DEFAULTS = {
|
|
|
172
163
|
contextLines: 0,
|
|
173
164
|
wholeWord: false,
|
|
174
165
|
isLiteral: true,
|
|
175
|
-
multiline: false,
|
|
176
166
|
includeHidden: false,
|
|
177
167
|
baseNameMatch: false,
|
|
178
168
|
caseSensitiveFileMatch: true,
|
|
@@ -188,7 +178,7 @@ function resolveOptions(options) {
|
|
|
188
178
|
const merged = mergeOptions(DEFAULTS, normalizedOptions);
|
|
189
179
|
const result = SearchOptionsSchema.safeParse(merged);
|
|
190
180
|
if (!result.success) {
|
|
191
|
-
throw new McpError(ErrorCode.E_INVALID_INPUT, `Invalid search options
|
|
181
|
+
throw new McpError(ErrorCode.E_INVALID_INPUT, `Invalid search options:\n${z.prettifyError(result.error)}`, undefined, { errors: z.treeifyError(result.error) });
|
|
192
182
|
}
|
|
193
183
|
return result.data;
|
|
194
184
|
}
|
|
@@ -376,7 +366,6 @@ function buildMatcherOptions(opts) {
|
|
|
376
366
|
caseSensitive: opts.caseSensitive,
|
|
377
367
|
wholeWord: opts.wholeWord,
|
|
378
368
|
isLiteral: opts.isLiteral,
|
|
379
|
-
multiline: opts.multiline,
|
|
380
369
|
};
|
|
381
370
|
}
|
|
382
371
|
function applyScanOutcome(summary, outcome) {
|
|
@@ -411,7 +400,6 @@ function buildSearchContentResult(root, pattern, filePattern, matches, summary)
|
|
|
411
400
|
skippedTooLarge: summary.skippedTooLarge,
|
|
412
401
|
skippedBinary: summary.skippedBinary,
|
|
413
402
|
skippedInaccessible: summary.skippedInaccessible,
|
|
414
|
-
linesSkippedDueToRegexTimeout: 0,
|
|
415
403
|
};
|
|
416
404
|
return {
|
|
417
405
|
basePath: root,
|
|
@@ -751,7 +739,7 @@ async function executeParallel(files, pattern, opts, signal, summary) {
|
|
|
751
739
|
return matches;
|
|
752
740
|
}
|
|
753
741
|
// --- Entry Points ---
|
|
754
|
-
|
|
742
|
+
async function scanFileInWorker(resolvedPath, requestedPath, matcher, options, maxMatches, isCancelled, isBinaryDetector) {
|
|
755
743
|
// Direct scan used by worker script
|
|
756
744
|
const res = await scanFileResolved(resolvedPath, requestedPath, matcher, options, undefined, maxMatches, isBinaryDetector);
|
|
757
745
|
return {
|
|
@@ -1051,7 +1039,7 @@ const SORT_COMPARATORS = {
|
|
|
1051
1039
|
path: (a, b) => comparePathThenName(a, b),
|
|
1052
1040
|
name: (a, b) => compareNameThenPath(a, b),
|
|
1053
1041
|
};
|
|
1054
|
-
|
|
1042
|
+
function sortSearchResults(results, sortBy) {
|
|
1055
1043
|
if (sortBy === 'name') {
|
|
1056
1044
|
stableSortByDerivedString(results, (item) => path.basename(item.path ?? ''), (left, right) => comparePathThenName(left, right));
|
|
1057
1045
|
return;
|
|
@@ -1086,8 +1074,7 @@ function getMatcherCacheKey(pattern, options) {
|
|
|
1086
1074
|
const cs = options.caseSensitive ? '1' : '0';
|
|
1087
1075
|
const ww = options.wholeWord ? '1' : '0';
|
|
1088
1076
|
const lit = options.isLiteral ? '1' : '0';
|
|
1089
|
-
|
|
1090
|
-
return `${pattern}|${cs}|${ww}|${lit}|${ml}`;
|
|
1077
|
+
return `${pattern}|${cs}|${ww}|${lit}`;
|
|
1091
1078
|
}
|
|
1092
1079
|
function getCachedMatcher(pattern, options) {
|
|
1093
1080
|
const key = getMatcherCacheKey(pattern, options);
|
|
@@ -6,7 +6,7 @@ interface GlobEntry {
|
|
|
6
6
|
dirent: DirentLike;
|
|
7
7
|
stats?: Stats;
|
|
8
8
|
}
|
|
9
|
-
|
|
9
|
+
interface GlobEntriesOptions {
|
|
10
10
|
cwd: string;
|
|
11
11
|
pattern: string;
|
|
12
12
|
excludePatterns: readonly string[];
|
|
@@ -20,7 +20,7 @@ export interface GlobEntriesOptions {
|
|
|
20
20
|
suppressErrors?: boolean;
|
|
21
21
|
}
|
|
22
22
|
export declare function globEntries(options: GlobEntriesOptions): AsyncGenerator<GlobEntry>;
|
|
23
|
-
|
|
23
|
+
interface GlobConfig {
|
|
24
24
|
cwd: string;
|
|
25
25
|
pattern: string;
|
|
26
26
|
excludePatterns?: readonly string[];
|
package/dist/lib/fs-helpers.d.ts
CHANGED
|
@@ -43,11 +43,10 @@ interface ReadFileResult {
|
|
|
43
43
|
linesRead?: number;
|
|
44
44
|
hasMoreLines?: boolean;
|
|
45
45
|
}
|
|
46
|
-
declare function headFile(handle: fsp.FileHandle, numLines: number, encoding?: BufferEncoding, maxBytesRead?: number, signal?: AbortSignal): Promise<string>;
|
|
47
46
|
export declare function readFileWithStats(filePath: string, validPath: string, stats: Stats, options?: ReadFileOptions): Promise<ReadFileResult>;
|
|
48
47
|
export declare function readFile(filePath: string, options?: ReadFileOptions): Promise<ReadFileResult>;
|
|
49
48
|
export declare function atomicWriteFile(filePath: string, content: string, options?: {
|
|
50
49
|
encoding?: BufferEncoding;
|
|
51
50
|
signal?: AbortSignal | undefined;
|
|
52
51
|
}): Promise<void>;
|
|
53
|
-
export {
|
|
52
|
+
export {};
|
package/dist/lib/fs-helpers.js
CHANGED
|
@@ -287,13 +287,11 @@ function validateReadOptions(options) {
|
|
|
287
287
|
if (hasTail && (hasHead || hasStart || hasEnd)) {
|
|
288
288
|
throw new McpError(ErrorCode.E_INVALID_INPUT, 'tail cannot be used together with head/startLine/endLine');
|
|
289
289
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
options.endLine < options.startLine) {
|
|
296
|
-
throw new McpError(ErrorCode.E_INVALID_INPUT, 'endLine must be greater than or equal to startLine');
|
|
290
|
+
{
|
|
291
|
+
const effectiveStart = options.startLine ?? 1;
|
|
292
|
+
if (options.endLine !== undefined && options.endLine < effectiveStart) {
|
|
293
|
+
throw new McpError(ErrorCode.E_INVALID_INPUT, 'endLine must be greater than or equal to startLine (default: 1)');
|
|
294
|
+
}
|
|
297
295
|
}
|
|
298
296
|
}
|
|
299
297
|
function normalizeOptions(options) {
|
|
@@ -309,12 +307,13 @@ function normalizeOptions(options) {
|
|
|
309
307
|
if (options.tail !== undefined) {
|
|
310
308
|
normalized.tail = options.tail;
|
|
311
309
|
}
|
|
312
|
-
if (options.startLine !== undefined) {
|
|
313
|
-
normalized.startLine = options.startLine;
|
|
314
|
-
}
|
|
315
310
|
if (options.endLine !== undefined) {
|
|
311
|
+
normalized.startLine = options.startLine ?? 1;
|
|
316
312
|
normalized.endLine = options.endLine;
|
|
317
313
|
}
|
|
314
|
+
else if (options.startLine !== undefined) {
|
|
315
|
+
normalized.startLine = options.startLine;
|
|
316
|
+
}
|
|
318
317
|
if (options.signal) {
|
|
319
318
|
normalized.signal = options.signal;
|
|
320
319
|
}
|
|
@@ -340,7 +339,7 @@ function resolveReadMode(options) {
|
|
|
340
339
|
return 'head';
|
|
341
340
|
if (options.tail !== undefined)
|
|
342
341
|
return 'tail';
|
|
343
|
-
if (options.startLine !== undefined)
|
|
342
|
+
if (options.startLine !== undefined || options.endLine !== undefined)
|
|
344
343
|
return 'range';
|
|
345
344
|
return 'full';
|
|
346
345
|
}
|
|
@@ -667,4 +666,3 @@ export async function atomicWriteFile(filePath, content, options = {}) {
|
|
|
667
666
|
throw error;
|
|
668
667
|
}
|
|
669
668
|
}
|
|
670
|
-
export { headFile };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ErrorCode, McpError } from './errors.js';
|
|
2
|
+
const ABSOLUTE_GLOB_RE = /^([/\\]|[A-Za-z]:[/\\]|\\\\)/u;
|
|
3
|
+
const PARENT_SEGMENT_RE = /[\\/]\.\.(?:[/\\]|$)/u;
|
|
4
|
+
export function isSafeGlobPattern(value) {
|
|
5
|
+
if (value.length === 0)
|
|
6
|
+
return false;
|
|
7
|
+
if (value.includes('**/**/**'))
|
|
8
|
+
return false;
|
|
9
|
+
if (ABSOLUTE_GLOB_RE.test(value))
|
|
10
|
+
return false;
|
|
11
|
+
if (value.startsWith('..') || PARENT_SEGMENT_RE.test(value))
|
|
12
|
+
return false;
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
export function assertSafeGlobPattern(value, message = 'Invalid glob or unsafe path (absolute/.. forbidden)') {
|
|
16
|
+
if (!isSafeGlobPattern(value)) {
|
|
17
|
+
throw new McpError(ErrorCode.E_INVALID_PATTERN, message);
|
|
18
|
+
}
|
|
19
|
+
}
|
package/dist/lib/paths.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { Root } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
-
import { McpError } from './errors.js';
|
|
3
2
|
export declare function toPosixPath(value: string): string;
|
|
4
3
|
export declare function isSensitivePath(requestedPath: string, resolvedPath?: string): boolean;
|
|
5
4
|
export declare function assertAllowedFileAccess(requestedPath: string, resolvedPath?: string): void;
|
|
@@ -15,7 +14,6 @@ export interface AllowedDirectoriesState {
|
|
|
15
14
|
*/
|
|
16
15
|
export declare function normalizePath(p: string): string;
|
|
17
16
|
export declare function withAllowedDirectoriesState<T>(state: AllowedDirectoriesState, run: () => T): T;
|
|
18
|
-
export declare function getAllowedDirectoriesState(): AllowedDirectoriesState;
|
|
19
17
|
export declare function setAllowedDirectoriesStateResolved(state: AllowedDirectoriesState): void;
|
|
20
18
|
export declare function getAllowedDirectories(): string[];
|
|
21
19
|
export declare function isAllowedDirectoryRoot(normalizedPath: string): boolean;
|
|
@@ -24,7 +22,6 @@ export declare function resolveAllowedDirectoriesState(dirs: readonly string[],
|
|
|
24
22
|
export declare function setAllowedDirectoriesResolved(dirs: readonly string[], signal?: AbortSignal): Promise<void>;
|
|
25
23
|
export declare function getReservedDeviceNameForPath(requestedPath: string): string | undefined;
|
|
26
24
|
export declare function isWindowsDriveRelativePath(requestedPath: string): boolean;
|
|
27
|
-
export declare function toAccessDeniedWithHint(requestedPath: string, resolvedPath: string, normalizedResolved: string): McpError;
|
|
28
25
|
interface ValidatedPathDetails {
|
|
29
26
|
requestedPath: string;
|
|
30
27
|
resolvedPath: string;
|
package/dist/lib/paths.js
CHANGED
|
@@ -252,9 +252,6 @@ function getActiveAllowedDirectoriesState() {
|
|
|
252
252
|
export function withAllowedDirectoriesState(state, run) {
|
|
253
253
|
return allowedDirectoriesContext.run(cloneAllowedDirectoriesState(state), run);
|
|
254
254
|
}
|
|
255
|
-
export function getAllowedDirectoriesState() {
|
|
256
|
-
return cloneAllowedDirectoriesState(getActiveAllowedDirectoriesState());
|
|
257
|
-
}
|
|
258
255
|
export function setAllowedDirectoriesStateResolved(state) {
|
|
259
256
|
setAllowedDirectoriesState(state.primary, state.expanded);
|
|
260
257
|
}
|
|
@@ -453,7 +450,7 @@ function toMcpError(requestedPath, error) {
|
|
|
453
450
|
}
|
|
454
451
|
return new McpError(ErrorCode.E_NOT_FOUND, `Path is not accessible: ${requestedPath}`, requestedPath, { originalCode: code, originalMessage }, error);
|
|
455
452
|
}
|
|
456
|
-
|
|
453
|
+
function toAccessDeniedWithHint(requestedPath, resolvedPath, normalizedResolved) {
|
|
457
454
|
const suggestion = buildAllowedDirectoriesHint();
|
|
458
455
|
return new McpError(ErrorCode.E_ACCESS_DENIED, `Access denied: Path '${requestedPath}' is outside allowed directories.\n${suggestion}`, requestedPath, { resolvedPath, normalizedResolvedPath: normalizedResolved });
|
|
459
456
|
}
|
package/dist/lib/utils.d.ts
CHANGED
|
@@ -5,15 +5,15 @@ export declare function debounce<Args extends unknown[]>(func: (...args: Args) =
|
|
|
5
5
|
};
|
|
6
6
|
export declare function mergeOptions<T extends object>(defaults: T, overrides: Partial<T>): T;
|
|
7
7
|
export declare function omitOptionKeys<T extends object, K extends keyof T>(input: T, keys: readonly K[]): Omit<T, K>;
|
|
8
|
-
|
|
9
|
-
export interface ProgressPayload {
|
|
8
|
+
interface ProgressPayload {
|
|
10
9
|
current: number;
|
|
11
10
|
total?: number;
|
|
12
11
|
}
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
type ProgressCallback = ((progress: ProgressPayload) => void) | undefined;
|
|
13
|
+
interface PeriodicProgressOptions {
|
|
15
14
|
total?: number;
|
|
16
15
|
throttleModulo?: number;
|
|
17
16
|
force?: boolean;
|
|
18
17
|
}
|
|
19
18
|
export declare function reportPeriodicProgress(onProgress: ProgressCallback, current: number, options?: PeriodicProgressOptions): void;
|
|
19
|
+
export {};
|
package/dist/lib/utils.js
CHANGED
|
@@ -14,11 +14,7 @@ export function debounce(func, waitMs) {
|
|
|
14
14
|
func(...args);
|
|
15
15
|
}, waitMs);
|
|
16
16
|
// Unref if in Node environment to not block process exit
|
|
17
|
-
|
|
18
|
-
if (typeof nodeTimeout === 'object' &&
|
|
19
|
-
typeof nodeTimeout.unref === 'function') {
|
|
20
|
-
nodeTimeout.unref();
|
|
21
|
-
}
|
|
17
|
+
timeoutId.unref();
|
|
22
18
|
};
|
|
23
19
|
debounced.cancel = () => {
|
|
24
20
|
if (timeoutId !== undefined) {
|
|
@@ -39,11 +35,6 @@ export function omitOptionKeys(input, keys) {
|
|
|
39
35
|
}
|
|
40
36
|
return output;
|
|
41
37
|
}
|
|
42
|
-
export function setIfDefined(target, key, value) {
|
|
43
|
-
if (value !== undefined) {
|
|
44
|
-
target[key] = value;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
38
|
export function reportPeriodicProgress(onProgress, current, options = {}) {
|
|
48
39
|
if (!onProgress || current === 0)
|
|
49
40
|
return;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export function createBase64JsonCodec(schema) {
|
|
3
|
+
return z.codec(z.string(), schema, {
|
|
4
|
+
decode: (value) => {
|
|
5
|
+
let parsed;
|
|
6
|
+
try {
|
|
7
|
+
parsed = JSON.parse(Buffer.from(value, 'base64url').toString('utf-8'));
|
|
8
|
+
}
|
|
9
|
+
catch (error) {
|
|
10
|
+
throw new Error('Invalid base64url-encoded JSON payload.', {
|
|
11
|
+
cause: error,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
return parsed;
|
|
15
|
+
},
|
|
16
|
+
encode: (value) => Buffer.from(JSON.stringify(value)).toString('base64url'),
|
|
17
|
+
});
|
|
18
|
+
}
|
package/dist/pkg-info.d.ts
CHANGED
package/dist/pkg-info.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import packageJsonRaw from '../package.json' with { type: 'json' };
|
|
3
|
-
const PkgInfoSchema = z.
|
|
3
|
+
const PkgInfoSchema = z.looseObject({
|
|
4
4
|
name: z.string(),
|
|
5
5
|
version: z.string(),
|
|
6
6
|
description: z.string().optional(),
|
|
7
|
-
homepage: z.
|
|
7
|
+
homepage: z.url().optional(),
|
|
8
8
|
});
|
|
9
9
|
export const pkgInfo = PkgInfoSchema.parse(packageJsonRaw);
|
package/dist/prompts.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { ErrorCode as SdkErrorCode, McpError as SdkMcpError, } from '@modelcontextprotocol/sdk/types.js';
|
|
1
2
|
import { z } from 'zod';
|
|
2
|
-
import { ErrorCode, McpError } from './lib/errors.js';
|
|
3
3
|
import { buildToolInfo, getSortedToolContracts, } from './resources/tool-info.js';
|
|
4
4
|
import { withDefaultIcons } from './tools/shared.js';
|
|
5
5
|
const HELP_PROMPT_NAME = 'get-help';
|
|
@@ -123,11 +123,11 @@ export function registerGetToolHelpPrompt(server, iconInfo) {
|
|
|
123
123
|
}, ({ name }) => {
|
|
124
124
|
const toolName = findKnownToolName(name);
|
|
125
125
|
if (!toolName) {
|
|
126
|
-
throw new
|
|
126
|
+
throw new SdkMcpError(SdkErrorCode.InvalidParams, `Unknown tool: ${name}`);
|
|
127
127
|
}
|
|
128
128
|
const toolInfo = buildToolInfo(toolName);
|
|
129
129
|
if (!toolInfo) {
|
|
130
|
-
throw new
|
|
130
|
+
throw new SdkMcpError(SdkErrorCode.InvalidParams, `Unknown tool: ${toolName}`);
|
|
131
131
|
}
|
|
132
132
|
return {
|
|
133
133
|
description: GET_TOOL_HELP_PROMPT_DESCRIPTION,
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { buildToolCatalogDetailsOnly } from './tool-catalog.js';
|
|
2
|
-
import { buildCoreContextPack, formatToolNameList, getSharedConstraints,
|
|
2
|
+
import { buildCoreContextPack, formatToolNameList, getSharedConstraints, getToolContracts, pickAvailableToolNames, } from './tool-info.js';
|
|
3
3
|
import { buildWorkflowGuide } from './workflows.js';
|
|
4
|
-
function formatTaskModeLine(label, names) {
|
|
5
|
-
return `${label}: ${names.length > 0 ? formatToolNameList(names) : 'none'}.`;
|
|
6
|
-
}
|
|
7
4
|
function buildToolsOverview() {
|
|
8
5
|
const rows = [
|
|
9
6
|
['Navigate', pickAvailableToolNames(['roots', 'ls', 'tree', 'find'])],
|
|
@@ -31,9 +28,6 @@ function buildToolsOverview() {
|
|
|
31
28
|
.join('\n');
|
|
32
29
|
}
|
|
33
30
|
function buildInstructionsHeader() {
|
|
34
|
-
const taskCapable = formatToolNameList(getTaskCapableToolNames());
|
|
35
|
-
const optionalTaskTools = getTaskToolNamesBySupport('optional');
|
|
36
|
-
const requiredTaskTools = getTaskToolNamesBySupport('required');
|
|
37
31
|
return `<role>
|
|
38
32
|
Filesystem agent. Scope: allowed roots only. Discover paths before acting — never guess.
|
|
39
33
|
</role>
|
|
@@ -54,12 +48,9 @@ ${buildToolsOverview()}
|
|
|
54
48
|
</resources>
|
|
55
49
|
|
|
56
50
|
<task_protocol>
|
|
57
|
-
Task execution:
|
|
58
|
-
Task results:
|
|
51
|
+
Task execution: Check \`execution.taskSupport\` per tool — \`forbidden\` (default): never send \`task\`; \`optional\`: send \`task\` only when durable polling or deferred results are needed; \`required\`: always send \`task\`.
|
|
52
|
+
Task results: Poll via \`tasks/get\`, then retrieve the final payload via \`tasks/result\`.
|
|
59
53
|
Progress: Pass \`_meta.progressToken\` in \`tools/call\` to receive \`notifications/progress\`.
|
|
60
|
-
Task-capable: ${taskCapable || 'none'}.
|
|
61
|
-
${formatTaskModeLine('Optional task mode', optionalTaskTools)}
|
|
62
|
-
${formatTaskModeLine('Required task mode', requiredTaskTools)}
|
|
63
54
|
</task_protocol>
|
|
64
55
|
`;
|
|
65
56
|
}
|
|
@@ -15,27 +15,13 @@ function buildCrossToolDataFlow() {
|
|
|
15
15
|
}
|
|
16
16
|
function buildCatalogGuide() {
|
|
17
17
|
const taskCapable = getTaskCapableToolNames();
|
|
18
|
-
return
|
|
18
|
+
return `<tool_selection_guide>
|
|
19
19
|
## Primitive Routing
|
|
20
20
|
|
|
21
|
-
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
-
|
|
25
|
-
'`resources`' +
|
|
26
|
-
`: application-driven context such as ` +
|
|
27
|
-
'`internal://instructions`' +
|
|
28
|
-
`, ` +
|
|
29
|
-
'`internal://tool-info/{name}`' +
|
|
30
|
-
`, and cached ` +
|
|
31
|
-
'`filesystem-mcp://result/{id}`' +
|
|
32
|
-
` output.
|
|
33
|
-
- ` +
|
|
34
|
-
'`prompts`' +
|
|
35
|
-
`: user-controlled workflow templates for help, comparison, and guided inspection.
|
|
36
|
-
- ` +
|
|
37
|
-
'`completion`' +
|
|
38
|
-
`: argument suggestions for prompts and resource templates; not a discovery mechanism.
|
|
21
|
+
- \`tools\`: model-controlled operations that inspect or mutate the allowed filesystem.
|
|
22
|
+
- \`resources\`: application-driven context such as \`internal://instructions\`, \`internal://tool-info/{name}\`, and cached \`filesystem-mcp://result/{id}\` output.
|
|
23
|
+
- \`prompts\`: user-controlled workflow templates for help, comparison, and guided inspection.
|
|
24
|
+
- \`completion\`: argument suggestions for prompts and resource templates; not a discovery mechanism.
|
|
39
25
|
|
|
40
26
|
## Cross-Tool Data Flow
|
|
41
27
|
|
|
@@ -45,21 +31,10 @@ ${buildCrossToolDataFlow()}
|
|
|
45
31
|
|
|
46
32
|
## Result Contract
|
|
47
33
|
|
|
48
|
-
- Successful tools return
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
`.
|
|
53
|
-
- Tool/business failures return ` +
|
|
54
|
-
'`isError: true`' +
|
|
55
|
-
` inside the tool result, not a JSON-RPC protocol error.
|
|
56
|
-
- When a tool returns ` +
|
|
57
|
-
'`resourceUri`' +
|
|
58
|
-
` or a ` +
|
|
59
|
-
'`resource_link`' +
|
|
60
|
-
`, follow it with ` +
|
|
61
|
-
'`resources/read`' +
|
|
62
|
-
` immediately.
|
|
34
|
+
- Successful tools return \`content\` and \`structuredContent\` (when \`outputSchema\` is declared).
|
|
35
|
+
- When \`isError: true\`, \`structuredContent\` is omitted — parse the \`content\` text instead.
|
|
36
|
+
- Tool/business failures return \`isError: true\` inside the tool result, not a JSON-RPC protocol error.
|
|
37
|
+
- When a tool returns \`resourceUri\` or a \`resource_link\`, follow it with \`resources/read\` immediately.
|
|
63
38
|
|
|
64
39
|
## Task Mode Routing
|
|
65
40
|
|
|
@@ -75,12 +50,6 @@ ${buildCrossToolDataFlow()}
|
|
|
75
50
|
|
|
76
51
|
## Write Strategy
|
|
77
52
|
|
|
78
|
-
- \`edit\`: precise first-occurrence replacements.
|
|
79
|
-
- \`write\`: create files or overwrite full contents.
|
|
80
|
-
- \`search_and_replace\`: bulk multi-file replacements.
|
|
81
|
-
|
|
82
|
-
### edit vs write vs search_and_replace Decision
|
|
83
|
-
|
|
84
53
|
1. **Single file, targeted change?** -> \`edit\` (match exact text, replace first occurrence)
|
|
85
54
|
2. **Single file, full rewrite?** -> \`write\` (overwrite entire content)
|
|
86
55
|
3. **Multiple files, same change?** -> \`search_and_replace\` (glob + pattern across files)
|
|
@@ -93,7 +62,7 @@ ${buildCrossToolDataFlow()}
|
|
|
93
62
|
- \`apply_patch\` accepts unified diffs - single-file or multi-file.
|
|
94
63
|
- Multi-file: \`path\` is base directory; each file is best-effort with per-file \`results[]\`.
|
|
95
64
|
</tool_selection_guide>
|
|
96
|
-
|
|
65
|
+
`;
|
|
97
66
|
}
|
|
98
67
|
export function buildToolCatalog() {
|
|
99
68
|
return `${buildCoreContextPack()}\n\n${buildCatalogGuide()}`;
|
|
@@ -4,7 +4,6 @@ export declare function getSortedToolContracts(): ToolContract[];
|
|
|
4
4
|
export declare function pickAvailableToolNames(names: readonly string[]): string[];
|
|
5
5
|
export declare function formatToolNameList(names: readonly string[]): string;
|
|
6
6
|
export declare function getTaskCapableToolNames(): string[];
|
|
7
|
-
export declare function getTaskToolNamesBySupport(taskSupport: Extract<ToolContract['taskSupport'], 'optional' | 'required'>): string[];
|
|
8
7
|
export declare function buildCoreContextPack(): string;
|
|
9
8
|
export declare function getSharedConstraints(): string[];
|
|
10
9
|
export declare function buildToolInfo(name: string): string | undefined;
|