@j0hanz/filesystem-mcp 1.13.0 → 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.
- package/dist/completions.d.ts +0 -11
- package/dist/completions.js +18 -1
- package/dist/lib/constants.d.ts +0 -1
- package/dist/lib/constants.js +0 -1
- package/dist/lib/errors.d.ts +0 -1
- package/dist/lib/errors.js +0 -7
- package/dist/lib/file-operations/core.d.ts +3 -2
- package/dist/lib/file-operations/search.d.ts +1 -58
- package/dist/lib/file-operations/search.js +4 -10
- package/dist/lib/file-operations/traversal.d.ts +2 -2
- package/dist/lib/fs-helpers.d.ts +1 -2
- package/dist/lib/fs-helpers.js +0 -1
- package/dist/lib/paths.d.ts +0 -3
- package/dist/lib/paths.js +1 -4
- package/dist/lib/utils.d.ts +4 -4
- package/dist/lib/utils.js +1 -10
- package/dist/resources/tool-info.js +13 -0
- package/dist/schemas.d.ts +0 -22
- package/dist/schemas.js +0 -4
- package/dist/server/bootstrap.d.ts +0 -10
- package/dist/server/bootstrap.js +3 -3
- package/dist/tools/create-directory.d.ts +1 -4
- package/dist/tools/create-directory.js +1 -1
- package/dist/tools/edit-file.d.ts +1 -7
- package/dist/tools/edit-file.js +2 -2
- package/dist/tools/move-file.d.ts +1 -4
- package/dist/tools/move-file.js +1 -1
- package/dist/tools/replace-in-files.d.ts +1 -10
- package/dist/tools/replace-in-files.js +62 -38
- package/dist/tools/search-content.js +38 -32
- package/dist/tools/search-files.js +5 -4
- package/dist/tools/shared.d.ts +3 -3
- package/dist/tools/shared.js +15 -1
- package/dist/tools/task-support.d.ts +0 -7
- package/dist/tools/task-support.js +7 -6
- package/dist/tools.d.ts +0 -1
- package/dist/tools.js +0 -1
- package/package.json +4 -5
package/dist/completions.d.ts
CHANGED
|
@@ -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 {};
|
package/dist/completions.js
CHANGED
|
@@ -47,6 +47,9 @@ const PATH_ARGUMENTS = new Set([
|
|
|
47
47
|
const DESTINATION_CONTEXT_KEYS = ['source', 'path', 'cwd', 'root'];
|
|
48
48
|
const PRIMARY_PATH_CONTEXT_KEYS = ['path', 'cwd', 'root'];
|
|
49
49
|
const DEFAULT_CONTEXT_KEYS = ['path', 'source', 'cwd', 'root'];
|
|
50
|
+
const ENUM_ARGUMENT_VALUES = new Map([
|
|
51
|
+
['sortby', ['modified', 'name', 'path', 'size', 'type']],
|
|
52
|
+
]);
|
|
50
53
|
function isPathLikeArgumentName(argName) {
|
|
51
54
|
return (PATH_ARGUMENTS.has(argName) ||
|
|
52
55
|
argName.endsWith('paths') ||
|
|
@@ -56,6 +59,16 @@ function isPathLikeArgumentName(argName) {
|
|
|
56
59
|
argName.endsWith('dirs') ||
|
|
57
60
|
argName.endsWith('dir'));
|
|
58
61
|
}
|
|
62
|
+
function getEnumCompletions(argName, currentValue) {
|
|
63
|
+
const values = ENUM_ARGUMENT_VALUES.get(argName);
|
|
64
|
+
if (!values)
|
|
65
|
+
return undefined;
|
|
66
|
+
const prefix = currentValue.toLowerCase();
|
|
67
|
+
const filtered = prefix
|
|
68
|
+
? values.filter((v) => v.startsWith(prefix))
|
|
69
|
+
: [...values];
|
|
70
|
+
return buildCompletionResult(filtered);
|
|
71
|
+
}
|
|
59
72
|
function isTemplateVariableChar(char) {
|
|
60
73
|
const code = char.charCodeAt(0);
|
|
61
74
|
const isDigit = code >= 48 && code <= 57;
|
|
@@ -396,7 +409,7 @@ function findMatchingRoots(searchDir, prefix, allowed) {
|
|
|
396
409
|
return path.basename(root).toLowerCase().startsWith(lowerPrefix);
|
|
397
410
|
});
|
|
398
411
|
}
|
|
399
|
-
|
|
412
|
+
async function getPathCompletions(currentValue, options = {}) {
|
|
400
413
|
const allowed = getAllowedDirectories();
|
|
401
414
|
try {
|
|
402
415
|
const contextBase = await resolveContextBaseDirectory(options.argumentName ?? '', options.contextArguments, allowed);
|
|
@@ -452,6 +465,10 @@ export function registerCompletions(server, instructions = '') {
|
|
|
452
465
|
: toolNameValues;
|
|
453
466
|
return buildCompletionResponse(buildCompletionResult(filtered));
|
|
454
467
|
}
|
|
468
|
+
const enumResult = getEnumCompletions(argName, argument.value);
|
|
469
|
+
if (enumResult) {
|
|
470
|
+
return buildCompletionResponse(enumResult);
|
|
471
|
+
}
|
|
455
472
|
const isPathArg = isPathLikeArgumentName(argName) ||
|
|
456
473
|
isPathArgumentFromReference(argName, ref);
|
|
457
474
|
if (!isPathArg) {
|
package/dist/lib/constants.d.ts
CHANGED
|
@@ -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;
|
package/dist/lib/constants.js
CHANGED
|
@@ -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);
|
package/dist/lib/errors.d.ts
CHANGED
|
@@ -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;
|
package/dist/lib/errors.js
CHANGED
|
@@ -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
|
-
|
|
12
|
+
interface IndexedValue<T> {
|
|
13
13
|
index: number;
|
|
14
14
|
value: T;
|
|
15
15
|
}
|
|
16
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
23
|
+
interface GlobConfig {
|
|
24
24
|
cwd: string;
|
|
25
25
|
pattern: string;
|
|
26
26
|
excludePatterns?: readonly string[];
|
package/dist/lib/fs-helpers.d.ts
CHANGED
|
@@ -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 {
|
|
52
|
+
export {};
|
package/dist/lib/fs-helpers.js
CHANGED
package/dist/lib/paths.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|
package/dist/lib/utils.d.ts
CHANGED
|
@@ -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
|
-
|
|
9
|
-
export interface ProgressPayload {
|
|
8
|
+
interface ProgressPayload {
|
|
10
9
|
current: number;
|
|
11
10
|
total?: number;
|
|
12
11
|
}
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
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 {};
|
package/dist/server/bootstrap.js
CHANGED
|
@@ -41,7 +41,7 @@ function detectTaskToolSupport() {
|
|
|
41
41
|
}
|
|
42
42
|
return cachedTaskToolSupport;
|
|
43
43
|
}
|
|
44
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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 {};
|
package/dist/tools/edit-file.js
CHANGED
|
@@ -185,7 +185,7 @@ function buildEditCompletionMessage(args, result) {
|
|
|
185
185
|
const added = structuredContent.linesAdded ?? 0;
|
|
186
186
|
const removed = structuredContent.linesRemoved ?? 0;
|
|
187
187
|
const dry = args.dryRun ? 'dry run ' : '';
|
|
188
|
-
return `🛠 edit: ${name} • ${dry}+${added} -${removed}`;
|
|
188
|
+
return `🛠 edit: ${name} • ${dry} +${added} -${removed}`;
|
|
189
189
|
}
|
|
190
190
|
async function applyEdits(content, edits, ignoreWhitespace) {
|
|
191
191
|
let newContent = content;
|
|
@@ -205,7 +205,7 @@ async function applyEdits(content, edits, ignoreWhitespace) {
|
|
|
205
205
|
}
|
|
206
206
|
return finalizeEditResult(content, newContent, appliedEdits, unmatchedEdits, lineRange);
|
|
207
207
|
}
|
|
208
|
-
|
|
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
|
|
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;
|
package/dist/tools/move-file.js
CHANGED
|
@@ -19,7 +19,7 @@ export const MOVE_FILE_TOOL = {
|
|
|
19
19
|
],
|
|
20
20
|
taskSupport: 'forbidden',
|
|
21
21
|
};
|
|
22
|
-
|
|
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
|
|
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 {};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as fs from 'node:fs/promises';
|
|
2
2
|
import * as path from 'node:path';
|
|
3
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
3
4
|
import { Buffer } from 'node:buffer';
|
|
5
|
+
import { performance } from 'node:perf_hooks';
|
|
4
6
|
import { createTwoFilesPatch } from 'diff';
|
|
5
7
|
import RE2 from 're2';
|
|
6
8
|
import safeRegex from 'safe-regex2';
|
|
@@ -11,7 +13,7 @@ import { atomicWriteFile } from '../lib/fs-helpers.js';
|
|
|
11
13
|
import { validateExistingPath, validatePathForWrite } from '../lib/paths.js';
|
|
12
14
|
import { reportPeriodicProgress } from '../lib/utils.js';
|
|
13
15
|
import { SearchAndReplaceInputSchema, SearchAndReplaceOutputSchema, } from '../schemas.js';
|
|
14
|
-
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';
|
|
15
17
|
import { registerToolTaskIfAvailable } from './task-support.js';
|
|
16
18
|
export const SEARCH_AND_REPLACE_TOOL = {
|
|
17
19
|
name: 'search_and_replace',
|
|
@@ -110,6 +112,13 @@ class LiteralReplacementMatcher {
|
|
|
110
112
|
return content.replaceAll(this.searchPattern, () => replacement);
|
|
111
113
|
}
|
|
112
114
|
}
|
|
115
|
+
const replaceContextStorage = new AsyncLocalStorage();
|
|
116
|
+
function getReplaceContext() {
|
|
117
|
+
const ctx = replaceContextStorage.getStore();
|
|
118
|
+
if (!ctx)
|
|
119
|
+
throw new Error('Replace context not found in AsyncLocalStorage');
|
|
120
|
+
return ctx;
|
|
121
|
+
}
|
|
113
122
|
function buildReplacementPlan(content, replacement, matcher) {
|
|
114
123
|
const matchCount = matcher.count(content);
|
|
115
124
|
if (matchCount === 0) {
|
|
@@ -124,8 +133,8 @@ function buildReplacementPlan(content, replacement, matcher) {
|
|
|
124
133
|
function formatFileTooLargeError(filePath, size, maxFileSize) {
|
|
125
134
|
return `File too large: ${filePath} (${size} bytes > ${maxFileSize} bytes)`;
|
|
126
135
|
}
|
|
127
|
-
async function processEntry(entryPath
|
|
128
|
-
const { options,
|
|
136
|
+
async function processEntry(entryPath) {
|
|
137
|
+
const { options, signal, summary } = getReplaceContext();
|
|
129
138
|
let validPath;
|
|
130
139
|
try {
|
|
131
140
|
validPath = await validatePathForWrite(entryPath, signal);
|
|
@@ -139,12 +148,7 @@ async function processEntry(entryPath, context) {
|
|
|
139
148
|
return;
|
|
140
149
|
}
|
|
141
150
|
try {
|
|
142
|
-
const plan = await readReplacementPlan(validPath
|
|
143
|
-
matcher,
|
|
144
|
-
replacement,
|
|
145
|
-
maxFileSize,
|
|
146
|
-
signal,
|
|
147
|
-
});
|
|
151
|
+
const plan = await readReplacementPlan(validPath);
|
|
148
152
|
if (!plan) {
|
|
149
153
|
return;
|
|
150
154
|
}
|
|
@@ -172,19 +176,20 @@ async function processEntry(entryPath, context) {
|
|
|
172
176
|
});
|
|
173
177
|
}
|
|
174
178
|
}
|
|
175
|
-
async function readReplacementPlan(validPath
|
|
179
|
+
async function readReplacementPlan(validPath) {
|
|
180
|
+
const { matcher, replacement, maxFileSize, signal } = getReplaceContext();
|
|
176
181
|
let fileHandle;
|
|
177
182
|
try {
|
|
178
183
|
const fd = await fs.open(validPath, 'r');
|
|
179
184
|
fileHandle = fd;
|
|
180
185
|
const stats = await fileHandle.stat();
|
|
181
|
-
if (stats.size >
|
|
182
|
-
throw new Error(formatFileTooLargeError(validPath, stats.size,
|
|
186
|
+
if (stats.size > maxFileSize) {
|
|
187
|
+
throw new Error(formatFileTooLargeError(validPath, stats.size, maxFileSize));
|
|
183
188
|
}
|
|
184
189
|
let content;
|
|
185
|
-
if (
|
|
186
|
-
const buffer = await fileHandle.readFile({ signal
|
|
187
|
-
if (!
|
|
190
|
+
if (matcher.testBuffer) {
|
|
191
|
+
const buffer = await fileHandle.readFile({ signal });
|
|
192
|
+
if (!matcher.testBuffer(buffer)) {
|
|
188
193
|
return undefined;
|
|
189
194
|
}
|
|
190
195
|
content = buffer.toString('utf-8');
|
|
@@ -192,10 +197,10 @@ async function readReplacementPlan(validPath, context) {
|
|
|
192
197
|
else {
|
|
193
198
|
content = await fileHandle.readFile({
|
|
194
199
|
encoding: 'utf-8',
|
|
195
|
-
signal
|
|
200
|
+
signal,
|
|
196
201
|
});
|
|
197
202
|
}
|
|
198
|
-
return buildReplacementPlan(content,
|
|
203
|
+
return buildReplacementPlan(content, replacement, matcher);
|
|
199
204
|
}
|
|
200
205
|
finally {
|
|
201
206
|
if (fileHandle) {
|
|
@@ -211,10 +216,13 @@ async function maybeAppendPatchDiff(summary, params) {
|
|
|
211
216
|
return;
|
|
212
217
|
}
|
|
213
218
|
const patch = await new Promise((resolve) => {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
219
|
+
// Defer to event loop to avoid blocking on large diffs
|
|
220
|
+
setImmediate(() => {
|
|
221
|
+
createTwoFilesPatch(path.basename(params.filePath), path.basename(params.filePath), params.originalContent, params.updatedContent, 'Original', 'Modified', {
|
|
222
|
+
callback: (res) => {
|
|
223
|
+
resolve(res ?? '');
|
|
224
|
+
},
|
|
225
|
+
});
|
|
218
226
|
});
|
|
219
227
|
});
|
|
220
228
|
if (summary.diff.length + patch.length <=
|
|
@@ -298,7 +306,7 @@ function createReplacementMatcher(args) {
|
|
|
298
306
|
}
|
|
299
307
|
return new LiteralReplacementMatcher(args.searchPattern, args.caseSensitive);
|
|
300
308
|
}
|
|
301
|
-
|
|
309
|
+
async function handleSearchAndReplace(args, signal, onProgress = () => { }) {
|
|
302
310
|
const maxFileSize = MAX_TEXT_FILE_SIZE;
|
|
303
311
|
const root = await resolveSearchRoot(args.path, signal);
|
|
304
312
|
const matcher = createReplacementMatcher(args);
|
|
@@ -315,7 +323,22 @@ export async function handleSearchAndReplace(args, signal, onProgress = () => {
|
|
|
315
323
|
suppressErrors: true,
|
|
316
324
|
});
|
|
317
325
|
const summary = createReplaceSummary(root);
|
|
318
|
-
const
|
|
326
|
+
const timerStartName = `searchAndReplaceStart_${Date.now()}`;
|
|
327
|
+
const timerEndName = `searchAndReplaceEnd_${Date.now()}`;
|
|
328
|
+
const metricName = `searchAndReplace_${Date.now()}`;
|
|
329
|
+
performance.mark(timerStartName);
|
|
330
|
+
const context = {
|
|
331
|
+
options: {
|
|
332
|
+
dryRun: args.dryRun,
|
|
333
|
+
returnDiff: args.returnDiff ?? false,
|
|
334
|
+
},
|
|
335
|
+
replacement: args.replacement,
|
|
336
|
+
matcher,
|
|
337
|
+
maxFileSize,
|
|
338
|
+
signal,
|
|
339
|
+
summary,
|
|
340
|
+
};
|
|
341
|
+
const { stoppedByLimit } = await replaceContextStorage.run(context, () => processEntriesConcurrently(entries, {
|
|
319
342
|
signal,
|
|
320
343
|
concurrency: REPLACE_CONCURRENCY,
|
|
321
344
|
...(args.maxFiles !== undefined ? { maxEntries: args.maxFiles } : {}),
|
|
@@ -325,18 +348,15 @@ export async function handleSearchAndReplace(args, signal, onProgress = () => {
|
|
|
325
348
|
throttleModulo: 25,
|
|
326
349
|
});
|
|
327
350
|
},
|
|
328
|
-
runEntry:
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
summary,
|
|
338
|
-
}),
|
|
339
|
-
});
|
|
351
|
+
runEntry: processEntry,
|
|
352
|
+
}));
|
|
353
|
+
performance.mark(timerEndName);
|
|
354
|
+
performance.measure(metricName, timerStartName, timerEndName);
|
|
355
|
+
const durationMs = performance.getEntriesByName(metricName)[0]?.duration ?? 0;
|
|
356
|
+
performance.clearMarks(timerStartName);
|
|
357
|
+
performance.clearMarks(timerEndName);
|
|
358
|
+
performance.clearMeasures(metricName);
|
|
359
|
+
summary.perfTimeMs = durationMs;
|
|
340
360
|
if (stoppedByLimit) {
|
|
341
361
|
summary.stoppedReason = 'maxFiles';
|
|
342
362
|
}
|
|
@@ -354,13 +374,14 @@ export function registerSearchAndReplaceTool(server, options = {}) {
|
|
|
354
374
|
...(args.path ? { context: { path: args.path } } : {}),
|
|
355
375
|
run: async (signal) => {
|
|
356
376
|
const dryLabel = args.dryRun ? ' [dry run]' : '';
|
|
357
|
-
const
|
|
377
|
+
const truncatedPattern = truncateProgressPattern(args.searchPattern);
|
|
378
|
+
const context = `"${truncatedPattern}" in ${args.filePattern}${dryLabel}`;
|
|
358
379
|
const progress = createToolProgressSession(extra, `🛠 replace: ${context}`);
|
|
359
380
|
const progressWithMessage = ({ current, total, }) => {
|
|
360
381
|
progress.update({
|
|
361
382
|
current,
|
|
362
383
|
...(total !== undefined ? { total } : {}),
|
|
363
|
-
message: `🛠 replace: ${
|
|
384
|
+
message: `🛠 replace: ${truncatedPattern} [${current} files]`,
|
|
364
385
|
});
|
|
365
386
|
};
|
|
366
387
|
try {
|
|
@@ -394,7 +415,10 @@ export function registerSearchAndReplaceTool(server, options = {}) {
|
|
|
394
415
|
function buildSearchAndReplaceText(summary, dryRun) {
|
|
395
416
|
const failureSuffix = summary.failedFiles > 0 ? ` (${summary.failedFiles} failed)` : '';
|
|
396
417
|
const dryRunSuffix = dryRun ? ' (Dry run)' : '';
|
|
397
|
-
|
|
418
|
+
const timing = summary.perfTimeMs
|
|
419
|
+
? ` [\u23F1\uFE0F ${summary.perfTimeMs.toFixed(0)}ms]`
|
|
420
|
+
: '';
|
|
421
|
+
return `Found ${summary.totalMatches} matches in ${summary.filesChanged} files${failureSuffix}.${dryRunSuffix}${timing}`;
|
|
398
422
|
}
|
|
399
423
|
function buildSearchAndReplaceStructuredResult(summary, args) {
|
|
400
424
|
return {
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import * as path from 'node:path';
|
|
2
|
+
import { performance } from 'node:perf_hooks';
|
|
2
3
|
import RE2 from 're2';
|
|
3
4
|
import { DEFAULT_EXCLUDE_PATTERNS } from '../lib/constants.js';
|
|
4
5
|
import { ErrorCode, formatUnknownErrorMessage, McpError, } from '../lib/errors.js';
|
|
5
6
|
import { searchContent } from '../lib/file-operations/search.js';
|
|
6
7
|
import { formatOperationSummary } from '../config.js';
|
|
7
8
|
import { SearchContentInputSchema, SearchContentOutputSchema, } from '../schemas.js';
|
|
8
|
-
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';
|
|
9
10
|
import { registerToolTaskIfAvailable } from './task-support.js';
|
|
10
11
|
/**
|
|
11
12
|
* Configuration constants for the Search Content tool.
|
|
@@ -18,6 +19,7 @@ const CONFIG = {
|
|
|
18
19
|
maxFiles: 'max files',
|
|
19
20
|
},
|
|
20
21
|
};
|
|
22
|
+
let searchMetricSequence = 0;
|
|
21
23
|
const TRUTHY_SUMMARY_FIELDS = [
|
|
22
24
|
'filesMatched',
|
|
23
25
|
'skippedTooLarge',
|
|
@@ -26,15 +28,20 @@ const TRUTHY_SUMMARY_FIELDS = [
|
|
|
26
28
|
'linesSkippedDueToRegexTimeout',
|
|
27
29
|
];
|
|
28
30
|
function buildStructuredSummaryFields(summary) {
|
|
29
|
-
const
|
|
31
|
+
const result = {};
|
|
32
|
+
for (const key of TRUTHY_SUMMARY_FIELDS) {
|
|
30
33
|
const value = summary[key];
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
34
|
+
if (value) {
|
|
35
|
+
result[key] = value;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (summary.truncated) {
|
|
39
|
+
result.truncated = true;
|
|
40
|
+
}
|
|
41
|
+
if (summary.stoppedReason) {
|
|
42
|
+
result.stoppedReason = summary.stoppedReason;
|
|
43
|
+
}
|
|
44
|
+
return result;
|
|
38
45
|
}
|
|
39
46
|
function buildCompletionSuffix(count, filesMatched, scope, stoppedReason) {
|
|
40
47
|
if (count === 0)
|
|
@@ -46,6 +53,15 @@ function buildCompletionSuffix(count, filesMatched, scope, stoppedReason) {
|
|
|
46
53
|
: '';
|
|
47
54
|
return `${count} ${matchWord} in ${filesMatched} ${fileWord}${reasonSuffix}`;
|
|
48
55
|
}
|
|
56
|
+
function createSearchMetricNames() {
|
|
57
|
+
searchMetricSequence += 1;
|
|
58
|
+
const metricSuffix = `${Date.now()}_${searchMetricSequence}`;
|
|
59
|
+
return {
|
|
60
|
+
timerStartName: `searchContentStart_${metricSuffix}`,
|
|
61
|
+
timerEndName: `searchContentEnd_${metricSuffix}`,
|
|
62
|
+
metricName: `searchContent_${metricSuffix}`,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
49
65
|
function compareNormalizedMatches(left, right) {
|
|
50
66
|
const fileCompare = left.relativeFile.localeCompare(right.relativeFile);
|
|
51
67
|
if (fileCompare !== 0)
|
|
@@ -159,30 +175,11 @@ const SearchResponseBuilder = {
|
|
|
159
175
|
buildMatchList(heading, matches) {
|
|
160
176
|
if (matches.length === 0)
|
|
161
177
|
return heading;
|
|
162
|
-
|
|
163
|
-
// +1 for newline after heading. Each match gets: " " (2) + relativeFile + ":" + line + ": " (2) + content + "\n"
|
|
164
|
-
let totalBytes = Buffer.byteLength(heading, 'utf8');
|
|
178
|
+
const parts = [heading];
|
|
165
179
|
for (const match of matches) {
|
|
166
|
-
|
|
167
|
-
1 + // \n
|
|
168
|
-
2 + // " "
|
|
169
|
-
Buffer.byteLength(match.relativeFile, 'utf8') +
|
|
170
|
-
1 + // ":"
|
|
171
|
-
Math.max(4, String(match.line).length) +
|
|
172
|
-
2 + // ": "
|
|
173
|
-
Buffer.byteLength(match.content, 'utf8');
|
|
180
|
+
parts.push(`\n ${match.relativeFile}:${String(match.line).padStart(4)}: ${match.content}`);
|
|
174
181
|
}
|
|
175
|
-
|
|
176
|
-
let offset = buf.write(heading, 0, 'utf8');
|
|
177
|
-
for (const match of matches) {
|
|
178
|
-
offset += buf.write('\n ', offset, 'utf8');
|
|
179
|
-
offset += buf.write(match.relativeFile, offset, 'utf8');
|
|
180
|
-
offset += buf.write(':', offset, 'utf8');
|
|
181
|
-
offset += buf.write(String(match.line).padStart(4), offset, 'utf8');
|
|
182
|
-
offset += buf.write(': ', offset, 'utf8');
|
|
183
|
-
offset += buf.write(match.content, offset, 'utf8');
|
|
184
|
-
}
|
|
185
|
-
return buf.toString('utf8', 0, offset);
|
|
182
|
+
return parts.join('');
|
|
186
183
|
},
|
|
187
184
|
resolveTruncatedReason(summary) {
|
|
188
185
|
if (summary.stoppedReason === 'timeout')
|
|
@@ -215,6 +212,7 @@ const SearchResponseBuilder = {
|
|
|
215
212
|
const SearchExecutor = {
|
|
216
213
|
async run(args, basePath, signal, onProgress) {
|
|
217
214
|
const excludePatterns = args.includeIgnored ? [] : DEFAULT_EXCLUDE_PATTERNS;
|
|
215
|
+
const { timerStartName, timerEndName, metricName } = createSearchMetricNames();
|
|
218
216
|
const options = {
|
|
219
217
|
includeHidden: args.includeHidden,
|
|
220
218
|
excludePatterns,
|
|
@@ -228,6 +226,7 @@ const SearchExecutor = {
|
|
|
228
226
|
...(signal ? { signal } : {}),
|
|
229
227
|
...(onProgress ? { onProgress } : {}),
|
|
230
228
|
};
|
|
229
|
+
performance.mark(timerStartName);
|
|
231
230
|
try {
|
|
232
231
|
return await searchContent(basePath, args.pattern, options);
|
|
233
232
|
}
|
|
@@ -237,6 +236,13 @@ const SearchExecutor = {
|
|
|
237
236
|
}
|
|
238
237
|
throw error;
|
|
239
238
|
}
|
|
239
|
+
finally {
|
|
240
|
+
performance.mark(timerEndName);
|
|
241
|
+
performance.measure(metricName, timerStartName, timerEndName);
|
|
242
|
+
performance.clearMarks(timerStartName);
|
|
243
|
+
performance.clearMarks(timerEndName);
|
|
244
|
+
performance.clearMeasures(metricName);
|
|
245
|
+
}
|
|
240
246
|
},
|
|
241
247
|
createMatcher(args) {
|
|
242
248
|
if (!args.isRegex)
|
|
@@ -303,7 +309,7 @@ export function registerSearchContentTool(server, options = {}) {
|
|
|
303
309
|
context: { path: args.path ?? '.' },
|
|
304
310
|
run: async (signal) => {
|
|
305
311
|
const { pattern, filePattern: scope } = args;
|
|
306
|
-
const progressLabel = `🔎︎ grep: ${pattern}`;
|
|
312
|
+
const progressLabel = `🔎︎ grep: ${truncateProgressPattern(pattern)}`;
|
|
307
313
|
const progress = createToolProgressSession(extra, progressLabel);
|
|
308
314
|
const progressWithMessage = ({ current, total, }) => {
|
|
309
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
|
|
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: ${
|
|
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: ${
|
|
126
|
+
message: `🔎︎ find: ${truncatedPattern} [${current} files]`,
|
|
126
127
|
});
|
|
127
128
|
};
|
|
128
129
|
try {
|
package/dist/tools/shared.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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;
|
package/dist/tools/shared.js
CHANGED
|
@@ -64,7 +64,7 @@ function normalizeToolExecution(tool) {
|
|
|
64
64
|
}
|
|
65
65
|
return normalized;
|
|
66
66
|
}
|
|
67
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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
|
-
"
|
|
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.
|
|
98
|
+
"typescript-eslint": "^8.57.0"
|
|
100
99
|
},
|
|
101
100
|
"engines": {
|
|
102
101
|
"node": ">=24"
|