@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.
Files changed (76) hide show
  1. package/README.md +635 -377
  2. package/dist/cli.js +2 -2
  3. package/dist/completions.js +2 -3
  4. package/dist/index.js +2 -2
  5. package/dist/lib/file-operations/{common.d.ts → core.d.ts} +6 -0
  6. package/dist/lib/file-operations/{common.js → core.js} +45 -0
  7. package/dist/lib/file-operations/metadata.d.ts +73 -0
  8. package/dist/lib/file-operations/metadata.js +889 -0
  9. package/dist/lib/file-operations/{search-content.d.ts → search.d.ts} +29 -3
  10. package/dist/lib/file-operations/{search-content.js → search.js} +409 -82
  11. package/dist/lib/file-operations/{glob-engine.d.ts → traversal.d.ts} +18 -1
  12. package/dist/lib/file-operations/{glob-engine.js → traversal.js} +25 -2
  13. package/dist/lib/fs-helpers.d.ts +1 -0
  14. package/dist/lib/fs-helpers.js +10 -2
  15. package/dist/lib/observability.js +1 -1
  16. package/dist/lib/{path-validation.d.ts → paths.d.ts} +4 -0
  17. package/dist/lib/{path-validation.js → paths.js} +112 -1
  18. package/dist/lib/utils.d.ts +15 -0
  19. package/dist/lib/utils.js +33 -0
  20. package/dist/resources/generated-instructions.js +6 -6
  21. package/dist/resources/tool-catalog.js +9 -9
  22. package/dist/resources/tool-info.js +3 -3
  23. package/dist/resources/workflows.js +10 -23
  24. package/dist/schemas.js +15 -15
  25. package/dist/server/bootstrap.d.ts +19 -1
  26. package/dist/server/bootstrap.js +103 -9
  27. package/dist/server/roots-manager.d.ts +2 -2
  28. package/dist/server/roots-manager.js +3 -3
  29. package/dist/tools/apply-patch.js +7 -3
  30. package/dist/tools/calculate-hash.js +15 -15
  31. package/dist/tools/create-directory.js +1 -1
  32. package/dist/tools/delete-file.js +7 -4
  33. package/dist/tools/diff-files.js +2 -2
  34. package/dist/tools/edit-file.js +14 -13
  35. package/dist/tools/list-directory.js +5 -7
  36. package/dist/tools/move-file.js +2 -1
  37. package/dist/tools/read-multiple.js +13 -26
  38. package/dist/tools/read.js +3 -3
  39. package/dist/tools/replace-in-files.js +17 -20
  40. package/dist/tools/roots.js +4 -6
  41. package/dist/tools/search-content.js +9 -11
  42. package/dist/tools/search-files.js +4 -6
  43. package/dist/tools/shared.d.ts +18 -1
  44. package/dist/tools/shared.js +39 -19
  45. package/dist/tools/stat-many.js +14 -24
  46. package/dist/tools/stat.js +4 -3
  47. package/dist/tools/task-support.js +1 -1
  48. package/dist/tools/tree.js +3 -4
  49. package/dist/tools/write-file.js +1 -1
  50. package/package.json +9 -5
  51. package/dist/lib/file-operations/file-info.d.ts +0 -10
  52. package/dist/lib/file-operations/file-info.js +0 -143
  53. package/dist/lib/file-operations/gitignore.d.ts +0 -6
  54. package/dist/lib/file-operations/gitignore.js +0 -45
  55. package/dist/lib/file-operations/list-directory.d.ts +0 -14
  56. package/dist/lib/file-operations/list-directory.js +0 -256
  57. package/dist/lib/file-operations/read-multiple-files.d.ts +0 -25
  58. package/dist/lib/file-operations/read-multiple-files.js +0 -252
  59. package/dist/lib/file-operations/search-files.d.ts +0 -27
  60. package/dist/lib/file-operations/search-files.js +0 -218
  61. package/dist/lib/file-operations/search-worker.d.ts +0 -2
  62. package/dist/lib/file-operations/search-worker.js +0 -129
  63. package/dist/lib/file-operations/tree.d.ts +0 -28
  64. package/dist/lib/file-operations/tree.js +0 -269
  65. package/dist/lib/path-format.d.ts +0 -1
  66. package/dist/lib/path-format.js +0 -7
  67. package/dist/lib/path-policy.d.ts +0 -2
  68. package/dist/lib/path-policy.js +0 -100
  69. package/dist/lib/type-guards.d.ts +0 -1
  70. package/dist/lib/type-guards.js +0 -3
  71. package/dist/server/capabilities.d.ts +0 -10
  72. package/dist/server/capabilities.js +0 -48
  73. package/dist/server/logging.d.ts +0 -7
  74. package/dist/server/logging.js +0 -41
  75. package/dist/server/types.d.ts +0 -4
  76. 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 '../path-format.js';
6
- import { isRecord } from '../type-guards.js';
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
+ }
@@ -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: {
@@ -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 './path-policy.js';
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 './type-guards.js';
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 IS_WINDOWS = os.platform() === 'win32';
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 for local paths only. Operate inside allowed roots. Discover before action. Never guess paths.
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-flow rules.
19
+ - \`internal://tool-catalog\`: Tool routing and data flow.
20
20
  - \`internal://workflows\`: Standard execution sequences.
21
- - \`internal://tool-info/{name}\`: Per-tool nuances and gotchas (example: \`internal://tool-info/read\`).
22
- - \`filesystem-mcp://result/{id}\`: Cached large output. If \`resourceUri\` is returned, call \`resources/read\` immediately.
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\`, then use an allowed path.
39
- - \`E_NOT_FOUND\` => call \`ls\` or \`find\`, then verify spelling.
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 (results[].path) -> grep.paths
7
- diff_files (patch text) -> apply_patch.patch
6
+ find(results[].path) -> grep.paths
7
+ diff_files(patch) -> apply_patch.patch
8
8
  \`\`\`
9
9
 
10
10
  ## Search Strategy
11
11
 
12
- - Use \`find\` for glob file discovery.
13
- - Use \`grep\` for text search.
14
- - Use \`search_and_replace\` only for replacement, never discovery.
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
- - Use \`edit\` for precise, first-occurrence replacements in existing files.
19
- - Use \`write\` to create files or overwrite full contents.
20
- - Use \`search_and_replace\` for bulk multi-file replacements.
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
- - Run \`apply_patch\` with \`dryRun: true\` before writing.
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 (provided by CLI negotiation).',
40
+ 'Use allowed roots only (from CLI negotiation).',
41
41
  'Sensitive paths are denylisted by default.',
42
- `Limits are enforced: max file size ${Math.floor(MAX_TEXT_FILE_SIZE / 1024 / 1024)}MB; search caps ${MAX_SEARCH_RESULTS} files and ${DEFAULT_SEARCH_CONTENT_RESULTS} lines.`,
43
- 'If a response includes `resourceUri`, call `resources/read` immediately; cached results expire on process restart.',
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
- Use when: you need directory layout or file content.
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
- Use when: you need files by pattern or content.
13
- 1. \`find\` (glob candidates).
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
- Use when: you need to modify files or layout.
20
- 1. \`edit\` (targeted string replacement).
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
- Use when: applying unified diffs from \`diff_files\`.
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 exist. Examples: "src", "src/components"';
20
- const DESC_PATH_REQUIRED = 'Absolute path to file or directory. Examples: "src/index.ts", "README.md"';
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('Literal text to search for by default; treated as RE2 regex when isRegex is true.'),
213
- isRegex: defaultFalseBoolean('Treat pattern as a RE2 regular expression. RE2 does not support lookahead, lookbehind, or backreferences.'),
214
- caseSensitive: defaultFalseBoolean('Case-sensitive matching (default: false — searches are case-insensitive).'),
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 — must match character-for-character including whitespace and indentation. Include 3–5 lines of surrounding context to uniquely identify the location.'),
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 preserve the indentation style of surrounding code.'),
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 the response to verify all oldText values were found.'),
470
- ignoreWhitespace: defaultFalseBoolean('Ignore leading/trailing whitespace and treat all whitespace sequences as equivalent when matching oldText.'),
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 content to apply — must include @@ hunk headers. Generate with `diff_files`.'),
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 the patch can be applied without writing. Check `applied` in the response before committing.'),
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. Matched literally by default; treated as RE2 regex when isRegex is true.'),
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 a RE2 regular expression. Supports capture group references ($1, $2) in replacement.'),
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 and directories (starting with .) in the search scope. Default: false.'),
612
+ .describe('Include hidden files/directories (starting with .). Default: false.'),
613
613
  includeIgnored: z
614
614
  .boolean()
615
615
  .optional()
616
- .describe('Include files and directories ignored by .gitignore rules (e.g. node_modules, dist). Default: false.'),
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 { ServerOptions } from './types.js';
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 {};