@j0hanz/filesystem-mcp 1.14.0 → 1.15.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/LICENSE +21 -0
- package/README.md +143 -71
- package/dist/cli.js +2 -2
- package/dist/completions.js +54 -51
- package/dist/config.d.ts +13 -13
- package/dist/config.js +12 -12
- package/dist/index.js +1 -1
- package/dist/lib/abort.d.ts +7 -0
- package/dist/lib/abort.js +81 -0
- package/dist/lib/constants.d.ts +3 -1
- package/dist/lib/constants.js +8 -2
- package/dist/lib/errors.d.ts +7 -3
- package/dist/lib/errors.js +59 -39
- package/dist/lib/file-operations/core.d.ts +3 -3
- package/dist/lib/file-operations/core.js +23 -20
- package/dist/lib/file-operations/metadata.d.ts +2 -2
- package/dist/lib/file-operations/metadata.js +60 -19
- package/dist/lib/file-operations/search.js +83 -84
- package/dist/lib/file-operations/traversal.js +13 -15
- package/dist/lib/fs-helpers.d.ts +3 -10
- package/dist/lib/fs-helpers.js +20 -98
- package/dist/lib/globs.js +1 -1
- package/dist/lib/logger.d.ts +28 -0
- package/dist/lib/logger.js +91 -0
- package/dist/lib/observability.d.ts +7 -0
- package/dist/lib/observability.js +19 -9
- package/dist/lib/paths.js +55 -55
- package/dist/lib/resource-store.js +4 -4
- package/dist/lib/utils.d.ts +0 -12
- package/dist/lib/utils.js +0 -13
- package/dist/resources/generated-instructions.js +40 -31
- package/dist/resources/tool-catalog.js +32 -26
- package/dist/resources/tool-info.js +34 -29
- package/dist/resources/workflows.js +39 -18
- package/dist/resources.d.ts +1 -1
- package/dist/resources.js +4 -4
- package/dist/schemas.d.ts +66 -66
- package/dist/schemas.js +21 -44
- package/dist/server/bootstrap.d.ts +12 -11
- package/dist/server/bootstrap.js +95 -86
- package/dist/server/roots-manager.d.ts +5 -2
- package/dist/server/roots-manager.js +8 -6
- package/dist/server/task-store.d.ts +10 -0
- package/dist/server/task-store.js +73 -0
- package/dist/tools/apply-patch.js +26 -18
- package/dist/tools/calculate-hash.js +11 -22
- package/dist/tools/create-directory.js +10 -8
- package/dist/tools/delete-file.js +17 -15
- package/dist/tools/diff-files.js +15 -15
- package/dist/tools/edit-file.js +7 -4
- package/dist/tools/list-directory.js +6 -6
- package/dist/tools/move-file.js +98 -81
- package/dist/tools/read-multiple.js +4 -4
- package/dist/tools/read.js +5 -5
- package/dist/tools/replace-in-files.js +20 -23
- package/dist/tools/roots.js +1 -1
- package/dist/tools/search-content.js +32 -41
- package/dist/tools/search-files.js +54 -41
- package/dist/tools/shared.d.ts +3 -0
- package/dist/tools/shared.js +70 -28
- package/dist/tools/stat-many.js +11 -7
- package/dist/tools/stat.js +4 -4
- package/dist/tools/task-support.d.ts +10 -9
- package/dist/tools/task-support.js +94 -23
- package/dist/tools/tree.js +3 -3
- package/dist/tools/write-file.js +10 -7
- package/package.json +9 -9
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 j0hanz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -8,6 +8,24 @@
|
|
|
8
8
|
|
|
9
9
|
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.
|
|
10
10
|
|
|
11
|
+
## Table of Contents
|
|
12
|
+
|
|
13
|
+
- [Overview](#overview)
|
|
14
|
+
- [Key Features](#key-features)
|
|
15
|
+
- [Quick Start](#quick-start) — [Docker](#docker) · [CLI Usage](#cli-usage)
|
|
16
|
+
- [Client Configuration](#client-configuration)
|
|
17
|
+
- [Use Cases](#use-cases)
|
|
18
|
+
- [Architecture](#architecture)
|
|
19
|
+
- [MCP Surface](#mcp-surface) — [Tools](#tools) · [Resources](#resources) · [Prompts](#prompts)
|
|
20
|
+
- [MCP Capabilities](#mcp-capabilities) — [Tool Annotations](#tool-annotations) · [Structured Output](#structured-output)
|
|
21
|
+
- [Configuration](#configuration) — [HTTP & Auth](#http--auth) · [File Size Limits](#file-size-limits) · [Access Control](#access-control) · [Output & Inline Limits](#output--inline-limits) · [Tasks](#tasks) · [Logging & Diagnostics](#logging--diagnostics) · [Performance](#performance)
|
|
22
|
+
- [HTTP Endpoints](#http-endpoints)
|
|
23
|
+
- [Security](#security)
|
|
24
|
+
- [Development](#development)
|
|
25
|
+
- [Troubleshooting](#troubleshooting)
|
|
26
|
+
- [Credits](#credits)
|
|
27
|
+
- [Contributing and License](#contributing-and-license)
|
|
28
|
+
|
|
11
29
|
## Overview
|
|
12
30
|
|
|
13
31
|
A secure, production-ready [Model Context Protocol](https://modelcontextprotocol.io) server that gives AI assistants controlled access to the local filesystem. All operations are sandboxed to explicitly allowed directories with path traversal prevention, sensitive file blocking, and optional Bearer token authentication.
|
|
@@ -469,37 +487,39 @@ Add to VS Code `settings.json` under `augment.advanced`:
|
|
|
469
487
|
|
|
470
488
|
</details>
|
|
471
489
|
|
|
490
|
+
[↑ Back to top](#table-of-contents)
|
|
491
|
+
|
|
472
492
|
## Use Cases
|
|
473
493
|
|
|
474
494
|
### Explore and Understand a Codebase
|
|
475
495
|
|
|
476
496
|
Discover project structure and navigate unfamiliar repositories. Start with `roots` to see allowed directories, use `tree` for an overview, `find` to locate files by pattern, and `read` or `read_many` to inspect contents.
|
|
477
497
|
|
|
478
|
-
**Relevant tools:** `roots
|
|
498
|
+
**Relevant tools:** [`roots`](#roots), [`ls`](#ls), [`find`](#find), [`tree`](#tree), [`read`](#read), [`read_many`](#read_many), [`stat`](#stat)
|
|
479
499
|
|
|
480
500
|
### Search Across Files
|
|
481
501
|
|
|
482
502
|
Locate specific code patterns, function definitions, or configuration values across a project. Use `grep` for content search with regex support and `find` for file name matching.
|
|
483
503
|
|
|
484
|
-
**Relevant tools:** `grep
|
|
504
|
+
**Relevant tools:** [`grep`](#grep), [`find`](#find)
|
|
485
505
|
|
|
486
506
|
### Edit and Refactor Code
|
|
487
507
|
|
|
488
508
|
Make precise, targeted edits to source files. Use `edit` for surgical replacements with dry-run preview, or `search_and_replace` for bulk changes across multiple files matching a glob pattern.
|
|
489
509
|
|
|
490
|
-
**Relevant tools:** `edit
|
|
510
|
+
**Relevant tools:** [`edit`](#edit), [`search_and_replace`](#search_and_replace), [`write`](#write)
|
|
491
511
|
|
|
492
512
|
### Diff and Patch Workflow
|
|
493
513
|
|
|
494
514
|
Compare file versions and apply patches. Generate a unified diff with `diff_files`, preview with `apply_patch(dryRun: true)`, then apply. Supports both single-file and multi-file patches (best-effort per file with per-file `results[]`).
|
|
495
515
|
|
|
496
|
-
**Relevant tools:** `diff_files
|
|
516
|
+
**Relevant tools:** [`diff_files`](#diff_files), [`apply_patch`](#apply_patch)
|
|
497
517
|
|
|
498
518
|
### File Management
|
|
499
519
|
|
|
500
520
|
Create directories, move/rename files, delete files, and verify file integrity via SHA-256 hashing.
|
|
501
521
|
|
|
502
|
-
**Relevant tools:** `mkdir
|
|
522
|
+
**Relevant tools:** [`mkdir`](#mkdir), [`mv`](#mv), [`rm`](#rm), [`calculate_hash`](#calculate_hash), [`write`](#write)
|
|
503
523
|
|
|
504
524
|
## Architecture
|
|
505
525
|
|
|
@@ -565,6 +585,8 @@ Create directories, move/rename files, delete files, and verify file integrity v
|
|
|
565
585
|
|
|
566
586
|
### Tools
|
|
567
587
|
|
|
588
|
+
[`roots`](#roots) · [`ls`](#ls) · [`find`](#find) · [`tree`](#tree) · [`read`](#read) · [`read_many`](#read_many) · [`stat`](#stat) · [`stat_many`](#stat_many) · [`grep`](#grep) · [`mkdir`](#mkdir) · [`write`](#write) · [`edit`](#edit) · [`mv`](#mv) · [`rm`](#rm) · [`calculate_hash`](#calculate_hash) · [`diff_files`](#diff_files) · [`apply_patch`](#apply_patch) · [`search_and_replace`](#search_and_replace)
|
|
589
|
+
|
|
568
590
|
#### `roots`
|
|
569
591
|
|
|
570
592
|
List allowed workspace roots. Call first — all other tools are scoped to these directories.
|
|
@@ -577,17 +599,17 @@ _No parameters._
|
|
|
577
599
|
|
|
578
600
|
List immediate directory contents: name, path, type, size, modified date.
|
|
579
601
|
|
|
580
|
-
| Parameter | Type | Required | Description
|
|
581
|
-
| ----------------------- | ------- | -------- |
|
|
582
|
-
| `path` | string | no | Base directory (default: root)
|
|
583
|
-
| `includeHidden` | boolean | no | Include dotfiles. Default: `false`
|
|
584
|
-
| `includeIgnored` | boolean | no | Include ignored items (node_modules, .git). Default: `false`
|
|
585
|
-
| `maxDepth` | integer | no | Max recursion depth (1-
|
|
586
|
-
| `maxEntries` | integer | no | Max entries before truncation. Default:
|
|
587
|
-
| `sortBy` | enum | no | `name` \| `size` \| `modified` \| `type`. Default: `name`
|
|
588
|
-
| `pattern` | string | no |
|
|
589
|
-
| `includeSymlinkTargets` | boolean | no | Resolve symlink targets. Default: `false`
|
|
590
|
-
| `cursor` | string | no | Pagination cursor from a previous response
|
|
602
|
+
| Parameter | Type | Required | Description |
|
|
603
|
+
| ----------------------- | ------- | -------- | --------------------------------------------------------------------------- |
|
|
604
|
+
| `path` | string | no | Base directory (default: root) |
|
|
605
|
+
| `includeHidden` | boolean | no | Include dotfiles. Default: `false` |
|
|
606
|
+
| `includeIgnored` | boolean | no | Include ignored items (node_modules, .git). Default: `false` |
|
|
607
|
+
| `maxDepth` | integer | no | Max recursion depth (1-50) when pattern is provided |
|
|
608
|
+
| `maxEntries` | integer | no | Max entries before truncation. Default: 20000, Max: 20000 |
|
|
609
|
+
| `sortBy` | enum | no | `name` \| `size` \| `modified` \| `type`. Default: `name` |
|
|
610
|
+
| `pattern` | string | no | Relative glob filter (e.g. `**/*.ts`). Absolute paths and `..` are rejected |
|
|
611
|
+
| `includeSymlinkTargets` | boolean | no | Resolve symlink targets. Default: `false` |
|
|
612
|
+
| `cursor` | string | no | Pagination cursor from a previous response |
|
|
591
613
|
|
|
592
614
|
---
|
|
593
615
|
|
|
@@ -595,16 +617,16 @@ List immediate directory contents: name, path, type, size, modified date.
|
|
|
595
617
|
|
|
596
618
|
Find files by glob pattern. Returns matching files with metadata.
|
|
597
619
|
|
|
598
|
-
| Parameter | Type | Required | Description
|
|
599
|
-
| ---------------- | ------- | -------- |
|
|
600
|
-
| `path` | string | no | Base directory (default: root)
|
|
601
|
-
| `pattern` | string | **yes** |
|
|
602
|
-
| `maxResults` | integer | no | Max results (1-
|
|
603
|
-
| `includeIgnored` | boolean | no | Include ignored items. Default: `false`
|
|
604
|
-
| `includeHidden` | boolean | no | Include dotfiles. Default: `false`
|
|
605
|
-
| `sortBy` | enum | no | `path` \| `name` \| `size` \| `modified`. Default: `path`
|
|
606
|
-
| `maxDepth` | integer | no | Max directory depth (0-
|
|
607
|
-
| `cursor` | string | no | Pagination cursor
|
|
620
|
+
| Parameter | Type | Required | Description |
|
|
621
|
+
| ---------------- | ------- | -------- | ---------------------------------------------------------------------------- |
|
|
622
|
+
| `path` | string | no | Base directory (default: root) |
|
|
623
|
+
| `pattern` | string | **yes** | Relative glob pattern (e.g. `**/*.ts`). Absolute paths and `..` are rejected |
|
|
624
|
+
| `maxResults` | integer | no | Max results (1-10000). Default: 100 |
|
|
625
|
+
| `includeIgnored` | boolean | no | Include ignored items. Default: `false` |
|
|
626
|
+
| `includeHidden` | boolean | no | Include dotfiles. Default: `false` |
|
|
627
|
+
| `sortBy` | enum | no | `path` \| `name` \| `size` \| `modified`. Default: `path` |
|
|
628
|
+
| `maxDepth` | integer | no | Max directory depth (0-100) |
|
|
629
|
+
| `cursor` | string | no | Pagination cursor |
|
|
608
630
|
|
|
609
631
|
---
|
|
610
632
|
|
|
@@ -615,8 +637,8 @@ Render a directory tree with bounded recursion. Returns ASCII tree + structured
|
|
|
615
637
|
| Parameter | Type | Required | Description |
|
|
616
638
|
| ---------------- | ------- | -------- | ---------------------------------------------------- |
|
|
617
639
|
| `path` | string | no | Base directory (default: root) |
|
|
618
|
-
| `maxDepth` | integer | no | Depth (0 = root node only). Default:
|
|
619
|
-
| `maxEntries` | integer | no | Max entries. Default:
|
|
640
|
+
| `maxDepth` | integer | no | Depth (0 = root node only). Default: 5, Max: 50 |
|
|
641
|
+
| `maxEntries` | integer | no | Max entries. Default: 1000, Max: 20000 |
|
|
620
642
|
| `includeHidden` | boolean | no | Include dotfiles. Default: `false` |
|
|
621
643
|
| `includeIgnored` | boolean | no | Include ignored items. Default: `false` |
|
|
622
644
|
| `includeSizes` | boolean | no | Include file sizes in tree entries. Default: `false` |
|
|
@@ -676,18 +698,18 @@ Get metadata for multiple files/directories in one request.
|
|
|
676
698
|
|
|
677
699
|
Search file contents (grep-like). Returns matching lines with optional context.
|
|
678
700
|
|
|
679
|
-
| Parameter | Type | Required | Description
|
|
680
|
-
| ---------------- | ------- | -------- |
|
|
681
|
-
| `path` | string | no | Base directory (default: root)
|
|
682
|
-
| `pattern` | string | **yes** | Search text or RE2 regex when `isRegex=true`
|
|
683
|
-
| `isRegex` | boolean | no | Treat pattern as RE2 regex. Default: `false`
|
|
684
|
-
| `caseSensitive` | boolean | no | Case-sensitive matching. Default: `false`
|
|
685
|
-
| `wholeWord` | boolean | no | Match whole words only. Default: `false`
|
|
686
|
-
| `contextLines` | integer | no | Lines of context before/after (0-50). Default: 0
|
|
687
|
-
| `maxResults` | integer | no | Max match rows (
|
|
688
|
-
| `filePattern` | string | no |
|
|
689
|
-
| `includeHidden` | boolean | no | Include dotfiles. Default: `false`
|
|
690
|
-
| `includeIgnored` | boolean | no | Include ignored items. Default: `false`
|
|
701
|
+
| Parameter | Type | Required | Description |
|
|
702
|
+
| ---------------- | ------- | -------- | ------------------------------------------------------------------- |
|
|
703
|
+
| `path` | string | no | Base directory (default: root) |
|
|
704
|
+
| `pattern` | string | **yes** | Search text or RE2 regex when `isRegex=true` |
|
|
705
|
+
| `isRegex` | boolean | no | Treat pattern as RE2 regex. Default: `false` |
|
|
706
|
+
| `caseSensitive` | boolean | no | Case-sensitive matching. Default: `false` |
|
|
707
|
+
| `wholeWord` | boolean | no | Match whole words only. Default: `false` |
|
|
708
|
+
| `contextLines` | integer | no | Lines of context before/after (0-50). Default: 0 |
|
|
709
|
+
| `maxResults` | integer | no | Max match rows (0-10000). Default: 500 |
|
|
710
|
+
| `filePattern` | string | no | Relative glob for candidate files (e.g. `**/*.ts`). Default: `**/*` |
|
|
711
|
+
| `includeHidden` | boolean | no | Include dotfiles. Default: `false` |
|
|
712
|
+
| `includeIgnored` | boolean | no | Include ignored items. Default: `false` |
|
|
691
713
|
|
|
692
714
|
---
|
|
693
715
|
|
|
@@ -720,7 +742,7 @@ Apply sequential literal string replacements (first occurrence per edit). Use `d
|
|
|
720
742
|
| Parameter | Type | Required | Description |
|
|
721
743
|
| ------------------ | ------- | -------- | ---------------------------------------------------------- |
|
|
722
744
|
| `path` | string | **yes** | Absolute path to file |
|
|
723
|
-
| `edits` | array | **yes** | List of `{oldText, newText}` replacements
|
|
745
|
+
| `edits` | array | **yes** | List of non-empty `{oldText, newText}` replacements |
|
|
724
746
|
| `dryRun` | boolean | no | Preview edits without writing. Default: `false` |
|
|
725
747
|
| `ignoreWhitespace` | boolean | no | Treat whitespace sequences as equivalent. Default: `false` |
|
|
726
748
|
|
|
@@ -792,19 +814,19 @@ Apply a unified diff patch to one or more files. Single-file: throws on failure.
|
|
|
792
814
|
|
|
793
815
|
Bulk search-and-replace across files matching a glob. Replaces **all** occurrences per file. Always `dryRun: true` first.
|
|
794
816
|
|
|
795
|
-
| Parameter | Type | Required | Description
|
|
796
|
-
| ---------------- | ------- | -------- |
|
|
797
|
-
| `path` | string | no | Base directory (default: root)
|
|
798
|
-
| `filePattern` | string |
|
|
799
|
-
| `searchPattern` | string | **yes** | Text to search. RE2 regex when `isRegex=true`
|
|
800
|
-
| `replacement` | string | **yes** | Replacement text. Supports `$1`, `$2` with regex
|
|
801
|
-
| `isRegex` | boolean | no | Treat as RE2 regex. Default: `false`
|
|
802
|
-
| `dryRun` | boolean | no | Preview matches with diff. Default: `false`
|
|
803
|
-
| `includeHidden` | boolean | no | Include dotfiles. Default: `false`
|
|
804
|
-
| `includeIgnored` | boolean | no | Include ignored items. Default: `false`
|
|
805
|
-
| `returnDiff` | boolean | no | Return diff even when not dry-run. Default: `false`
|
|
806
|
-
| `maxFiles` | integer | no | Max files to process before stopping (1-10000)
|
|
807
|
-
| `caseSensitive` | boolean | no | Case-sensitive matching. Default: `true`
|
|
817
|
+
| Parameter | Type | Required | Description |
|
|
818
|
+
| ---------------- | ------- | -------- | ------------------------------------------------------- |
|
|
819
|
+
| `path` | string | no | Base directory (default: root) |
|
|
820
|
+
| `filePattern` | string | no | Relative glob pattern (e.g. `**/*.ts`). Default: `**/*` |
|
|
821
|
+
| `searchPattern` | string | **yes** | Text to search. RE2 regex when `isRegex=true` |
|
|
822
|
+
| `replacement` | string | **yes** | Replacement text. Supports `$1`, `$2` with regex |
|
|
823
|
+
| `isRegex` | boolean | no | Treat as RE2 regex. Default: `false` |
|
|
824
|
+
| `dryRun` | boolean | no | Preview matches with diff. Default: `false` |
|
|
825
|
+
| `includeHidden` | boolean | no | Include dotfiles. Default: `false` |
|
|
826
|
+
| `includeIgnored` | boolean | no | Include ignored items. Default: `false` |
|
|
827
|
+
| `returnDiff` | boolean | no | Return diff even when not dry-run. Default: `false` |
|
|
828
|
+
| `maxFiles` | integer | no | Max files to process before stopping (1-10000) |
|
|
829
|
+
| `caseSensitive` | boolean | no | Case-sensitive matching. Default: `true` |
|
|
808
830
|
|
|
809
831
|
### Resources
|
|
810
832
|
|
|
@@ -849,21 +871,69 @@ Bulk search-and-replace across files matching a glob. Replaces **all** occurrenc
|
|
|
849
871
|
|
|
850
872
|
All 18 tools define `outputSchema` (Zod -> JSON Schema) and return `structuredContent` alongside text `content`. Set `FS_CONTEXT_STRIP_STRUCTURED=true` to strip output schemas from tool definitions (reduces token usage for LLMs that don't use structured output).
|
|
851
873
|
|
|
874
|
+
[↑ Back to top](#table-of-contents)
|
|
875
|
+
|
|
852
876
|
## Configuration
|
|
853
877
|
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
|
857
|
-
|
|
|
858
|
-
| `
|
|
859
|
-
| `
|
|
860
|
-
| `
|
|
861
|
-
| `
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
|
866
|
-
|
|
|
878
|
+
### HTTP & Auth
|
|
879
|
+
|
|
880
|
+
| Variable | Default | Description |
|
|
881
|
+
| ---------------------------------- | ---------------- | -------------------------------------------------------------- |
|
|
882
|
+
| `FILESYSTEM_MCP_API_KEY` | _(none)_ | Bearer token required when binding HTTP to a non-loopback host |
|
|
883
|
+
| `FILESYSTEM_MCP_MAX_HTTP_SESSIONS` | `100` | Max concurrent HTTP sessions (1-10,000) |
|
|
884
|
+
| `FILESYSTEM_MCP_HTTP_HOST` | `127.0.0.1` | HTTP server bind address |
|
|
885
|
+
| `FS_CONTEXT_MAX_REQUEST_BYTES` | `4194304` (4 MB) | Max HTTP request body size (1 KB - 256 MB) |
|
|
886
|
+
|
|
887
|
+
### File Size Limits
|
|
888
|
+
|
|
889
|
+
| Variable | Default | Description |
|
|
890
|
+
| -------------------------- | ------------------ | ------------------------------------------------------------- |
|
|
891
|
+
| `MAX_FILE_SIZE` | `10485760` (10 MB) | Max file size for text read operations (1 MB - 100 MB) |
|
|
892
|
+
| `MAX_SEARCH_SIZE` | `1048576` (1 MB) | Max file size for content search/grep (100 KB - 10 MB) |
|
|
893
|
+
| `MAX_READ_MANY_TOTAL_SIZE` | `524288` (512 KB) | Max cumulative size for `read_many` requests (10 KB - 100 MB) |
|
|
894
|
+
| `DEFAULT_SEARCH_TIMEOUT` | `5000` | Search operation timeout in ms (100 - 60,000) |
|
|
895
|
+
|
|
896
|
+
### Access Control
|
|
897
|
+
|
|
898
|
+
| Variable | Default | Description |
|
|
899
|
+
| ---------------------------- | -------- | ------------------------------------------------------------------------------- |
|
|
900
|
+
| `FS_CONTEXT_ALLOW_SENSITIVE` | `false` | Allow reading sensitive files (.env, .key, credentials, tokens) |
|
|
901
|
+
| `FS_CONTEXT_DENYLIST` | _(none)_ | CSV/newline-separated glob patterns to block (in addition to built-in denylist) |
|
|
902
|
+
| `FS_CONTEXT_ALLOWLIST` | _(none)_ | CSV/newline-separated glob patterns to permit (overrides denylist) |
|
|
903
|
+
|
|
904
|
+
### Output & Inline Limits
|
|
905
|
+
|
|
906
|
+
| Variable | Default | Description |
|
|
907
|
+
| ------------------------------- | ------- | ------------------------------------------------------------------------------ |
|
|
908
|
+
| `FS_CONTEXT_MAX_INLINE_CHARS` | `20000` | Max inline result chars before externalizing to `filesystem-mcp://result/{id}` |
|
|
909
|
+
| `FS_CONTEXT_MAX_INLINE_MATCHES` | `50` | Max inline search matches before truncation |
|
|
910
|
+
| `FS_CONTEXT_STRIP_STRUCTURED` | `false` | Strip `outputSchema` from tool definitions (reduces tokens) |
|
|
911
|
+
|
|
912
|
+
### Tasks
|
|
913
|
+
|
|
914
|
+
| Variable | Default | Description |
|
|
915
|
+
| ------------------------------------- | ---------------- | ----------------------------------------------- |
|
|
916
|
+
| `FILESYSTEM_MCP_MAX_TASK_TTL_MS` | `3600000` (1 hr) | Max task TTL before auto-eviction (1 s - 24 hr) |
|
|
917
|
+
| `FILESYSTEM_MCP_MAX_CONCURRENT_TASKS` | `100` | Max simultaneous task executions (1-10,000) |
|
|
918
|
+
|
|
919
|
+
### Logging & Diagnostics
|
|
920
|
+
|
|
921
|
+
| Variable | Default | Description |
|
|
922
|
+
| --------------------------------- | ------- | ------------------------------------------------------------------------------ |
|
|
923
|
+
| `FILESYSTEM_MCP_LOG_LEVEL` | `info` | MCP log level: debug, info, notice, warning, error, critical, alert, emergency |
|
|
924
|
+
| `FS_CONTEXT_DIAGNOSTICS` | `false` | Enable diagnostic logging |
|
|
925
|
+
| `FS_CONTEXT_DIAGNOSTICS_DETAIL` | `false` | Enable detailed diagnostic output |
|
|
926
|
+
| `FS_CONTEXT_TOOL_LOG_ERRORS` | `false` | Log tool errors to stderr |
|
|
927
|
+
| `FS_CONTEXT_SEARCH_WORKERS_DEBUG` | `false` | Debug logging for search worker pool |
|
|
928
|
+
|
|
929
|
+
### Performance
|
|
930
|
+
|
|
931
|
+
| Variable | Default | Description |
|
|
932
|
+
| ------------------------------- | ---------------- | ---------------------------------------- |
|
|
933
|
+
| `FS_CONTEXT_SEARCH_WORKERS` | CPU cores (≤ 8) | Concurrent search worker threads (1-16) |
|
|
934
|
+
| `FS_CONTEXT_LIST_CURSOR_TTL_MS` | `300000` (5 min) | Cursor TTL for `ls` pagination snapshots |
|
|
935
|
+
|
|
936
|
+
[↑ Back to top](#table-of-contents)
|
|
867
937
|
|
|
868
938
|
## HTTP Endpoints
|
|
869
939
|
|
|
@@ -898,6 +968,8 @@ When started with `--port <number>`, the server exposes a single MCP endpoint:
|
|
|
898
968
|
| Request body limit | confirmed | `src/server/bootstrap.ts` — configurable max request size (413 on overflow) |
|
|
899
969
|
| Remote bind guard | confirmed | `src/server/bootstrap.ts` — refuses non-loopback bind without `FILESYSTEM_MCP_API_KEY` |
|
|
900
970
|
|
|
971
|
+
[↑ Back to top](#table-of-contents)
|
|
972
|
+
|
|
901
973
|
## Development
|
|
902
974
|
|
|
903
975
|
- `dev` — `tsc --watch --preserveWatchOutput` — Watch mode TypeScript compilation
|
|
@@ -935,10 +1007,10 @@ When started with `--port <number>`, the server exposes a single MCP endpoint:
|
|
|
935
1007
|
| [diff](https://www.npmjs.com/package/diff) | Unified diff generation and patch application |
|
|
936
1008
|
| [ignore](https://www.npmjs.com/package/ignore) | `.gitignore` pattern matching |
|
|
937
1009
|
| [re2](https://www.npmjs.com/package/re2) | Safe RE2 regex engine (no ReDoS) |
|
|
938
|
-
| [safe-regex2](https://www.npmjs.com/package/safe-regex2) | Regex safety validation |
|
|
939
1010
|
| [zod](https://www.npmjs.com/package/zod) | Schema validation and JSON Schema generation |
|
|
940
1011
|
|
|
941
|
-
##
|
|
1012
|
+
## License
|
|
1013
|
+
|
|
1014
|
+
MIT License. See [LICENSE](LICENSE) for details.
|
|
942
1015
|
|
|
943
|
-
|
|
944
|
-
- Contributions welcome via pull requests.
|
|
1016
|
+
[↑ Back to top](#table-of-contents)
|
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { stat } from 'node:fs/promises';
|
|
2
2
|
import { getSystemErrorMessage, getSystemErrorName } from 'node:util';
|
|
3
3
|
import { Command, CommanderError, InvalidArgumentError } from 'commander';
|
|
4
4
|
import { processInParallel } from './lib/fs-helpers.js';
|
|
@@ -83,7 +83,7 @@ function assertDirectory(stats, inputPath) {
|
|
|
83
83
|
async function validateDirectoryPath(inputPath) {
|
|
84
84
|
const normalized = normalizePath(inputPath);
|
|
85
85
|
try {
|
|
86
|
-
const stats = await
|
|
86
|
+
const stats = await stat(normalized);
|
|
87
87
|
assertDirectory(stats, inputPath);
|
|
88
88
|
return normalized;
|
|
89
89
|
}
|
package/dist/completions.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import * as fs from 'node:fs/promises';
|
|
2
|
-
import * as path from 'node:path';
|
|
3
1
|
import { CompleteRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
import { readdir, stat } from 'node:fs/promises';
|
|
3
|
+
import { basename, dirname, isAbsolute, join, parse, resolve, sep, } from 'node:path';
|
|
4
4
|
import { getAllowedDirectories, isPathWithinDirectories, normalizePath, toPosixPath, } from './lib/paths.js';
|
|
5
5
|
import { isRecord } from './lib/utils.js';
|
|
6
6
|
import { getSortedToolContracts } from './resources/tool-info.js';
|
|
@@ -191,21 +191,21 @@ function rememberCompletionCacheValue(cache, key, value) {
|
|
|
191
191
|
}
|
|
192
192
|
}
|
|
193
193
|
function hasTrailingSeparator(value) {
|
|
194
|
-
return
|
|
194
|
+
return value.endsWith(sep) || value.endsWith('/') || value.endsWith('\\');
|
|
195
195
|
}
|
|
196
196
|
function isAbsolutePathInput(value) {
|
|
197
|
-
return (
|
|
197
|
+
return (isAbsolute(value) ||
|
|
198
198
|
/^[A-Za-z]:[\\/]/u.test(value) ||
|
|
199
199
|
value.startsWith('\\\\'));
|
|
200
200
|
}
|
|
201
201
|
function resolveFromBase(base, rawValue, trailingSeparator) {
|
|
202
|
-
const normalizedValue = normalizePath(
|
|
202
|
+
const normalizedValue = normalizePath(resolve(base, rawValue));
|
|
203
203
|
if (trailingSeparator) {
|
|
204
204
|
return { searchDir: normalizedValue, prefix: '' };
|
|
205
205
|
}
|
|
206
206
|
return {
|
|
207
|
-
searchDir:
|
|
208
|
-
prefix:
|
|
207
|
+
searchDir: dirname(normalizedValue),
|
|
208
|
+
prefix: basename(normalizedValue),
|
|
209
209
|
};
|
|
210
210
|
}
|
|
211
211
|
function resolveNamedRootContext(currentValue, allowed) {
|
|
@@ -225,18 +225,18 @@ function resolveNamedRootPath(value, allowed) {
|
|
|
225
225
|
const root = findAllowedRootByName(parsed.rootName, allowed);
|
|
226
226
|
if (!root)
|
|
227
227
|
return undefined;
|
|
228
|
-
return normalizePath(
|
|
228
|
+
return normalizePath(resolve(root, parsed.remainder));
|
|
229
229
|
}
|
|
230
230
|
function parseNamedRootInput(value) {
|
|
231
231
|
const normalizedInput = toPosixPath(value);
|
|
232
232
|
const [rootName, ...rest] = normalizedInput.split('/');
|
|
233
233
|
if (!rootName)
|
|
234
234
|
return undefined;
|
|
235
|
-
return { rootName, remainder: rest.join(
|
|
235
|
+
return { rootName, remainder: rest.join(sep) };
|
|
236
236
|
}
|
|
237
237
|
function findAllowedRootByName(rootName, allowed) {
|
|
238
238
|
const normalizedRootName = rootName.toLowerCase();
|
|
239
|
-
return allowed.find((candidate) =>
|
|
239
|
+
return allowed.find((candidate) => basename(candidate).toLowerCase() === normalizedRootName);
|
|
240
240
|
}
|
|
241
241
|
function chooseContextKeys(argumentName) {
|
|
242
242
|
const normalized = argumentName.toLowerCase();
|
|
@@ -263,7 +263,7 @@ function resolveContextCandidatePath(candidate, allowed) {
|
|
|
263
263
|
const base = allowed[0];
|
|
264
264
|
if (!base)
|
|
265
265
|
return undefined;
|
|
266
|
-
return normalizePath(
|
|
266
|
+
return normalizePath(resolve(base, candidate));
|
|
267
267
|
}
|
|
268
268
|
return resolveNamedRootPath(candidate, allowed);
|
|
269
269
|
}
|
|
@@ -271,14 +271,14 @@ async function toAllowedContextDirectory(resolved, allowed) {
|
|
|
271
271
|
if (!isPathWithinDirectories(resolved, allowed))
|
|
272
272
|
return undefined;
|
|
273
273
|
try {
|
|
274
|
-
const stats = await
|
|
274
|
+
const stats = await stat(resolved);
|
|
275
275
|
if (stats.isDirectory())
|
|
276
276
|
return resolved;
|
|
277
277
|
}
|
|
278
278
|
catch {
|
|
279
279
|
// Fall back to parent path best-effort resolution.
|
|
280
280
|
}
|
|
281
|
-
const parent =
|
|
281
|
+
const parent = dirname(resolved);
|
|
282
282
|
return isPathWithinDirectories(parent, allowed) ? parent : undefined;
|
|
283
283
|
}
|
|
284
284
|
async function resolveContextBaseDirectory(argumentName, contextArguments, allowed) {
|
|
@@ -300,7 +300,7 @@ async function resolveContextBaseDirectory(argumentName, contextArguments, allow
|
|
|
300
300
|
return undefined;
|
|
301
301
|
}
|
|
302
302
|
function withDirectorySeparator(value) {
|
|
303
|
-
return value.endsWith(
|
|
303
|
+
return value.endsWith(sep) ? value : `${value}${sep}`;
|
|
304
304
|
}
|
|
305
305
|
function buildCompletionResult(values) {
|
|
306
306
|
return {
|
|
@@ -314,8 +314,8 @@ function buildCompletionResponse(result) {
|
|
|
314
314
|
}
|
|
315
315
|
function sortCompletionMatches(matches) {
|
|
316
316
|
matches.sort((left, right) => {
|
|
317
|
-
const leftIsDir = left.endsWith(
|
|
318
|
-
const rightIsDir = right.endsWith(
|
|
317
|
+
const leftIsDir = left.endsWith(sep);
|
|
318
|
+
const rightIsDir = right.endsWith(sep);
|
|
319
319
|
if (leftIsDir && !rightIsDir)
|
|
320
320
|
return -1;
|
|
321
321
|
if (!leftIsDir && rightIsDir)
|
|
@@ -350,7 +350,7 @@ function collectAllowedRoots(allowed, predicate) {
|
|
|
350
350
|
function getSearchContext(currentValue, allowed, contextBase) {
|
|
351
351
|
const trailingSeparator = hasTrailingSeparator(currentValue);
|
|
352
352
|
if (isAbsolutePathInput(currentValue)) {
|
|
353
|
-
return resolveFromBase(
|
|
353
|
+
return resolveFromBase(parse(currentValue).root || sep, currentValue, trailingSeparator);
|
|
354
354
|
}
|
|
355
355
|
const namedRootContext = resolveNamedRootContext(currentValue, allowed);
|
|
356
356
|
if (namedRootContext) {
|
|
@@ -376,13 +376,13 @@ async function findMatchesInDirectory(searchDir, prefix, allowed) {
|
|
|
376
376
|
return matches;
|
|
377
377
|
}
|
|
378
378
|
try {
|
|
379
|
-
const entries = await
|
|
379
|
+
const entries = await readdir(searchDir, { withFileTypes: true });
|
|
380
380
|
const lowerPrefix = prefix.toLowerCase();
|
|
381
381
|
for (const entry of entries) {
|
|
382
382
|
if (entry.name.toLowerCase().startsWith(lowerPrefix)) {
|
|
383
|
-
const fullPath =
|
|
383
|
+
const fullPath = join(searchDir, entry.name);
|
|
384
384
|
const isDir = entry.isDirectory();
|
|
385
|
-
matches.push(isDir ? `${fullPath}${
|
|
385
|
+
matches.push(isDir ? `${fullPath}${sep}` : fullPath);
|
|
386
386
|
}
|
|
387
387
|
}
|
|
388
388
|
}
|
|
@@ -396,17 +396,17 @@ function findRootPrefixMatches(currentValue, allowed) {
|
|
|
396
396
|
if (!rootPrefix) {
|
|
397
397
|
return collectAllowedRoots(allowed, () => true);
|
|
398
398
|
}
|
|
399
|
-
return collectAllowedRoots(allowed, (root) =>
|
|
399
|
+
return collectAllowedRoots(allowed, (root) => basename(root).toLowerCase().startsWith(rootPrefix));
|
|
400
400
|
}
|
|
401
401
|
function findMatchingRoots(searchDir, prefix, allowed) {
|
|
402
402
|
const lowerPrefix = prefix.toLowerCase();
|
|
403
403
|
const normalizedSearchDir = normalizePath(searchDir);
|
|
404
404
|
return collectAllowedRoots(allowed, (root) => {
|
|
405
|
-
const rootDir =
|
|
405
|
+
const rootDir = dirname(root);
|
|
406
406
|
// Check if root is a direct child of searchDir
|
|
407
407
|
if (normalizePath(rootDir) !== normalizedSearchDir)
|
|
408
408
|
return false;
|
|
409
|
-
return
|
|
409
|
+
return basename(root).toLowerCase().startsWith(lowerPrefix);
|
|
410
410
|
});
|
|
411
411
|
}
|
|
412
412
|
async function getPathCompletions(currentValue, options = {}) {
|
|
@@ -430,6 +430,34 @@ async function getPathCompletions(currentValue, options = {}) {
|
|
|
430
430
|
return { values: [] };
|
|
431
431
|
}
|
|
432
432
|
}
|
|
433
|
+
function handleTopicAndToolCompletions(ref, argName, argumentValue, topicValues, toolNameValues) {
|
|
434
|
+
if (!isRecord(ref))
|
|
435
|
+
return undefined;
|
|
436
|
+
const currentValue = argumentValue.toLowerCase();
|
|
437
|
+
if (ref['type'] === 'ref/prompt' && argName === 'topic') {
|
|
438
|
+
const filtered = currentValue
|
|
439
|
+
? topicValues.filter((v) => v.startsWith(currentValue))
|
|
440
|
+
: topicValues;
|
|
441
|
+
return buildCompletionResponse(buildCompletionResult(filtered));
|
|
442
|
+
}
|
|
443
|
+
if (ref['type'] === 'ref/prompt' &&
|
|
444
|
+
ref['name'] === 'get-tool-help' &&
|
|
445
|
+
argName === 'name') {
|
|
446
|
+
const filtered = currentValue
|
|
447
|
+
? toolNameValues.filter((value) => value.startsWith(currentValue))
|
|
448
|
+
: toolNameValues;
|
|
449
|
+
return buildCompletionResponse(buildCompletionResult(filtered));
|
|
450
|
+
}
|
|
451
|
+
if (ref['type'] === 'ref/resource' &&
|
|
452
|
+
ref['uri'] === 'internal://tool-info/{name}' &&
|
|
453
|
+
argName === 'name') {
|
|
454
|
+
const filtered = currentValue
|
|
455
|
+
? toolNameValues.filter((value) => value.startsWith(currentValue))
|
|
456
|
+
: toolNameValues;
|
|
457
|
+
return buildCompletionResponse(buildCompletionResult(filtered));
|
|
458
|
+
}
|
|
459
|
+
return undefined;
|
|
460
|
+
}
|
|
433
461
|
export function registerCompletions(server, instructions = '') {
|
|
434
462
|
const topicValues = extractTopicCompletions(instructions);
|
|
435
463
|
const toolNameValues = extractToolNameCompletions();
|
|
@@ -437,34 +465,9 @@ export function registerCompletions(server, instructions = '') {
|
|
|
437
465
|
const { params } = request;
|
|
438
466
|
const { argument, ref } = params;
|
|
439
467
|
const argName = argument.name.toLowerCase();
|
|
440
|
-
|
|
441
|
-
if (
|
|
442
|
-
|
|
443
|
-
const filtered = currentValue
|
|
444
|
-
? topicValues.filter((v) => v.startsWith(currentValue))
|
|
445
|
-
: topicValues;
|
|
446
|
-
return buildCompletionResponse(buildCompletionResult(filtered));
|
|
447
|
-
}
|
|
448
|
-
if (isRecord(ref) &&
|
|
449
|
-
ref['type'] === 'ref/prompt' &&
|
|
450
|
-
ref['name'] === 'get-tool-help' &&
|
|
451
|
-
argName === 'name') {
|
|
452
|
-
const currentValue = argument.value.toLowerCase();
|
|
453
|
-
const filtered = currentValue
|
|
454
|
-
? toolNameValues.filter((value) => value.startsWith(currentValue))
|
|
455
|
-
: toolNameValues;
|
|
456
|
-
return buildCompletionResponse(buildCompletionResult(filtered));
|
|
457
|
-
}
|
|
458
|
-
if (isRecord(ref) &&
|
|
459
|
-
ref['type'] === 'ref/resource' &&
|
|
460
|
-
ref['uri'] === 'internal://tool-info/{name}' &&
|
|
461
|
-
argName === 'name') {
|
|
462
|
-
const currentValue = argument.value.toLowerCase();
|
|
463
|
-
const filtered = currentValue
|
|
464
|
-
? toolNameValues.filter((value) => value.startsWith(currentValue))
|
|
465
|
-
: toolNameValues;
|
|
466
|
-
return buildCompletionResponse(buildCompletionResult(filtered));
|
|
467
|
-
}
|
|
468
|
+
const predef = handleTopicAndToolCompletions(ref, argName, argument.value, topicValues, toolNameValues);
|
|
469
|
+
if (predef)
|
|
470
|
+
return predef;
|
|
468
471
|
const enumResult = getEnumCompletions(argName, argument.value);
|
|
469
472
|
if (enumResult) {
|
|
470
473
|
return buildCompletionResponse(enumResult);
|
package/dist/config.d.ts
CHANGED
|
@@ -83,7 +83,7 @@ export interface SearchContentResult {
|
|
|
83
83
|
export interface MultipleFileInfoResult {
|
|
84
84
|
readonly path: string;
|
|
85
85
|
readonly info?: FileInfo;
|
|
86
|
-
readonly error?:
|
|
86
|
+
readonly error?: Error;
|
|
87
87
|
}
|
|
88
88
|
export interface GetMultipleFileInfoResult {
|
|
89
89
|
readonly results: readonly MultipleFileInfoResult[];
|
|
@@ -95,18 +95,18 @@ export interface GetMultipleFileInfoResult {
|
|
|
95
95
|
};
|
|
96
96
|
}
|
|
97
97
|
export declare const ErrorCode: {
|
|
98
|
-
readonly
|
|
99
|
-
readonly
|
|
100
|
-
readonly
|
|
101
|
-
readonly
|
|
102
|
-
readonly
|
|
103
|
-
readonly
|
|
104
|
-
readonly
|
|
105
|
-
readonly
|
|
106
|
-
readonly
|
|
107
|
-
readonly
|
|
108
|
-
readonly
|
|
109
|
-
readonly
|
|
98
|
+
readonly ACCESS_DENIED: "ACCESS_DENIED";
|
|
99
|
+
readonly NOT_FOUND: "NOT_FOUND";
|
|
100
|
+
readonly NOT_FILE: "NOT_FILE";
|
|
101
|
+
readonly NOT_DIRECTORY: "NOT_DIRECTORY";
|
|
102
|
+
readonly TOO_LARGE: "TOO_LARGE";
|
|
103
|
+
readonly TIMEOUT: "TIMEOUT";
|
|
104
|
+
readonly CANCELLED: "CANCELLED";
|
|
105
|
+
readonly INVALID_PATTERN: "INVALID_PATTERN";
|
|
106
|
+
readonly INVALID_INPUT: "INVALID_INPUT";
|
|
107
|
+
readonly PERMISSION_DENIED: "PERMISSION_DENIED";
|
|
108
|
+
readonly SYMLINK_NOT_ALLOWED: "SYMLINK_NOT_ALLOWED";
|
|
109
|
+
readonly UNKNOWN: "UNKNOWN";
|
|
110
110
|
};
|
|
111
111
|
export type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];
|
|
112
112
|
export declare function formatBytes(bytes: number): string;
|