@j0hanz/filesystem-mcp 1.7.3 → 1.9.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 +635 -377
- package/dist/cli.js +2 -2
- package/dist/completions.js +2 -3
- package/dist/index.js +2 -2
- package/dist/lib/file-operations/{common.d.ts → core.d.ts} +6 -0
- package/dist/lib/file-operations/{common.js → core.js} +45 -0
- package/dist/lib/file-operations/metadata.d.ts +73 -0
- package/dist/lib/file-operations/metadata.js +889 -0
- package/dist/lib/file-operations/{search-content.d.ts → search.d.ts} +29 -3
- package/dist/lib/file-operations/{search-content.js → search.js} +409 -82
- package/dist/lib/file-operations/{glob-engine.d.ts → traversal.d.ts} +18 -1
- package/dist/lib/file-operations/{glob-engine.js → traversal.js} +25 -2
- package/dist/lib/fs-helpers.d.ts +1 -0
- package/dist/lib/fs-helpers.js +10 -2
- package/dist/lib/observability.js +1 -1
- package/dist/lib/{path-validation.d.ts → paths.d.ts} +4 -0
- package/dist/lib/{path-validation.js → paths.js} +112 -1
- package/dist/lib/utils.d.ts +15 -0
- package/dist/lib/utils.js +33 -0
- package/dist/resources/generated-instructions.js +6 -6
- package/dist/resources/tool-catalog.js +9 -9
- package/dist/resources/tool-info.js +3 -3
- package/dist/resources/workflows.js +10 -23
- package/dist/schemas.js +15 -15
- package/dist/server/bootstrap.d.ts +19 -1
- package/dist/server/bootstrap.js +103 -9
- package/dist/server/roots-manager.d.ts +2 -2
- package/dist/server/roots-manager.js +3 -3
- package/dist/tools/apply-patch.js +7 -3
- package/dist/tools/calculate-hash.js +15 -15
- package/dist/tools/create-directory.js +1 -1
- package/dist/tools/delete-file.js +7 -4
- package/dist/tools/diff-files.js +2 -2
- package/dist/tools/edit-file.js +14 -13
- package/dist/tools/list-directory.js +5 -7
- package/dist/tools/move-file.js +2 -1
- package/dist/tools/read-multiple.js +13 -26
- package/dist/tools/read.js +3 -3
- package/dist/tools/replace-in-files.js +17 -20
- package/dist/tools/roots.js +4 -6
- package/dist/tools/search-content.js +9 -11
- package/dist/tools/search-files.js +4 -6
- package/dist/tools/shared.d.ts +18 -1
- package/dist/tools/shared.js +39 -19
- package/dist/tools/stat-many.js +14 -24
- package/dist/tools/stat.js +4 -3
- package/dist/tools/task-support.js +1 -1
- package/dist/tools/tree.js +3 -4
- package/dist/tools/write-file.js +1 -1
- package/package.json +9 -5
- package/dist/lib/file-operations/file-info.d.ts +0 -10
- package/dist/lib/file-operations/file-info.js +0 -143
- package/dist/lib/file-operations/gitignore.d.ts +0 -6
- package/dist/lib/file-operations/gitignore.js +0 -45
- package/dist/lib/file-operations/list-directory.d.ts +0 -14
- package/dist/lib/file-operations/list-directory.js +0 -256
- package/dist/lib/file-operations/read-multiple-files.d.ts +0 -25
- package/dist/lib/file-operations/read-multiple-files.js +0 -252
- package/dist/lib/file-operations/search-files.d.ts +0 -27
- package/dist/lib/file-operations/search-files.js +0 -218
- package/dist/lib/file-operations/search-worker.d.ts +0 -2
- package/dist/lib/file-operations/search-worker.js +0 -129
- package/dist/lib/file-operations/tree.d.ts +0 -28
- package/dist/lib/file-operations/tree.js +0 -269
- package/dist/lib/path-format.d.ts +0 -1
- package/dist/lib/path-format.js +0 -7
- package/dist/lib/path-policy.d.ts +0 -2
- package/dist/lib/path-policy.js +0 -100
- package/dist/lib/type-guards.d.ts +0 -1
- package/dist/lib/type-guards.js +0 -3
- package/dist/server/capabilities.d.ts +0 -10
- package/dist/server/capabilities.js +0 -48
- package/dist/server/logging.d.ts +0 -7
- package/dist/server/logging.js +0 -41
- package/dist/server/types.d.ts +0 -4
- package/dist/server/types.js +0 -1
|
@@ -2,8 +2,8 @@ import * as fs from 'node:fs/promises';
|
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import { glob as fsGlob } from 'node:fs/promises';
|
|
4
4
|
import { getToolContextSnapshot, publishOpsTraceEnd, publishOpsTraceError, publishOpsTraceStart, shouldPublishOpsTrace, startPerfMeasure, } from '../observability.js';
|
|
5
|
-
import { toPosixPath } from '../
|
|
6
|
-
import { isRecord } from '../
|
|
5
|
+
import { toPosixPath } from '../paths.js';
|
|
6
|
+
import { isRecord } from '../utils.js';
|
|
7
7
|
const GLOB_MAGIC_RE = /[*?[\]{}!]/u;
|
|
8
8
|
const DEFAULT_MAX_HIDDEN_DEPTH = 10;
|
|
9
9
|
const GLOB_BATCH_CONCURRENCY = 64;
|
|
@@ -344,3 +344,26 @@ export async function* globEntries(options) {
|
|
|
344
344
|
endMeasure?.(ok);
|
|
345
345
|
}
|
|
346
346
|
}
|
|
347
|
+
/**
|
|
348
|
+
* Builds standard options for globEntries to ensure consistency across search tools.
|
|
349
|
+
*/
|
|
350
|
+
export function buildGlobOptions(config) {
|
|
351
|
+
const options = {
|
|
352
|
+
cwd: config.cwd,
|
|
353
|
+
pattern: config.pattern,
|
|
354
|
+
excludePatterns: config.excludePatterns ?? [],
|
|
355
|
+
includeHidden: config.includeHidden ?? false,
|
|
356
|
+
baseNameMatch: config.baseNameMatch ?? false,
|
|
357
|
+
caseSensitiveMatch: config.caseSensitiveMatch ?? true,
|
|
358
|
+
followSymbolicLinks: config.followSymbolicLinks ?? false,
|
|
359
|
+
onlyFiles: config.onlyFiles ?? true,
|
|
360
|
+
stats: config.stats ?? false,
|
|
361
|
+
};
|
|
362
|
+
if (config.suppressErrors) {
|
|
363
|
+
options.suppressErrors = config.suppressErrors;
|
|
364
|
+
}
|
|
365
|
+
if (config.maxDepth !== undefined) {
|
|
366
|
+
options.maxDepth = config.maxDepth;
|
|
367
|
+
}
|
|
368
|
+
return options;
|
|
369
|
+
}
|
package/dist/lib/fs-helpers.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export declare function createTimedAbortSignal(baseSignal: AbortSignal | undefin
|
|
|
7
7
|
signal: AbortSignal;
|
|
8
8
|
cleanup: () => void;
|
|
9
9
|
};
|
|
10
|
+
export declare function withTimedAbortSignal<T>(baseSignal: AbortSignal | undefined, timeoutMs: number | undefined, run: (signal: AbortSignal) => Promise<T>): Promise<T>;
|
|
10
11
|
interface ParallelResult<R> {
|
|
11
12
|
results: R[];
|
|
12
13
|
errors: {
|
package/dist/lib/fs-helpers.js
CHANGED
|
@@ -6,8 +6,7 @@ import { Writable } from 'node:stream';
|
|
|
6
6
|
import { pipeline } from 'node:stream/promises';
|
|
7
7
|
import { BINARY_CHECK_BUFFER_SIZE, KNOWN_BINARY_EXTENSIONS, MAX_TEXT_FILE_SIZE, PARALLEL_CONCURRENCY, } from './constants.js';
|
|
8
8
|
import { ErrorCode, McpError, normalizeUnknownError } from './errors.js';
|
|
9
|
-
import { assertAllowedFileAccess } from './
|
|
10
|
-
import { validateExistingPath } from './path-validation.js';
|
|
9
|
+
import { assertAllowedFileAccess, validateExistingPath } from './paths.js';
|
|
11
10
|
function createAbortError(message = 'Operation aborted') {
|
|
12
11
|
return new DOMException(message, 'AbortError');
|
|
13
12
|
}
|
|
@@ -110,6 +109,15 @@ export function createTimedAbortSignal(baseSignal, timeoutMs) {
|
|
|
110
109
|
}
|
|
111
110
|
return createNoopSignal();
|
|
112
111
|
}
|
|
112
|
+
export async function withTimedAbortSignal(baseSignal, timeoutMs, run) {
|
|
113
|
+
const { signal, cleanup } = createTimedAbortSignal(baseSignal, timeoutMs);
|
|
114
|
+
try {
|
|
115
|
+
return await run(signal);
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
cleanup();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
113
121
|
function createNoopSignal() {
|
|
114
122
|
return { signal: SHARED_NOOP_SIGNAL, cleanup: () => { } };
|
|
115
123
|
}
|
|
@@ -3,7 +3,7 @@ import { hash } from 'node:crypto';
|
|
|
3
3
|
import { channel, tracingChannel } from 'node:diagnostics_channel';
|
|
4
4
|
import { monitorEventLoopDelay, performance, PerformanceObserver, } from 'node:perf_hooks';
|
|
5
5
|
import { parseTrueEnvFlag } from './constants.js';
|
|
6
|
-
import { isRecord } from './
|
|
6
|
+
import { isRecord } from './utils.js';
|
|
7
7
|
// --- Configuration ---
|
|
8
8
|
const ENV = process.env;
|
|
9
9
|
let _cachedConfig;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { Root } from '@modelcontextprotocol/sdk/types.js';
|
|
2
2
|
import { McpError } from './errors.js';
|
|
3
|
+
export declare function toPosixPath(value: string): string;
|
|
4
|
+
export declare function isSensitivePath(requestedPath: string, resolvedPath?: string): boolean;
|
|
5
|
+
export declare function assertAllowedFileAccess(requestedPath: string, resolvedPath?: string): void;
|
|
3
6
|
/**
|
|
4
7
|
* Normalizes any path-like input to an absolute path suitable for comparisons.
|
|
5
8
|
* - Expands "~" home directory shorthand.
|
|
@@ -8,6 +11,7 @@ import { McpError } from './errors.js';
|
|
|
8
11
|
*/
|
|
9
12
|
export declare function normalizePath(p: string): string;
|
|
10
13
|
export declare function getAllowedDirectories(): string[];
|
|
14
|
+
export declare function isAllowedDirectoryRoot(normalizedPath: string): boolean;
|
|
11
15
|
export declare function isPathWithinDirectories(normalizedPath: string, allowedDirs: readonly string[]): boolean;
|
|
12
16
|
export declare function setAllowedDirectoriesResolved(dirs: readonly string[], signal?: AbortSignal): Promise<void>;
|
|
13
17
|
export declare function getReservedDeviceNameForPath(requestedPath: string): string | undefined;
|
|
@@ -1,10 +1,113 @@
|
|
|
1
1
|
import * as fs from 'node:fs/promises';
|
|
2
2
|
import * as os from 'node:os';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
|
+
import { platform } from 'node:os';
|
|
4
5
|
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { SENSITIVE_FILE_ALLOWLIST, SENSITIVE_FILE_DENYLIST, } from './constants.js';
|
|
5
7
|
import { ErrorCode, isAbortError, isNodeError, McpError } from './errors.js';
|
|
6
8
|
import { assertNotAborted, withAbort } from './fs-helpers.js';
|
|
7
|
-
const
|
|
9
|
+
const WINDOWS_PATH_SEPARATOR = '\\';
|
|
10
|
+
const POSIX_PATH_SEPARATOR = '/';
|
|
11
|
+
export function toPosixPath(value) {
|
|
12
|
+
return value.includes(WINDOWS_PATH_SEPARATOR)
|
|
13
|
+
? value.replace(/\\/gu, POSIX_PATH_SEPARATOR)
|
|
14
|
+
: value;
|
|
15
|
+
}
|
|
16
|
+
const IS_WINDOWS = platform() === 'win32';
|
|
17
|
+
const WINDOWS_ABSOLUTE_RE = /^[a-z]:\//iu;
|
|
18
|
+
function normalizePathForMatch(input) {
|
|
19
|
+
return toPosixPath(path.normalize(input));
|
|
20
|
+
}
|
|
21
|
+
function normalizeForMatch(input) {
|
|
22
|
+
const normalized = normalizePathForMatch(input);
|
|
23
|
+
return IS_WINDOWS ? normalized.toLowerCase() : normalized;
|
|
24
|
+
}
|
|
25
|
+
function compilePatternGlobs(normalizedPattern) {
|
|
26
|
+
const globs = new Set([normalizedPattern]);
|
|
27
|
+
const isWindowsAbsolute = WINDOWS_ABSOLUTE_RE.test(normalizedPattern);
|
|
28
|
+
if (!normalizedPattern.startsWith('**/') && !isWindowsAbsolute) {
|
|
29
|
+
const withoutRoot = normalizedPattern.replace(/^\/+/u, '');
|
|
30
|
+
if (withoutRoot.length > 0) {
|
|
31
|
+
globs.add(`**/${withoutRoot}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return [...globs];
|
|
35
|
+
}
|
|
36
|
+
function compilePatterns(patterns) {
|
|
37
|
+
const unique = new Set();
|
|
38
|
+
for (const pattern of patterns) {
|
|
39
|
+
const trimmed = pattern.trim();
|
|
40
|
+
if (trimmed.length > 0) {
|
|
41
|
+
unique.add(trimmed);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const compiled = [];
|
|
45
|
+
for (const pattern of unique) {
|
|
46
|
+
const normalized = normalizeForMatch(pattern);
|
|
47
|
+
const matchesPath = normalized.includes('/');
|
|
48
|
+
compiled.push({
|
|
49
|
+
globs: matchesPath ? compilePatternGlobs(normalized) : [normalized],
|
|
50
|
+
matchesPath,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return compiled;
|
|
54
|
+
}
|
|
55
|
+
function toPatternSet(patterns) {
|
|
56
|
+
const pathGlobs = new Set();
|
|
57
|
+
const nameGlobs = new Set();
|
|
58
|
+
for (const pattern of patterns) {
|
|
59
|
+
const target = pattern.matchesPath ? pathGlobs : nameGlobs;
|
|
60
|
+
for (const glob of pattern.globs) {
|
|
61
|
+
target.add(glob);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
pathGlobs: [...pathGlobs],
|
|
66
|
+
nameGlobs: [...nameGlobs],
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const DENY_PATTERNS = toPatternSet(compilePatterns(SENSITIVE_FILE_DENYLIST));
|
|
70
|
+
const ALLOW_PATTERNS = toPatternSet(compilePatterns(SENSITIVE_FILE_ALLOWLIST));
|
|
71
|
+
function uniquePair(primary, secondary) {
|
|
72
|
+
if (!secondary || secondary === primary)
|
|
73
|
+
return [primary];
|
|
74
|
+
return [primary, secondary];
|
|
75
|
+
}
|
|
76
|
+
function matchesAnyGlobs(globs, candidates) {
|
|
77
|
+
if (globs.length === 0 || candidates.length === 0)
|
|
78
|
+
return false;
|
|
79
|
+
for (const candidate of candidates) {
|
|
80
|
+
for (const glob of globs) {
|
|
81
|
+
if (path.posix.matchesGlob(candidate, glob))
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
export function isSensitivePath(requestedPath, resolvedPath) {
|
|
88
|
+
if (DENY_PATTERNS.pathGlobs.length === 0 &&
|
|
89
|
+
DENY_PATTERNS.nameGlobs.length === 0) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
const normalizedRequested = normalizeForMatch(requestedPath);
|
|
93
|
+
const normalizedResolved = resolvedPath
|
|
94
|
+
? normalizeForMatch(resolvedPath)
|
|
95
|
+
: undefined;
|
|
96
|
+
const pathCandidates = uniquePair(normalizedRequested, normalizedResolved);
|
|
97
|
+
const nameCandidates = uniquePair(path.posix.basename(normalizedRequested), normalizedResolved ? path.posix.basename(normalizedResolved) : undefined);
|
|
98
|
+
if (matchesAnyGlobs(ALLOW_PATTERNS.pathGlobs, pathCandidates) ||
|
|
99
|
+
matchesAnyGlobs(ALLOW_PATTERNS.nameGlobs, nameCandidates)) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
return (matchesAnyGlobs(DENY_PATTERNS.pathGlobs, pathCandidates) ||
|
|
103
|
+
matchesAnyGlobs(DENY_PATTERNS.nameGlobs, nameCandidates));
|
|
104
|
+
}
|
|
105
|
+
export function assertAllowedFileAccess(requestedPath, resolvedPath) {
|
|
106
|
+
if (!isSensitivePath(requestedPath, resolvedPath))
|
|
107
|
+
return;
|
|
108
|
+
throw new McpError(ErrorCode.E_ACCESS_DENIED, `Access denied: sensitive file blocked by policy (${requestedPath}). ` +
|
|
109
|
+
'Set FS_CONTEXT_ALLOW_SENSITIVE=1 or use FS_CONTEXT_ALLOWLIST to override.', requestedPath);
|
|
110
|
+
}
|
|
8
111
|
const HOMEDIR = os.homedir();
|
|
9
112
|
const PATH_SEPARATOR = path.sep;
|
|
10
113
|
const DRIVE_LETTER_REGEX = /^[A-Za-z]:/;
|
|
@@ -124,6 +227,13 @@ function setAllowedDirectoriesState(primary, expanded) {
|
|
|
124
227
|
export function getAllowedDirectories() {
|
|
125
228
|
return [...allowedDirectoriesExpanded];
|
|
126
229
|
}
|
|
230
|
+
export function isAllowedDirectoryRoot(normalizedPath) {
|
|
231
|
+
for (const dir of allowedDirectoriesExpanded) {
|
|
232
|
+
if (isSamePath(normalizedPath, dir))
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
127
237
|
function getAllowedDirectoriesForRelativeResolution() {
|
|
128
238
|
return allowedDirectoriesPrimary.length > 0
|
|
129
239
|
? allowedDirectoriesPrimary
|
|
@@ -386,6 +496,7 @@ export async function validatePathForWrite(requestedPath, signal) {
|
|
|
386
496
|
allowedDirs,
|
|
387
497
|
details: { normalizedPath: normalizedRequested },
|
|
388
498
|
});
|
|
499
|
+
assertAllowedFileAccess(requestedPath, normalizedRequested);
|
|
389
500
|
let current = normalizedRequested;
|
|
390
501
|
for (;;) {
|
|
391
502
|
try {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
2
|
+
export declare function mergeOptions<T extends object>(defaults: T, overrides: Partial<T>): T;
|
|
3
|
+
export declare function omitOptionKeys<T extends object, K extends keyof T>(input: T, keys: readonly K[]): Omit<T, K>;
|
|
4
|
+
export declare function setIfDefined<T extends object, K extends keyof T>(target: T, key: K, value: T[K] | undefined): void;
|
|
5
|
+
export interface ProgressPayload {
|
|
6
|
+
current: number;
|
|
7
|
+
total?: number;
|
|
8
|
+
}
|
|
9
|
+
export type ProgressCallback = ((progress: ProgressPayload) => void) | undefined;
|
|
10
|
+
export interface PeriodicProgressOptions {
|
|
11
|
+
total?: number;
|
|
12
|
+
throttleModulo?: number;
|
|
13
|
+
force?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function reportPeriodicProgress(onProgress: ProgressCallback, current: number, options?: PeriodicProgressOptions): void;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// type-guards.ts
|
|
2
|
+
export function isRecord(value) {
|
|
3
|
+
return value !== null && typeof value === 'object';
|
|
4
|
+
}
|
|
5
|
+
// option-utils.ts
|
|
6
|
+
export function mergeOptions(defaults, overrides) {
|
|
7
|
+
return { ...defaults, ...overrides };
|
|
8
|
+
}
|
|
9
|
+
export function omitOptionKeys(input, keys) {
|
|
10
|
+
const output = { ...input };
|
|
11
|
+
for (const key of keys) {
|
|
12
|
+
Reflect.deleteProperty(output, key);
|
|
13
|
+
}
|
|
14
|
+
return output;
|
|
15
|
+
}
|
|
16
|
+
export function setIfDefined(target, key, value) {
|
|
17
|
+
if (value !== undefined) {
|
|
18
|
+
target[key] = value;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function reportPeriodicProgress(onProgress, current, options = {}) {
|
|
22
|
+
if (!onProgress || current === 0)
|
|
23
|
+
return;
|
|
24
|
+
const throttleModulo = options.throttleModulo ?? 1;
|
|
25
|
+
const force = options.force ?? false;
|
|
26
|
+
if (!force && throttleModulo > 1 && current % throttleModulo !== 0) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
onProgress({
|
|
30
|
+
current,
|
|
31
|
+
...(options.total !== undefined ? { total: options.total } : {}),
|
|
32
|
+
});
|
|
33
|
+
}
|
|
@@ -2,7 +2,7 @@ import { buildToolCatalogDetailsOnly } from './tool-catalog.js';
|
|
|
2
2
|
import { buildCoreContextPack, getSharedConstraints, getToolContracts, } from './tool-info.js';
|
|
3
3
|
import { buildWorkflowGuide } from './workflows.js';
|
|
4
4
|
const INSTRUCTIONS_HEADER = `<role>
|
|
5
|
-
Filesystem agent
|
|
5
|
+
Filesystem agent. Scope: allowed roots only. Discover paths before acting — never guess.
|
|
6
6
|
</role>
|
|
7
7
|
|
|
8
8
|
<tools_overview>
|
|
@@ -16,10 +16,10 @@ Filesystem agent for local paths only. Operate inside allowed roots. Discover be
|
|
|
16
16
|
|
|
17
17
|
<resources>
|
|
18
18
|
- \`internal://instructions\`: Full usage reference.
|
|
19
|
-
- \`internal://tool-catalog\`: Tool routing and data
|
|
19
|
+
- \`internal://tool-catalog\`: Tool routing and data flow.
|
|
20
20
|
- \`internal://workflows\`: Standard execution sequences.
|
|
21
|
-
- \`internal://tool-info/{name}\`: Per-tool nuances
|
|
22
|
-
- \`filesystem-mcp://result/{id}\`: Cached large output
|
|
21
|
+
- \`internal://tool-info/{name}\`: Per-tool nuances (e.g. \`internal://tool-info/read\`).
|
|
22
|
+
- \`filesystem-mcp://result/{id}\`: Cached large output — call \`resources/read\` immediately when \`resourceUri\` is returned.
|
|
23
23
|
- \`filesystem-mcp://metrics\`: Per-tool runtime metrics.
|
|
24
24
|
</resources>
|
|
25
25
|
|
|
@@ -35,8 +35,8 @@ ${getSharedConstraints()
|
|
|
35
35
|
</constraints>
|
|
36
36
|
|
|
37
37
|
<error_handling>
|
|
38
|
-
- \`E_ACCESS_DENIED\` => call \`roots\`,
|
|
39
|
-
- \`E_NOT_FOUND\` => call \`ls\` or \`find\`,
|
|
38
|
+
- \`E_ACCESS_DENIED\` => call \`roots\`, use an allowed path.
|
|
39
|
+
- \`E_NOT_FOUND\` => call \`ls\` or \`find\`, verify spelling.
|
|
40
40
|
- \`E_TOO_LARGE\` => use \`head\`, line ranges, or \`read_many\`.
|
|
41
41
|
- \`E_TIMEOUT\` => reduce scope or result limits.
|
|
42
42
|
</error_handling>
|
|
@@ -3,26 +3,26 @@ const CATALOG_GUIDE = `<tool_selection_guide>
|
|
|
3
3
|
## Cross-Tool Data Flow
|
|
4
4
|
|
|
5
5
|
\`\`\`
|
|
6
|
-
find
|
|
7
|
-
diff_files
|
|
6
|
+
find(results[].path) -> grep.paths
|
|
7
|
+
diff_files(patch) -> apply_patch.patch
|
|
8
8
|
\`\`\`
|
|
9
9
|
|
|
10
10
|
## Search Strategy
|
|
11
11
|
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
12
|
+
- \`find\`: glob file discovery.
|
|
13
|
+
- \`grep\`: text content search.
|
|
14
|
+
- \`search_and_replace\`: replacement only, not discovery.
|
|
15
15
|
|
|
16
16
|
## Write Strategy
|
|
17
17
|
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
18
|
+
- \`edit\`: precise first-occurrence replacements.
|
|
19
|
+
- \`write\`: create files or overwrite full contents.
|
|
20
|
+
- \`search_and_replace\`: bulk multi-file replacements.
|
|
21
21
|
|
|
22
22
|
## Patch Management
|
|
23
23
|
|
|
24
24
|
- Generate patches with \`diff_files\` first.
|
|
25
|
-
-
|
|
25
|
+
- Validate with \`apply_patch(dryRun:true)\` before writing.
|
|
26
26
|
- \`apply_patch\` accepts unified diffs only.
|
|
27
27
|
</tool_selection_guide>
|
|
28
28
|
`;
|
|
@@ -37,10 +37,10 @@ export function buildCoreContextPack() {
|
|
|
37
37
|
}
|
|
38
38
|
export function getSharedConstraints() {
|
|
39
39
|
return [
|
|
40
|
-
'Use allowed roots only (
|
|
40
|
+
'Use allowed roots only (from CLI negotiation).',
|
|
41
41
|
'Sensitive paths are denylisted by default.',
|
|
42
|
-
`Limits
|
|
43
|
-
'If
|
|
42
|
+
`Limits enforced: max file size ${Math.floor(MAX_TEXT_FILE_SIZE / 1024 / 1024)}MB; search caps ${MAX_SEARCH_RESULTS} files, ${DEFAULT_SEARCH_CONTENT_RESULTS} content matches.`,
|
|
43
|
+
'If response includes `resourceUri`, call `resources/read` immediately — cached results expire on restart.',
|
|
44
44
|
];
|
|
45
45
|
}
|
|
46
46
|
export function buildToolInfo(name) {
|
|
@@ -1,33 +1,20 @@
|
|
|
1
1
|
export function buildWorkflowGuide() {
|
|
2
2
|
return `<workflows>
|
|
3
|
-
### A: EXPLORE
|
|
4
|
-
|
|
5
|
-
1. \`roots\` (list allowed paths).
|
|
6
|
-
2. \`ls\` (flat view) or \`tree\` (recursive view).
|
|
7
|
-
3. \`stat\` or \`stat_many\` (type and size checks).
|
|
8
|
-
4. \`read\` or \`read_many\` (read content).
|
|
3
|
+
### A: EXPLORE — directory layout or file content
|
|
4
|
+
1. \`roots\` → \`ls\` or \`tree\` → \`stat\`/\`stat_many\` → \`read\`/\`read_many\`.
|
|
9
5
|
> **Strict:** Resolve paths first. Never guess.
|
|
10
6
|
|
|
11
|
-
### B: SEARCH
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
2. \`grep\` (content matches).
|
|
15
|
-
3. \`read\` (verify matched context).
|
|
16
|
-
> **Strict:** Do content search with \`grep\`, not \`find\`.
|
|
7
|
+
### B: SEARCH — files by pattern or content
|
|
8
|
+
1. \`find\` (glob) → \`grep\` (content) → \`read\` (verify).
|
|
9
|
+
> **Strict:** Content search with \`grep\`, not \`find\`.
|
|
17
10
|
|
|
18
|
-
### C: EDIT
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
2. \`search_and_replace\` (bulk replacements).
|
|
22
|
-
3. \`mv\` or \`rm\` (layout changes).
|
|
23
|
-
4. \`mkdir\` (directory creation).
|
|
11
|
+
### C: EDIT — modify files or layout
|
|
12
|
+
1. \`edit\` (targeted replacement) or \`search_and_replace\` (bulk).
|
|
13
|
+
2. \`mv\`/\`rm\` (layout) or \`mkdir\` (create dirs).
|
|
24
14
|
> **Strict:** Confirm destructive ops (\`write\`, \`mv\`, \`rm\`, bulk replace).
|
|
25
15
|
|
|
26
|
-
### D: PATCH
|
|
27
|
-
|
|
28
|
-
1. \`diff_files\` (generate).
|
|
29
|
-
2. \`apply_patch\` (dryRun: true).
|
|
30
|
-
3. \`apply_patch\` (dryRun: false).
|
|
16
|
+
### D: PATCH — apply unified diffs
|
|
17
|
+
1. \`diff_files\` → \`apply_patch(dryRun:true)\` → \`apply_patch\`.
|
|
31
18
|
> **Tip:** Feed \`diff_files\` output directly to \`apply_patch\`.
|
|
32
19
|
</workflows>`;
|
|
33
20
|
}
|
package/dist/schemas.js
CHANGED
|
@@ -16,8 +16,8 @@ function isSafeGlobPattern(value) {
|
|
|
16
16
|
return true;
|
|
17
17
|
}
|
|
18
18
|
const MAX_PATH_LENGTH = 4096;
|
|
19
|
-
const DESC_PATH_ROOT = 'Base directory (default: root). Absolute path required if multiple roots
|
|
20
|
-
const DESC_PATH_REQUIRED = 'Absolute path to file or directory.
|
|
19
|
+
const DESC_PATH_ROOT = 'Base directory (default: root). Absolute path required if multiple roots.';
|
|
20
|
+
const DESC_PATH_REQUIRED = 'Absolute path to file or directory.';
|
|
21
21
|
function defaultFalseBoolean(description) {
|
|
22
22
|
return z.boolean().optional().default(false).describe(description);
|
|
23
23
|
}
|
|
@@ -209,9 +209,9 @@ export const SearchContentInputSchema = z.strictObject({
|
|
|
209
209
|
.string()
|
|
210
210
|
.min(1, 'Pattern required')
|
|
211
211
|
.max(1000, 'Max 1000 chars')
|
|
212
|
-
.describe('
|
|
213
|
-
isRegex: defaultFalseBoolean('Treat pattern as
|
|
214
|
-
caseSensitive: defaultFalseBoolean('Case-sensitive matching
|
|
212
|
+
.describe('Search text. RE2 regex when `isRegex=true`.'),
|
|
213
|
+
isRegex: defaultFalseBoolean('Treat pattern as RE2 regex (no lookahead/lookbehind/backrefs).'),
|
|
214
|
+
caseSensitive: defaultFalseBoolean('Case-sensitive matching. Default: case-insensitive.'),
|
|
215
215
|
wholeWord: defaultFalseBoolean('Match whole words only'),
|
|
216
216
|
contextLines: z
|
|
217
217
|
.number()
|
|
@@ -459,15 +459,15 @@ export const EditFileInputSchema = z.strictObject({
|
|
|
459
459
|
.array(z.strictObject({
|
|
460
460
|
oldText: z
|
|
461
461
|
.string()
|
|
462
|
-
.describe('Exact literal string to replace
|
|
462
|
+
.describe('Exact literal string to replace (character-for-character). Include 3–5 lines of context for unique targeting.'),
|
|
463
463
|
newText: z
|
|
464
464
|
.string()
|
|
465
|
-
.describe('Replacement string
|
|
465
|
+
.describe('Replacement string. Preserve surrounding indentation style.'),
|
|
466
466
|
}))
|
|
467
467
|
.min(1, 'Min 1 edit required')
|
|
468
468
|
.describe('List of replacements to apply sequentially. Each edit replaces the first occurrence of oldText.'),
|
|
469
|
-
dryRun: defaultFalseBoolean('Preview edits without writing. Check unmatchedEdits in
|
|
470
|
-
ignoreWhitespace: defaultFalseBoolean('
|
|
469
|
+
dryRun: defaultFalseBoolean('Preview edits without writing. Check `unmatchedEdits` in response.'),
|
|
470
|
+
ignoreWhitespace: defaultFalseBoolean('Treat all whitespace sequences as equivalent when matching oldText.'),
|
|
471
471
|
});
|
|
472
472
|
export const EditFileOutputSchema = z.strictObject({
|
|
473
473
|
ok: z.boolean(),
|
|
@@ -564,7 +564,7 @@ export const ApplyPatchInputSchema = z.strictObject({
|
|
|
564
564
|
path: RequiredPathSchema.describe('Path to file to patch'),
|
|
565
565
|
patch: z
|
|
566
566
|
.string()
|
|
567
|
-
.describe('Unified diff
|
|
567
|
+
.describe('Unified diff with @@ hunk headers. Generate with `diff_files`.'),
|
|
568
568
|
fuzzFactor: z
|
|
569
569
|
.number()
|
|
570
570
|
.int({ error: 'Must be integer' })
|
|
@@ -581,7 +581,7 @@ export const ApplyPatchInputSchema = z.strictObject({
|
|
|
581
581
|
.boolean()
|
|
582
582
|
.optional()
|
|
583
583
|
.default(false)
|
|
584
|
-
.describe('Validate
|
|
584
|
+
.describe('Validate patch without writing. Check `applied` before committing.'),
|
|
585
585
|
});
|
|
586
586
|
export const ApplyPatchOutputSchema = z.strictObject({
|
|
587
587
|
ok: z.boolean(),
|
|
@@ -602,18 +602,18 @@ export const SearchAndReplaceInputSchema = z.strictObject({
|
|
|
602
602
|
searchPattern: z
|
|
603
603
|
.string()
|
|
604
604
|
.min(1, 'Search pattern required')
|
|
605
|
-
.describe('Text to search for.
|
|
605
|
+
.describe('Text to search for. Literal by default; RE2 regex when `isRegex=true`.'),
|
|
606
606
|
replacement: z.string().describe('Replacement text'),
|
|
607
|
-
isRegex: defaultFalseBoolean('Treat searchPattern as
|
|
607
|
+
isRegex: defaultFalseBoolean('Treat searchPattern as RE2 regex. Supports capture groups ($1, $2) in replacement.'),
|
|
608
608
|
dryRun: defaultFalseBoolean('Preview matches without writing. Check changedFiles and matches in the response before committing.'),
|
|
609
609
|
includeHidden: z
|
|
610
610
|
.boolean()
|
|
611
611
|
.optional()
|
|
612
|
-
.describe('Include hidden files
|
|
612
|
+
.describe('Include hidden files/directories (starting with .). Default: false.'),
|
|
613
613
|
includeIgnored: z
|
|
614
614
|
.boolean()
|
|
615
615
|
.optional()
|
|
616
|
-
.describe('Include
|
|
616
|
+
.describe('Include .gitignore-ignored files (node_modules, dist). Default: false.'),
|
|
617
617
|
returnDiff: z
|
|
618
618
|
.boolean()
|
|
619
619
|
.optional()
|
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
import * as http from 'node:http';
|
|
2
2
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
-
import type {
|
|
3
|
+
import type { LoggingLevel } from '@modelcontextprotocol/sdk/types.js';
|
|
4
|
+
export interface ServerOptions {
|
|
5
|
+
allowCwd?: boolean;
|
|
6
|
+
cliAllowedDirs?: string[];
|
|
7
|
+
}
|
|
8
|
+
interface CapabilityOptions {
|
|
9
|
+
enablePromptListChanged?: boolean;
|
|
10
|
+
enableTaskToolRequests?: boolean;
|
|
11
|
+
}
|
|
12
|
+
type ServerCapabilities = NonNullable<ConstructorParameters<typeof McpServer>[1]>['capabilities'];
|
|
13
|
+
type NonOptionalServerCapabilities = NonNullable<ServerCapabilities>;
|
|
14
|
+
export declare function buildServerCapabilities(options?: CapabilityOptions): NonOptionalServerCapabilities;
|
|
15
|
+
export declare function supportsTaskToolRequests(): boolean;
|
|
16
|
+
export interface LoggingState {
|
|
17
|
+
minimumLevel: LoggingLevel;
|
|
18
|
+
}
|
|
19
|
+
export declare function createLoggingState(minimumLevel?: LoggingLevel): LoggingState;
|
|
20
|
+
export declare function logToMcp(server: McpServer | undefined, level: LoggingLevel, data: string, minLevel?: LoggingLevel): void;
|
|
4
21
|
export declare function createServer(options?: ServerOptions): Promise<McpServer>;
|
|
5
22
|
export declare function startServer(server: McpServer): Promise<void>;
|
|
6
23
|
export declare function startHttpServer(port: number, options: ServerOptions): Promise<http.Server>;
|
|
24
|
+
export {};
|