@j0hanz/filesystem-mcp 1.9.0 → 1.10.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 (48) hide show
  1. package/README.md +15 -15
  2. package/dist/completions.js +140 -115
  3. package/dist/lib/constants.d.ts +1 -0
  4. package/dist/lib/constants.js +2 -0
  5. package/dist/lib/file-operations/metadata.d.ts +5 -1
  6. package/dist/lib/file-operations/metadata.js +14 -1
  7. package/dist/lib/file-operations/search.d.ts +7 -5
  8. package/dist/lib/file-operations/search.js +64 -32
  9. package/dist/lib/fs-helpers.d.ts +3 -1
  10. package/dist/lib/fs-helpers.js +63 -0
  11. package/dist/lib/paths.d.ts +8 -0
  12. package/dist/lib/paths.js +119 -64
  13. package/dist/lib/resource-store.d.ts +2 -0
  14. package/dist/lib/resource-store.js +58 -17
  15. package/dist/prompts.d.ts +2 -0
  16. package/dist/prompts.js +51 -0
  17. package/dist/resources/generated-instructions.js +36 -9
  18. package/dist/resources/tool-catalog.js +30 -7
  19. package/dist/resources/tool-info.d.ts +4 -0
  20. package/dist/resources/tool-info.js +21 -3
  21. package/dist/resources/workflows.js +17 -5
  22. package/dist/schemas.d.ts +47 -12
  23. package/dist/schemas.js +75 -18
  24. package/dist/server/bootstrap.js +103 -91
  25. package/dist/server/roots-manager.d.ts +3 -0
  26. package/dist/server/roots-manager.js +15 -3
  27. package/dist/tools/apply-patch.js +135 -31
  28. package/dist/tools/calculate-hash.js +13 -8
  29. package/dist/tools/create-directory.js +14 -3
  30. package/dist/tools/delete-file.js +1 -0
  31. package/dist/tools/diff-files.js +26 -8
  32. package/dist/tools/edit-file.js +11 -8
  33. package/dist/tools/list-directory.js +1 -6
  34. package/dist/tools/move-file.js +39 -7
  35. package/dist/tools/read-multiple.js +9 -1
  36. package/dist/tools/read.js +38 -6
  37. package/dist/tools/replace-in-files.js +72 -25
  38. package/dist/tools/roots.js +1 -0
  39. package/dist/tools/search-content.js +76 -48
  40. package/dist/tools/search-files.js +6 -7
  41. package/dist/tools/shared.d.ts +2 -1
  42. package/dist/tools/shared.js +36 -20
  43. package/dist/tools/stat-many.js +1 -1
  44. package/dist/tools/stat.js +4 -0
  45. package/dist/tools/task-support.js +4 -12
  46. package/dist/tools/tree.js +4 -0
  47. package/dist/tools/write-file.js +4 -2
  48. package/package.json +17 -8
@@ -6,6 +6,7 @@ export interface TextResourceEntry {
6
6
  hash: string;
7
7
  size: number;
8
8
  storedAt: string;
9
+ expiresAt: string;
9
10
  }
10
11
  export interface ResourceStore {
11
12
  putText(params: {
@@ -21,6 +22,7 @@ interface ResourceStoreOptions {
21
22
  maxEntries: number;
22
23
  maxTotalBytes: number;
23
24
  maxEntryBytes: number;
25
+ entryTtlMs: number;
24
26
  }
25
27
  export declare function createInMemoryResourceStore(options?: Partial<ResourceStoreOptions>): ResourceStore;
26
28
  export {};
@@ -5,6 +5,7 @@ const DEFAULT_RESOURCE_STORE_OPTIONS = {
5
5
  maxEntries: 64,
6
6
  maxTotalBytes: 25 * 1024 * 1024,
7
7
  maxEntryBytes: 10 * 1024 * 1024,
8
+ entryTtlMs: 30 * 60 * 1000, // 30 minutes
8
9
  };
9
10
  const RESOURCE_STORE_DIAGNOSTICS_CHANNEL = channel('filesystem-mcp:resource-store');
10
11
  function publishResourceStoreDiagnostics(event) {
@@ -18,7 +19,15 @@ function estimateBytes(text) {
18
19
  function computeSha256(text) {
19
20
  return hash('sha256', text, 'hex');
20
21
  }
22
+ function buildIndexKey(mimeType, contentHash) {
23
+ return `${mimeType}:${contentHash}`;
24
+ }
25
+ function isExpired(entry, now = Date.now()) {
26
+ const expiresAt = Date.parse(entry.expiresAt);
27
+ return Number.isFinite(expiresAt) && expiresAt <= now;
28
+ }
21
29
  function createTextEntry(params) {
30
+ const storedAt = new Date();
22
31
  return {
23
32
  uri: params.uri,
24
33
  name: params.name,
@@ -26,7 +35,8 @@ function createTextEntry(params) {
26
35
  text: params.text,
27
36
  hash: computeSha256(params.text),
28
37
  size: estimateBytes(params.text),
29
- storedAt: new Date().toISOString(),
38
+ storedAt: storedAt.toISOString(),
39
+ expiresAt: new Date(storedAt.getTime() + params.ttlMs).toISOString(),
30
40
  };
31
41
  }
32
42
  export function createInMemoryResourceStore(options = {}) {
@@ -35,26 +45,36 @@ export function createInMemoryResourceStore(options = {}) {
35
45
  ...options,
36
46
  };
37
47
  const byUri = new Map();
38
- const byHashIndex = new Map(); // sha256hex uri
48
+ const byHashIndex = new Map(); // mimeType:sha256hex -> uri
39
49
  let totalBytes = 0;
40
- function evictOldest() {
41
- const first = byUri.keys().next();
42
- if (first.done)
43
- return;
44
- const uri = first.value;
50
+ function removeEntry(uri, reason) {
45
51
  const existing = byUri.get(uri);
46
52
  if (!existing)
47
53
  return;
48
54
  totalBytes -= existing.size;
49
55
  byUri.delete(uri);
50
- byHashIndex.delete(existing.hash);
56
+ byHashIndex.delete(buildIndexKey(existing.mimeType, existing.hash));
51
57
  publishResourceStoreDiagnostics({
52
58
  phase: 'cache_evict',
53
59
  uri,
54
60
  name: existing.name,
55
61
  bytes: existing.size,
62
+ ...(reason !== undefined ? { reason } : {}),
56
63
  });
57
64
  }
65
+ function evictOldest() {
66
+ const first = byUri.keys().next();
67
+ if (first.done)
68
+ return;
69
+ removeEntry(first.value);
70
+ }
71
+ function pruneExpiredEntries(now = Date.now()) {
72
+ for (const [uri, entry] of byUri) {
73
+ if (isExpired(entry, now)) {
74
+ removeEntry(uri, 'expired');
75
+ }
76
+ }
77
+ }
58
78
  function enforceLimits() {
59
79
  while (byUri.size > resolved.maxEntries)
60
80
  evictOldest();
@@ -65,6 +85,7 @@ export function createInMemoryResourceStore(options = {}) {
65
85
  }
66
86
  }
67
87
  function putText(params) {
88
+ pruneExpiredEntries();
68
89
  const mimeType = params.mimeType ?? 'text/plain';
69
90
  const entryBytes = estimateBytes(params.text);
70
91
  if (entryBytes > resolved.maxEntryBytes) {
@@ -76,17 +97,26 @@ export function createInMemoryResourceStore(options = {}) {
76
97
  throw new McpError(ErrorCode.E_TOO_LARGE, `Resource too large to cache (${entryBytes} bytes)`);
77
98
  }
78
99
  const contentHash = computeSha256(params.text);
79
- const existingUri = byHashIndex.get(contentHash);
100
+ const indexKey = buildIndexKey(mimeType, contentHash);
101
+ const existingUri = byHashIndex.get(indexKey);
80
102
  if (existingUri !== undefined) {
81
103
  const cached = byUri.get(existingUri);
82
104
  if (cached !== undefined) {
83
- publishResourceStoreDiagnostics({
84
- phase: 'cache_hit',
85
- uri: cached.uri,
86
- name: cached.name,
87
- bytes: cached.size,
88
- });
89
- return cached;
105
+ if (isExpired(cached)) {
106
+ removeEntry(existingUri, 'expired');
107
+ }
108
+ else {
109
+ publishResourceStoreDiagnostics({
110
+ phase: 'cache_hit',
111
+ uri: cached.uri,
112
+ name: cached.name,
113
+ bytes: cached.size,
114
+ });
115
+ return cached;
116
+ }
117
+ }
118
+ else {
119
+ byHashIndex.delete(indexKey);
90
120
  }
91
121
  }
92
122
  const id = randomUUID();
@@ -96,9 +126,10 @@ export function createInMemoryResourceStore(options = {}) {
96
126
  name: params.name,
97
127
  mimeType,
98
128
  text: params.text,
129
+ ttlMs: resolved.entryTtlMs,
99
130
  });
100
131
  byUri.set(uri, entry);
101
- byHashIndex.set(contentHash, uri);
132
+ byHashIndex.set(indexKey, uri);
102
133
  totalBytes += entryBytes;
103
134
  publishResourceStoreDiagnostics({
104
135
  phase: 'cache_store',
@@ -129,6 +160,15 @@ export function createInMemoryResourceStore(options = {}) {
129
160
  });
130
161
  throw new McpError(ErrorCode.E_NOT_FOUND, `Resource not found: ${uri}. The cached result may have been evicted. Re-run the originating tool to regenerate.`);
131
162
  }
163
+ if (isExpired(existing)) {
164
+ removeEntry(uri, 'expired');
165
+ publishResourceStoreDiagnostics({
166
+ phase: 'cache_miss',
167
+ uri,
168
+ reason: 'expired',
169
+ });
170
+ throw new McpError(ErrorCode.E_NOT_FOUND, `Resource expired: ${uri}. Re-run the originating tool to regenerate.`);
171
+ }
132
172
  publishResourceStoreDiagnostics({
133
173
  phase: 'cache_hit',
134
174
  uri: existing.uri,
@@ -148,6 +188,7 @@ export function createInMemoryResourceStore(options = {}) {
148
188
  });
149
189
  }
150
190
  function keys() {
191
+ pruneExpiredEntries();
151
192
  return Array.from(byUri.keys());
152
193
  }
153
194
  return { putText, getText, clear, keys };
package/dist/prompts.d.ts CHANGED
@@ -1,3 +1,5 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { type IconInfo } from './tools/shared.js';
3
3
  export declare function registerGetHelpPrompt(server: McpServer, instructions: string, iconInfo?: IconInfo): void;
4
+ export declare function registerCompareFilesPrompt(server: McpServer, iconInfo?: IconInfo): void;
5
+ export declare function registerAnalyzePathPrompt(server: McpServer, iconInfo?: IconInfo): void;
package/dist/prompts.js CHANGED
@@ -3,6 +3,12 @@ import { withDefaultIcons } from './tools/shared.js';
3
3
  const HELP_PROMPT_NAME = 'get-help';
4
4
  const HELP_PROMPT_TITLE = 'Get Help';
5
5
  const HELP_PROMPT_DESCRIPTION = 'Return filesystem-mcp usage instructions.';
6
+ const COMPARE_FILES_PROMPT_NAME = 'compare-files';
7
+ const COMPARE_FILES_PROMPT_TITLE = 'Compare Files';
8
+ const COMPARE_FILES_PROMPT_DESCRIPTION = 'Generate a workflow for comparing two files using diff_files.';
9
+ const ANALYZE_PATH_PROMPT_NAME = 'analyze-path';
10
+ const ANALYZE_PATH_PROMPT_TITLE = 'Analyze Path';
11
+ const ANALYZE_PATH_PROMPT_DESCRIPTION = 'Generate a workflow for analyzing a file or directory using stat, read, and tree.';
6
12
  function filterInstructionsByTopic(instructions, topic) {
7
13
  const normalized = topic.trim().toLowerCase();
8
14
  if (!normalized)
@@ -46,3 +52,48 @@ export function registerGetHelpPrompt(server, instructions, iconInfo) {
46
52
  };
47
53
  });
48
54
  }
55
+ export function registerCompareFilesPrompt(server, iconInfo) {
56
+ server.registerPrompt(COMPARE_FILES_PROMPT_NAME, {
57
+ ...withDefaultIcons({
58
+ title: COMPARE_FILES_PROMPT_TITLE,
59
+ description: COMPARE_FILES_PROMPT_DESCRIPTION,
60
+ }, iconInfo),
61
+ argsSchema: {
62
+ original: z.string().describe('Path to the original file.'),
63
+ modified: z.string().describe('Path to the modified file.'),
64
+ },
65
+ }, ({ original, modified }) => ({
66
+ description: COMPARE_FILES_PROMPT_DESCRIPTION,
67
+ messages: [
68
+ {
69
+ role: 'user',
70
+ content: {
71
+ type: 'text',
72
+ text: `Compare files and explain differences.\n\n1. Call \`diff_files\` with:\n - original: ${original}\n - modified: ${modified}\n2. Summarize: additions, deletions, and semantic changes.\n3. Flag any potential issues (conflicts, regressions, breaking changes).`,
73
+ },
74
+ },
75
+ ],
76
+ }));
77
+ }
78
+ export function registerAnalyzePathPrompt(server, iconInfo) {
79
+ server.registerPrompt(ANALYZE_PATH_PROMPT_NAME, {
80
+ ...withDefaultIcons({
81
+ title: ANALYZE_PATH_PROMPT_TITLE,
82
+ description: ANALYZE_PATH_PROMPT_DESCRIPTION,
83
+ }, iconInfo),
84
+ argsSchema: {
85
+ path: z.string().describe('Absolute path to analyze.'),
86
+ },
87
+ }, ({ path: targetPath }) => ({
88
+ description: ANALYZE_PATH_PROMPT_DESCRIPTION,
89
+ messages: [
90
+ {
91
+ role: 'user',
92
+ content: {
93
+ type: 'text',
94
+ text: `Analyze the path: ${targetPath}\n\n1. Call \`stat\` to determine if it is a file or directory.\n2. If file: call \`read\` with \`includeHash: true\` and summarize contents.\n3. If directory: call \`tree\` (maxDepth: 3) and \`ls\` to summarize structure.\n4. Report: type, size, permissions, key observations.`,
95
+ },
96
+ },
97
+ ],
98
+ }));
99
+ }
@@ -1,17 +1,42 @@
1
1
  import { buildToolCatalogDetailsOnly } from './tool-catalog.js';
2
- import { buildCoreContextPack, getSharedConstraints, getToolContracts, } from './tool-info.js';
2
+ import { buildCoreContextPack, formatToolNameList, getSharedConstraints, getTaskCapableToolNames, getToolContracts, pickAvailableToolNames, } from './tool-info.js';
3
3
  import { buildWorkflowGuide } from './workflows.js';
4
- const INSTRUCTIONS_HEADER = `<role>
4
+ function buildToolsOverview() {
5
+ const rows = [
6
+ ['Navigate', pickAvailableToolNames(['roots', 'ls', 'tree', 'find'])],
7
+ [
8
+ 'Inspect',
9
+ pickAvailableToolNames(['stat', 'stat_many', 'grep', 'calculate_hash']),
10
+ ],
11
+ ['Read', pickAvailableToolNames(['read', 'read_many', 'diff_files'])],
12
+ [
13
+ 'Write',
14
+ pickAvailableToolNames([
15
+ 'mkdir',
16
+ 'write',
17
+ 'edit',
18
+ 'mv',
19
+ 'rm',
20
+ 'apply_patch',
21
+ 'search_and_replace',
22
+ ]),
23
+ ],
24
+ ];
25
+ return rows
26
+ .filter(([, names]) => names.length > 0)
27
+ .map(([category, names]) => `| ${category} | ${formatToolNameList(names)} |`)
28
+ .join('\n');
29
+ }
30
+ function buildInstructionsHeader() {
31
+ const taskCapable = formatToolNameList(getTaskCapableToolNames());
32
+ return `<role>
5
33
  Filesystem agent. Scope: allowed roots only. Discover paths before acting — never guess.
6
34
  </role>
7
35
 
8
36
  <tools_overview>
9
37
  | Category | Tools |
10
38
  |----------|-------|
11
- | Navigate | \`roots\`, \`ls\`, \`tree\`, \`find\` |
12
- | Inspect | \`stat\`, \`stat_many\`, \`grep\`, \`calculate_hash\` |
13
- | Read | \`read\`, \`read_many\`, \`diff_files\` |
14
- | Write | \`mkdir\`, \`write\`, \`edit\`, \`mv\`, \`rm\`, \`apply_patch\`, \`search_and_replace\` |
39
+ ${buildToolsOverview()}
15
40
  </tools_overview>
16
41
 
17
42
  <resources>
@@ -24,10 +49,12 @@ Filesystem agent. Scope: allowed roots only. Discover paths before acting — ne
24
49
  </resources>
25
50
 
26
51
  <task_protocol>
27
- Async execution: pass \`_meta.progressToken\` in \`tools/call\`, poll \`tasks/get\`, then call \`tasks/result\`.
28
- Task-capable: \`find\`, \`tree\`, \`read\`, \`read_many\`, \`stat_many\`, \`grep\`, \`mkdir\`, \`write\`, \`mv\`, \`rm\`, \`calculate_hash\`, \`apply_patch\`, \`search_and_replace\`.
52
+ Task execution: Tools returning a task ID must be polled via \`tasks/get\`, then retrieved via \`tasks/result\`.
53
+ Progress: Pass \`_meta.progressToken\` in \`tools/call\` to receive \`notifications/progress\`.
54
+ Task-capable: ${taskCapable}.
29
55
  </task_protocol>
30
56
  `;
57
+ }
31
58
  const INSTRUCTIONS_FOOTER = `<constraints>
32
59
  ${getSharedConstraints()
33
60
  .map((c) => `- ${c}`)
@@ -54,7 +81,7 @@ function formatToolSection(tool) {
54
81
  export function buildServerInstructions() {
55
82
  const toolSections = getToolContracts().map(formatToolSection).join('\n\n');
56
83
  return [
57
- INSTRUCTIONS_HEADER,
84
+ buildInstructionsHeader(),
58
85
  buildCoreContextPack(),
59
86
  '',
60
87
  buildToolCatalogDetailsOnly(),
@@ -1,10 +1,24 @@
1
- import { buildCoreContextPack } from './tool-info.js';
2
- const CATALOG_GUIDE = `<tool_selection_guide>
1
+ import { buildCoreContextPack, pickAvailableToolNames } from './tool-info.js';
2
+ function buildCrossToolDataFlow() {
3
+ const flows = [];
4
+ if (pickAvailableToolNames(['find', 'read']).length === 2) {
5
+ flows.push('find.results[].path -> read.path');
6
+ }
7
+ if (pickAvailableToolNames(['grep', 'read']).length === 2) {
8
+ flows.push('grep.matches[].file -> read.path');
9
+ }
10
+ if (pickAvailableToolNames(['diff_files', 'apply_patch']).length === 2) {
11
+ flows.push('diff_files.diff -> apply_patch.patch');
12
+ }
13
+ flows.push('toolResult.resourceUri -> resources/read.uri');
14
+ return flows.join('\n');
15
+ }
16
+ function buildCatalogGuide() {
17
+ return `<tool_selection_guide>
3
18
  ## Cross-Tool Data Flow
4
19
 
5
20
  \`\`\`
6
- find(results[].path) -> grep.paths
7
- diff_files(patch) -> apply_patch.patch
21
+ ${buildCrossToolDataFlow()}
8
22
  \`\`\`
9
23
 
10
24
  ## Search Strategy
@@ -19,16 +33,25 @@ diff_files(patch) -> apply_patch.patch
19
33
  - \`write\`: create files or overwrite full contents.
20
34
  - \`search_and_replace\`: bulk multi-file replacements.
21
35
 
36
+ ### edit vs write vs search_and_replace Decision
37
+
38
+ 1. **Single file, targeted change?** -> \`edit\` (match exact text, replace first occurrence)
39
+ 2. **Single file, full rewrite?** -> \`write\` (overwrite entire content)
40
+ 3. **Multiple files, same change?** -> \`search_and_replace\` (glob + pattern across files)
41
+ 4. **Not sure what to change?** -> \`grep\` first, then decide
42
+
22
43
  ## Patch Management
23
44
 
24
45
  - Generate patches with \`diff_files\` first.
25
46
  - Validate with \`apply_patch(dryRun:true)\` before writing.
26
- - \`apply_patch\` accepts unified diffs only.
47
+ - \`apply_patch\` accepts unified diffs - single-file or multi-file.
48
+ - Multi-file: \`path\` is base directory; each file is best-effort with per-file \`results[]\`.
27
49
  </tool_selection_guide>
28
50
  `;
51
+ }
29
52
  export function buildToolCatalog() {
30
- return `${buildCoreContextPack()}\n\n${CATALOG_GUIDE}`;
53
+ return `${buildCoreContextPack()}\n\n${buildCatalogGuide()}`;
31
54
  }
32
55
  export function buildToolCatalogDetailsOnly() {
33
- return CATALOG_GUIDE;
56
+ return buildCatalogGuide();
34
57
  }
@@ -1,5 +1,9 @@
1
1
  import type { ToolContract } from '../tools/contract.js';
2
2
  export declare function getToolContracts(): ToolContract[];
3
+ export declare function getSortedToolContracts(): ToolContract[];
4
+ export declare function pickAvailableToolNames(names: readonly string[]): string[];
5
+ export declare function formatToolNameList(names: readonly string[]): string;
6
+ export declare function getTaskCapableToolNames(): string[];
3
7
  export declare function buildCoreContextPack(): string;
4
8
  export declare function getSharedConstraints(): string[];
5
9
  export declare function buildToolInfo(name: string): string | undefined;
@@ -8,6 +8,9 @@ function toEntry(contract) {
8
8
  annotations.push('[Idempotent]');
9
9
  if (contract.annotations?.readOnlyHint)
10
10
  annotations.push('[Read-Only]');
11
+ if (contract.taskSupport === 'optional' ||
12
+ contract.taskSupport === 'required')
13
+ annotations.push('[Task]');
11
14
  return {
12
15
  name: contract.name,
13
16
  description: contract.description,
@@ -21,13 +24,28 @@ function toEntry(contract) {
21
24
  };
22
25
  }
23
26
  const ENTRIES = Object.fromEntries(ALL_TOOLS.map((contract) => [contract.name, toEntry(contract)]));
27
+ const CONTRACTS_BY_NAME = new Map(ALL_TOOLS.map((contract) => [contract.name, contract]));
24
28
  export function getToolContracts() {
25
29
  return ALL_TOOLS;
26
30
  }
31
+ export function getSortedToolContracts() {
32
+ return [...ALL_TOOLS].sort((left, right) => left.name.localeCompare(right.name));
33
+ }
34
+ export function pickAvailableToolNames(names) {
35
+ return names.filter((name) => CONTRACTS_BY_NAME.has(name));
36
+ }
37
+ export function formatToolNameList(names) {
38
+ return names.map((name) => `\`${name}\``).join(', ');
39
+ }
40
+ export function getTaskCapableToolNames() {
41
+ return getSortedToolContracts()
42
+ .filter((contract) => contract.taskSupport === 'optional' ||
43
+ contract.taskSupport === 'required')
44
+ .map((contract) => contract.name);
45
+ }
27
46
  export function buildCoreContextPack() {
28
- const names = Object.keys(ENTRIES).sort((a, b) => a.localeCompare(b));
29
- const rows = names.map((name) => {
30
- const e = ENTRIES[name];
47
+ const rows = getSortedToolContracts().map((contract) => {
48
+ const e = ENTRIES[contract.name];
31
49
  if (!e)
32
50
  return '';
33
51
  const annotations = e.annotations ? ` ${e.annotations.join(' ')}` : '';
@@ -1,20 +1,32 @@
1
+ import { formatToolNameList, pickAvailableToolNames } from './tool-info.js';
1
2
  export function buildWorkflowGuide() {
3
+ const exploreTools = formatToolNameList(pickAvailableToolNames([
4
+ 'roots',
5
+ 'ls',
6
+ 'tree',
7
+ 'stat',
8
+ 'stat_many',
9
+ 'read',
10
+ 'read_many',
11
+ ]));
12
+ const searchTools = formatToolNameList(pickAvailableToolNames(['find', 'grep', 'read']));
13
+ const editTools = formatToolNameList(pickAvailableToolNames(['edit', 'search_and_replace', 'mv', 'rm', 'mkdir']));
2
14
  return `<workflows>
3
15
  ### A: EXPLORE — directory layout or file content
4
- 1. \`roots\` → \`ls\` or \`tree\` → \`stat\`/\`stat_many\` → \`read\`/\`read_many\`.
16
+ 1. ${exploreTools}.
5
17
  > **Strict:** Resolve paths first. Never guess.
6
18
 
7
19
  ### B: SEARCH — files by pattern or content
8
- 1. \`find\` (glob) → \`grep\` (content) → \`read\` (verify).
20
+ 1. ${searchTools}.
9
21
  > **Strict:** Content search with \`grep\`, not \`find\`.
10
22
 
11
23
  ### 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
+ 1. ${editTools}.
25
+ 2. Use \`edit\` for targeted changes, \`search_and_replace\` for bulk changes, and \`mv\`/\`rm\`/\`mkdir\` for layout updates.
14
26
  > **Strict:** Confirm destructive ops (\`write\`, \`mv\`, \`rm\`, bulk replace).
15
27
 
16
28
  ### D: PATCH — apply unified diffs
17
29
  1. \`diff_files\` → \`apply_patch(dryRun:true)\` → \`apply_patch\`.
18
- > **Tip:** Feed \`diff_files\` output directly to \`apply_patch\`.
30
+ > **Tip:** Feed \`diff_files\` output directly. Multi-file patches: \`path\` = base dir, results per file.
19
31
  </workflows>`;
20
32
  }
package/dist/schemas.d.ts CHANGED
@@ -9,6 +9,7 @@ interface TreeEntry {
9
9
  name: string;
10
10
  type: z.infer<typeof FileTypeSchema>;
11
11
  relativePath: string;
12
+ size?: number | undefined;
12
13
  children?: TreeEntry[] | undefined;
13
14
  }
14
15
  export declare const ToolErrorResponseSchema: z.ZodObject<{
@@ -34,13 +35,14 @@ export declare const ToolErrorResponseSchema: z.ZodObject<{
34
35
  }, z.core.$strict>;
35
36
  }, z.core.$strict>;
36
37
  declare const HeadLinesSchema: z.ZodOptional<z.ZodInt>;
37
- declare const LineNumberSchema: z.ZodNumber;
38
+ declare const TailLinesSchema: z.ZodOptional<z.ZodInt>;
39
+ declare const LineNumberSchema: z.ZodInt;
38
40
  export declare const ListDirectoryInputSchema: z.ZodObject<{
39
41
  path: z.ZodOptional<z.ZodString>;
40
42
  includeHidden: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
41
43
  includeIgnored: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
42
- maxDepth: z.ZodOptional<z.ZodNumber>;
43
- maxEntries: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
44
+ maxDepth: z.ZodOptional<z.ZodInt>;
45
+ maxEntries: z.ZodDefault<z.ZodOptional<z.ZodInt>>;
44
46
  sortBy: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
45
47
  name: "name";
46
48
  size: "size";
@@ -55,7 +57,7 @@ export declare const ListAllowedDirectoriesInputSchema: z.ZodObject<{}, z.core.$
55
57
  export declare const SearchFilesInputSchema: z.ZodObject<{
56
58
  path: z.ZodOptional<z.ZodString>;
57
59
  pattern: z.ZodString;
58
- maxResults: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
60
+ maxResults: z.ZodDefault<z.ZodOptional<z.ZodInt>>;
59
61
  includeIgnored: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
60
62
  includeHidden: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
61
63
  sortBy: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
@@ -64,15 +66,16 @@ export declare const SearchFilesInputSchema: z.ZodObject<{
64
66
  path: "path";
65
67
  modified: "modified";
66
68
  }>>>;
67
- maxDepth: z.ZodOptional<z.ZodNumber>;
69
+ maxDepth: z.ZodOptional<z.ZodInt>;
68
70
  cursor: z.ZodOptional<z.ZodString>;
69
71
  }, z.core.$strict>;
70
72
  export declare const TreeInputSchema: z.ZodObject<{
71
73
  path: z.ZodOptional<z.ZodString>;
72
- maxDepth: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
73
- maxEntries: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
74
+ maxDepth: z.ZodDefault<z.ZodOptional<z.ZodInt>>;
75
+ maxEntries: z.ZodDefault<z.ZodOptional<z.ZodInt>>;
74
76
  includeHidden: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
75
77
  includeIgnored: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
78
+ includeSizes: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
76
79
  }, z.core.$strict>;
77
80
  export declare const SearchContentInputSchema: z.ZodObject<{
78
81
  path: z.ZodOptional<z.ZodString>;
@@ -80,20 +83,24 @@ export declare const SearchContentInputSchema: z.ZodObject<{
80
83
  isRegex: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
81
84
  caseSensitive: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
82
85
  wholeWord: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
83
- contextLines: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
84
- maxResults: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
86
+ contextLines: z.ZodDefault<z.ZodOptional<z.ZodInt>>;
87
+ maxResults: z.ZodDefault<z.ZodOptional<z.ZodInt>>;
85
88
  filePattern: z.ZodDefault<z.ZodOptional<z.ZodString>>;
86
89
  includeHidden: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
87
90
  includeIgnored: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
91
+ multiline: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
88
92
  }, z.core.$strict>;
89
93
  export declare const ReadFileInputSchema: z.ZodObject<{
94
+ includeHash: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
90
95
  head: typeof HeadLinesSchema;
96
+ tail: typeof TailLinesSchema;
91
97
  startLine: z.ZodOptional<typeof LineNumberSchema>;
92
98
  endLine: z.ZodOptional<typeof LineNumberSchema>;
93
99
  path: z.ZodString;
94
100
  }, z.core.$strict>;
95
101
  export declare const ReadMultipleFilesInputSchema: z.ZodObject<{
96
102
  head: typeof HeadLinesSchema;
103
+ tail: typeof TailLinesSchema;
97
104
  startLine: z.ZodOptional<typeof LineNumberSchema>;
98
105
  endLine: z.ZodOptional<typeof LineNumberSchema>;
99
106
  paths: z.ZodArray<z.ZodString>;
@@ -250,6 +257,7 @@ export declare const SearchContentOutputSchema: z.ZodObject<{
250
257
  matches: z.ZodOptional<z.ZodArray<z.ZodObject<{
251
258
  file: z.ZodString;
252
259
  line: z.ZodNumber;
260
+ column: z.ZodOptional<z.ZodNumber>;
253
261
  content: z.ZodString;
254
262
  matchCount: z.ZodNumber;
255
263
  contextBefore: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -303,14 +311,17 @@ export declare const ReadFileOutputSchema: z.ZodObject<{
303
311
  head: "head";
304
312
  full: "full";
305
313
  range: "range";
314
+ tail: "tail";
306
315
  }>>;
307
316
  head: z.ZodOptional<z.ZodNumber>;
317
+ tail: z.ZodOptional<z.ZodNumber>;
308
318
  startLine: z.ZodOptional<z.ZodNumber>;
309
319
  endLine: z.ZodOptional<z.ZodNumber>;
310
320
  linesRead: z.ZodOptional<z.ZodNumber>;
311
321
  hasMoreLines: z.ZodOptional<z.ZodBoolean>;
312
322
  ok: z.ZodBoolean;
313
323
  path: z.ZodOptional<z.ZodString>;
324
+ contentHash: z.ZodOptional<z.ZodString>;
314
325
  error: z.ZodOptional<z.ZodObject<{
315
326
  code: z.ZodEnum<{
316
327
  readonly E_ACCESS_DENIED: "E_ACCESS_DENIED";
@@ -342,8 +353,10 @@ export declare const ReadMultipleFilesOutputSchema: z.ZodObject<{
342
353
  head: "head";
343
354
  full: "full";
344
355
  range: "range";
356
+ tail: "tail";
345
357
  }>>;
346
358
  head: z.ZodOptional<z.ZodNumber>;
359
+ tail: z.ZodOptional<z.ZodNumber>;
347
360
  startLine: z.ZodOptional<z.ZodNumber>;
348
361
  endLine: z.ZodOptional<z.ZodNumber>;
349
362
  linesRead: z.ZodOptional<z.ZodNumber>;
@@ -352,6 +365,7 @@ export declare const ReadMultipleFilesOutputSchema: z.ZodObject<{
352
365
  truncationReason: z.ZodOptional<z.ZodEnum<{
353
366
  head: "head";
354
367
  range: "range";
368
+ tail: "tail";
355
369
  externalized: "externalized";
356
370
  }>>;
357
371
  maxTotalSize: z.ZodOptional<z.ZodNumber>;
@@ -544,6 +558,7 @@ export declare const EditFileOutputSchema: z.ZodObject<{
544
558
  appliedEdits: z.ZodOptional<z.ZodNumber>;
545
559
  lineRange: z.ZodOptional<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>;
546
560
  unmatchedEdits: z.ZodOptional<z.ZodArray<z.ZodString>>;
561
+ diff: z.ZodOptional<z.ZodString>;
547
562
  error: z.ZodOptional<z.ZodObject<{
548
563
  code: z.ZodEnum<{
549
564
  readonly E_ACCESS_DENIED: "E_ACCESS_DENIED";
@@ -658,7 +673,7 @@ export declare const CalculateHashOutputSchema: z.ZodObject<{
658
673
  export declare const DiffFilesInputSchema: z.ZodObject<{
659
674
  original: z.ZodString;
660
675
  modified: z.ZodString;
661
- context: z.ZodOptional<z.ZodNumber>;
676
+ context: z.ZodOptional<z.ZodInt>;
662
677
  ignoreWhitespace: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
663
678
  stripTrailingCr: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
664
679
  }, z.core.$strict>;
@@ -666,6 +681,9 @@ export declare const DiffFilesOutputSchema: z.ZodObject<{
666
681
  ok: z.ZodBoolean;
667
682
  diff: z.ZodOptional<z.ZodString>;
668
683
  isIdentical: z.ZodOptional<z.ZodBoolean>;
684
+ linesAdded: z.ZodOptional<z.ZodNumber>;
685
+ linesRemoved: z.ZodOptional<z.ZodNumber>;
686
+ hunksCount: z.ZodOptional<z.ZodNumber>;
669
687
  truncated: z.ZodOptional<z.ZodBoolean>;
670
688
  resourceUri: z.ZodOptional<z.ZodString>;
671
689
  error: z.ZodOptional<z.ZodObject<{
@@ -691,7 +709,7 @@ export declare const DiffFilesOutputSchema: z.ZodObject<{
691
709
  export declare const ApplyPatchInputSchema: z.ZodObject<{
692
710
  path: z.ZodString;
693
711
  patch: z.ZodString;
694
- fuzzFactor: z.ZodOptional<z.ZodNumber>;
712
+ fuzzFactor: z.ZodOptional<z.ZodInt>;
695
713
  autoConvertLineEndings: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
696
714
  dryRun: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
697
715
  }, z.core.$strict>;
@@ -699,6 +717,17 @@ export declare const ApplyPatchOutputSchema: z.ZodObject<{
699
717
  ok: z.ZodBoolean;
700
718
  path: z.ZodOptional<z.ZodString>;
701
719
  applied: z.ZodOptional<z.ZodBoolean>;
720
+ hunksApplied: z.ZodOptional<z.ZodNumber>;
721
+ linesAdded: z.ZodOptional<z.ZodNumber>;
722
+ linesRemoved: z.ZodOptional<z.ZodNumber>;
723
+ results: z.ZodOptional<z.ZodArray<z.ZodObject<{
724
+ path: z.ZodString;
725
+ applied: z.ZodBoolean;
726
+ hunksApplied: z.ZodOptional<z.ZodNumber>;
727
+ linesAdded: z.ZodOptional<z.ZodNumber>;
728
+ linesRemoved: z.ZodOptional<z.ZodNumber>;
729
+ error: z.ZodOptional<z.ZodString>;
730
+ }, z.core.$strict>>>;
702
731
  error: z.ZodOptional<z.ZodObject<{
703
732
  code: z.ZodEnum<{
704
733
  readonly E_ACCESS_DENIED: "E_ACCESS_DENIED";
@@ -721,14 +750,16 @@ export declare const ApplyPatchOutputSchema: z.ZodObject<{
721
750
  }, z.core.$strict>;
722
751
  export declare const SearchAndReplaceInputSchema: z.ZodObject<{
723
752
  path: z.ZodOptional<z.ZodString>;
724
- filePattern: z.ZodString;
753
+ filePattern: z.ZodDefault<z.ZodOptional<z.ZodString>>;
725
754
  searchPattern: z.ZodString;
726
755
  replacement: z.ZodString;
727
756
  isRegex: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
757
+ caseSensitive: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
728
758
  dryRun: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
729
759
  includeHidden: z.ZodOptional<z.ZodBoolean>;
730
760
  includeIgnored: z.ZodOptional<z.ZodBoolean>;
731
761
  returnDiff: z.ZodOptional<z.ZodBoolean>;
762
+ maxFiles: z.ZodOptional<z.ZodInt>;
732
763
  }, z.core.$strict>;
733
764
  export declare const SearchAndReplaceOutputSchema: z.ZodObject<{
734
765
  ok: z.ZodBoolean;
@@ -746,6 +777,10 @@ export declare const SearchAndReplaceOutputSchema: z.ZodObject<{
746
777
  }, z.core.$strict>>>;
747
778
  changedFilesTruncated: z.ZodOptional<z.ZodBoolean>;
748
779
  diff: z.ZodOptional<z.ZodString>;
780
+ diffTruncated: z.ZodOptional<z.ZodBoolean>;
781
+ stoppedReason: z.ZodOptional<z.ZodEnum<{
782
+ maxFiles: "maxFiles";
783
+ }>>;
749
784
  dryRun: z.ZodOptional<z.ZodBoolean>;
750
785
  error: z.ZodOptional<z.ZodObject<{
751
786
  code: z.ZodEnum<{