@j0hanz/filesystem-mcp 1.4.0 → 1.5.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/dist/config.d.ts CHANGED
@@ -112,8 +112,9 @@ export declare const ErrorCode: {
112
112
  export type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];
113
113
  export declare function formatBytes(bytes: number): string;
114
114
  export declare function joinLines(lines: readonly string[]): string;
115
- export interface OperationSummary {
115
+ interface OperationSummary {
116
116
  truncated?: boolean;
117
117
  truncatedReason?: string;
118
118
  }
119
119
  export declare function formatOperationSummary(summary: OperationSummary): string;
120
+ export {};
@@ -1,5 +1,3 @@
1
- declare const VALID_LOG_LEVELS: readonly ["debug", "info", "notice", "warning", "error", "critical", "alert", "emergency"];
2
- export type ValidLogLevel = (typeof VALID_LOG_LEVELS)[number];
3
1
  export declare const DEFAULT_LOG_LEVEL: "debug" | "info" | "notice" | "warning" | "error" | "critical" | "alert" | "emergency";
4
2
  export declare const PARALLEL_CONCURRENCY: number;
5
3
  export declare const MAX_SEARCHABLE_FILE_SIZE: number;
@@ -13,8 +11,17 @@ export declare const DEFAULT_SEARCH_TIMEOUT_MS: number;
13
11
  */
14
12
  export declare const SEARCH_WORKERS: number;
15
13
  export declare const DEFAULT_MAX_DEPTH = 10;
16
- export declare const DEFAULT_LIST_MAX_ENTRIES = 10000;
14
+ export declare const DEFAULT_LIST_MAX_ENTRIES = 20000;
17
15
  export declare const DEFAULT_SEARCH_MAX_FILES = 20000;
16
+ export declare const MAX_TREE_DEPTH = 50;
17
+ export declare const DEFAULT_TREE_DEPTH = 5;
18
+ export declare const MAX_TREE_ENTRIES = 20000;
19
+ export declare const DEFAULT_TREE_ENTRIES = 1000;
20
+ export declare const MAX_LIST_ENTRIES = 20000;
21
+ export declare const MAX_SEARCH_RESULTS = 10000;
22
+ export declare const DEFAULT_SEARCH_RESULTS = 100;
23
+ export declare const MAX_SEARCH_DEPTH = 100;
24
+ export declare const DEFAULT_SEARCH_CONTENT_RESULTS = 500;
18
25
  export declare const MAX_LINE_CONTENT_LENGTH = 200;
19
26
  export declare const BINARY_CHECK_BUFFER_SIZE = 512;
20
27
  export declare const SENSITIVE_FILE_DENYLIST: string[];
@@ -22,4 +29,3 @@ export declare const SENSITIVE_FILE_ALLOWLIST: string[];
22
29
  export declare const KNOWN_BINARY_EXTENSIONS: Set<string>;
23
30
  export declare const DEFAULT_EXCLUDE_PATTERNS: string[];
24
31
  export declare function getMimeType(ext: string): string;
25
- export {};
@@ -109,8 +109,18 @@ const ENV_ALLOWLIST = parseEnvList('FS_CONTEXT_ALLOWLIST');
109
109
  export const SEARCH_WORKERS = parseEnvInt('FS_CONTEXT_SEARCH_WORKERS', getDefaultSearchWorkers(), 1, 16);
110
110
  // Hardcoded defaults
111
111
  export const DEFAULT_MAX_DEPTH = 10;
112
- export const DEFAULT_LIST_MAX_ENTRIES = 10000;
112
+ export const DEFAULT_LIST_MAX_ENTRIES = 20000;
113
113
  export const DEFAULT_SEARCH_MAX_FILES = 20000;
114
+ // Schema limits and defaults
115
+ export const MAX_TREE_DEPTH = 50;
116
+ export const DEFAULT_TREE_DEPTH = 5;
117
+ export const MAX_TREE_ENTRIES = 20000;
118
+ export const DEFAULT_TREE_ENTRIES = 1000;
119
+ export const MAX_LIST_ENTRIES = 20000;
120
+ export const MAX_SEARCH_RESULTS = 10000;
121
+ export const DEFAULT_SEARCH_RESULTS = 100;
122
+ export const MAX_SEARCH_DEPTH = 100;
123
+ export const DEFAULT_SEARCH_CONTENT_RESULTS = 500;
114
124
  // Non-configurable constants
115
125
  export const MAX_LINE_CONTENT_LENGTH = 200;
116
126
  export const BINARY_CHECK_BUFFER_SIZE = 512;
@@ -5,7 +5,7 @@ interface OpsTraceContext {
5
5
  path?: string | undefined;
6
6
  [key: string]: unknown;
7
7
  }
8
- export interface ToolMetrics {
8
+ interface ToolMetrics {
9
9
  calls: number;
10
10
  errors: number;
11
11
  totalDurationMs: number;
@@ -1,4 +1,6 @@
1
- import { ALL_TOOLS } from '../tools.js';
1
+ import { buildToolCatalogDetailsOnly } from './tool-catalog.js';
2
+ import { buildCoreContextPack, getSharedConstraints, getToolContracts, } from './tool-info.js';
3
+ import { buildWorkflowGuide } from './workflows.js';
2
4
  const INSTRUCTIONS_HEADER = `# FILESYSTEM-MCP INSTRUCTIONS
3
5
 
4
6
  > Resource: \`internal://instructions\` | Prompt: \`get-help\`
@@ -24,39 +26,15 @@ const INSTRUCTIONS_HEADER = `# FILESYSTEM-MCP INSTRUCTIONS
24
26
 
25
27
  ## GOLDEN PATH WORKFLOWS
26
28
 
27
- ### A: EXPLORE
28
- 1. \`roots\` (List allowed paths).
29
- 2. \`ls\` (files) | \`tree\` (structure).
30
- 3. \`stat\` | \`stat_many\` (size/type check).
31
- 4. \`read\` | \`read_many\` (content).
32
- > **Strict:** Never guess paths. Resolve first.
29
+ See "Workflow Reference" below for detailed execution sequences.
33
30
 
34
- ### B: SEARCH
35
- 1. \`find\` (glob candidates).
36
- 2. \`grep\` (content search).
37
- 3. \`read\` (verify context).
38
- > **Tip:** Content search requires \`grep\`, not \`find\`.
39
-
40
- ### C: EDIT
41
- 1. \`edit\` (precise string match).
42
- 2. \`search_and_replace\` (bulk regex/glob).
43
- 3. \`mv\` | \`rm\` (file layout).
44
- 4. \`mkdir\` (create dirs).
45
- > **Strict:** Confirm destructive ops (\`write\`, \`mv\`, \`rm\`, bulk replace).
46
-
47
- ### D: PATCH
48
- 1. \`diff_files\` (generate).
49
- 2. \`apply_patch\` (dryRun: true).
50
- 3. \`apply_patch\` (dryRun: false).
51
- > **Tip:** Use \`diff_files\` output directly.
52
31
  `;
53
32
  const INSTRUCTIONS_FOOTER = `
54
33
  ## CONSTRAINTS
55
34
 
56
- - **Scope:** Allowed roots only (negotiated via CLI).
57
- - **Security:** Sensitive files denylisted by default.
58
- - **Limits:** Max file size & search results enforced.
59
- - **Cache:** Externalized results are ephemeral (in-memory).
35
+ ${getSharedConstraints()
36
+ .map((c) => `- ${c}`)
37
+ .join('\n')}
60
38
 
61
39
  ## ERROR HANDLING
62
40
 
@@ -87,13 +65,19 @@ function formatToolSection(tool) {
87
65
  return parts.join('\n');
88
66
  }
89
67
  export function buildServerInstructions() {
90
- const toolSections = ALL_TOOLS.map(formatToolSection).join('\n\n');
68
+ const toolSections = getToolContracts().map(formatToolSection).join('\n\n');
91
69
  return [
92
70
  INSTRUCTIONS_HEADER,
71
+ buildCoreContextPack(),
72
+ '',
73
+ buildToolCatalogDetailsOnly(),
74
+ '',
93
75
  '## TOOL REFERENCE',
94
76
  '',
95
77
  toolSections,
96
78
  '',
79
+ buildWorkflowGuide(),
80
+ '',
97
81
  '---',
98
82
  INSTRUCTIONS_FOOTER,
99
83
  ].join('\n');
@@ -0,0 +1,2 @@
1
+ export declare function buildToolCatalog(): string;
2
+ export declare function buildToolCatalogDetailsOnly(): string;
@@ -0,0 +1,29 @@
1
+ import { buildCoreContextPack } from './tool-info.js';
2
+ const CATALOG_GUIDE = `## Tool Catalog Details
3
+
4
+ ## Cross-Tool Data Flow
5
+
6
+ \`\`\`
7
+ find -> output_paths -> grep.paths
8
+ diff_files -> output_patch -> apply_patch.patch
9
+ \`\`\`
10
+
11
+ ## Search Strategy Strategy
12
+
13
+ - Use \`find\` for glob-based file discovery.
14
+ - Use \`grep\` for content-based searches.
15
+ - Use \`search_and_replace\` ONLY for bulk replacements, not for discovery.
16
+
17
+ ## Patch Management
18
+
19
+ - Always generate a patch with \`diff_files\` first.
20
+ - Always use \`dryRun: true\` with \`apply_patch\` to verify changes.
21
+ - \`apply_patch\` works on unified diff format.
22
+ `;
23
+ export function buildToolCatalog() {
24
+ // Return combined view for standalone resource usage
25
+ return `${buildCoreContextPack()}\n\n${CATALOG_GUIDE}`;
26
+ }
27
+ export function buildToolCatalogDetailsOnly() {
28
+ return CATALOG_GUIDE;
29
+ }
@@ -0,0 +1,4 @@
1
+ import type { ToolContract } from '../tools/contract.js';
2
+ export declare function getToolContracts(): ToolContract[];
3
+ export declare function buildCoreContextPack(): string;
4
+ export declare function getSharedConstraints(): string[];
@@ -0,0 +1,45 @@
1
+ import { DEFAULT_SEARCH_CONTENT_RESULTS, MAX_SEARCH_RESULTS, MAX_TEXT_FILE_SIZE, } from '../lib/constants.js';
2
+ import { ALL_TOOLS } from '../tools.js';
3
+ function toEntry(contract) {
4
+ const annotations = [];
5
+ if (contract.annotations?.destructiveHint)
6
+ annotations.push('[Destructive]');
7
+ if (contract.annotations?.idempotentHint)
8
+ annotations.push('[Idempotent]');
9
+ if (contract.annotations?.readOnlyHint)
10
+ annotations.push('[Read-Only]');
11
+ return {
12
+ name: contract.name,
13
+ description: contract.description,
14
+ ...(annotations.length > 0 ? { annotations } : {}),
15
+ ...(contract.nuances && contract.nuances.length > 0
16
+ ? { nuances: contract.nuances }
17
+ : {}),
18
+ ...(contract.gotchas && contract.gotchas.length > 0
19
+ ? { gotchas: contract.gotchas }
20
+ : {}),
21
+ };
22
+ }
23
+ const ENTRIES = Object.fromEntries(ALL_TOOLS.map((contract) => [contract.name, toEntry(contract)]));
24
+ export function getToolContracts() {
25
+ return ALL_TOOLS;
26
+ }
27
+ export function buildCoreContextPack() {
28
+ const names = Object.keys(ENTRIES).sort((a, b) => a.localeCompare(b));
29
+ const rows = names.map((name) => {
30
+ const e = ENTRIES[name];
31
+ if (!e)
32
+ return '';
33
+ const annotations = e.annotations ? ` ${e.annotations.join(' ')}` : '';
34
+ return `| \`${e.name}\` | ${e.description}${annotations} |`;
35
+ });
36
+ return `## Core Context Pack\n\n| Tool | Purpose |\n|------|---------|\n${rows.join('\n')}`;
37
+ }
38
+ export function getSharedConstraints() {
39
+ return [
40
+ 'Allowed roots only (negotiated via CLI).',
41
+ 'Sensitive files denylisted by default.',
42
+ `Max file size (${Math.floor(MAX_TEXT_FILE_SIZE / 1024 / 1024)}MB) & search results (${MAX_SEARCH_RESULTS} files, ${DEFAULT_SEARCH_CONTENT_RESULTS} lines) enforced.`,
43
+ 'Externalized results are ephemeral (in-memory).',
44
+ ];
45
+ }
@@ -0,0 +1 @@
1
+ export declare function buildWorkflowGuide(): string;
@@ -0,0 +1,36 @@
1
+ import { getSharedConstraints } from './tool-info.js';
2
+ export function buildWorkflowGuide() {
3
+ return `## Workflow Reference
4
+
5
+ ### A: EXPLORE
6
+ 1. \`roots\` (List allowed paths).
7
+ 2. \`ls\` (files) | \`tree\` (structure).
8
+ 3. \`stat\` | \`stat_many\` (size/type check).
9
+ 4. \`read\` | \`read_many\` (content).
10
+ > **Strict:** Never guess paths. Resolve first.
11
+
12
+ ### B: SEARCH
13
+ 1. \`find\` (glob candidates).
14
+ 2. \`grep\` (content search).
15
+ 3. \`read\` (verify context).
16
+ > **Tip:** Content search requires \`grep\`, not \`find\`.
17
+
18
+ ### C: EDIT
19
+ 1. \`edit\` (precise string match).
20
+ 2. \`search_and_replace\` (bulk regex/glob).
21
+ 3. \`mv\` | \`rm\` (file layout).
22
+ 4. \`mkdir\` (create dirs).
23
+ > **Strict:** Confirm destructive ops (\`write\`, \`mv\`, \`rm\`, bulk replace).
24
+
25
+ ### D: PATCH
26
+ 1. \`diff_files\` (generate).
27
+ 2. \`apply_patch\` (dryRun: true).
28
+ 3. \`apply_patch\` (dryRun: false).
29
+ > **Tip:** Use \`diff_files\` output directly.
30
+
31
+ ## Shared Constraints
32
+ ${getSharedConstraints()
33
+ .map((c) => `- ${c}`)
34
+ .join('\n')}
35
+ `;
36
+ }
@@ -2,5 +2,7 @@ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import type { ResourceStore } from './lib/resource-store.js';
3
3
  import { type IconInfo } from './tools/shared.js';
4
4
  export declare function registerInstructionResource(server: McpServer, instructions: string, iconInfo?: IconInfo): void;
5
+ export declare function registerToolCatalogResource(server: McpServer, iconInfo?: IconInfo): void;
6
+ export declare function registerWorkflowGuideResource(server: McpServer, iconInfo?: IconInfo): void;
5
7
  export declare function registerResultResources(server: McpServer, store: ResourceStore, iconInfo?: IconInfo): void;
6
8
  export declare function registerMetricsResource(server: McpServer, iconInfo?: IconInfo): void;
package/dist/resources.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { ErrorCode, McpError } from './lib/errors.js';
3
3
  import { globalMetrics } from './lib/observability.js';
4
+ import { buildToolCatalog } from './resources/tool-catalog.js';
5
+ import { buildWorkflowGuide } from './resources/workflows.js';
4
6
  import { withDefaultIcons } from './tools/shared.js';
5
7
  const RESULT_TEMPLATE = new ResourceTemplate('filesystem-mcp://result/{id}', {
6
8
  list: undefined,
@@ -13,6 +15,12 @@ const RESULT_RESOURCE_DESCRIPTION = 'Ephemeral cached tool output exposed as an
13
15
  const METRICS_RESOURCE_NAME = 'filesystem-mcp-metrics';
14
16
  const METRICS_RESOURCE_URI = 'filesystem-mcp://metrics';
15
17
  const METRICS_RESOURCE_DESCRIPTION = 'Live per-tool call/error/avgDurationMs metrics snapshot.';
18
+ const CATALOG_RESOURCE_NAME = 'filesystem-mcp-catalog';
19
+ const CATALOG_RESOURCE_URI = 'internal://tool-catalog';
20
+ const CATALOG_RESOURCE_DESCRIPTION = 'Detailed catalog of tools and their inter-dependencies.';
21
+ const WORKFLOW_RESOURCE_NAME = 'filesystem-mcp-workflows';
22
+ const WORKFLOW_RESOURCE_URI = 'internal://workflows';
23
+ const WORKFLOW_RESOURCE_DESCRIPTION = 'Recommended workflows for common tasks.';
16
24
  export function registerInstructionResource(server, instructions, iconInfo) {
17
25
  server.registerResource(INSTRUCTIONS_RESOURCE_NAME, INSTRUCTIONS_RESOURCE_URI, withDefaultIcons({
18
26
  title: 'Server Instructions',
@@ -32,6 +40,44 @@ export function registerInstructionResource(server, instructions, iconInfo) {
32
40
  ],
33
41
  }));
34
42
  }
43
+ export function registerToolCatalogResource(server, iconInfo) {
44
+ server.registerResource(CATALOG_RESOURCE_NAME, CATALOG_RESOURCE_URI, withDefaultIcons({
45
+ title: 'Tool Catalog',
46
+ description: CATALOG_RESOURCE_DESCRIPTION,
47
+ mimeType: 'text/markdown',
48
+ annotations: {
49
+ audience: ['assistant'],
50
+ priority: 0.6,
51
+ },
52
+ }, iconInfo), (uri) => ({
53
+ contents: [
54
+ {
55
+ uri: uri.href,
56
+ mimeType: 'text/markdown',
57
+ text: buildToolCatalog(),
58
+ },
59
+ ],
60
+ }));
61
+ }
62
+ export function registerWorkflowGuideResource(server, iconInfo) {
63
+ server.registerResource(WORKFLOW_RESOURCE_NAME, WORKFLOW_RESOURCE_URI, withDefaultIcons({
64
+ title: 'Workflow Guide',
65
+ description: WORKFLOW_RESOURCE_DESCRIPTION,
66
+ mimeType: 'text/markdown',
67
+ annotations: {
68
+ audience: ['assistant'],
69
+ priority: 0.7,
70
+ },
71
+ }, iconInfo), (uri) => ({
72
+ contents: [
73
+ {
74
+ uri: uri.href,
75
+ mimeType: 'text/markdown',
76
+ text: buildWorkflowGuide(),
77
+ },
78
+ ],
79
+ }));
80
+ }
35
81
  export function registerResultResources(server, store, iconInfo) {
36
82
  server.registerResource(RESULT_RESOURCE_NAME, RESULT_TEMPLATE, withDefaultIcons({
37
83
  title: 'Cached Tool Result',
package/dist/schemas.d.ts CHANGED
@@ -38,7 +38,7 @@ export declare const ListDirectoryInputSchema: z.ZodObject<{
38
38
  includeHidden: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
39
39
  includeIgnored: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
40
40
  maxDepth: z.ZodOptional<z.ZodNumber>;
41
- maxEntries: z.ZodOptional<z.ZodNumber>;
41
+ maxEntries: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
42
42
  sortBy: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
43
43
  name: "name";
44
44
  size: "size";
package/dist/schemas.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import { DEFAULT_LIST_MAX_ENTRIES, DEFAULT_SEARCH_CONTENT_RESULTS, DEFAULT_SEARCH_RESULTS, DEFAULT_TREE_DEPTH, DEFAULT_TREE_ENTRIES, MAX_LIST_ENTRIES, MAX_SEARCH_DEPTH, MAX_SEARCH_RESULTS, MAX_TREE_DEPTH, MAX_TREE_ENTRIES, } from './lib/constants.js';
2
3
  import { ErrorCode } from './lib/errors.js';
3
4
  function isSafeGlobPattern(value) {
4
5
  if (value.length === 0)
@@ -111,16 +112,17 @@ export const ListDirectoryInputSchema = z.strictObject({
111
112
  .number()
112
113
  .int({ error: 'Must be integer' })
113
114
  .min(1, 'Min: 1')
114
- .max(50, 'Max: 50')
115
+ .max(MAX_TREE_DEPTH, `Max: ${MAX_TREE_DEPTH}`)
115
116
  .optional()
116
117
  .describe('Max recursion depth when pattern is provided'),
117
118
  maxEntries: z
118
119
  .number()
119
120
  .int({ error: 'Must be integer' })
120
121
  .min(1, 'Min: 1')
121
- .max(20000, 'Max: 20,000')
122
+ .max(MAX_LIST_ENTRIES, `Max: ${MAX_LIST_ENTRIES}`)
122
123
  .optional()
123
- .describe('Maximum entries to return before truncation'),
124
+ .default(DEFAULT_LIST_MAX_ENTRIES)
125
+ .describe(`Maximum entries to return before truncation. Default: ${DEFAULT_LIST_MAX_ENTRIES}`),
124
126
  sortBy: ListDirectorySortSchema.optional()
125
127
  .default('name')
126
128
  .describe('Sort field (name, size, modified, type)'),
@@ -157,10 +159,10 @@ export const SearchFilesInputSchema = z.strictObject({
157
159
  .number()
158
160
  .int({ error: 'Must be integer' })
159
161
  .min(1, 'Min: 1')
160
- .max(10000, 'Max: 10,000')
162
+ .max(MAX_SEARCH_RESULTS, `Max: ${MAX_SEARCH_RESULTS}`)
161
163
  .optional()
162
- .default(100)
163
- .describe('Max results (1-10000)'),
164
+ .default(DEFAULT_SEARCH_RESULTS)
165
+ .describe(`Max results (1-${MAX_SEARCH_RESULTS}). Default: ${DEFAULT_SEARCH_RESULTS}`),
164
166
  includeIgnored: z
165
167
  .boolean()
166
168
  .optional()
@@ -178,7 +180,7 @@ export const SearchFilesInputSchema = z.strictObject({
178
180
  .number()
179
181
  .int({ error: 'Must be integer' })
180
182
  .min(0, 'Min: 0')
181
- .max(100, 'Max: 100')
183
+ .max(MAX_SEARCH_DEPTH, `Max: ${MAX_SEARCH_DEPTH}`)
182
184
  .optional()
183
185
  .describe('Maximum directory depth to scan'),
184
186
  cursor: z
@@ -192,18 +194,18 @@ export const TreeInputSchema = z.strictObject({
192
194
  .number()
193
195
  .int({ error: 'Must be integer' })
194
196
  .min(0, 'Min: 0')
195
- .max(50, 'Max: 50')
197
+ .max(MAX_TREE_DEPTH, `Max: ${MAX_TREE_DEPTH}`)
196
198
  .optional()
197
- .default(5)
198
- .describe('Depth (0=root node only, no children). Default: 5'),
199
+ .default(DEFAULT_TREE_DEPTH)
200
+ .describe(`Depth (0=root node only, no children). Default: ${DEFAULT_TREE_DEPTH}`),
199
201
  maxEntries: z
200
202
  .number()
201
203
  .int({ error: 'Must be integer' })
202
204
  .min(1, 'Min: 1')
203
- .max(20000, 'Max: 20,000')
205
+ .max(MAX_TREE_ENTRIES, `Max: ${MAX_TREE_ENTRIES}`)
204
206
  .optional()
205
- .default(1000)
206
- .describe('Max entries (Default: 1000)'),
207
+ .default(DEFAULT_TREE_ENTRIES)
208
+ .describe(`Max entries. Default: ${DEFAULT_TREE_ENTRIES}`),
207
209
  includeHidden: z
208
210
  .boolean()
209
211
  .optional()
@@ -249,10 +251,10 @@ export const SearchContentInputSchema = z.strictObject({
249
251
  .number()
250
252
  .int({ error: 'Must be integer' })
251
253
  .min(0, 'Min: 0')
252
- .max(10000, 'Max: 10,000')
254
+ .max(MAX_SEARCH_RESULTS, `Max: ${MAX_SEARCH_RESULTS}`)
253
255
  .optional()
254
- .default(500)
255
- .describe('Maximum match rows to return'),
256
+ .default(DEFAULT_SEARCH_CONTENT_RESULTS)
257
+ .describe(`Maximum match rows to return. Default: ${DEFAULT_SEARCH_CONTENT_RESULTS}`),
256
258
  filePattern: z
257
259
  .string()
258
260
  .min(1, 'Pattern required')
@@ -12,7 +12,7 @@ import { formatUnknownErrorMessage } from '../lib/errors.js';
12
12
  import { createInMemoryResourceStore } from '../lib/resource-store.js';
13
13
  import { pkgInfo } from '../pkg-info.js';
14
14
  import { registerGetHelpPrompt } from '../prompts.js';
15
- import { registerInstructionResource, registerMetricsResource, registerResultResources, } from '../resources.js';
15
+ import { registerInstructionResource, registerMetricsResource, registerResultResources, registerToolCatalogResource, registerWorkflowGuideResource, } from '../resources.js';
16
16
  import { buildServerInstructions } from '../resources/generated-instructions.js';
17
17
  import { registerAllTools } from '../tools.js';
18
18
  import { withDefaultIcons } from '../tools/shared.js';
@@ -86,6 +86,8 @@ export async function createServer(options = {}) {
86
86
  return {};
87
87
  });
88
88
  registerInstructionResource(server, serverInstructions, localIcon);
89
+ registerToolCatalogResource(server, localIcon);
90
+ registerWorkflowGuideResource(server, localIcon);
89
91
  registerGetHelpPrompt(server, serverInstructions, localIcon);
90
92
  registerResultResources(server, resourceStore, localIcon);
91
93
  registerMetricsResource(server, localIcon);
@@ -1,5 +1,5 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- export interface CapabilityOptions {
2
+ interface CapabilityOptions {
3
3
  enablePromptListChanged?: boolean;
4
4
  enableTaskToolRequests?: boolean;
5
5
  }
package/dist/server.d.ts CHANGED
@@ -1,2 +1 @@
1
1
  export { createServer, startHttpServer, startServer, } from './server/bootstrap.js';
2
- export type { ServerOptions } from './server/types.js';
@@ -171,7 +171,7 @@ export function registerCalculateHashTool(server, options = {}) {
171
171
  baseReporter({
172
172
  current,
173
173
  ...(total !== undefined ? { total } : {}),
174
- message: `🕮 calculate_hash: ${baseName} ${current} ${fileWord} hashed`,
174
+ message: `🕮 calculate_hash: ${baseName} [${current} ${fileWord} hashed]`,
175
175
  });
176
176
  };
177
177
  try {
@@ -34,11 +34,11 @@ export function registerCreateDirectoryTool(server, options = {}) {
34
34
  const wrappedHandler = wrapToolHandler(handler, {
35
35
  guard: options.isInitialized,
36
36
  progressMessage: (args) => {
37
- const name = path.basename(args.path) || args.path;
37
+ const name = path.basename(args.path) || '.';
38
38
  return `🛠 mkdir: ${name}`;
39
39
  },
40
40
  completionMessage: (args, result) => {
41
- const name = path.basename(args.path) || args.path;
41
+ const name = path.basename(args.path) || '.';
42
42
  if (result.isError)
43
43
  return `🛠 mkdir: ${name} • failed`;
44
44
  return `🛠 mkdir: ${name} • created`;
@@ -96,7 +96,8 @@ export function registerEditFileTool(server, options = {}) {
96
96
  guard: options.isInitialized,
97
97
  progressMessage: (args) => {
98
98
  const name = path.basename(args.path);
99
- return `🛠 edit: ${name} [${args.edits.length} edits]`;
99
+ const dryTag = args.dryRun ? ' [dry run]' : '';
100
+ return `🛠 edit: ${name} [${args.edits.length} edits]${dryTag}`;
100
101
  },
101
102
  completionMessage: (args, result) => {
102
103
  const name = path.basename(args.path);
@@ -105,10 +106,16 @@ export function registerEditFileTool(server, options = {}) {
105
106
  const sc = result.structuredContent;
106
107
  if (!sc.ok)
107
108
  return `🛠 edit: ${name} • failed`;
109
+ const applied = sc.appliedEdits ?? 0;
110
+ const unmatched = sc.unmatchedEdits?.length ?? 0;
111
+ const dryPrefix = args.dryRun ? 'dry run — ' : '';
112
+ if (unmatched > 0) {
113
+ return `🛠 edit: ${name} • ${dryPrefix}${applied} applied, ${unmatched} unmatched`;
114
+ }
108
115
  if (sc.lineRange) {
109
- return `🛠 edit: ${name} • [${sc.lineRange[0]}-${sc.lineRange[1]}]`;
116
+ return `🛠 edit: ${name} • ${dryPrefix}lines ${sc.lineRange[0]}–${sc.lineRange[1]}`;
110
117
  }
111
- return `🛠 edit: ${name} • [${sc.appliedEdits ?? 0} edits]`;
118
+ return `🛠 edit: ${name} • ${dryPrefix}${applied} applied`;
112
119
  },
113
120
  });
114
121
  const validatedHandler = withValidatedArgs(EditFileInputSchema, wrappedHandler);
@@ -98,7 +98,7 @@ function decodeCursor(cursor) {
98
98
  async function handleListDirectory(args, signal) {
99
99
  const dirPath = resolvePathOrRoot(args.path);
100
100
  const cursorOffset = args.cursor !== undefined ? decodeCursor(args.cursor) : 0;
101
- const pageSize = args.maxEntries ?? 20_000;
101
+ const pageSize = args.maxEntries;
102
102
  const options = {
103
103
  includeHidden: args.includeHidden,
104
104
  excludePatterns: args.includeIgnored ? [] : DEFAULT_EXCLUDE_PATTERNS,
@@ -265,7 +265,7 @@ export function registerSearchAndReplaceTool(server, options = {}) {
265
265
  baseReporter({
266
266
  current,
267
267
  ...(total !== undefined ? { total } : {}),
268
- message: `🛠 search_and_replace: "${args.searchPattern}" ${current} files processed`,
268
+ message: `🛠 search_and_replace: ${args.searchPattern} [${current} files processed]`,
269
269
  });
270
270
  };
271
271
  try {
@@ -220,7 +220,7 @@ export function registerSearchContentTool(server, options = {}) {
220
220
  baseReporter({
221
221
  current,
222
222
  ...(total !== undefined ? { total } : {}),
223
- message: `🔎︎ grep: ${pattern} ${current} ${fileWord} scanned`,
223
+ message: `🔎︎ grep: ${pattern} [${current} ${fileWord} scanned]`,
224
224
  });
225
225
  };
226
226
  try {
@@ -8,6 +8,7 @@ export { type ToolContract } from './contract.js';
8
8
  export declare const READ_ONLY_TOOL_ANNOTATIONS: {
9
9
  readonly readOnlyHint: true;
10
10
  readonly idempotentHint: true;
11
+ readonly destructiveHint: false;
11
12
  readonly openWorldHint: false;
12
13
  };
13
14
  export declare const DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS: {
@@ -18,6 +19,7 @@ export declare const DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS: {
18
19
  export declare const IDEMPOTENT_WRITE_TOOL_ANNOTATIONS: {
19
20
  readonly readOnlyHint: false;
20
21
  readonly idempotentHint: true;
22
+ readonly destructiveHint: false;
21
23
  readonly openWorldHint: false;
22
24
  };
23
25
  export declare function shouldStripStructuredOutput(): boolean;
@@ -48,7 +50,6 @@ interface ToolErrorResponse extends Record<string, unknown> {
48
50
  isError: true;
49
51
  }
50
52
  export type ToolResult<T> = ToolResponse<T> | ToolErrorResponse;
51
- export declare function parseToolArgs<Schema extends z.ZodType>(schema: Schema, args: unknown): z.infer<Schema>;
52
53
  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>>;
53
54
  type ProgressToken = string | number;
54
55
  export interface ToolExtra {
@@ -18,6 +18,7 @@ function publishContextDiagnostics(event) {
18
18
  export const READ_ONLY_TOOL_ANNOTATIONS = {
19
19
  readOnlyHint: true,
20
20
  idempotentHint: true,
21
+ destructiveHint: false,
21
22
  openWorldHint: false,
22
23
  };
23
24
  export const DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS = {
@@ -28,6 +29,7 @@ export const DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS = {
28
29
  export const IDEMPOTENT_WRITE_TOOL_ANNOTATIONS = {
29
30
  readOnlyHint: false,
30
31
  idempotentHint: true,
32
+ destructiveHint: false,
31
33
  openWorldHint: false,
32
34
  };
33
35
  export function shouldStripStructuredOutput() {
@@ -107,7 +109,7 @@ function resolveDetailedError(error, defaultCode, path) {
107
109
  export function buildToolResponse(text, structuredContent, extraContent = []) {
108
110
  return buildContentBlock(text, structuredContent, extraContent);
109
111
  }
110
- export function parseToolArgs(schema, args) {
112
+ function parseToolArgs(schema, args) {
111
113
  const candidate = args === undefined ? {} : args;
112
114
  const parsed = schema.safeParse(candidate);
113
115
  if (parsed.success) {
@@ -9,12 +9,6 @@ type TaskToolExtra = ToolExtra & {
9
9
  taskRequestedTtl?: number | null;
10
10
  };
11
11
  type ToolArgs<Args extends ZodRawShapeCompat | AnySchema | undefined> = Args extends ZodRawShapeCompat ? ShapeOutput<Args> : Args extends AnySchema ? SchemaOutput<Args> : undefined;
12
- /**
13
- * Registers a tool preferring task-capable registration when available, and
14
- * returns `true`. Returns `false` so the caller can fall through to standard
15
- * `server.registerTool`.
16
- */
17
- export declare function tryRegisterToolTask<Args extends ZodRawShapeCompat | AnySchema | undefined>(server: McpServer, toolName: string, toolDef: object, taskHandler: ToolTaskHandler<Args>, iconInfo: IconInfo | undefined): boolean;
18
12
  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
13
  export declare function createToolTaskHandler<Result>(run: (args: undefined, extra: TaskToolExtra) => Promise<ToolResult<Result>>, options?: {
20
14
  guard?: () => boolean;
@@ -298,7 +298,7 @@ async function runTaskInBackground(run, args, extra, taskStore, taskId, toolName
298
298
  * returns `true`. Returns `false` so the caller can fall through to standard
299
299
  * `server.registerTool`.
300
300
  */
301
- export function tryRegisterToolTask(server, toolName, toolDef, taskHandler, iconInfo) {
301
+ function tryRegisterToolTask(server, toolName, toolDef, taskHandler, iconInfo) {
302
302
  if (!hasTaskToolCapability(server))
303
303
  return false;
304
304
  const tasks = getExperimentalTaskRegistration(server);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@j0hanz/filesystem-mcp",
3
- "version": "1.4.0",
3
+ "version": "1.5.1",
4
4
  "mcpName": "io.github.j0hanz/filesystem-mcp",
5
5
  "description": "MCP Server that enables LLMs to interact with the local filesystem.",
6
6
  "type": "module",
@@ -73,7 +73,7 @@
73
73
  },
74
74
  "devDependencies": {
75
75
  "@eslint/js": "^10.0.1",
76
- "eslint": "^10.0.0",
76
+ "eslint": "^10.0.1",
77
77
  "@trivago/prettier-plugin-sort-imports": "^6.0.2",
78
78
  "@types/node": "^24",
79
79
  "eslint-config-prettier": "^10.1.8",
@@ -81,7 +81,7 @@
81
81
  "eslint-plugin-depend": "^1.4.0",
82
82
  "eslint-plugin-unused-imports": "^4.4.1",
83
83
  "jscpd": "^4.0.8",
84
- "knip": "^5.84.1",
84
+ "knip": "^5.85.0",
85
85
  "prettier": "^3.8.1",
86
86
  "tsx": "^4.21.0",
87
87
  "typescript": "^5.9.3",