@j0hanz/filesystem-mcp 1.2.4 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/dist/completions.d.ts +1 -1
- package/dist/completions.js +36 -1
- package/dist/lib/observability.d.ts +6 -0
- package/dist/lib/observability.js +1 -1
- package/dist/lib/resource-store.js +53 -0
- package/dist/prompts.js +34 -14
- package/dist/resources/generated-instructions.d.ts +1 -0
- package/dist/resources/generated-instructions.js +100 -0
- package/dist/resources.d.ts +1 -0
- package/dist/resources.js +36 -1
- package/dist/schemas.d.ts +6 -0
- package/dist/schemas.js +24 -0
- package/dist/server/bootstrap.js +45 -22
- package/dist/server.d.ts +1 -1
- package/dist/server.js +1 -1
- package/dist/tools/apply-patch.d.ts +2 -1
- package/dist/tools/apply-patch.js +7 -5
- package/dist/tools/calculate-hash.d.ts +2 -1
- package/dist/tools/calculate-hash.js +9 -5
- package/dist/tools/contract.d.ts +41 -0
- package/dist/tools/contract.js +1 -0
- package/dist/tools/create-directory.d.ts +2 -1
- package/dist/tools/create-directory.js +6 -5
- package/dist/tools/delete-file.d.ts +2 -1
- package/dist/tools/delete-file.js +9 -5
- package/dist/tools/diff-files.d.ts +2 -1
- package/dist/tools/diff-files.js +7 -4
- package/dist/tools/edit-file.d.ts +2 -1
- package/dist/tools/edit-file.js +19 -6
- package/dist/tools/list-directory.d.ts +2 -1
- package/dist/tools/list-directory.js +36 -7
- package/dist/tools/move-file.d.ts +2 -1
- package/dist/tools/move-file.js +7 -5
- package/dist/tools/read-multiple.d.ts +2 -1
- package/dist/tools/read-multiple.js +10 -5
- package/dist/tools/read.d.ts +2 -1
- package/dist/tools/read.js +9 -5
- package/dist/tools/replace-in-files.d.ts +2 -1
- package/dist/tools/replace-in-files.js +14 -7
- package/dist/tools/roots.d.ts +2 -1
- package/dist/tools/roots.js +7 -4
- package/dist/tools/search-content.d.ts +2 -1
- package/dist/tools/search-content.js +14 -6
- package/dist/tools/search-files.d.ts +2 -1
- package/dist/tools/search-files.js +39 -7
- package/dist/tools/shared.d.ts +2 -2
- package/dist/tools/shared.js +16 -1
- package/dist/tools/stat-many.d.ts +2 -1
- package/dist/tools/stat-many.js +7 -5
- package/dist/tools/stat.d.ts +2 -1
- package/dist/tools/stat.js +7 -4
- package/dist/tools/task-support.d.ts +2 -0
- package/dist/tools/task-support.js +61 -9
- package/dist/tools/tree.d.ts +2 -1
- package/dist/tools/tree.js +7 -5
- package/dist/tools/write-file.d.ts +2 -1
- package/dist/tools/write-file.js +12 -5
- package/dist/tools.d.ts +2 -0
- package/dist/tools.js +39 -18
- package/package.json +1 -2
- package/dist/instructions.md +0 -200
|
@@ -10,7 +10,8 @@ import { validateExistingPath, validatePathForWrite, } from '../lib/path-validat
|
|
|
10
10
|
import { SearchAndReplaceInputSchema, SearchAndReplaceOutputSchema, } from '../schemas.js';
|
|
11
11
|
import { buildToolErrorResponse, buildToolResponse, createProgressReporter, DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS, executeToolWithDiagnostics, notifyProgress, resolvePathOrRoot, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
12
12
|
import { registerToolTaskIfAvailable } from './task-support.js';
|
|
13
|
-
const SEARCH_AND_REPLACE_TOOL = {
|
|
13
|
+
export const SEARCH_AND_REPLACE_TOOL = {
|
|
14
|
+
name: 'search_and_replace',
|
|
14
15
|
title: 'Search and Replace',
|
|
15
16
|
description: 'Search and replace text across multiple files matching a glob pattern. ' +
|
|
16
17
|
'Replaces ALL occurrences in each file (unlike `edit` which replaces only the first). ' +
|
|
@@ -20,6 +21,12 @@ const SEARCH_AND_REPLACE_TOOL = {
|
|
|
20
21
|
inputSchema: SearchAndReplaceInputSchema,
|
|
21
22
|
outputSchema: SearchAndReplaceOutputSchema,
|
|
22
23
|
annotations: DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS,
|
|
24
|
+
gotchas: [
|
|
25
|
+
'Literal mode is default; `isRegex=true` enables RE2 + capture replacements (`$1`, `$2`).',
|
|
26
|
+
],
|
|
27
|
+
nuances: [
|
|
28
|
+
'Changed-file sample and failure sample are capped/truncated in output.',
|
|
29
|
+
],
|
|
23
30
|
};
|
|
24
31
|
const MAX_FAILURES = 20;
|
|
25
32
|
const REPLACE_CONCURRENCY = Math.min(PARALLEL_CONCURRENCY, 8);
|
|
@@ -189,8 +196,8 @@ async function handleSearchAndReplace(args, signal, onProgress = () => { }) {
|
|
|
189
196
|
const entries = globEntries({
|
|
190
197
|
cwd: root,
|
|
191
198
|
pattern: args.filePattern,
|
|
192
|
-
excludePatterns: DEFAULT_EXCLUDE_PATTERNS,
|
|
193
|
-
includeHidden: false,
|
|
199
|
+
excludePatterns: args.includeIgnored ? [] : DEFAULT_EXCLUDE_PATTERNS,
|
|
200
|
+
includeHidden: args.includeHidden ?? false,
|
|
194
201
|
baseNameMatch: false,
|
|
195
202
|
caseSensitiveMatch: true, // Default to sensitive for file paths
|
|
196
203
|
followSymbolicLinks: false,
|
|
@@ -279,11 +286,11 @@ export function registerSearchAndReplaceTool(server, options = {}) {
|
|
|
279
286
|
onError: (error) => buildToolErrorResponse(error, ErrorCode.E_UNKNOWN, args.path),
|
|
280
287
|
});
|
|
281
288
|
const { isInitialized } = options;
|
|
282
|
-
const
|
|
283
|
-
const wrappedHandler = wrapToolHandler(validatedHandler, {
|
|
289
|
+
const wrappedHandler = wrapToolHandler(handler, {
|
|
284
290
|
guard: isInitialized,
|
|
285
291
|
});
|
|
286
|
-
|
|
292
|
+
const validatedHandler = withValidatedArgs(SearchAndReplaceInputSchema, wrappedHandler);
|
|
293
|
+
if (registerToolTaskIfAvailable(server, 'search_and_replace', SEARCH_AND_REPLACE_TOOL, validatedHandler, options.iconInfo, isInitialized))
|
|
287
294
|
return;
|
|
288
|
-
server.registerTool('search_and_replace', withDefaultIcons({ ...SEARCH_AND_REPLACE_TOOL }, options.iconInfo),
|
|
295
|
+
server.registerTool('search_and_replace', withDefaultIcons({ ...SEARCH_AND_REPLACE_TOOL }, options.iconInfo), validatedHandler);
|
|
289
296
|
}
|
package/dist/tools/roots.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { type ToolRegistrationOptions } from './shared.js';
|
|
2
|
+
import { type ToolContract, type ToolRegistrationOptions } from './shared.js';
|
|
3
|
+
export declare const LIST_ALLOWED_DIRECTORIES_TOOL: ToolContract;
|
|
3
4
|
export declare function registerListAllowedDirectoriesTool(server: McpServer, options?: ToolRegistrationOptions): void;
|
package/dist/tools/roots.js
CHANGED
|
@@ -3,7 +3,8 @@ import { ErrorCode } from '../lib/errors.js';
|
|
|
3
3
|
import { getAllowedDirectories } from '../lib/path-validation.js';
|
|
4
4
|
import { ListAllowedDirectoriesInputSchema, ListAllowedDirectoriesOutputSchema, } from '../schemas.js';
|
|
5
5
|
import { buildToolErrorResponse, buildToolResponse, executeToolWithDiagnostics, READ_ONLY_TOOL_ANNOTATIONS, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
6
|
-
const LIST_ALLOWED_DIRECTORIES_TOOL = {
|
|
6
|
+
export const LIST_ALLOWED_DIRECTORIES_TOOL = {
|
|
7
|
+
name: 'roots',
|
|
7
8
|
title: 'Workspace Roots',
|
|
8
9
|
description: 'List the workspace roots this server can access. ' +
|
|
9
10
|
'Call this first to see available directories. ' +
|
|
@@ -11,6 +12,7 @@ const LIST_ALLOWED_DIRECTORIES_TOOL = {
|
|
|
11
12
|
inputSchema: ListAllowedDirectoriesInputSchema,
|
|
12
13
|
outputSchema: ListAllowedDirectoriesOutputSchema,
|
|
13
14
|
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
|
15
|
+
nuances: ['Other tools are constrained to these roots.'],
|
|
14
16
|
};
|
|
15
17
|
function buildTextRoots(dirs) {
|
|
16
18
|
if (dirs.length === 0) {
|
|
@@ -38,8 +40,7 @@ export function registerListAllowedDirectoriesTool(server, options = {}) {
|
|
|
38
40
|
run: () => handleListAllowedDirectories(),
|
|
39
41
|
onError: (error) => buildToolErrorResponse(error, ErrorCode.E_UNKNOWN),
|
|
40
42
|
});
|
|
41
|
-
const
|
|
42
|
-
server.registerTool('roots', withDefaultIcons({ ...LIST_ALLOWED_DIRECTORIES_TOOL }, options.iconInfo), wrapToolHandler(validatedHandler, {
|
|
43
|
+
const wrappedHandler = wrapToolHandler(handler, {
|
|
43
44
|
guard: options.isInitialized,
|
|
44
45
|
progressMessage: () => '≣ roots',
|
|
45
46
|
completionMessage: (_args, result) => {
|
|
@@ -51,5 +52,7 @@ export function registerListAllowedDirectoriesTool(server, options = {}) {
|
|
|
51
52
|
const count = sc.rootsCount ?? 0;
|
|
52
53
|
return `≣ roots • ${count} ${count === 1 ? 'root' : 'roots'}`;
|
|
53
54
|
},
|
|
54
|
-
})
|
|
55
|
+
});
|
|
56
|
+
const validatedHandler = withValidatedArgs(ListAllowedDirectoriesInputSchema, wrappedHandler);
|
|
57
|
+
server.registerTool('roots', withDefaultIcons({ ...LIST_ALLOWED_DIRECTORIES_TOOL }, options.iconInfo), validatedHandler);
|
|
55
58
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { type ToolRegistrationOptions } from './shared.js';
|
|
2
|
+
import { type ToolContract, type ToolRegistrationOptions } from './shared.js';
|
|
3
|
+
export declare const SEARCH_CONTENT_TOOL: ToolContract;
|
|
3
4
|
export declare function registerSearchContentTool(server: McpServer, options?: ToolRegistrationOptions): void;
|
|
@@ -7,8 +7,9 @@ import { searchContent } from '../lib/file-operations/search-content.js';
|
|
|
7
7
|
import { SearchContentInputSchema, SearchContentOutputSchema, } from '../schemas.js';
|
|
8
8
|
import { buildResourceLink, buildToolErrorResponse, buildToolResponse, createProgressReporter, executeToolWithDiagnostics, notifyProgress, READ_ONLY_TOOL_ANNOTATIONS, resolvePathOrRoot, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
9
9
|
import { registerToolTaskIfAvailable } from './task-support.js';
|
|
10
|
-
const MAX_INLINE_MATCHES = 50;
|
|
11
|
-
const SEARCH_CONTENT_TOOL = {
|
|
10
|
+
const MAX_INLINE_MATCHES = parseInt(process.env['FS_CONTEXT_MAX_INLINE_MATCHES'] ?? '', 10) || 50;
|
|
11
|
+
export const SEARCH_CONTENT_TOOL = {
|
|
12
|
+
name: 'grep',
|
|
12
13
|
title: 'Search Content',
|
|
13
14
|
description: 'Search for text within file contents (grep-like). ' +
|
|
14
15
|
'Returns matching lines. ' +
|
|
@@ -18,6 +19,13 @@ const SEARCH_CONTENT_TOOL = {
|
|
|
18
19
|
inputSchema: SearchContentInputSchema,
|
|
19
20
|
outputSchema: SearchContentOutputSchema,
|
|
20
21
|
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
|
22
|
+
nuances: [
|
|
23
|
+
'Inline match rows are capped (first 50); full structured results are externalized via `resourceUri`.',
|
|
24
|
+
'Skips binary and oversized files.',
|
|
25
|
+
],
|
|
26
|
+
gotchas: [
|
|
27
|
+
'Inline match rows are capped (first 50); full structured results are externalized via `resourceUri`.',
|
|
28
|
+
],
|
|
21
29
|
};
|
|
22
30
|
function assertValidRegexPattern(pattern) {
|
|
23
31
|
try {
|
|
@@ -261,11 +269,11 @@ export function registerSearchContentTool(server, options = {}) {
|
|
|
261
269
|
onError: (error) => buildToolErrorResponse(error, ErrorCode.E_UNKNOWN, args.path ?? '.'),
|
|
262
270
|
});
|
|
263
271
|
const { isInitialized } = options;
|
|
264
|
-
const
|
|
265
|
-
const wrappedHandler = wrapToolHandler(validatedHandler, {
|
|
272
|
+
const wrappedHandler = wrapToolHandler(handler, {
|
|
266
273
|
guard: isInitialized,
|
|
267
274
|
});
|
|
268
|
-
|
|
275
|
+
const validatedHandler = withValidatedArgs(SearchContentInputSchema, wrappedHandler);
|
|
276
|
+
if (registerToolTaskIfAvailable(server, 'grep', SEARCH_CONTENT_TOOL, validatedHandler, options.iconInfo, isInitialized))
|
|
269
277
|
return;
|
|
270
|
-
server.registerTool('grep', withDefaultIcons({ ...SEARCH_CONTENT_TOOL }, options.iconInfo),
|
|
278
|
+
server.registerTool('grep', withDefaultIcons({ ...SEARCH_CONTENT_TOOL }, options.iconInfo), validatedHandler);
|
|
271
279
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { type ToolRegistrationOptions } from './shared.js';
|
|
2
|
+
import { type ToolContract, type ToolRegistrationOptions } from './shared.js';
|
|
3
|
+
export declare const SEARCH_FILES_TOOL: ToolContract;
|
|
3
4
|
export declare function registerSearchFilesTool(server: McpServer, options?: ToolRegistrationOptions): void;
|
|
@@ -6,7 +6,26 @@ import { searchFiles } from '../lib/file-operations/search-files.js';
|
|
|
6
6
|
import { SearchFilesInputSchema, SearchFilesOutputSchema } from '../schemas.js';
|
|
7
7
|
import { buildToolErrorResponse, buildToolResponse, createProgressReporter, executeToolWithDiagnostics, notifyProgress, READ_ONLY_TOOL_ANNOTATIONS, resolvePathOrRoot, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
8
8
|
import { registerToolTaskIfAvailable } from './task-support.js';
|
|
9
|
-
|
|
9
|
+
function encodeCursor(offset) {
|
|
10
|
+
return Buffer.from(JSON.stringify({ offset })).toString('base64url');
|
|
11
|
+
}
|
|
12
|
+
function decodeCursor(cursor) {
|
|
13
|
+
try {
|
|
14
|
+
const parsed = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf-8'));
|
|
15
|
+
if (typeof parsed === 'object' &&
|
|
16
|
+
parsed !== null &&
|
|
17
|
+
typeof parsed.offset === 'number') {
|
|
18
|
+
const { offset } = parsed;
|
|
19
|
+
return Number.isInteger(offset) && offset >= 0 ? offset : 0;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// ignore malformed cursor
|
|
24
|
+
}
|
|
25
|
+
return 0;
|
|
26
|
+
}
|
|
27
|
+
export const SEARCH_FILES_TOOL = {
|
|
28
|
+
name: 'find',
|
|
10
29
|
title: 'Find Files',
|
|
11
30
|
description: 'Find files by glob pattern (e.g., **/*.ts). ' +
|
|
12
31
|
'Returns a list of matching files with metadata. ' +
|
|
@@ -15,12 +34,19 @@ const SEARCH_FILES_TOOL = {
|
|
|
15
34
|
inputSchema: SearchFilesInputSchema,
|
|
16
35
|
outputSchema: SearchFilesOutputSchema,
|
|
17
36
|
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
|
37
|
+
nuances: [
|
|
38
|
+
'Respects `.gitignore` unless `includeIgnored=true`.',
|
|
39
|
+
'Returns relative paths plus metadata; may truncate.',
|
|
40
|
+
],
|
|
18
41
|
};
|
|
19
42
|
async function handleSearchFiles(args, signal, onProgress) {
|
|
20
43
|
const basePath = resolvePathOrRoot(args.path);
|
|
21
44
|
const excludePatterns = args.includeIgnored ? [] : DEFAULT_EXCLUDE_PATTERNS;
|
|
45
|
+
const cursorOffset = args.cursor !== undefined ? decodeCursor(args.cursor) : 0;
|
|
46
|
+
const pageSize = args.maxResults;
|
|
47
|
+
const fetchMax = cursorOffset + pageSize;
|
|
22
48
|
const searchOptions = {
|
|
23
|
-
maxResults:
|
|
49
|
+
maxResults: fetchMax,
|
|
24
50
|
includeHidden: args.includeHidden,
|
|
25
51
|
sortBy: args.sortBy,
|
|
26
52
|
respectGitignore: !args.includeIgnored,
|
|
@@ -29,8 +55,13 @@ async function handleSearchFiles(args, signal, onProgress) {
|
|
|
29
55
|
...(signal ? { signal } : {}),
|
|
30
56
|
};
|
|
31
57
|
const result = await searchFiles(basePath, args.pattern, excludePatterns, searchOptions);
|
|
58
|
+
const allResults = result.results;
|
|
59
|
+
const displayResults = cursorOffset > 0 ? allResults.slice(cursorOffset) : allResults;
|
|
60
|
+
const nextCursor = result.summary.truncated && displayResults.length > 0
|
|
61
|
+
? encodeCursor(cursorOffset + displayResults.length)
|
|
62
|
+
: undefined;
|
|
32
63
|
const relativeResults = [];
|
|
33
|
-
for (const entry of
|
|
64
|
+
for (const entry of displayResults) {
|
|
34
65
|
relativeResults.push({
|
|
35
66
|
path: path.relative(result.basePath, entry.path),
|
|
36
67
|
size: entry.size,
|
|
@@ -53,6 +84,7 @@ async function handleSearchFiles(args, signal, onProgress) {
|
|
|
53
84
|
...(result.summary.stoppedReason
|
|
54
85
|
? { stoppedReason: result.summary.stoppedReason }
|
|
55
86
|
: {}),
|
|
87
|
+
...(nextCursor !== undefined ? { nextCursor } : {}),
|
|
56
88
|
};
|
|
57
89
|
let truncatedReason;
|
|
58
90
|
if (result.summary.truncated) {
|
|
@@ -152,11 +184,11 @@ export function registerSearchFilesTool(server, options = {}) {
|
|
|
152
184
|
onError: (error) => buildToolErrorResponse(error, ErrorCode.E_UNKNOWN, args.path),
|
|
153
185
|
});
|
|
154
186
|
const { isInitialized } = options;
|
|
155
|
-
const
|
|
156
|
-
const wrappedHandler = wrapToolHandler(validatedHandler, {
|
|
187
|
+
const wrappedHandler = wrapToolHandler(handler, {
|
|
157
188
|
guard: isInitialized,
|
|
158
189
|
});
|
|
159
|
-
|
|
190
|
+
const validatedHandler = withValidatedArgs(SearchFilesInputSchema, wrappedHandler);
|
|
191
|
+
if (registerToolTaskIfAvailable(server, 'find', SEARCH_FILES_TOOL, validatedHandler, options.iconInfo, isInitialized))
|
|
160
192
|
return;
|
|
161
|
-
server.registerTool('find', withDefaultIcons({ ...SEARCH_FILES_TOOL }, options.iconInfo),
|
|
193
|
+
server.registerTool('find', withDefaultIcons({ ...SEARCH_FILES_TOOL }, options.iconInfo), validatedHandler);
|
|
162
194
|
}
|
package/dist/tools/shared.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { FileInfo } from '../config.js';
|
|
|
4
4
|
import { ErrorCode } from '../lib/errors.js';
|
|
5
5
|
import type { ResourceStore } from '../lib/resource-store.js';
|
|
6
6
|
import type { ToolErrorResponseSchema } from '../schemas.js';
|
|
7
|
+
export { type ToolContract } from './contract.js';
|
|
7
8
|
export declare const READ_ONLY_TOOL_ANNOTATIONS: {
|
|
8
9
|
readonly readOnlyHint: true;
|
|
9
10
|
readonly idempotentHint: true;
|
|
@@ -48,7 +49,7 @@ interface ToolErrorResponse extends Record<string, unknown> {
|
|
|
48
49
|
}
|
|
49
50
|
export type ToolResult<T> = ToolResponse<T> | ToolErrorResponse;
|
|
50
51
|
export declare function parseToolArgs<Schema extends z.ZodType>(schema: Schema, args: unknown): z.infer<Schema>;
|
|
51
|
-
export declare function withValidatedArgs<Args, Result>(schema: z.ZodType<Args>, handler: (args: Args, extra: ToolExtra) => Promise<ToolResult<Result>>): (args:
|
|
52
|
+
export declare function withValidatedArgs<Args, Result>(schema: z.ZodType<Args>, handler: (args: Args, extra: ToolExtra) => Promise<ToolResult<Result>>): (args: unknown, extra: ToolExtra) => Promise<ToolResult<Result>>;
|
|
52
53
|
type ProgressToken = string | number;
|
|
53
54
|
export interface ToolExtra {
|
|
54
55
|
signal?: AbortSignal;
|
|
@@ -116,4 +117,3 @@ export declare function wrapToolHandler<Args, Result>(handler: (args: Args, extr
|
|
|
116
117
|
completionMessage?: (args: Args, result: ToolResult<Result>) => string | undefined;
|
|
117
118
|
}): (args: Args, extra?: ToolExtra) => Promise<ToolResult<Result>>;
|
|
118
119
|
export declare function resolvePathOrRoot(pathValue: string | undefined): string;
|
|
119
|
-
export {};
|
package/dist/tools/shared.js
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
|
+
import { channel } from 'node:diagnostics_channel';
|
|
1
2
|
import { z } from 'zod';
|
|
2
3
|
import { createDetailedError, ErrorCode, formatDetailedError, getSuggestion, McpError, } from '../lib/errors.js';
|
|
3
4
|
import { createTimedAbortSignal } from '../lib/fs-helpers.js';
|
|
4
5
|
import { withToolDiagnostics } from '../lib/observability.js';
|
|
5
6
|
import { getAllowedDirectories } from '../lib/path-validation.js';
|
|
6
|
-
|
|
7
|
+
export {} from './contract.js';
|
|
8
|
+
const MAX_INLINE_CONTENT_CHARS = parseInt(process.env['FS_CONTEXT_MAX_INLINE_CHARS'] ?? '', 10) || 20_000;
|
|
7
9
|
const MAX_INLINE_PREVIEW_CHARS = 4_000;
|
|
8
10
|
const PROGRESS_RATE_LIMIT_MS = 50;
|
|
9
11
|
const TRUE_ENV_VALUES = new Set(['1', 'true', 'yes']);
|
|
12
|
+
const CONTEXT_DIAGNOSTICS_CHANNEL = channel('filesystem-mcp:context');
|
|
13
|
+
function publishContextDiagnostics(event) {
|
|
14
|
+
if (!CONTEXT_DIAGNOSTICS_CHANNEL.hasSubscribers)
|
|
15
|
+
return;
|
|
16
|
+
CONTEXT_DIAGNOSTICS_CHANNEL.publish(event);
|
|
17
|
+
}
|
|
10
18
|
export const READ_ONLY_TOOL_ANNOTATIONS = {
|
|
11
19
|
readOnlyHint: true,
|
|
12
20
|
idempotentHint: true,
|
|
@@ -61,6 +69,13 @@ export function maybeExternalizeTextContent(resourceStore, content, params) {
|
|
|
61
69
|
...(params.mimeType !== undefined ? { mimeType: params.mimeType } : {}),
|
|
62
70
|
text: content,
|
|
63
71
|
});
|
|
72
|
+
publishContextDiagnostics({
|
|
73
|
+
phase: 'externalize_text',
|
|
74
|
+
name: params.name,
|
|
75
|
+
...(params.mimeType !== undefined ? { mimeType: params.mimeType } : {}),
|
|
76
|
+
chars: content.length,
|
|
77
|
+
uri: entry.uri,
|
|
78
|
+
});
|
|
64
79
|
return {
|
|
65
80
|
entry,
|
|
66
81
|
preview: buildTextPreview(content),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { type ToolRegistrationOptions } from './shared.js';
|
|
2
|
+
import { type ToolContract, type ToolRegistrationOptions } from './shared.js';
|
|
3
|
+
export declare const GET_MULTIPLE_FILE_INFO_TOOL: ToolContract;
|
|
3
4
|
export declare function registerGetMultipleFileInfoTool(server: McpServer, options?: ToolRegistrationOptions): void;
|
package/dist/tools/stat-many.js
CHANGED
|
@@ -6,12 +6,14 @@ import { getMultipleFileInfo } from '../lib/file-operations/file-info.js';
|
|
|
6
6
|
import { GetMultipleFileInfoInputSchema, GetMultipleFileInfoOutputSchema, } from '../schemas.js';
|
|
7
7
|
import { buildFileInfoPayload, buildToolErrorResponse, buildToolResponse, executeToolWithDiagnostics, READ_ONLY_TOOL_ANNOTATIONS, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
8
8
|
import { registerToolTaskIfAvailable } from './task-support.js';
|
|
9
|
-
const GET_MULTIPLE_FILE_INFO_TOOL = {
|
|
9
|
+
export const GET_MULTIPLE_FILE_INFO_TOOL = {
|
|
10
|
+
name: 'stat_many',
|
|
10
11
|
title: 'Get Multiple File Info',
|
|
11
12
|
description: 'Get metadata for multiple files or directories in one request.',
|
|
12
13
|
inputSchema: GetMultipleFileInfoInputSchema,
|
|
13
14
|
outputSchema: GetMultipleFileInfoOutputSchema,
|
|
14
15
|
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
|
16
|
+
nuances: ['Use before read/search when file size/type uncertainty exists.'],
|
|
15
17
|
};
|
|
16
18
|
function formatFileInfoDetail(info) {
|
|
17
19
|
const lines = [
|
|
@@ -73,8 +75,7 @@ export function registerGetMultipleFileInfoTool(server, options = {}) {
|
|
|
73
75
|
onError: (error) => buildToolErrorResponse(error, ErrorCode.E_NOT_FOUND, primaryPath),
|
|
74
76
|
});
|
|
75
77
|
};
|
|
76
|
-
const
|
|
77
|
-
const wrappedHandler = wrapToolHandler(validatedHandler, {
|
|
78
|
+
const wrappedHandler = wrapToolHandler(handler, {
|
|
78
79
|
guard: options.isInitialized,
|
|
79
80
|
progressMessage: (args) => {
|
|
80
81
|
const first = path.basename(args.paths[0] ?? '');
|
|
@@ -95,7 +96,8 @@ export function registerGetMultipleFileInfoTool(server, options = {}) {
|
|
|
95
96
|
return `🕮 stat_many: ${total} OK`;
|
|
96
97
|
},
|
|
97
98
|
});
|
|
98
|
-
|
|
99
|
+
const validatedHandler = withValidatedArgs(GetMultipleFileInfoInputSchema, wrappedHandler);
|
|
100
|
+
if (registerToolTaskIfAvailable(server, 'stat_many', GET_MULTIPLE_FILE_INFO_TOOL, validatedHandler, options.iconInfo, options.isInitialized))
|
|
99
101
|
return;
|
|
100
|
-
server.registerTool('stat_many', withDefaultIcons({ ...GET_MULTIPLE_FILE_INFO_TOOL }, options.iconInfo),
|
|
102
|
+
server.registerTool('stat_many', withDefaultIcons({ ...GET_MULTIPLE_FILE_INFO_TOOL }, options.iconInfo), validatedHandler);
|
|
101
103
|
}
|
package/dist/tools/stat.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { type ToolRegistrationOptions } from './shared.js';
|
|
2
|
+
import { type ToolContract, type ToolRegistrationOptions } from './shared.js';
|
|
3
|
+
export declare const GET_FILE_INFO_TOOL: ToolContract;
|
|
3
4
|
export declare function registerGetFileInfoTool(server: McpServer, options?: ToolRegistrationOptions): void;
|
package/dist/tools/stat.js
CHANGED
|
@@ -5,12 +5,14 @@ import { ErrorCode } from '../lib/errors.js';
|
|
|
5
5
|
import { getFileInfo } from '../lib/file-operations/file-info.js';
|
|
6
6
|
import { GetFileInfoInputSchema, GetFileInfoOutputSchema } from '../schemas.js';
|
|
7
7
|
import { buildFileInfoPayload, buildToolErrorResponse, buildToolResponse, executeToolWithDiagnostics, READ_ONLY_TOOL_ANNOTATIONS, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
8
|
-
const GET_FILE_INFO_TOOL = {
|
|
8
|
+
export const GET_FILE_INFO_TOOL = {
|
|
9
|
+
name: 'stat',
|
|
9
10
|
title: 'Get File Info',
|
|
10
11
|
description: 'Get metadata (size, modified time, permissions, mime type) for a file or directory.',
|
|
11
12
|
inputSchema: GetFileInfoInputSchema,
|
|
12
13
|
outputSchema: GetFileInfoOutputSchema,
|
|
13
14
|
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
|
15
|
+
nuances: ['Use before read/search when file size/type uncertainty exists.'],
|
|
14
16
|
};
|
|
15
17
|
function formatFileInfoDetails(info) {
|
|
16
18
|
const lines = [
|
|
@@ -45,8 +47,7 @@ export function registerGetFileInfoTool(server, options = {}) {
|
|
|
45
47
|
run: (signal) => handleGetFileInfo(args, signal),
|
|
46
48
|
onError: (error) => buildToolErrorResponse(error, ErrorCode.E_NOT_FOUND, args.path),
|
|
47
49
|
});
|
|
48
|
-
const
|
|
49
|
-
server.registerTool('stat', withDefaultIcons({ ...GET_FILE_INFO_TOOL }, options.iconInfo), wrapToolHandler(validatedHandler, {
|
|
50
|
+
const wrappedHandler = wrapToolHandler(handler, {
|
|
50
51
|
guard: options.isInitialized,
|
|
51
52
|
progressMessage: (args) => `🕮 stat: ${path.basename(args.path)}`,
|
|
52
53
|
completionMessage: (args, result) => {
|
|
@@ -58,5 +59,7 @@ export function registerGetFileInfoTool(server, options = {}) {
|
|
|
58
59
|
return `🕮 stat: ${name} • failed`;
|
|
59
60
|
return `🕮 stat: ${sc.info.name} • ${sc.info.type}, ${formatBytes(sc.info.size)}`;
|
|
60
61
|
},
|
|
61
|
-
})
|
|
62
|
+
});
|
|
63
|
+
const validatedHandler = withValidatedArgs(GetFileInfoInputSchema, wrappedHandler);
|
|
64
|
+
server.registerTool('stat', withDefaultIcons({ ...GET_FILE_INFO_TOOL }, options.iconInfo), validatedHandler);
|
|
62
65
|
}
|
|
@@ -18,8 +18,10 @@ export declare function tryRegisterToolTask<Args extends ZodRawShapeCompat | Any
|
|
|
18
18
|
export declare function registerToolTaskIfAvailable<Args extends ZodRawShapeCompat | AnySchema | undefined, Result>(server: McpServer, toolName: string, toolDef: object, run: (args: ToolArgs<Args>, extra: TaskToolExtra) => Promise<ToolResult<Result>>, iconInfo: IconInfo | undefined, guard?: () => boolean): boolean;
|
|
19
19
|
export declare function createToolTaskHandler<Result>(run: (args: undefined, extra: TaskToolExtra) => Promise<ToolResult<Result>>, options?: {
|
|
20
20
|
guard?: () => boolean;
|
|
21
|
+
toolName?: string;
|
|
21
22
|
}): ToolTaskHandler;
|
|
22
23
|
export declare function createToolTaskHandler<Args extends ZodRawShapeCompat | AnySchema, Result>(run: (args: ToolArgs<Args>, extra: TaskToolExtra) => Promise<ToolResult<Result>>, options?: {
|
|
23
24
|
guard?: () => boolean;
|
|
25
|
+
toolName?: string;
|
|
24
26
|
}): ToolTaskHandler<Args>;
|
|
25
27
|
export {};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { channel } from 'node:diagnostics_channel';
|
|
1
2
|
import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
2
3
|
import { ErrorCode, McpError } from '../lib/errors.js';
|
|
3
4
|
import { isRecord } from '../lib/type-guards.js';
|
|
@@ -43,6 +44,12 @@ function hasTaskToolCapability(server) {
|
|
|
43
44
|
}
|
|
44
45
|
const RELATED_TASK_META_KEY = 'io.modelcontextprotocol/related-task';
|
|
45
46
|
const TASK_STATUS_NOTIFICATION_METHOD = 'notifications/tasks/status';
|
|
47
|
+
const TASK_DIAGNOSTICS_CHANNEL = channel('filesystem-mcp:tasks');
|
|
48
|
+
function publishTaskDiagnostics(event) {
|
|
49
|
+
if (!TASK_DIAGNOSTICS_CHANNEL.hasSubscribers)
|
|
50
|
+
return;
|
|
51
|
+
TASK_DIAGNOSTICS_CHANNEL.publish(event);
|
|
52
|
+
}
|
|
46
53
|
function isRequestTaskStore(value) {
|
|
47
54
|
if (!isRecord(value))
|
|
48
55
|
return false;
|
|
@@ -174,7 +181,7 @@ function buildTaskStatusNotificationParams(task) {
|
|
|
174
181
|
params.statusMessage = task.statusMessage;
|
|
175
182
|
return params;
|
|
176
183
|
}
|
|
177
|
-
async function notifyTaskStatusIfPossible(extra, taskStore, taskId) {
|
|
184
|
+
async function notifyTaskStatusIfPossible(extra, taskStore, taskId, toolName) {
|
|
178
185
|
const { sendNotification } = extra;
|
|
179
186
|
if (typeof sendNotification !== 'function')
|
|
180
187
|
return;
|
|
@@ -186,8 +193,19 @@ async function notifyTaskStatusIfPossible(extra, taskStore, taskId) {
|
|
|
186
193
|
method: TASK_STATUS_NOTIFICATION_METHOD,
|
|
187
194
|
params: buildTaskStatusNotificationParams(normalized),
|
|
188
195
|
});
|
|
196
|
+
publishTaskDiagnostics({
|
|
197
|
+
phase: 'task_status_notified',
|
|
198
|
+
taskId,
|
|
199
|
+
status: normalized.status,
|
|
200
|
+
...(toolName !== undefined ? { toolName } : {}),
|
|
201
|
+
});
|
|
189
202
|
}
|
|
190
203
|
catch {
|
|
204
|
+
publishTaskDiagnostics({
|
|
205
|
+
phase: 'task_status_notify_failed',
|
|
206
|
+
taskId,
|
|
207
|
+
...(toolName !== undefined ? { toolName } : {}),
|
|
208
|
+
});
|
|
191
209
|
// Never fail task execution because status notifications are optional.
|
|
192
210
|
}
|
|
193
211
|
}
|
|
@@ -206,6 +224,14 @@ function getTaskId(extra) {
|
|
|
206
224
|
function isErrorResult(result) {
|
|
207
225
|
return 'isError' in result && result.isError === true;
|
|
208
226
|
}
|
|
227
|
+
// Strips structuredContent from a tool result if present, without modifying the original object. This is used when storing error results as 'completed' to prevent client-side output schema validation errors, while still allowing the human-readable error message in content[0].text to be returned to clients.
|
|
228
|
+
function withoutStructuredContent(result) {
|
|
229
|
+
if (!Object.hasOwn(result, 'structuredContent'))
|
|
230
|
+
return result;
|
|
231
|
+
const stripped = { ...result };
|
|
232
|
+
delete stripped['structuredContent'];
|
|
233
|
+
return stripped;
|
|
234
|
+
}
|
|
209
235
|
const TERMINAL_TASK_STATUSES = new Set([
|
|
210
236
|
'completed',
|
|
211
237
|
'failed',
|
|
@@ -234,18 +260,33 @@ async function tryStoreTaskResult(taskStore, taskId, status, result) {
|
|
|
234
260
|
throw error;
|
|
235
261
|
}
|
|
236
262
|
}
|
|
237
|
-
async function runTaskInBackground(run, args, extra, taskStore, taskId) {
|
|
263
|
+
async function runTaskInBackground(run, args, extra, taskStore, taskId, toolName) {
|
|
238
264
|
try {
|
|
239
|
-
const
|
|
240
|
-
const status =
|
|
265
|
+
const rawResult = await run(args, extra);
|
|
266
|
+
const status = isCancelledToolResult(rawResult) ? 'failed' : 'completed';
|
|
267
|
+
const result = isErrorResult(rawResult) && status === 'completed'
|
|
268
|
+
? withoutStructuredContent(rawResult)
|
|
269
|
+
: maybeStripStructuredContentFromResult(rawResult);
|
|
241
270
|
await tryStoreTaskResult(taskStore, taskId, status, result);
|
|
242
|
-
|
|
271
|
+
publishTaskDiagnostics({
|
|
272
|
+
phase: 'task_result_stored',
|
|
273
|
+
taskId,
|
|
274
|
+
status,
|
|
275
|
+
...(toolName !== undefined ? { toolName } : {}),
|
|
276
|
+
});
|
|
277
|
+
await notifyTaskStatusIfPossible(extra, taskStore, taskId, toolName);
|
|
243
278
|
}
|
|
244
279
|
catch (error) {
|
|
245
280
|
const fallback = maybeStripStructuredContentFromResult(buildToolErrorResponse(error, ErrorCode.E_UNKNOWN));
|
|
246
281
|
try {
|
|
247
282
|
await tryStoreTaskResult(taskStore, taskId, 'failed', fallback);
|
|
248
|
-
|
|
283
|
+
publishTaskDiagnostics({
|
|
284
|
+
phase: 'task_result_stored',
|
|
285
|
+
taskId,
|
|
286
|
+
status: 'failed',
|
|
287
|
+
...(toolName !== undefined ? { toolName } : {}),
|
|
288
|
+
});
|
|
289
|
+
await notifyTaskStatusIfPossible(extra, taskStore, taskId, toolName);
|
|
249
290
|
}
|
|
250
291
|
catch (innerError) {
|
|
251
292
|
console.error(`Failed to store task failure result for task ${taskId}:`, innerError);
|
|
@@ -267,7 +308,10 @@ export function tryRegisterToolTask(server, toolName, toolDef, taskHandler, icon
|
|
|
267
308
|
return true;
|
|
268
309
|
}
|
|
269
310
|
export function registerToolTaskIfAvailable(server, toolName, toolDef, run, iconInfo, guard) {
|
|
270
|
-
const taskOptions =
|
|
311
|
+
const taskOptions = {
|
|
312
|
+
...(guard ? { guard } : {}),
|
|
313
|
+
toolName,
|
|
314
|
+
};
|
|
271
315
|
return tryRegisterToolTask(server, toolName, toolDef, createToolTaskHandler(run, taskOptions), iconInfo);
|
|
272
316
|
}
|
|
273
317
|
export function createToolTaskHandler(run, options) {
|
|
@@ -281,13 +325,21 @@ export function createToolTaskHandler(run, options) {
|
|
|
281
325
|
const task = await taskStore.createTask({
|
|
282
326
|
ttl: extra.taskRequestedTtl ?? null,
|
|
283
327
|
});
|
|
328
|
+
publishTaskDiagnostics({
|
|
329
|
+
phase: 'task_created',
|
|
330
|
+
taskId: task.taskId,
|
|
331
|
+
status: task.status,
|
|
332
|
+
...(options?.toolName !== undefined
|
|
333
|
+
? { toolName: options.toolName }
|
|
334
|
+
: {}),
|
|
335
|
+
});
|
|
284
336
|
const taskExtra = {
|
|
285
337
|
...extra,
|
|
286
338
|
taskStore,
|
|
287
339
|
taskId: task.taskId,
|
|
288
340
|
};
|
|
289
|
-
void notifyTaskStatusIfPossible(taskExtra, taskStore, task.taskId);
|
|
290
|
-
void runTaskInBackground(run, args, taskExtra, taskStore, task.taskId);
|
|
341
|
+
void notifyTaskStatusIfPossible(taskExtra, taskStore, task.taskId, options?.toolName);
|
|
342
|
+
void runTaskInBackground(run, args, taskExtra, taskStore, task.taskId, options?.toolName);
|
|
291
343
|
return { task };
|
|
292
344
|
});
|
|
293
345
|
const getTask = (async (argsOrExtra, maybeExtra) => {
|
package/dist/tools/tree.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { type ToolRegistrationOptions } from './shared.js';
|
|
2
|
+
import { type ToolContract, type ToolRegistrationOptions } from './shared.js';
|
|
3
|
+
export declare const TREE_TOOL: ToolContract;
|
|
3
4
|
export declare function registerTreeTool(server: McpServer, options?: ToolRegistrationOptions): void;
|
package/dist/tools/tree.js
CHANGED
|
@@ -5,7 +5,8 @@ import { formatTreeAscii, treeDirectory } from '../lib/file-operations/tree.js';
|
|
|
5
5
|
import { TreeInputSchema, TreeOutputSchema } from '../schemas.js';
|
|
6
6
|
import { buildToolErrorResponse, buildToolResponse, executeToolWithDiagnostics, READ_ONLY_TOOL_ANNOTATIONS, resolvePathOrRoot, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
7
7
|
import { registerToolTaskIfAvailable } from './task-support.js';
|
|
8
|
-
const TREE_TOOL = {
|
|
8
|
+
export const TREE_TOOL = {
|
|
9
|
+
name: 'tree',
|
|
9
10
|
title: 'Tree',
|
|
10
11
|
description: 'Render a directory tree (bounded recursion). ' +
|
|
11
12
|
'Returns an ASCII tree for quick scanning and a structured JSON tree for programmatic use. ' +
|
|
@@ -13,6 +14,7 @@ const TREE_TOOL = {
|
|
|
13
14
|
inputSchema: TreeInputSchema,
|
|
14
15
|
outputSchema: TreeOutputSchema,
|
|
15
16
|
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
|
17
|
+
gotchas: ['`maxDepth=0` returns only the root node.'],
|
|
16
18
|
};
|
|
17
19
|
async function handleTree(args, signal) {
|
|
18
20
|
const basePath = resolvePathOrRoot(args.path);
|
|
@@ -47,8 +49,7 @@ export function registerTreeTool(server, options = {}) {
|
|
|
47
49
|
onError: (error) => buildToolErrorResponse(error, ErrorCode.E_NOT_DIRECTORY, targetPath),
|
|
48
50
|
});
|
|
49
51
|
};
|
|
50
|
-
const
|
|
51
|
-
const wrappedHandler = wrapToolHandler(validatedHandler, {
|
|
52
|
+
const wrappedHandler = wrapToolHandler(handler, {
|
|
52
53
|
guard: options.isInitialized,
|
|
53
54
|
progressMessage: (args) => {
|
|
54
55
|
if (args.path) {
|
|
@@ -69,7 +70,8 @@ export function registerTreeTool(server, options = {}) {
|
|
|
69
70
|
return `≣ tree: ${base} • ${count} ${count === 1 ? 'entry' : 'entries'}`;
|
|
70
71
|
},
|
|
71
72
|
});
|
|
72
|
-
|
|
73
|
+
const validatedHandler = withValidatedArgs(TreeInputSchema, wrappedHandler);
|
|
74
|
+
if (registerToolTaskIfAvailable(server, 'tree', TREE_TOOL, validatedHandler, options.iconInfo, options.isInitialized))
|
|
73
75
|
return;
|
|
74
|
-
server.registerTool('tree', withDefaultIcons({ ...TREE_TOOL }, options.iconInfo),
|
|
76
|
+
server.registerTool('tree', withDefaultIcons({ ...TREE_TOOL }, options.iconInfo), validatedHandler);
|
|
75
77
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { type ToolRegistrationOptions } from './shared.js';
|
|
2
|
+
import { type ToolContract, type ToolRegistrationOptions } from './shared.js';
|
|
3
|
+
export declare const WRITE_FILE_TOOL: ToolContract;
|
|
3
4
|
export declare function registerWriteFileTool(server: McpServer, options?: ToolRegistrationOptions): void;
|
package/dist/tools/write-file.js
CHANGED
|
@@ -6,12 +6,19 @@ import { validatePathForWrite } from '../lib/path-validation.js';
|
|
|
6
6
|
import { WriteFileInputSchema, WriteFileOutputSchema } from '../schemas.js';
|
|
7
7
|
import { buildToolErrorResponse, buildToolResponse, DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS, executeToolWithDiagnostics, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
8
8
|
import { registerToolTaskIfAvailable } from './task-support.js';
|
|
9
|
-
const WRITE_FILE_TOOL = {
|
|
9
|
+
export const WRITE_FILE_TOOL = {
|
|
10
|
+
name: 'write',
|
|
10
11
|
title: 'Write File',
|
|
11
12
|
description: 'Write content to a file. Creates the file if it does not exist.',
|
|
12
13
|
inputSchema: WriteFileInputSchema,
|
|
13
14
|
outputSchema: WriteFileOutputSchema,
|
|
14
15
|
annotations: DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS,
|
|
16
|
+
nuances: [
|
|
17
|
+
'Creates parent directories automatically; overwrites existing content.',
|
|
18
|
+
],
|
|
19
|
+
gotchas: [
|
|
20
|
+
'Creates parent directories automatically; overwrites existing content.',
|
|
21
|
+
],
|
|
15
22
|
};
|
|
16
23
|
async function handleWriteFile(args, signal) {
|
|
17
24
|
const validPath = await validatePathForWrite(args.path, signal);
|
|
@@ -34,8 +41,7 @@ export function registerWriteFileTool(server, options = {}) {
|
|
|
34
41
|
run: (signal) => handleWriteFile(args, signal),
|
|
35
42
|
onError: (error) => buildToolErrorResponse(error, ErrorCode.E_UNKNOWN, args.path),
|
|
36
43
|
});
|
|
37
|
-
const
|
|
38
|
-
const wrappedHandler = wrapToolHandler(validatedHandler, {
|
|
44
|
+
const wrappedHandler = wrapToolHandler(handler, {
|
|
39
45
|
guard: options.isInitialized,
|
|
40
46
|
progressMessage: (args) => `🛠 write: ${path.basename(args.path)} [${args.content.length} chars]`,
|
|
41
47
|
completionMessage: (args, result) => {
|
|
@@ -48,7 +54,8 @@ export function registerWriteFileTool(server, options = {}) {
|
|
|
48
54
|
return `🛠 write: ${name} • ${sc.bytesWritten ?? 0} bytes`;
|
|
49
55
|
},
|
|
50
56
|
});
|
|
51
|
-
|
|
57
|
+
const validatedHandler = withValidatedArgs(WriteFileInputSchema, wrappedHandler);
|
|
58
|
+
if (registerToolTaskIfAvailable(server, 'write', WRITE_FILE_TOOL, validatedHandler, options.iconInfo, options.isInitialized))
|
|
52
59
|
return;
|
|
53
|
-
server.registerTool('write', withDefaultIcons({ ...WRITE_FILE_TOOL }, options.iconInfo),
|
|
60
|
+
server.registerTool('write', withDefaultIcons({ ...WRITE_FILE_TOOL }, options.iconInfo), validatedHandler);
|
|
54
61
|
}
|
package/dist/tools.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { type ToolContract } from './tools/contract.js';
|
|
2
3
|
import type { ToolRegistrationOptions } from './tools/shared.js';
|
|
3
4
|
export { buildToolErrorResponse, buildToolResponse } from './tools/shared.js';
|
|
5
|
+
export declare const ALL_TOOLS: ToolContract[];
|
|
4
6
|
export declare function registerAllTools(server: McpServer, options?: ToolRegistrationOptions): void;
|