@j0hanz/filesystem-mcp 1.13.2 → 1.14.1

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 (74) hide show
  1. package/README.md +162 -145
  2. package/dist/cli.js +2 -2
  3. package/dist/completions.js +54 -51
  4. package/dist/config.d.ts +13 -14
  5. package/dist/config.js +12 -12
  6. package/dist/index.js +1 -1
  7. package/dist/lib/abort.d.ts +7 -0
  8. package/dist/lib/abort.js +81 -0
  9. package/dist/lib/constants.d.ts +3 -1
  10. package/dist/lib/constants.js +8 -2
  11. package/dist/lib/errors.d.ts +7 -3
  12. package/dist/lib/errors.js +64 -41
  13. package/dist/lib/file-operations/core.d.ts +3 -3
  14. package/dist/lib/file-operations/core.js +23 -20
  15. package/dist/lib/file-operations/metadata.d.ts +2 -2
  16. package/dist/lib/file-operations/metadata.js +69 -22
  17. package/dist/lib/file-operations/search.d.ts +0 -1
  18. package/dist/lib/file-operations/search.js +87 -95
  19. package/dist/lib/file-operations/traversal.js +13 -15
  20. package/dist/lib/fs-helpers.d.ts +3 -10
  21. package/dist/lib/fs-helpers.js +29 -108
  22. package/dist/lib/globs.d.ts +2 -0
  23. package/dist/lib/globs.js +19 -0
  24. package/dist/lib/logger.d.ts +28 -0
  25. package/dist/lib/logger.js +91 -0
  26. package/dist/lib/observability.d.ts +7 -0
  27. package/dist/lib/observability.js +19 -9
  28. package/dist/lib/paths.js +55 -55
  29. package/dist/lib/resource-store.js +4 -4
  30. package/dist/lib/utils.d.ts +0 -12
  31. package/dist/lib/utils.js +0 -13
  32. package/dist/lib/zod-codecs.d.ts +2 -0
  33. package/dist/lib/zod-codecs.js +18 -0
  34. package/dist/pkg-info.d.ts +1 -0
  35. package/dist/pkg-info.js +2 -2
  36. package/dist/prompts.js +3 -3
  37. package/dist/resources/generated-instructions.js +41 -41
  38. package/dist/resources/tool-catalog.js +33 -58
  39. package/dist/resources/tool-info.d.ts +0 -1
  40. package/dist/resources/tool-info.js +44 -67
  41. package/dist/resources/workflows.js +47 -19
  42. package/dist/resources.d.ts +1 -1
  43. package/dist/resources.js +4 -4
  44. package/dist/schemas.d.ts +185 -465
  45. package/dist/schemas.js +174 -206
  46. package/dist/server/bootstrap.d.ts +12 -11
  47. package/dist/server/bootstrap.js +95 -86
  48. package/dist/server/roots-manager.d.ts +5 -2
  49. package/dist/server/roots-manager.js +9 -7
  50. package/dist/server/task-store.d.ts +10 -0
  51. package/dist/server/task-store.js +73 -0
  52. package/dist/tools/apply-patch.js +39 -20
  53. package/dist/tools/calculate-hash.js +14 -27
  54. package/dist/tools/create-directory.js +11 -9
  55. package/dist/tools/delete-file.js +19 -19
  56. package/dist/tools/diff-files.js +16 -18
  57. package/dist/tools/edit-file.js +11 -5
  58. package/dist/tools/list-directory.js +16 -21
  59. package/dist/tools/move-file.js +105 -100
  60. package/dist/tools/read-multiple.js +15 -10
  61. package/dist/tools/read.js +6 -7
  62. package/dist/tools/replace-in-files.js +76 -115
  63. package/dist/tools/roots.js +3 -7
  64. package/dist/tools/search-content.js +158 -203
  65. package/dist/tools/search-files.js +59 -50
  66. package/dist/tools/shared.d.ts +10 -0
  67. package/dist/tools/shared.js +105 -36
  68. package/dist/tools/stat-many.js +15 -9
  69. package/dist/tools/stat.js +6 -6
  70. package/dist/tools/task-support.d.ts +10 -9
  71. package/dist/tools/task-support.js +94 -23
  72. package/dist/tools/tree.js +4 -4
  73. package/dist/tools/write-file.js +11 -12
  74. package/package.json +10 -9
package/dist/lib/paths.js CHANGED
@@ -1,12 +1,12 @@
1
- import * as fs from 'node:fs/promises';
2
- import * as os from 'node:os';
3
- import * as path from 'node:path';
4
1
  import { AsyncLocalStorage } from 'node:async_hooks';
5
- import { platform } from 'node:os';
2
+ import { realpath, stat } from 'node:fs/promises';
3
+ import { homedir, platform } from 'node:os';
4
+ import { dirname, isAbsolute, join, normalize, parse, posix, relative, resolve, sep, win32, } from 'node:path';
6
5
  import { fileURLToPath } from 'node:url';
6
+ import { assertNotAborted, withAbort } from './abort.js';
7
7
  import { SENSITIVE_FILE_ALLOWLIST, SENSITIVE_FILE_DENYLIST, } from './constants.js';
8
8
  import { ErrorCode, isAbortError, isNodeError, McpError } from './errors.js';
9
- import { assertNotAborted, withAbort } from './fs-helpers.js';
9
+ import { Logger } from './logger.js';
10
10
  const WINDOWS_PATH_SEPARATOR = '\\';
11
11
  const POSIX_PATH_SEPARATOR = '/';
12
12
  export function toPosixPath(value) {
@@ -20,7 +20,7 @@ const HOME_PREFIX_LENGTH = 2;
20
20
  const CHAR_CODE_SPACE = 32;
21
21
  const CHAR_CODE_DOT = 46;
22
22
  function normalizePathForMatch(input) {
23
- return toPosixPath(path.normalize(input));
23
+ return toPosixPath(normalize(input));
24
24
  }
25
25
  function normalizeForMatch(input) {
26
26
  const normalized = normalizePathForMatch(input);
@@ -84,7 +84,7 @@ function matchesAnyGlobs(globs, candidates) {
84
84
  return false;
85
85
  for (const candidate of candidates) {
86
86
  for (const glob of globs) {
87
- if (path.posix.matchesGlob(candidate, glob))
87
+ if (posix.matchesGlob(candidate, glob))
88
88
  return true;
89
89
  }
90
90
  }
@@ -100,7 +100,7 @@ export function isSensitivePath(requestedPath, resolvedPath) {
100
100
  ? normalizeForMatch(resolvedPath)
101
101
  : undefined;
102
102
  const pathCandidates = uniquePair(normalizedRequested, normalizedResolved);
103
- const nameCandidates = uniquePair(path.posix.basename(normalizedRequested), normalizedResolved ? path.posix.basename(normalizedResolved) : undefined);
103
+ const nameCandidates = uniquePair(posix.basename(normalizedRequested), normalizedResolved ? posix.basename(normalizedResolved) : undefined);
104
104
  if (matchesAnyGlobs(ALLOW_PATTERNS.pathGlobs, pathCandidates) ||
105
105
  matchesAnyGlobs(ALLOW_PATTERNS.nameGlobs, nameCandidates)) {
106
106
  return false;
@@ -111,11 +111,11 @@ export function isSensitivePath(requestedPath, resolvedPath) {
111
111
  export function assertAllowedFileAccess(requestedPath, resolvedPath) {
112
112
  if (!isSensitivePath(requestedPath, resolvedPath))
113
113
  return;
114
- throw new McpError(ErrorCode.E_ACCESS_DENIED, `Access denied: sensitive file blocked by policy (${requestedPath}). ` +
115
- 'Set FS_CONTEXT_ALLOW_SENSITIVE=1 or use FS_CONTEXT_ALLOWLIST to override.', requestedPath);
114
+ Logger.warn(`Access denied: sensitive file blocked by policy (${requestedPath})`);
115
+ throw new McpError(ErrorCode.ACCESS_DENIED, 'Sensitive file blocked. Set FS_CONTEXT_ALLOW_SENSITIVE=1 to override.', requestedPath);
116
116
  }
117
- const HOMEDIR = os.homedir();
118
- const PATH_SEPARATOR = path.sep;
117
+ const HOMEDIR = homedir();
118
+ const PATH_SEPARATOR = sep;
119
119
  const DRIVE_LETTER_REGEX = /^[A-Za-z]:/;
120
120
  const WINDOWS_DRIVE_REL_REGEX = /^[A-Za-z]:$/u;
121
121
  const LEADING_SEPARATORS_RE = /^[/\\]+/;
@@ -160,11 +160,11 @@ function expandHome(filepath) {
160
160
  return HOMEDIR;
161
161
  // Accept both "~/" and "~\\" for cross-platform UX.
162
162
  if (filepath.startsWith('~/') || filepath.startsWith('~\\')) {
163
- // Avoid `path.join(HOMEDIR, "/foo")` resetting to the filesystem root.
163
+ // Avoid `join(HOMEDIR, "/foo")` resetting to the filesystem root.
164
164
  const rest = filepath
165
165
  .slice(HOME_PREFIX_LENGTH)
166
166
  .replace(LEADING_SEPARATORS_RE, '');
167
- return rest.length === 0 ? HOMEDIR : path.join(HOMEDIR, rest);
167
+ return rest.length === 0 ? HOMEDIR : join(HOMEDIR, rest);
168
168
  }
169
169
  return filepath;
170
170
  }
@@ -175,7 +175,7 @@ function expandHome(filepath) {
175
175
  * - Lowercases Windows drive letter for stable comparisons.
176
176
  */
177
177
  export function normalizePath(p) {
178
- const resolved = path.resolve(expandHome(p));
178
+ const resolved = resolve(expandHome(p));
179
179
  if (IS_WINDOWS && DRIVE_LETTER_REGEX.test(resolved)) {
180
180
  return resolved.charAt(0).toLowerCase() + resolved.slice(1);
181
181
  }
@@ -194,8 +194,8 @@ function rethrowIfAborted(error) {
194
194
  function isSamePath(left, right) {
195
195
  if (left === right)
196
196
  return true;
197
- const leftResolved = normalizeCaseForComparison(path.resolve(left));
198
- const rightResolved = normalizeCaseForComparison(path.resolve(right));
197
+ const leftResolved = normalizeCaseForComparison(resolve(left));
198
+ const rightResolved = normalizeCaseForComparison(resolve(right));
199
199
  return leftResolved === rightResolved;
200
200
  }
201
201
  function stripTrailingSeparator(normalized) {
@@ -211,7 +211,7 @@ function normalizeAllowedDirectory(dir) {
211
211
  if (trimmed.length === 0)
212
212
  return '';
213
213
  const normalized = normalizePath(trimmed);
214
- const { root } = path.parse(normalized);
214
+ const { root } = parse(normalized);
215
215
  // Keep filesystem roots as-is ("/", "c:\\", "\\\\server\\share\\").
216
216
  if (isFileSystemRootPath(normalized, root)) {
217
217
  return root;
@@ -274,14 +274,12 @@ function isPathInsideDirectory(normalizedDirectory, normalizedCandidate) {
274
274
  const candidate = normalizeForComparison(normalizedCandidate);
275
275
  if (root === candidate)
276
276
  return true;
277
- const relative = path.relative(root, candidate);
278
- if (relative.length === 0)
277
+ const rel = relative(root, candidate);
278
+ if (rel.length === 0)
279
279
  return true;
280
- if (relative === '..')
280
+ if (rel === '..')
281
281
  return false;
282
- return (!relative.startsWith('..\\') &&
283
- !relative.startsWith('../') &&
284
- !path.isAbsolute(relative));
282
+ return !rel.startsWith('..\\') && !rel.startsWith('../') && !isAbsolute(rel);
285
283
  }
286
284
  export function isPathWithinDirectories(normalizedPath, allowedDirs) {
287
285
  for (const allowedDir of allowedDirs) {
@@ -293,7 +291,7 @@ export function isPathWithinDirectories(normalizedPath, allowedDirs) {
293
291
  async function resolveRealPath(normalized, signal) {
294
292
  try {
295
293
  assertNotAborted(signal);
296
- const realPath = await withAbort(fs.realpath(normalized), signal);
294
+ const realPath = await withAbort(realpath(normalized), signal);
297
295
  return normalizeAllowedDirectory(realPath);
298
296
  }
299
297
  catch (error) {
@@ -327,12 +325,12 @@ export async function setAllowedDirectoriesResolved(dirs, signal) {
327
325
  }
328
326
  function ensureNonEmptyPath(requestedPath) {
329
327
  if (!requestedPath || requestedPath.trim().length === 0) {
330
- throw new McpError(ErrorCode.E_INVALID_INPUT, 'Path cannot be empty or whitespace', requestedPath);
328
+ throw new McpError(ErrorCode.INVALID_INPUT, 'Path cannot be empty or whitespace', requestedPath);
331
329
  }
332
330
  }
333
331
  function ensureNoNullBytes(requestedPath) {
334
332
  if (requestedPath.includes('\0')) {
335
- throw new McpError(ErrorCode.E_INVALID_INPUT, 'Path contains null bytes', requestedPath);
333
+ throw new McpError(ErrorCode.INVALID_INPUT, 'Path contains null bytes', requestedPath);
336
334
  }
337
335
  }
338
336
  function getReservedDeviceName(segment) {
@@ -371,31 +369,31 @@ function ensureNoReservedWindowsNames(requestedPath) {
371
369
  const reserved = getReservedDeviceNameForPath(requestedPath);
372
370
  if (!reserved)
373
371
  return;
374
- throw new McpError(ErrorCode.E_INVALID_INPUT, `Windows reserved device name not allowed: ${reserved}`, requestedPath);
372
+ throw new McpError(ErrorCode.INVALID_INPUT, `Windows reserved device name not allowed: ${reserved}`, requestedPath);
375
373
  }
376
374
  export function isWindowsDriveRelativePath(requestedPath) {
377
375
  if (!IS_WINDOWS)
378
376
  return false;
379
- const parsed = path.win32.parse(requestedPath);
377
+ const parsed = win32.parse(requestedPath);
380
378
  if (!WINDOWS_DRIVE_REL_REGEX.test(parsed.root))
381
379
  return false;
382
- return !path.win32.isAbsolute(requestedPath);
380
+ return !win32.isAbsolute(requestedPath);
383
381
  }
384
382
  function ensureNoWindowsDriveRelativePath(requestedPath) {
385
383
  if (!isWindowsDriveRelativePath(requestedPath))
386
384
  return;
387
- throw new McpError(ErrorCode.E_INVALID_INPUT, 'Windows drive-relative paths are not allowed. Use C:\\path or C:/path instead of C:path.', requestedPath);
385
+ throw new McpError(ErrorCode.INVALID_INPUT, 'Drive-relative path not allowed. Use C:\\path instead of C:path.', requestedPath);
388
386
  }
389
387
  function resolveRequestedPath(requestedPath) {
390
388
  const expanded = expandHome(requestedPath);
391
- if (!path.isAbsolute(expanded)) {
389
+ if (!isAbsolute(expanded)) {
392
390
  const roots = getAllowedDirectoriesForRelativeResolution();
393
391
  if (roots.length > 1) {
394
- throw new McpError(ErrorCode.E_INVALID_INPUT, 'Relative paths are ambiguous when multiple roots are configured. Provide an absolute path or specify the full root path.', requestedPath);
392
+ throw new McpError(ErrorCode.INVALID_INPUT, 'Ambiguous relative path with multiple roots. Use an absolute path.', requestedPath);
395
393
  }
396
394
  const baseDir = roots[0];
397
395
  if (baseDir) {
398
- return normalizePath(path.resolve(baseDir, expanded));
396
+ return normalizePath(resolve(baseDir, expanded));
399
397
  }
400
398
  }
401
399
  return normalizePath(expanded);
@@ -409,24 +407,24 @@ function validateRequestedPath(requestedPath) {
409
407
  }
410
408
  const NODE_ERROR_MAP = {
411
409
  ENOENT: {
412
- code: ErrorCode.E_NOT_FOUND,
413
- message: (requestedPath) => `Path does not exist: ${requestedPath}`,
410
+ code: ErrorCode.NOT_FOUND,
411
+ message: () => 'Path does not exist',
414
412
  },
415
413
  EACCES: {
416
- code: ErrorCode.E_PERMISSION_DENIED,
417
- message: (requestedPath) => `Permission denied accessing path: ${requestedPath}`,
414
+ code: ErrorCode.PERMISSION_DENIED,
415
+ message: () => 'Permission denied',
418
416
  },
419
417
  EPERM: {
420
- code: ErrorCode.E_PERMISSION_DENIED,
421
- message: (requestedPath) => `Permission denied accessing path: ${requestedPath}`,
418
+ code: ErrorCode.PERMISSION_DENIED,
419
+ message: () => 'Permission denied',
422
420
  },
423
421
  ELOOP: {
424
- code: ErrorCode.E_SYMLINK_NOT_ALLOWED,
425
- message: (requestedPath) => `Too many symbolic links in path (possible circular reference): ${requestedPath}`,
422
+ code: ErrorCode.SYMLINK_NOT_ALLOWED,
423
+ message: () => 'Too many symbolic links (circular reference)',
426
424
  },
427
425
  ENAMETOOLONG: {
428
- code: ErrorCode.E_INVALID_INPUT,
429
- message: (requestedPath) => `Path name too long: ${requestedPath}`,
426
+ code: ErrorCode.INVALID_INPUT,
427
+ message: () => 'Path name too long',
430
428
  },
431
429
  };
432
430
  function buildAllowedDirectoriesHint() {
@@ -448,26 +446,28 @@ function toMcpError(requestedPath, error) {
448
446
  else if (typeof error === 'string') {
449
447
  originalMessage = error;
450
448
  }
451
- return new McpError(ErrorCode.E_NOT_FOUND, `Path is not accessible: ${requestedPath}`, requestedPath, { originalCode: code, originalMessage }, error);
449
+ return new McpError(ErrorCode.NOT_FOUND, 'Path is not accessible', requestedPath, { originalCode: code, originalMessage }, error);
452
450
  }
453
451
  function toAccessDeniedWithHint(requestedPath, resolvedPath, normalizedResolved) {
454
452
  const suggestion = buildAllowedDirectoriesHint();
455
- return new McpError(ErrorCode.E_ACCESS_DENIED, `Access denied: Path '${requestedPath}' is outside allowed directories.\n${suggestion}`, requestedPath, { resolvedPath, normalizedResolvedPath: normalizedResolved });
453
+ return new McpError(ErrorCode.ACCESS_DENIED, `Outside allowed directories. ${suggestion}`, requestedPath, { resolvedPath, normalizedResolvedPath: normalizedResolved });
456
454
  }
457
455
  function ensureWithinAllowedDirectories(options) {
458
456
  const { normalizedPath, requestedPath, allowedDirs, details } = options;
459
457
  if (isPathWithinDirectories(normalizedPath, allowedDirs))
460
458
  return;
461
459
  if (allowedDirs.length === 0) {
462
- throw new McpError(ErrorCode.E_ACCESS_DENIED, 'Access denied: No allowed directories configured. Use --allow-cwd or configure roots via the MCP Roots protocol.', requestedPath, details);
460
+ Logger.warn('Access denied: no allowed directories configured');
461
+ throw new McpError(ErrorCode.ACCESS_DENIED, 'No allowed directories configured. Use --allow-cwd or configure roots.', requestedPath, details);
463
462
  }
464
- throw new McpError(ErrorCode.E_ACCESS_DENIED, `Access denied: Path '${requestedPath}' is outside allowed directories`, requestedPath, details);
463
+ Logger.warn(`Access denied: path outside allowed directories (${requestedPath})`);
464
+ throw new McpError(ErrorCode.ACCESS_DENIED, 'Outside allowed directories', requestedPath, details);
465
465
  }
466
466
  async function resolveRealPathOrThrow(options) {
467
467
  const { requestedPath, normalizedRequested, signal } = options;
468
468
  try {
469
469
  assertNotAborted(signal);
470
- return await withAbort(fs.realpath(normalizedRequested), signal);
470
+ return await withAbort(realpath(normalizedRequested), signal);
471
471
  }
472
472
  catch (error) {
473
473
  rethrowIfAborted(error);
@@ -494,7 +494,7 @@ function ensureResolvedPathAllowed(options) {
494
494
  async function statPathOrThrow(requestedPath, resolvedPath, signal) {
495
495
  try {
496
496
  assertNotAborted(signal);
497
- return await withAbort(fs.stat(resolvedPath), signal);
497
+ return await withAbort(stat(resolvedPath), signal);
498
498
  }
499
499
  catch (error) {
500
500
  rethrowIfAborted(error);
@@ -507,7 +507,7 @@ async function resolveNearestExistingRealPathOrThrow(options) {
507
507
  for (;;) {
508
508
  try {
509
509
  assertNotAborted(signal);
510
- return await withAbort(fs.realpath(current), signal);
510
+ return await withAbort(realpath(current), signal);
511
511
  }
512
512
  catch (error) {
513
513
  rethrowIfAborted(error);
@@ -515,7 +515,7 @@ async function resolveNearestExistingRealPathOrThrow(options) {
515
515
  if (code !== 'ENOENT') {
516
516
  throw toMcpError(requestedPath, error);
517
517
  }
518
- const parent = path.dirname(current);
518
+ const parent = dirname(current);
519
519
  if (parent === current) {
520
520
  throw toMcpError(requestedPath, error);
521
521
  }
@@ -554,7 +554,7 @@ export async function validateExistingDirectory(requestedPath, signal) {
554
554
  const details = await validateExistingPathDetailsInternal(requestedPath, signal);
555
555
  const stats = await statPathOrThrow(requestedPath, details.resolvedPath, signal);
556
556
  if (!stats.isDirectory()) {
557
- throw new McpError(ErrorCode.E_NOT_DIRECTORY, `Not a directory: ${requestedPath}`, requestedPath);
557
+ throw new McpError(ErrorCode.NOT_DIRECTORY, 'Not a directory', requestedPath);
558
558
  }
559
559
  return details.resolvedPath;
560
560
  }
@@ -581,7 +581,7 @@ function isFileRoot(root) {
581
581
  async function maybeAddRealPath(normalizedPath, validDirs, signal) {
582
582
  try {
583
583
  assertNotAborted(signal);
584
- const realPath = await withAbort(fs.realpath(normalizedPath), signal);
584
+ const realPath = await withAbort(realpath(normalizedPath), signal);
585
585
  const normalizedReal = normalizePath(realPath);
586
586
  if (!isSamePath(normalizedReal, normalizedPath)) {
587
587
  validDirs.push(normalizedReal);
@@ -596,7 +596,7 @@ async function resolveRootDirectory(root, signal) {
596
596
  const dirPath = fileURLToPath(root.uri);
597
597
  const normalizedPath = normalizePath(dirPath);
598
598
  assertNotAborted(signal);
599
- const stats = await withAbort(fs.stat(normalizedPath), signal);
599
+ const stats = await withAbort(stat(normalizedPath), signal);
600
600
  if (!stats.isDirectory())
601
601
  return null;
602
602
  return normalizedPath;
@@ -94,7 +94,7 @@ export function createInMemoryResourceStore(options = {}) {
94
94
  bytes: entryBytes,
95
95
  reason: 'entry_too_large',
96
96
  });
97
- throw new McpError(ErrorCode.E_TOO_LARGE, `Resource too large to cache (${entryBytes} bytes)`);
97
+ throw new McpError(ErrorCode.TOO_LARGE, `Resource too large to cache (${entryBytes} bytes).`);
98
98
  }
99
99
  const contentHash = computeSha256(params.text);
100
100
  const indexKey = buildIndexKey(mimeType, contentHash);
@@ -146,7 +146,7 @@ export function createInMemoryResourceStore(options = {}) {
146
146
  bytes: entry.size,
147
147
  reason: 'evicted_immediately',
148
148
  });
149
- throw new McpError(ErrorCode.E_TOO_LARGE, 'Resource cache full: entry evicted immediately');
149
+ throw new McpError(ErrorCode.TOO_LARGE, 'Cache full: entry evicted.');
150
150
  }
151
151
  return entry;
152
152
  }
@@ -158,7 +158,7 @@ export function createInMemoryResourceStore(options = {}) {
158
158
  uri,
159
159
  reason: 'not_found',
160
160
  });
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.`);
161
+ throw new McpError(ErrorCode.NOT_FOUND, `Resource not found: ${uri}. Re-run the tool to regenerate.`);
162
162
  }
163
163
  if (isExpired(existing)) {
164
164
  removeEntry(uri, 'expired');
@@ -167,7 +167,7 @@ export function createInMemoryResourceStore(options = {}) {
167
167
  uri,
168
168
  reason: 'expired',
169
169
  });
170
- throw new McpError(ErrorCode.E_NOT_FOUND, `Resource expired: ${uri}. Re-run the originating tool to regenerate.`);
170
+ throw new McpError(ErrorCode.NOT_FOUND, `Resource expired: ${uri}. Re-run the tool to regenerate.`);
171
171
  }
172
172
  publishResourceStoreDiagnostics({
173
173
  phase: 'cache_hit',
@@ -5,15 +5,3 @@ 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
- interface ProgressPayload {
9
- current: number;
10
- total?: number;
11
- }
12
- type ProgressCallback = ((progress: ProgressPayload) => void) | undefined;
13
- interface PeriodicProgressOptions {
14
- total?: number;
15
- throttleModulo?: number;
16
- force?: boolean;
17
- }
18
- export declare function reportPeriodicProgress(onProgress: ProgressCallback, current: number, options?: PeriodicProgressOptions): void;
19
- export {};
package/dist/lib/utils.js CHANGED
@@ -35,16 +35,3 @@ export function omitOptionKeys(input, keys) {
35
35
  }
36
36
  return output;
37
37
  }
38
- export function reportPeriodicProgress(onProgress, current, options = {}) {
39
- if (!onProgress || current === 0)
40
- return;
41
- const throttleModulo = options.throttleModulo ?? 1;
42
- const force = options.force ?? false;
43
- if (!force && throttleModulo > 1 && current % throttleModulo !== 0) {
44
- return;
45
- }
46
- onProgress({
47
- current,
48
- ...(options.total !== undefined ? { total: options.total } : {}),
49
- });
50
- }
@@ -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,58 +28,61 @@ 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
- return `<role>
38
- Filesystem agent. Scope: allowed roots only. Discover paths before acting — never guess.
39
- </role>
31
+ return `## Role
32
+
33
+ Secure filesystem agent. Operate strictly within allowed roots. Resolve paths before acting — never assume.
34
+
35
+ ## Tools Overview
40
36
 
41
- <tools_overview>
42
37
  | Category | Tools |
43
- |----------|-------|
38
+ | -------- | ----- |
39
+
44
40
  ${buildToolsOverview()}
45
- </tools_overview>
46
41
 
47
- <resources>
48
- - \`internal://instructions\`: Full usage reference.
49
- - \`internal://tool-catalog\`: Tool routing and data flow.
50
- - \`internal://workflows\`: Standard execution sequences.
51
- - \`internal://tool-info/{name}\`: Per-tool nuances (e.g. \`internal://tool-info/read\`).
52
- - \`filesystem-mcp://result/{id}\`: Cached large output — call \`resources/read\` immediately when \`resourceUri\` is returned.
53
- - \`filesystem-mcp://metrics\`: Per-tool runtime metrics.
54
- </resources>
42
+ ## Resources
43
+
44
+ | URI | Purpose |
45
+ | -------------------------------- | -------------------------------------------------------------- |
46
+ | \`internal://instructions\` | Full usage reference (this document) |
47
+ | \`internal://tool-catalog\` | Tool routing, data flow, and selection guide |
48
+ | \`internal://workflows\` | Step-by-step execution sequences |
49
+ | \`internal://tool-info/{name}\` | Per-tool contract (e.g. \`internal://tool-info/read\`) |
50
+ | \`filesystem-mcp://result/{id}\` | Cached large output — fetch via \`resources/read\` immediately |
51
+ | \`filesystem-mcp://metrics\` | Per-tool call count, error rate, and avg duration |
55
52
 
56
- <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\`.
59
- 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
- </task_protocol>
53
+ ## Task Protocol
54
+
55
+ - Check \`execution.taskSupport\` before sending task metadata.
56
+ - \`forbidden\` (default): Do not send \`task\`.
57
+ - \`optional\`: Send \`task\` only when durable polling or deferred retrieval is needed.
58
+ - \`required\`: Always send \`task\`.
59
+ - Poll status via \`tasks/get\`, retrieve final payload via \`tasks/result\`.
60
+ - Pass \`_meta.progressToken\` in \`tools/call\` to receive \`notifications/progress\`.
64
61
  `;
65
62
  }
66
- const INSTRUCTIONS_FOOTER = `<constraints>
63
+ const INSTRUCTIONS_FOOTER = `## Constraints
64
+
67
65
  ${getSharedConstraints()
68
66
  .map((c) => `- ${c}`)
69
67
  .join('\n')}
70
- </constraints>
71
68
 
72
- <error_handling>
73
- - \`E_ACCESS_DENIED\` => call \`roots\`, use an allowed path.
74
- - \`E_NOT_FOUND\` => call \`ls\` or \`find\`, verify spelling.
75
- - \`E_TOO_LARGE\` => use \`head\`, line ranges, or \`read_many\`.
76
- - \`E_TIMEOUT\` => reduce scope or result limits.
77
- </error_handling>
69
+ ## Error Recovery
70
+
71
+ | Error Code | Action |
72
+ | ------------------- | ------------------------------------------------------------------------- |
73
+ | \`ACCESS_DENIED\` | Run \`roots\` to list allowed directories, retry with a valid path. |
74
+ | \`NOT_FOUND\` | Run \`ls\` or \`find\` to verify the path. |
75
+ | \`TOO_LARGE\` | Use \`head\`/\`tail\`, line ranges, or split across \`read_many\`. |
76
+ | \`TIMEOUT\` | Reduce scope, depth, or maxResults. |
77
+ | \`INVALID_INPUT\` | Re-read tool contract via \`internal://tool-info/{name}\`. |
78
78
  `;
79
79
  function formatToolSection(tool) {
80
80
  const parts = [`### ${tool.name}\n${tool.description}`];
81
81
  if (tool.nuances && tool.nuances.length > 0) {
82
- parts.push(...tool.nuances.map((n) => `- Nuance: ${n}`));
82
+ parts.push(...tool.nuances.map((n) => `- ${n}`));
83
83
  }
84
84
  if (tool.gotchas && tool.gotchas.length > 0) {
85
- parts.push(...tool.gotchas.map((g) => `- Gotcha: ${g}`));
85
+ parts.push(...tool.gotchas.map((g) => `- ⚠ ${g}`));
86
86
  }
87
87
  return parts.join('\n');
88
88
  }
@@ -94,9 +94,9 @@ export function buildServerInstructions() {
94
94
  '',
95
95
  buildToolCatalogDetailsOnly(),
96
96
  '',
97
- '<tool_reference>',
97
+ '## Tool Reference',
98
+ '',
98
99
  toolSections,
99
- '</tool_reference>',
100
100
  '',
101
101
  buildWorkflowGuide(),
102
102
  '',