@j0hanz/filesystem-mcp 1.13.1 → 1.13.2

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 (38) hide show
  1. package/dist/completions.d.ts +0 -11
  2. package/dist/completions.js +1 -1
  3. package/dist/lib/constants.d.ts +0 -1
  4. package/dist/lib/constants.js +0 -1
  5. package/dist/lib/errors.d.ts +0 -1
  6. package/dist/lib/errors.js +0 -7
  7. package/dist/lib/file-operations/core.d.ts +3 -2
  8. package/dist/lib/file-operations/search.d.ts +1 -58
  9. package/dist/lib/file-operations/search.js +4 -10
  10. package/dist/lib/file-operations/traversal.d.ts +2 -2
  11. package/dist/lib/fs-helpers.d.ts +1 -2
  12. package/dist/lib/fs-helpers.js +0 -1
  13. package/dist/lib/paths.d.ts +0 -3
  14. package/dist/lib/paths.js +1 -4
  15. package/dist/lib/utils.d.ts +4 -4
  16. package/dist/lib/utils.js +1 -10
  17. package/dist/resources/tool-info.js +13 -0
  18. package/dist/schemas.d.ts +0 -22
  19. package/dist/schemas.js +0 -4
  20. package/dist/server/bootstrap.d.ts +0 -10
  21. package/dist/server/bootstrap.js +3 -3
  22. package/dist/tools/create-directory.d.ts +1 -4
  23. package/dist/tools/create-directory.js +1 -1
  24. package/dist/tools/edit-file.d.ts +1 -7
  25. package/dist/tools/edit-file.js +1 -1
  26. package/dist/tools/move-file.d.ts +1 -4
  27. package/dist/tools/move-file.js +1 -1
  28. package/dist/tools/replace-in-files.d.ts +1 -10
  29. package/dist/tools/replace-in-files.js +5 -4
  30. package/dist/tools/search-content.js +2 -2
  31. package/dist/tools/search-files.js +5 -4
  32. package/dist/tools/shared.d.ts +3 -3
  33. package/dist/tools/shared.js +15 -1
  34. package/dist/tools/task-support.d.ts +0 -7
  35. package/dist/tools/task-support.js +7 -6
  36. package/dist/tools.d.ts +0 -1
  37. package/dist/tools.js +0 -1
  38. package/package.json +4 -5
@@ -1,13 +1,2 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- interface CompletionResult {
3
- values: string[];
4
- total?: number;
5
- hasMore?: boolean;
6
- }
7
- interface CompletionOptions {
8
- argumentName?: string;
9
- contextArguments?: Record<string, string>;
10
- }
11
- export declare function getPathCompletions(currentValue: string, options?: CompletionOptions): Promise<CompletionResult>;
12
2
  export declare function registerCompletions(server: McpServer, instructions?: string): void;
13
- export {};
@@ -409,7 +409,7 @@ function findMatchingRoots(searchDir, prefix, allowed) {
409
409
  return path.basename(root).toLowerCase().startsWith(lowerPrefix);
410
410
  });
411
411
  }
412
- export async function getPathCompletions(currentValue, options = {}) {
412
+ async function getPathCompletions(currentValue, options = {}) {
413
413
  const allowed = getAllowedDirectories();
414
414
  try {
415
415
  const contextBase = await resolveContextBaseDirectory(options.argumentName ?? '', options.contextArguments, allowed);
@@ -1,7 +1,6 @@
1
1
  export declare function parseTrueEnvFlag(value: string | undefined): boolean;
2
2
  export declare function parseEnvInt(envVar: string, defaultValue: number, min: number, max: number): number;
3
3
  export declare const DEFAULT_LOG_LEVEL: "debug" | "info" | "notice" | "warning" | "error" | "critical" | "alert" | "emergency";
4
- export declare const REQUIRED_MCP_PROTOCOL_VERSION = "2025-11-25";
5
4
  export declare const DEFAULT_TASK_TTL_MS: number;
6
5
  export declare const MAX_TASK_TTL_MS: number;
7
6
  export declare const MAX_CONCURRENT_TASKS: number;
@@ -70,7 +70,6 @@ function parseEnvLogLevel(envVar, defaultValue) {
70
70
  return defaultValue;
71
71
  }
72
72
  export const DEFAULT_LOG_LEVEL = parseEnvLogLevel('FILESYSTEM_MCP_LOG_LEVEL', 'info');
73
- export const REQUIRED_MCP_PROTOCOL_VERSION = '2025-11-25';
74
73
  // Default TTL for MCP tasks when the client does not specify one (5 minutes).
75
74
  export const DEFAULT_TASK_TTL_MS = 5 * 60 * 1000;
76
75
  export const MAX_TASK_TTL_MS = parseEnvInt('FILESYSTEM_MCP_MAX_TASK_TTL_MS', 60 * 60 * 1000, 1_000, 24 * 60 * 60 * 1000);
@@ -17,7 +17,6 @@ export declare class McpError extends Error {
17
17
  path?: string | undefined;
18
18
  details?: Record<string, unknown> | undefined;
19
19
  constructor(code: ErrorCode, message: string, path?: string | undefined, details?: Record<string, unknown> | undefined, cause?: unknown);
20
- static fromError(code: ErrorCode, message: string, originalError: unknown, path?: string, details?: Record<string, unknown>): McpError;
21
20
  }
22
21
  export declare function createDetailedError(error: unknown, path?: string, additionalDetails?: Record<string, unknown>): DetailedError;
23
22
  export declare function formatDetailedError(error: DetailedError): string;
@@ -173,13 +173,6 @@ export class McpError extends Error {
173
173
  this.name = 'McpError';
174
174
  Object.setPrototypeOf(this, McpError.prototype);
175
175
  }
176
- static fromError(code, message, originalError, path, details) {
177
- const mcpError = new McpError(code, message, path, details, originalError);
178
- if (originalError instanceof Error && originalError.stack) {
179
- mcpError.stack = `${String(mcpError.stack)}\nCaused by: ${originalError.stack}`;
180
- }
181
- return mcpError;
182
- }
183
176
  }
184
177
  const ERROR_SUGGESTIONS = {
185
178
  [ErrorCode.E_ACCESS_DENIED]: 'Check that the path is within an allowed directory. Use roots to see available workspace roots.',
@@ -9,11 +9,11 @@ export interface DirentLike {
9
9
  isSymbolicLink(): boolean;
10
10
  }
11
11
  export type EntryType = 'file' | 'directory' | 'symlink' | 'other';
12
- export interface IndexedValue<T> {
12
+ interface IndexedValue<T> {
13
13
  index: number;
14
14
  value: T;
15
15
  }
16
- export interface IndexedError {
16
+ interface IndexedError {
17
17
  index: number;
18
18
  error: Error;
19
19
  }
@@ -50,3 +50,4 @@ export declare function isIgnoredByGitignore(matcher: Ignore, root: string, abso
50
50
  isDirectory?: boolean;
51
51
  relativePath?: string;
52
52
  }): boolean;
53
+ export {};
@@ -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>;
@@ -41,40 +25,6 @@ export interface SearchContentOptions extends Partial<ResolvedOptions> {
41
25
  current: number;
42
26
  }) => void;
43
27
  }
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
28
  export declare function searchContent(basePath: string, pattern: string, options?: SearchContentOptions): Promise<SearchContentResult>;
79
29
  type SortBy = 'name' | 'size' | 'modified' | 'path';
80
30
  interface SearchFilesOptions {
@@ -93,12 +43,5 @@ interface SearchFilesOptions {
93
43
  current: number;
94
44
  }) => void;
95
45
  }
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
46
  export declare function searchFiles(basePath: string, pattern: string, excludePatterns?: readonly string[], options?: SearchFilesOptions): Promise<SearchFilesResult>;
104
47
  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)
@@ -130,7 +124,7 @@ function buildRegexMatcher(final, caseSensitive, multiline) {
130
124
  const regex = new RE2(final, flags);
131
125
  return (line) => countRegexLineMatches(regex, line);
132
126
  }
133
- export function buildMatcher(pattern, options) {
127
+ function buildMatcher(pattern, options) {
134
128
  if (options.isLiteral && pattern.length === 0)
135
129
  return () => 0;
136
130
  if (options.isLiteral && !options.wholeWord) {
@@ -751,7 +745,7 @@ async function executeParallel(files, pattern, opts, signal, summary) {
751
745
  return matches;
752
746
  }
753
747
  // --- Entry Points ---
754
- export async function scanFileInWorker(resolvedPath, requestedPath, matcher, options, maxMatches, isCancelled, isBinaryDetector) {
748
+ async function scanFileInWorker(resolvedPath, requestedPath, matcher, options, maxMatches, isCancelled, isBinaryDetector) {
755
749
  // Direct scan used by worker script
756
750
  const res = await scanFileResolved(resolvedPath, requestedPath, matcher, options, undefined, maxMatches, isBinaryDetector);
757
751
  return {
@@ -1051,7 +1045,7 @@ const SORT_COMPARATORS = {
1051
1045
  path: (a, b) => comparePathThenName(a, b),
1052
1046
  name: (a, b) => compareNameThenPath(a, b),
1053
1047
  };
1054
- export function sortSearchResults(results, sortBy) {
1048
+ function sortSearchResults(results, sortBy) {
1055
1049
  if (sortBy === 'name') {
1056
1050
  stableSortByDerivedString(results, (item) => path.basename(item.path ?? ''), (left, right) => comparePathThenName(left, right));
1057
1051
  return;
@@ -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 {};
@@ -667,4 +667,3 @@ export async function atomicWriteFile(filePath, content, options = {}) {
667
667
  throw error;
668
668
  }
669
669
  }
670
- export { headFile };
@@ -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;
@@ -98,6 +98,9 @@ function summarizeSchemaType(schema) {
98
98
  if (Array.isArray(schema.enum) && schema.enum.length > 0) {
99
99
  return `enum(${schema.enum.map((value) => JSON.stringify(value)).join(', ')})`;
100
100
  }
101
+ if (schema.const !== undefined) {
102
+ return `const(${JSON.stringify(schema.const)})`;
103
+ }
101
104
  if (Array.isArray(schema.anyOf) && schema.anyOf.length > 0) {
102
105
  return schema.anyOf.map(summarizeSchemaType).join(' | ');
103
106
  }
@@ -105,11 +108,18 @@ function summarizeSchemaType(schema) {
105
108
  return schema.oneOf.map(summarizeSchemaType).join(' | ');
106
109
  }
107
110
  if (schema.type === 'array') {
111
+ if (Array.isArray(schema.prefixItems) && schema.prefixItems.length > 0) {
112
+ const itemTypes = schema.prefixItems.map(summarizeSchemaType).join(', ');
113
+ return `tuple<${itemTypes}>`;
114
+ }
108
115
  const itemType = schema.items
109
116
  ? summarizeSchemaType(schema.items)
110
117
  : 'unknown';
111
118
  return `array<${itemType}>`;
112
119
  }
120
+ if (schema.type === 'object' && schema.additionalProperties === false) {
121
+ return 'object (strict)';
122
+ }
113
123
  if (typeof schema.type === 'string' && schema.type.length > 0) {
114
124
  return schema.type;
115
125
  }
@@ -129,6 +139,9 @@ function buildSchemaFieldLines(label, schema) {
129
139
  jsonSchema.description.length > 0) {
130
140
  lines.push(`- Schema constraints: ${jsonSchema.description}`);
131
141
  }
142
+ if (jsonSchema.additionalProperties === false) {
143
+ lines.push('- Unknown fields are rejected (`additionalProperties: false`).');
144
+ }
132
145
  if (fieldNames.length === 0) {
133
146
  lines.push('- object with no fields', `</${label}>`);
134
147
  return lines;
package/dist/schemas.d.ts CHANGED
@@ -12,28 +12,6 @@ interface TreeEntry {
12
12
  size?: number | undefined;
13
13
  children?: TreeEntry[] | undefined;
14
14
  }
15
- export declare const ToolErrorResponseSchema: z.ZodObject<{
16
- ok: z.ZodLiteral<false>;
17
- error: z.ZodObject<{
18
- code: z.ZodEnum<{
19
- readonly E_ACCESS_DENIED: "E_ACCESS_DENIED";
20
- readonly E_NOT_FOUND: "E_NOT_FOUND";
21
- readonly E_NOT_FILE: "E_NOT_FILE";
22
- readonly E_NOT_DIRECTORY: "E_NOT_DIRECTORY";
23
- readonly E_TOO_LARGE: "E_TOO_LARGE";
24
- readonly E_TIMEOUT: "E_TIMEOUT";
25
- readonly E_CANCELLED: "E_CANCELLED";
26
- readonly E_INVALID_PATTERN: "E_INVALID_PATTERN";
27
- readonly E_INVALID_INPUT: "E_INVALID_INPUT";
28
- readonly E_PERMISSION_DENIED: "E_PERMISSION_DENIED";
29
- readonly E_SYMLINK_NOT_ALLOWED: "E_SYMLINK_NOT_ALLOWED";
30
- readonly E_UNKNOWN: "E_UNKNOWN";
31
- }>;
32
- message: z.ZodString;
33
- path: z.ZodOptional<z.ZodString>;
34
- suggestion: z.ZodOptional<z.ZodString>;
35
- }, z.core.$strict>;
36
- }, z.core.$strict>;
37
15
  declare const HeadLinesSchema: z.ZodOptional<z.ZodInt>;
38
16
  declare const TailLinesSchema: z.ZodOptional<z.ZodInt>;
39
17
  declare const LineNumberSchema: z.ZodInt;
package/dist/schemas.js CHANGED
@@ -48,10 +48,6 @@ const ErrorSchema = z.strictObject({
48
48
  path: z.string().optional().describe('Relevant path'),
49
49
  suggestion: z.string().optional().describe('Fix suggestion'),
50
50
  });
51
- export const ToolErrorResponseSchema = z.strictObject({
52
- ok: z.literal(false).describe('Operation failed'),
53
- error: ErrorSchema.describe('Error details'),
54
- });
55
51
  const HeadLinesSchema = z
56
52
  .int({ error: 'Must be integer' })
57
53
  .min(1, 'Min: 1')
@@ -5,20 +5,10 @@ export interface ServerOptions {
5
5
  allowCwd?: boolean;
6
6
  cliAllowedDirs?: string[];
7
7
  }
8
- interface CapabilityOptions {
9
- enablePromptListChanged?: boolean;
10
- enableTaskToolRequests?: boolean;
11
- }
12
- type ServerCapabilities = NonNullable<ConstructorParameters<typeof McpServer>[1]>['capabilities'];
13
- type NonOptionalServerCapabilities = NonNullable<ServerCapabilities>;
14
- export declare function buildServerCapabilities(options?: CapabilityOptions): NonOptionalServerCapabilities;
15
- export declare function supportsTaskToolRequests(): boolean;
16
8
  export interface LoggingState {
17
9
  minimumLevel: LoggingLevel;
18
10
  }
19
- export declare function createLoggingState(minimumLevel?: LoggingLevel): LoggingState;
20
11
  export declare function logToMcp(server: McpServer | undefined, level: LoggingLevel, data: string, minLevel?: LoggingLevel): void;
21
12
  export declare function createServer(options?: ServerOptions): Promise<McpServer>;
22
13
  export declare function startServer(server: McpServer): Promise<void>;
23
14
  export declare function startHttpServer(port: number, options: ServerOptions): Promise<http.Server>;
24
- export {};
@@ -41,7 +41,7 @@ function detectTaskToolSupport() {
41
41
  }
42
42
  return cachedTaskToolSupport;
43
43
  }
44
- export function buildServerCapabilities(options = {}) {
44
+ function buildServerCapabilities(options = {}) {
45
45
  const capabilities = {
46
46
  logging: {},
47
47
  resources: {},
@@ -61,7 +61,7 @@ export function buildServerCapabilities(options = {}) {
61
61
  }
62
62
  return capabilities;
63
63
  }
64
- export function supportsTaskToolRequests() {
64
+ function supportsTaskToolRequests() {
65
65
  return detectTaskToolSupport();
66
66
  }
67
67
  const MCP_LOGGER_NAME = 'filesystem-mcp';
@@ -75,7 +75,7 @@ const LOG_LEVEL_ORDER = {
75
75
  alert: 6,
76
76
  emergency: 7,
77
77
  };
78
- export function createLoggingState(minimumLevel = 'debug') {
78
+ function createLoggingState(minimumLevel = 'debug') {
79
79
  return { minimumLevel };
80
80
  }
81
81
  function canSendMcpLogs(server) {
@@ -1,7 +1,4 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import type { z } from 'zod';
3
- import { CreateDirectoryInputSchema, CreateDirectoryOutputSchema } from '../schemas.js';
4
- import { type ToolContract, type ToolRegistrationOptions, type ToolResponse } from './shared.js';
2
+ import { type ToolContract, type ToolRegistrationOptions } from './shared.js';
5
3
  export declare const CREATE_DIRECTORY_TOOL: ToolContract;
6
- export declare function handleCreateDirectory(args: z.infer<typeof CreateDirectoryInputSchema>, signal?: AbortSignal): Promise<ToolResponse<z.infer<typeof CreateDirectoryOutputSchema>>>;
7
4
  export declare function registerCreateDirectoryTool(server: McpServer, options?: ToolRegistrationOptions): void;
@@ -16,7 +16,7 @@ export const CREATE_DIRECTORY_TOOL = {
16
16
  nuances: ['Succeeds silently if the directory already exists (idempotent).'],
17
17
  taskSupport: 'forbidden',
18
18
  };
19
- export async function handleCreateDirectory(args, signal) {
19
+ async function handleCreateDirectory(args, signal) {
20
20
  const allPaths = [];
21
21
  if (args.path)
22
22
  allPaths.push(args.path);
@@ -1,10 +1,4 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import type { z } from 'zod';
3
- import { EditFileInputSchema, EditFileOutputSchema } from '../schemas.js';
4
- import { type ToolContract, type ToolRegistrationOptions, type ToolResponse } from './shared.js';
2
+ import { type ToolContract, type ToolRegistrationOptions } from './shared.js';
5
3
  export declare const EDIT_FILE_TOOL: ToolContract;
6
- type EditInput = z.infer<typeof EditFileInputSchema>;
7
- type EditOutput = z.infer<typeof EditFileOutputSchema>;
8
- export declare function handleEditFile(args: EditInput, signal?: AbortSignal): Promise<ToolResponse<EditOutput>>;
9
4
  export declare function registerEditFileTool(server: McpServer, options?: ToolRegistrationOptions): void;
10
- export {};
@@ -205,7 +205,7 @@ async function applyEdits(content, edits, ignoreWhitespace) {
205
205
  }
206
206
  return finalizeEditResult(content, newContent, appliedEdits, unmatchedEdits, lineRange);
207
207
  }
208
- export async function handleEditFile(args, signal) {
208
+ async function handleEditFile(args, signal) {
209
209
  const { validPath, content } = await loadEditableFile(args.path, signal);
210
210
  const editResult = await applyEdits(content, args.edits, args.ignoreWhitespace);
211
211
  const structured = buildStructuredEditOutput(validPath, editResult);
@@ -1,7 +1,4 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import type { z } from 'zod';
3
- import { MoveFileInputSchema, MoveFileOutputSchema } from '../schemas.js';
4
- import { type ToolContract, type ToolRegistrationOptions, type ToolResponse } from './shared.js';
2
+ import { type ToolContract, type ToolRegistrationOptions } from './shared.js';
5
3
  export declare const MOVE_FILE_TOOL: ToolContract;
6
- export declare function handleMoveFile(args: z.infer<typeof MoveFileInputSchema>, signal?: AbortSignal): Promise<ToolResponse<z.infer<typeof MoveFileOutputSchema>>>;
7
4
  export declare function registerMoveFileTool(server: McpServer, options?: ToolRegistrationOptions): void;
@@ -19,7 +19,7 @@ export const MOVE_FILE_TOOL = {
19
19
  ],
20
20
  taskSupport: 'forbidden',
21
21
  };
22
- export async function handleMoveFile(args, signal) {
22
+ async function handleMoveFile(args, signal) {
23
23
  const sources = args.sources ?? (args.source ? [args.source] : []);
24
24
  if (sources.length === 0) {
25
25
  throw new McpError(ErrorCode.E_INVALID_INPUT, 'No sources provided.');
@@ -1,13 +1,4 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import type { z } from 'zod';
3
- import { SearchAndReplaceInputSchema, SearchAndReplaceOutputSchema } from '../schemas.js';
4
- import { type ToolContract, type ToolRegistrationOptions, type ToolResponse } from './shared.js';
2
+ import { type ToolContract, type ToolRegistrationOptions } from './shared.js';
5
3
  export declare const SEARCH_AND_REPLACE_TOOL: ToolContract;
6
- type SearchAndReplaceArgs = z.infer<typeof SearchAndReplaceInputSchema>;
7
- type SearchAndReplaceOutput = z.infer<typeof SearchAndReplaceOutputSchema>;
8
- export declare function handleSearchAndReplace(args: SearchAndReplaceArgs, signal?: AbortSignal, onProgress?: (progress: {
9
- total?: number;
10
- current: number;
11
- }) => void): Promise<ToolResponse<SearchAndReplaceOutput>>;
12
4
  export declare function registerSearchAndReplaceTool(server: McpServer, options?: ToolRegistrationOptions): void;
13
- export {};
@@ -13,7 +13,7 @@ import { atomicWriteFile } from '../lib/fs-helpers.js';
13
13
  import { validateExistingPath, validatePathForWrite } from '../lib/paths.js';
14
14
  import { reportPeriodicProgress } from '../lib/utils.js';
15
15
  import { SearchAndReplaceInputSchema, SearchAndReplaceOutputSchema, } from '../schemas.js';
16
- import { buildToolErrorResponse, buildToolResponse, createToolProgressSession, DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS, executeToolWithDiagnostics, resolveFinalProgressCurrent, resolvePathOrRoot, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
16
+ import { buildToolErrorResponse, buildToolResponse, createToolProgressSession, DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS, executeToolWithDiagnostics, resolveFinalProgressCurrent, resolvePathOrRoot, truncateProgressPattern, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
17
17
  import { registerToolTaskIfAvailable } from './task-support.js';
18
18
  export const SEARCH_AND_REPLACE_TOOL = {
19
19
  name: 'search_and_replace',
@@ -306,7 +306,7 @@ function createReplacementMatcher(args) {
306
306
  }
307
307
  return new LiteralReplacementMatcher(args.searchPattern, args.caseSensitive);
308
308
  }
309
- export async function handleSearchAndReplace(args, signal, onProgress = () => { }) {
309
+ async function handleSearchAndReplace(args, signal, onProgress = () => { }) {
310
310
  const maxFileSize = MAX_TEXT_FILE_SIZE;
311
311
  const root = await resolveSearchRoot(args.path, signal);
312
312
  const matcher = createReplacementMatcher(args);
@@ -374,13 +374,14 @@ export function registerSearchAndReplaceTool(server, options = {}) {
374
374
  ...(args.path ? { context: { path: args.path } } : {}),
375
375
  run: async (signal) => {
376
376
  const dryLabel = args.dryRun ? ' [dry run]' : '';
377
- const context = `"${args.searchPattern}" in ${args.filePattern}${dryLabel}`;
377
+ const truncatedPattern = truncateProgressPattern(args.searchPattern);
378
+ const context = `"${truncatedPattern}" in ${args.filePattern}${dryLabel}`;
378
379
  const progress = createToolProgressSession(extra, `🛠 replace: ${context}`);
379
380
  const progressWithMessage = ({ current, total, }) => {
380
381
  progress.update({
381
382
  current,
382
383
  ...(total !== undefined ? { total } : {}),
383
- message: `🛠 replace: ${args.searchPattern} [${current} files]`,
384
+ message: `🛠 replace: ${truncatedPattern} [${current} files]`,
384
385
  });
385
386
  };
386
387
  try {
@@ -6,7 +6,7 @@ import { ErrorCode, formatUnknownErrorMessage, McpError, } from '../lib/errors.j
6
6
  import { searchContent } from '../lib/file-operations/search.js';
7
7
  import { formatOperationSummary } from '../config.js';
8
8
  import { SearchContentInputSchema, SearchContentOutputSchema, } from '../schemas.js';
9
- import { buildResourceLink, buildToolErrorResponse, buildToolResponse, createToolProgressSession, executeToolWithDiagnostics, READ_ONLY_TOOL_ANNOTATIONS, resolveFinalProgressCurrent, resolvePathOrRoot, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
9
+ import { buildResourceLink, buildToolErrorResponse, buildToolResponse, createToolProgressSession, executeToolWithDiagnostics, READ_ONLY_TOOL_ANNOTATIONS, resolveFinalProgressCurrent, resolvePathOrRoot, truncateProgressPattern, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
10
10
  import { registerToolTaskIfAvailable } from './task-support.js';
11
11
  /**
12
12
  * Configuration constants for the Search Content tool.
@@ -309,7 +309,7 @@ export function registerSearchContentTool(server, options = {}) {
309
309
  context: { path: args.path ?? '.' },
310
310
  run: async (signal) => {
311
311
  const { pattern, filePattern: scope } = args;
312
- const progressLabel = `🔎︎ grep: ${pattern}`;
312
+ const progressLabel = `🔎︎ grep: ${truncateProgressPattern(pattern)}`;
313
313
  const progress = createToolProgressSession(extra, progressLabel);
314
314
  const progressWithMessage = ({ current, total, }) => {
315
315
  progress.update({
@@ -4,7 +4,7 @@ import { ErrorCode } from '../lib/errors.js';
4
4
  import { searchFiles } from '../lib/file-operations/search.js';
5
5
  import { formatOperationSummary, joinLines } from '../config.js';
6
6
  import { SearchFilesInputSchema, SearchFilesOutputSchema } from '../schemas.js';
7
- import { buildToolErrorResponse, buildToolResponse, createProgressReporter, decodeOffsetCursor, encodeOffsetCursor, executeToolWithDiagnostics, notifyProgress, READ_ONLY_TOOL_ANNOTATIONS, resolvePathOrRoot, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
7
+ import { buildToolErrorResponse, buildToolResponse, createProgressReporter, decodeOffsetCursor, encodeOffsetCursor, executeToolWithDiagnostics, notifyProgress, READ_ONLY_TOOL_ANNOTATIONS, resolvePathOrRoot, truncateProgressPattern, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
8
8
  import { registerToolTaskIfAvailable } from './task-support.js';
9
9
  export const SEARCH_FILES_TOOL = {
10
10
  name: 'find',
@@ -109,11 +109,12 @@ export function registerSearchFilesTool(server, options = {}) {
109
109
  const rawScopeLabel = args.path ? path.basename(args.path) : '.';
110
110
  const scopeLabel = rawScopeLabel || '.';
111
111
  const { pattern } = args;
112
- const context = `${pattern} in ${scopeLabel}`;
112
+ const truncatedPattern = truncateProgressPattern(pattern);
113
+ const context = `${truncatedPattern} in ${scopeLabel}`;
113
114
  let progressCursor = 0;
114
115
  notifyProgress(extra, {
115
116
  current: 0,
116
- message: `🔎︎ find: ${pattern}`,
117
+ message: `🔎︎ find: ${truncatedPattern}`,
117
118
  });
118
119
  const baseReporter = createProgressReporter(extra);
119
120
  const progressWithMessage = ({ current, total, }) => {
@@ -122,7 +123,7 @@ export function registerSearchFilesTool(server, options = {}) {
122
123
  baseReporter({
123
124
  current,
124
125
  ...(total !== undefined ? { total } : {}),
125
- message: `🔎︎ find: ${pattern} [${current} files]`,
126
+ message: `🔎︎ find: ${truncatedPattern} [${current} files]`,
126
127
  });
127
128
  };
128
129
  try {
@@ -22,7 +22,6 @@ export declare const IDEMPOTENT_WRITE_TOOL_ANNOTATIONS: {
22
22
  readonly destructiveHint: false;
23
23
  readonly openWorldHint: false;
24
24
  };
25
- export declare function shouldStripStructuredOutput(): boolean;
26
25
  export declare function maybeStripStructuredContentFromResult<T extends object>(result: T): T;
27
26
  type ResourceEntry = ReturnType<ResourceStore['putText']>;
28
27
  export declare function maybeExternalizeTextContent(resourceStore: ResourceStore | undefined, content: string, params: {
@@ -114,7 +113,7 @@ export declare function notifyProgress(extra: ToolExtra, progress: {
114
113
  total?: number;
115
114
  message?: string;
116
115
  }): void;
117
- export interface ToolProgressSession {
116
+ interface ToolProgressSession {
118
117
  update: (progress: {
119
118
  current: number;
120
119
  total?: number;
@@ -125,7 +124,7 @@ export interface ToolProgressSession {
125
124
  fail: (message: string, minimumCurrent?: number) => void;
126
125
  getCurrent: () => number;
127
126
  }
128
- export interface BatchProgressCallbacks {
127
+ interface BatchProgressCallbacks {
129
128
  progress: ToolProgressSession;
130
129
  onItemComplete: () => void;
131
130
  }
@@ -155,6 +154,7 @@ export declare function resolvePathOrRoot(pathValue: string | undefined): string
155
154
  export declare function encodeOffsetCursor(offset: number): string;
156
155
  export declare function decodeOffsetCursor(cursor: string): number;
157
156
  export declare function buildBatchPathContext(paths: readonly string[], unitLabel?: string): string;
157
+ export declare function truncateProgressPattern(pattern: string, maxLength?: number): string;
158
158
  export declare function buildBatchCompletionSuffix(summary: {
159
159
  total?: number;
160
160
  failed?: number;
@@ -64,7 +64,7 @@ function normalizeToolExecution(tool) {
64
64
  }
65
65
  return normalized;
66
66
  }
67
- export function shouldStripStructuredOutput() {
67
+ function shouldStripStructuredOutput() {
68
68
  return parseTrueEnvFlag(process.env['FS_CONTEXT_STRIP_STRUCTURED']);
69
69
  }
70
70
  export function maybeStripStructuredContentFromResult(result) {
@@ -488,6 +488,20 @@ export function buildBatchPathContext(paths, unitLabel = 'paths') {
488
488
  : '';
489
489
  return `${paths.length} ${normalizedLabel} [${first}${extraPaths}]`;
490
490
  }
491
+ export function truncateProgressPattern(pattern, maxLength = 40) {
492
+ if (pattern.length <= maxLength)
493
+ return pattern;
494
+ if (pattern.includes('|')) {
495
+ const segments = pattern.split('|');
496
+ const first = segments[0] ?? '';
497
+ const second = segments[1];
498
+ const preview = second !== undefined ? `${first}|${second}` : first;
499
+ return preview.length <= maxLength
500
+ ? `${preview}…`
501
+ : `${preview.slice(0, maxLength)}…`;
502
+ }
503
+ return `${pattern.slice(0, maxLength)}…`;
504
+ }
491
505
  export function buildBatchCompletionSuffix(summary, successWord, singularWord) {
492
506
  const total = summary?.total ?? 0;
493
507
  const failed = summary?.failed ?? 0;
@@ -1,15 +1,8 @@
1
- import { AsyncLocalStorage } from 'node:async_hooks';
2
1
  import type { ToolTaskHandler } from '@modelcontextprotocol/sdk/experimental/tasks/interfaces.js';
3
2
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
3
  import type { AnySchema, SchemaOutput, ShapeOutput, ZodRawShapeCompat } from '@modelcontextprotocol/sdk/server/zod-compat.js';
5
4
  import type { RequestTaskStore } from '@modelcontextprotocol/sdk/shared/protocol.js';
6
5
  import type { IconInfo, ToolExtra, ToolResult } from './shared.js';
7
- export interface TaskContext {
8
- taskId: string;
9
- toolName?: string | undefined;
10
- startTime: number;
11
- }
12
- export declare const taskContext: AsyncLocalStorage<TaskContext>;
13
6
  type TaskToolExtra = ToolExtra & {
14
7
  taskId?: string;
15
8
  taskStore?: RequestTaskStore;
@@ -7,13 +7,16 @@ import { DEFAULT_TASK_TTL_MS, MAX_CONCURRENT_TASKS, MAX_TASK_TTL_MS, } from '../
7
7
  import { ErrorCode, McpError } from '../lib/errors.js';
8
8
  import { isRecord } from '../lib/utils.js';
9
9
  import { buildToolErrorResponse, maybeStripStructuredContentFromResult, withDefaultIcons, } from './shared.js';
10
- export const taskContext = new AsyncLocalStorage();
10
+ const taskContext = new AsyncLocalStorage();
11
11
  const TASK_DIAGNOSTICS_CHANNEL = channel('filesystem-mcp:tasks');
12
12
  function publishTaskDiagnostics(event) {
13
13
  if (TASK_DIAGNOSTICS_CHANNEL.hasSubscribers) {
14
14
  TASK_DIAGNOSTICS_CHANNEL.publish(event);
15
15
  }
16
16
  }
17
+ function getDynamicProperty(target, key) {
18
+ return Reflect.get(target, key);
19
+ }
17
20
  // --- Type Guards & Helpers ---
18
21
  function isExperimentalTaskRegistration(value) {
19
22
  if (!isRecord(value))
@@ -22,8 +25,7 @@ function isExperimentalTaskRegistration(value) {
22
25
  return (registerToolTask === undefined || typeof registerToolTask === 'function');
23
26
  }
24
27
  function getExperimentalTaskRegistration(server) {
25
- const serverWithExperimental = server;
26
- const { experimental } = serverWithExperimental;
28
+ const experimental = getDynamicProperty(server, 'experimental');
27
29
  if (!isRecord(experimental))
28
30
  return undefined;
29
31
  const { tasks } = experimental;
@@ -32,11 +34,10 @@ function getExperimentalTaskRegistration(server) {
32
34
  return tasks;
33
35
  }
34
36
  function hasTaskToolCapability(server) {
35
- const serverRecord = server;
36
- const { server: serverRuntime } = serverRecord;
37
+ const serverRuntime = getDynamicProperty(server, 'server');
37
38
  if (!isRecord(serverRuntime))
38
39
  return true; // Assume capability if runtime structure is opaque
39
- const { getCapabilities } = serverRuntime;
40
+ const getCapabilities = getDynamicProperty(serverRuntime, 'getCapabilities');
40
41
  if (typeof getCapabilities !== 'function')
41
42
  return true;
42
43
  const capabilities = getCapabilities.call(serverRuntime);
package/dist/tools.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { type ToolContract } from './tools/contract.js';
3
3
  import type { ToolRegistrationOptions } from './tools/shared.js';
4
- export { buildToolErrorResponse, buildToolResponse } from './tools/shared.js';
5
4
  export declare const ALL_TOOLS: ToolContract[];
6
5
  export declare function registerAllTools(server: McpServer, options?: ToolRegistrationOptions): void;
package/dist/tools.js CHANGED
@@ -17,7 +17,6 @@ import { GET_MULTIPLE_FILE_INFO_TOOL, registerGetMultipleFileInfoTool, } from '.
17
17
  import { GET_FILE_INFO_TOOL, registerGetFileInfoTool } from './tools/stat.js';
18
18
  import { registerTreeTool, TREE_TOOL } from './tools/tree.js';
19
19
  import { registerWriteFileTool, WRITE_FILE_TOOL } from './tools/write-file.js';
20
- export { buildToolErrorResponse, buildToolResponse } from './tools/shared.js';
21
20
  const TOOL_ENTRIES = [
22
21
  {
23
22
  contract: LIST_ALLOWED_DIRECTORIES_TOOL,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@j0hanz/filesystem-mcp",
3
- "version": "1.13.1",
3
+ "version": "1.13.2",
4
4
  "mcpName": "io.github.j0hanz/filesystem-mcp",
5
5
  "description": "A local filesystem MCP server that lets LLMs and AI agents read, write, search, diff, patch, and manage files safely and efficiently. Built for reliable, structured, and controlled filesystem interaction.",
6
6
  "type": "module",
@@ -86,17 +86,16 @@
86
86
  "@eslint/js": "^10.0.1",
87
87
  "@trivago/prettier-plugin-sort-imports": "^6.0.2",
88
88
  "@types/node": "^24",
89
- "eslint": "^10.0.2",
89
+ "eslint": "^10.0.3",
90
90
  "eslint-config-prettier": "^10.1.8",
91
91
  "eslint-plugin-de-morgan": "^2.1.1",
92
92
  "eslint-plugin-depend": "^1.5.0",
93
93
  "eslint-plugin-unused-imports": "^4.4.1",
94
- "jscpd": "^4.0.8",
95
- "knip": "^5.85.0",
94
+ "knip": "^5.86.0",
96
95
  "prettier": "^3.8.1",
97
96
  "tsx": "^4.21.0",
98
97
  "typescript": "^5.9.3",
99
- "typescript-eslint": "^8.56.1"
98
+ "typescript-eslint": "^8.57.0"
100
99
  },
101
100
  "engines": {
102
101
  "node": ">=24"