@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.
Files changed (65) hide show
  1. package/README.md +117 -100
  2. package/dist/completions.d.ts +0 -11
  3. package/dist/completions.js +1 -1
  4. package/dist/config.d.ts +0 -1
  5. package/dist/lib/constants.d.ts +0 -1
  6. package/dist/lib/constants.js +0 -1
  7. package/dist/lib/errors.d.ts +0 -1
  8. package/dist/lib/errors.js +5 -9
  9. package/dist/lib/file-operations/core.d.ts +3 -2
  10. package/dist/lib/file-operations/metadata.js +9 -3
  11. package/dist/lib/file-operations/search.d.ts +1 -59
  12. package/dist/lib/file-operations/search.js +9 -22
  13. package/dist/lib/file-operations/traversal.d.ts +2 -2
  14. package/dist/lib/fs-helpers.d.ts +1 -2
  15. package/dist/lib/fs-helpers.js +10 -12
  16. package/dist/lib/globs.d.ts +2 -0
  17. package/dist/lib/globs.js +19 -0
  18. package/dist/lib/paths.d.ts +0 -3
  19. package/dist/lib/paths.js +1 -4
  20. package/dist/lib/utils.d.ts +4 -4
  21. package/dist/lib/utils.js +1 -10
  22. package/dist/lib/zod-codecs.d.ts +2 -0
  23. package/dist/lib/zod-codecs.js +18 -0
  24. package/dist/pkg-info.d.ts +1 -0
  25. package/dist/pkg-info.js +2 -2
  26. package/dist/prompts.js +3 -3
  27. package/dist/resources/generated-instructions.js +3 -12
  28. package/dist/resources/tool-catalog.js +10 -41
  29. package/dist/resources/tool-info.d.ts +0 -1
  30. package/dist/resources/tool-info.js +24 -39
  31. package/dist/resources/workflows.js +8 -1
  32. package/dist/schemas.d.ts +179 -481
  33. package/dist/schemas.js +156 -169
  34. package/dist/server/bootstrap.d.ts +0 -10
  35. package/dist/server/bootstrap.js +3 -3
  36. package/dist/server/roots-manager.js +1 -1
  37. package/dist/tools/apply-patch.js +19 -8
  38. package/dist/tools/calculate-hash.js +3 -5
  39. package/dist/tools/create-directory.d.ts +1 -4
  40. package/dist/tools/create-directory.js +2 -2
  41. package/dist/tools/delete-file.js +2 -4
  42. package/dist/tools/diff-files.js +1 -3
  43. package/dist/tools/edit-file.d.ts +1 -7
  44. package/dist/tools/edit-file.js +6 -3
  45. package/dist/tools/list-directory.js +10 -15
  46. package/dist/tools/move-file.d.ts +1 -4
  47. package/dist/tools/move-file.js +15 -27
  48. package/dist/tools/read-multiple.js +12 -7
  49. package/dist/tools/read.js +1 -2
  50. package/dist/tools/replace-in-files.d.ts +1 -10
  51. package/dist/tools/replace-in-files.js +62 -97
  52. package/dist/tools/roots.js +2 -6
  53. package/dist/tools/search-content.js +152 -188
  54. package/dist/tools/search-files.js +10 -13
  55. package/dist/tools/shared.d.ts +10 -3
  56. package/dist/tools/shared.js +53 -12
  57. package/dist/tools/stat-many.js +6 -4
  58. package/dist/tools/stat.js +2 -2
  59. package/dist/tools/task-support.d.ts +0 -7
  60. package/dist/tools/task-support.js +7 -6
  61. package/dist/tools/tree.js +1 -1
  62. package/dist/tools/write-file.js +1 -5
  63. package/dist/tools.d.ts +0 -1
  64. package/dist/tools.js +0 -1
  65. 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.startLine !== undefined)
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 { ContentMatch, SearchContentResult, SearchFilesResult } from '../../config.js';
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
- export function validatePattern(pattern, options) {
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, multiline) {
127
- let flags = caseSensitive ? 'g' : 'gi';
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
- export function buildMatcher(pattern, options) {
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, options.multiline);
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: ${result.error.message}`, undefined, { errors: z.treeifyError(result.error) });
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
- export async function scanFileInWorker(resolvedPath, requestedPath, matcher, options, maxMatches, isCancelled, isBinaryDetector) {
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
- export function sortSearchResults(results, sortBy) {
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
- const ml = options.multiline ? '1' : '0';
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
- export interface GlobEntriesOptions {
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
- export interface GlobConfig {
23
+ interface GlobConfig {
24
24
  cwd: string;
25
25
  pattern: string;
26
26
  excludePatterns?: readonly string[];
@@ -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 { headFile };
52
+ export {};
@@ -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
- if (hasEnd && !hasStart) {
291
- throw new McpError(ErrorCode.E_INVALID_INPUT, 'endLine requires startLine');
292
- }
293
- if (options.startLine !== undefined &&
294
- options.endLine !== undefined &&
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,2 @@
1
+ export declare function isSafeGlobPattern(value: string): boolean;
2
+ export declare function assertSafeGlobPattern(value: string, message?: string): void;
@@ -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
+ }
@@ -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
- export function toAccessDeniedWithHint(requestedPath, resolvedPath, normalizedResolved) {
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
  }
@@ -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
- export declare function setIfDefined<T extends object, K extends keyof T>(target: T, key: K, value: T[K] | undefined): void;
9
- export interface ProgressPayload {
8
+ interface ProgressPayload {
10
9
  current: number;
11
10
  total?: number;
12
11
  }
13
- export type ProgressCallback = ((progress: ProgressPayload) => void) | undefined;
14
- export interface PeriodicProgressOptions {
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
- const nodeTimeout = timeoutId;
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,2 @@
1
+ import { z } from 'zod';
2
+ export declare function createBase64JsonCodec<Schema extends z.ZodType>(schema: Schema): z.ZodCodec<z.ZodString, Schema>;
@@ -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
+ }
@@ -1,4 +1,5 @@
1
1
  export declare const pkgInfo: {
2
+ [x: string]: unknown;
2
3
  name: string;
3
4
  version: string;
4
5
  description?: string | undefined;
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.object({
3
+ const PkgInfoSchema = z.looseObject({
4
4
  name: z.string(),
5
5
  version: z.string(),
6
6
  description: z.string().optional(),
7
- homepage: z.string().optional(),
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 McpError(ErrorCode.E_INVALID_INPUT, `Unknown tool: ${name}`);
126
+ throw new SdkMcpError(SdkErrorCode.InvalidParams, `Unknown tool: ${name}`);
127
127
  }
128
128
  const toolInfo = buildToolInfo(toolName);
129
129
  if (!toolInfo) {
130
- throw new McpError(ErrorCode.E_INVALID_INPUT, `Unknown tool: ${toolName}`);
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, getTaskCapableToolNames, getTaskToolNamesBySupport, getToolContracts, pickAvailableToolNames, } from './tool-info.js';
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: Call task-capable tools inline by default; add \`task\` only when durable polling or deferred results are needed.
58
- Task results: When a task is requested, poll via \`tasks/get\`, then retrieve the final payload via \`tasks/result\`.
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 (`<tool_selection_guide>
18
+ return `<tool_selection_guide>
19
19
  ## Primitive Routing
20
20
 
21
- - ` +
22
- '`tools`' +
23
- `: model-controlled operations that inspect or mutate the allowed filesystem.
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
- '`content`' +
50
- ` and usually ` +
51
- '`structuredContent`' +
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;