@j0hanz/filesystem-mcp 1.4.0 → 1.5.0

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;
@@ -22,4 +20,3 @@ export declare const SENSITIVE_FILE_ALLOWLIST: string[];
22
20
  export declare const KNOWN_BINARY_EXTENSIONS: Set<string>;
23
21
  export declare const DEFAULT_EXCLUDE_PATTERNS: string[];
24
22
  export declare function getMimeType(ext: string): string;
25
- export {};
@@ -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,44 @@
1
+ import { ALL_TOOLS } from '../tools.js';
2
+ function toEntry(contract) {
3
+ const annotations = [];
4
+ if (contract.annotations?.destructiveHint)
5
+ annotations.push('[Destructive]');
6
+ if (contract.annotations?.idempotentHint)
7
+ annotations.push('[Idempotent]');
8
+ if (contract.annotations?.readOnlyHint)
9
+ annotations.push('[Read-Only]');
10
+ return {
11
+ name: contract.name,
12
+ description: contract.description,
13
+ ...(annotations.length > 0 ? { annotations } : {}),
14
+ ...(contract.nuances && contract.nuances.length > 0
15
+ ? { nuances: contract.nuances }
16
+ : {}),
17
+ ...(contract.gotchas && contract.gotchas.length > 0
18
+ ? { gotchas: contract.gotchas }
19
+ : {}),
20
+ };
21
+ }
22
+ const ENTRIES = Object.fromEntries(ALL_TOOLS.map((contract) => [contract.name, toEntry(contract)]));
23
+ export function getToolContracts() {
24
+ return ALL_TOOLS;
25
+ }
26
+ export function buildCoreContextPack() {
27
+ const names = Object.keys(ENTRIES).sort((a, b) => a.localeCompare(b));
28
+ const rows = names.map((name) => {
29
+ const e = ENTRIES[name];
30
+ if (!e)
31
+ return '';
32
+ const annotations = e.annotations ? ` ${e.annotations.join(' ')}` : '';
33
+ return `| \`${e.name}\` | ${e.description}${annotations} |`;
34
+ });
35
+ return `## Core Context Pack\n\n| Tool | Purpose |\n|------|---------|\n${rows.join('\n')}`;
36
+ }
37
+ export function getSharedConstraints() {
38
+ return [
39
+ 'Allowed roots only (negotiated via CLI).',
40
+ 'Sensitive files denylisted by default.',
41
+ 'Max file size & search results enforced.',
42
+ 'Externalized results are ephemeral (in-memory).',
43
+ ];
44
+ }
@@ -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',
@@ -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';
@@ -48,7 +48,6 @@ interface ToolErrorResponse extends Record<string, unknown> {
48
48
  isError: true;
49
49
  }
50
50
  export type ToolResult<T> = ToolResponse<T> | ToolErrorResponse;
51
- export declare function parseToolArgs<Schema extends z.ZodType>(schema: Schema, args: unknown): z.infer<Schema>;
52
51
  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
52
  type ProgressToken = string | number;
54
53
  export interface ToolExtra {
@@ -107,7 +107,7 @@ function resolveDetailedError(error, defaultCode, path) {
107
107
  export function buildToolResponse(text, structuredContent, extraContent = []) {
108
108
  return buildContentBlock(text, structuredContent, extraContent);
109
109
  }
110
- export function parseToolArgs(schema, args) {
110
+ function parseToolArgs(schema, args) {
111
111
  const candidate = args === undefined ? {} : args;
112
112
  const parsed = schema.safeParse(candidate);
113
113
  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.0",
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",