Skip to content

MCP Server for interacting with text-based files (read & write). Written in TypeScript, Node and Hono.dev

Notifications You must be signed in to change notification settings

iceener/files-stdio-mcp-server

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Files MCP Server

Stdio MCP server for sandboxed file access — explore directories, read files, search content, and safely edit with checksum verification.

Author: overment

Warning

This server provides filesystem access to an AI agent. While it's sandboxed to specific directories, always:

  • Review tool outputs before confirming changes
  • Use dryRun=true to preview destructive operations
  • Keep backups of important files
  • Set FS_ROOTS to only the directories you want the agent to access

Motivation

Traditional file operations require precise paths and exact content — things LLMs struggle with. This server is designed so AI agents can:

  • Explore first — understand directory structure before acting
  • Find by name or content — locate files without knowing exact paths
  • Edit safely — checksum verification prevents stale overwrites
  • Preview changes — dry-run mode shows diffs before applying
  • Recover from errors — hints guide the agent to correct mistakes

The result: an agent that can reliably manage your Obsidian vault, documentation, notes, or any text-based file collection.

Features

  • Directory Exploration — tree view with file counts, sizes, timestamps
  • File Reading — line-numbered content with checksums for safe editing
  • Find Files — recursive search by filename patterns (*.md, config.json)
  • Content Search — literal, regex, fuzzy pattern matching with optional case-insensitivity
  • Preset Patterns — built-in Obsidian patterns (wikilinks, tags, tasks, headings)
  • Safe Editing — checksum verification, dry-run preview, unified diffs
  • Batch OperationsreplaceAll for bulk renames across a file
  • Multi-Mount Support — access multiple directories as virtual mount points
  • Sandboxed — cannot access paths outside configured mounts

Design Principles

  • Explore before edit: Agent must read a file before modifying it (gets checksum + line numbers)
  • Preview before apply: dryRun=true shows exactly what would change
  • Clear feedback: Every response includes hints for next steps and error recovery
  • Compact by default: File details (size, modified) only shown when details=true
  • Single mount optimization: When one mount is configured, fs_read(".") shows contents directly

Quick Start

1. Install

cd files-mcp
bun install

2. Configure

Create .env:

# Directories the agent can access (comma-separated)
FS_ROOTS=/path/to/vault,/path/to/docs

# Or for a single directory:
# FS_ROOT=/path/to/vault

# Optional
LOG_LEVEL=info
MAX_FILE_SIZE=1048576

3. Run

bun dev

4. Connect to Client

Claude Desktop / Cursor:

{
  "mcpServers": {
    "filesystem": {
      "command": "bun",
      "args": ["run", "/absolute/path/to/files-mcp/src/index.ts"],
      "env": {
        "FS_ROOTS": "/Users/you/vault,/Users/you/docs"
      }
    }
  }
}

MCP Bundle (MCPB)

This server is also available as an MCP Bundle (.mcpb) for one-click installation in supported apps like Claude Desktop, Alice, and other MCPB-compatible applications.

What is MCPB?

MCP Bundles are zip archives containing a local MCP server and a manifest.json that describes the server and its capabilities. The format enables end users to install local MCP servers with a single click — no manual configuration required.

Installing from MCPB

  1. Download the files-mcp.mcpb file
  2. Open it with a compatible app (Claude Desktop, Alice, etc.)
  3. Configure the Root Directory when prompted — this is the directory the agent will have access to
  4. Done! The server is installed and ready to use

manifest.json

The manifest defines:

  • Server configuration — command, args, environment variables
  • Toolsfs_read and fs_write with descriptions
  • User config — prompts for FS_ROOT directory during installation
{
  "manifest_version": "0.2",
  "name": "files-mcp",
  "version": "1.0.0",
  "server": {
    "type": "node",
    "entry_point": "dist/index.js",
    "mcp_config": {
      "command": "node",
      "args": ["${__dirname}/dist/index.js"],
      "env": {
        "FS_ROOT": "${user_config.FS_ROOT}"
      }
    }
  },
  "user_config": {
    "FS_ROOT": {
      "type": "directory",
      "title": "Root Directory",
      "description": "The directory the agent will have access to.",
      "required": true
    }
  }
}

The ${user_config.FS_ROOT} syntax injects the user-selected directory into the server's environment at runtime.


Server Instructions (What the Model Sees)

🔒 SANDBOXED FILESYSTEM — This tool can ONLY access specific mounted directories.
   You CANNOT access arbitrary system paths like /Users or C:\.
   Always start with fs_read(".") to see available mounts.

⚠️ ALWAYS read a file BEFORE answering questions about its content.
⚠️ ALWAYS read a file BEFORE modifying it (you need the checksum).

MANDATORY WORKFLOW:
1. fs_read(".") → see available mounts
2. fs_read("path/file.md") → get content + checksum
3. fs_write with dryRun=true → preview diff
4. fs_write with dryRun=false + checksum → apply change
5. Verify diff in response matches your intent

Tools

fs_read

Explore directories, read files, find files by name, or search content.

Input:

{
  path: string;                    // "." for root, "docs/", "notes/todo.md"
  
  // Finding files by name
  find?: string;                   // "*.md", "config.json"
  
  // Searching content
  pattern?: string;                // Text to search for
  patternMode?: "literal" | "regex" | "fuzzy";
  caseInsensitive?: boolean;        // Ignore case when matching
  preset?: "wikilinks" | "tags" | "tasks" | "tasks_open" | "tasks_done" 
         | "headings" | "codeblocks" | "frontmatter";
  
  // Options
  depth?: number;                  // Directory traversal depth (default 3)
  details?: boolean;               // Include size/modified (default false)
  lines?: string;                  // "10-50" for partial read
  context?: number;                // Lines around matches (default 3)
  maxFiles?: number;               // Max files with matches to return (default: no limit)
  maxMatches?: number;             // Limit matches (default 100)
}

Output:

{
  success: boolean;
  path: string;
  type: "directory" | "file" | "search";
  
  // For directories
  tree?: {
    entries: Array<{ path, kind, children?, size?, modified? }>;
    summary: string;
  };
  
  // For files
  content?: {
    text: string;        // With line numbers
    checksum: string;    // Pass to fs_write
    totalLines: number;
  };
  
  // For searches
  matches?: Array<{ file, line, column, text, context }>;
  matchCount?: number;
  
  hint: string;          // Next action suggestion
}

fs_write

Create, modify, or delete files with safety features.

Input:

{
  path: string;
  operation: "create" | "update" | "delete";
  
  // For create
  content?: string;
  
  // For update — target by lines OR pattern
  lines?: string;                  // "10-15" — PREFERRED
  pattern?: string;                // Text to find
  patternMode?: "literal" | "regex" | "fuzzy";
  caseInsensitive?: boolean;       // Ignore case
  
  // For update — action
  action?: "replace" | "insert_before" | "insert_after" | "delete_lines";
  content?: string;                // New content
  
  // Batch replace
  replaceAll?: boolean;            // Replace ALL occurrences
  
  // Safety
  checksum?: string;               // From fs_read — RECOMMENDED
  dryRun?: boolean;                // Preview only (default false)
}

Output:

{
  success: boolean;
  path: string;
  operation: "create" | "update" | "delete";
  applied: boolean;
  
  result?: {
    action: string;
    linesAffected?: number;
    newChecksum?: string;
    diff?: string;               // Unified diff
  };
  
  error?: {
    code: string;
    message: string;
    recoveryHint?: string;
  };
  
  hint: string;
}

Preset Patterns (Obsidian/Markdown)

Built-in patterns for common searches:

Preset Finds
wikilinks [[Note]] and [[Note|Display]]
tags #tag and #nested/tag
tasks - [ ] and - [x] (all tasks)
tasks_open - [ ] only (incomplete)
tasks_done - [x] only (completed)
headings # through ######
codeblocks ``` code blocks
frontmatter YAML --- blocks

Example:

{ "path": ".", "preset": "tasks_open" }

Examples

1. Explore the vault

{ "path": "." }

Response:

18 items (15 files, 3 directories)

- Core/
- Projects/
- Books/
- map.md
- inbox.md
...

hint: "Showing contents of 'vault'. Use fs_read on any path to explore deeper."

2. Find a file

{ "path": ".", "find": "*.md" }

Response:

Found 42 item(s) matching "*.md"

- Core/Values.md
- Core/Process.md
- Projects/Alice.md
...

hint: "Found 42 matching files. Use fs_read on a specific path to see its content."

3. Read a file

{ "path": "Core/Values.md" }

Response:

File read complete. Checksum: a1b2c3d4e5f6.

   1| # Values
   2|
   3| ## Integrity
   4| Be honest, even when it's hard.
   5|
   6| ## Growth
   7| Learn something new every day.
...

hint: "To edit this file, use fs_write with checksum a1b2c3d4e5f6."

4. Find all incomplete tasks

{ "path": ".", "preset": "tasks_open" }

Response:

Found 7 matches in 42 files across all mounts.

- Projects/Alice.md:12 — "- [ ] Implement search"
- Projects/Alice.md:15 — "- [ ] Add tests"
- inbox.md:3 — "- [ ] Review PR"
...

5. Replace text (preview first)

{
  "path": "Core/Values.md",
  "operation": "update",
  "pattern": "Be honest",
  "action": "replace",
  "content": "Act with integrity",
  "checksum": "a1b2c3d4e5f6",
  "dryRun": true
}

Response:

DRY RUN — no changes applied.

--- a/Core/Values.md
+++ b/Core/Values.md
@@ -3,1 +3,1 @@
-Be honest, even when it's hard.
+Act with integrity, even when it's hard.

hint: "Review the diff above. Run with dryRun=false to apply."

6. Bulk rename wikilinks

{
  "path": "Projects/Alice.md",
  "operation": "update",
  "pattern": "[[Old Name]]",
  "action": "replace",
  "content": "[[New Name]]",
  "replaceAll": true,
  "dryRun": true
}

Response:

DRY RUN — would replace 3 occurrence(s) at lines 5, 12, 28.

7. Mark task as complete

{
  "path": "inbox.md",
  "operation": "update",
  "pattern": "- [ ] Review PR",
  "action": "replace",
  "content": "- [x] Review PR",
  "checksum": "xyz789"
}

Response:

replaced 1 line(s). New checksum: abc123.

hint: "The diff above shows what changed."

Configuration

Variable Default Description
FS_ROOTS . Comma-separated paths the agent can access
FS_ROOT . Single path (backward compatibility)
MCP_NAME files-mcp Server name
MCP_VERSION 1.0.0 Server version
LOG_LEVEL info Log level: debug, info, warning, error
MAX_FILE_SIZE 1048576 Max file size in bytes (1MB)

Multi-Mount Setup

Access multiple directories:

FS_ROOTS=/Users/me/vault,/Users/me/projects,/Users/me/notes

Each path becomes a mount named after its folder:

  • vault//Users/me/vault
  • projects//Users/me/projects
  • notes//Users/me/notes

Client Configuration

Claude Desktop:

{
  "mcpServers": {
    "filesystem": {
      "command": "bun",
      "args": ["run", "/path/to/files-mcp/src/index.ts"],
      "env": {
        "FS_ROOTS": "/Users/me/vault"
      }
    }
  }
}

Cursor:

{
  "filesystem": {
    "command": "bun",
    "args": ["run", "/path/to/files-mcp/src/index.ts"],
    "env": {
      "FS_ROOTS": "/Users/me/vault"
    }
  }
}

Development

bun dev           # Start with hot reload
bun test          # Run tests
bun run typecheck # TypeScript check
bun run lint      # Lint code
bun run build     # Production build
bun run inspector # Test with MCP Inspector

Architecture

src/
├── index.ts              # Entry point: stdio transport
├── config/
│   ├── env.ts            # Environment config & mount parsing
│   └── metadata.ts       # Tool descriptions
├── core/
│   ├── capabilities.ts   # Server capabilities
│   └── mcp.ts            # McpServer builder
├── tools/
│   ├── index.ts          # Tool registration
│   ├── fs-read.tool.ts   # Read, explore, search
│   └── fs-write.tool.ts  # Create, update, delete
├── lib/
│   ├── checksum.ts       # SHA256 checksums
│   ├── diff.ts           # Unified diff generation
│   ├── filetypes.ts      # Text/binary detection
│   ├── ignore.ts         # .gitignore support
│   ├── lines.ts          # Line manipulation
│   ├── paths.ts          # Multi-mount path resolution
│   └── patterns.ts       # Pattern matching & presets
└── utils/
    ├── errors.ts         # Error utilities
    └── logger.ts         # Logging

Troubleshooting

Issue Solution
"SANDBOXED FILESYSTEM: Absolute paths not allowed" Use relative paths within mounts. Start with fs_read(".") to see available mounts.
"Path does not match any mount" Check FS_ROOTS is set correctly. Paths must start with a mount name (e.g., vault/notes.md).
"CHECKSUM_MISMATCH" File changed since you read it. Re-read with fs_read to get fresh content.
"PATTERN_NOT_FOUND" Pattern doesn't exist in file. Try patternMode="fuzzy" or read file first.
"MULTIPLE_MATCHES" Pattern matches multiple times. Use replaceAll=true or be more specific.
Binary file errors Only text files can be read/written. Check file extension.
Single mount still shows "docs" Restart the MCP server after changing FS_ROOTS.

License

MIT

About

MCP Server for interacting with text-based files (read & write). Written in TypeScript, Node and Hono.dev

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published