Skip to main content
Glama
yarenty

emperor-folder

emperor-folder

CI license

An MCP server confined to one folder. You name a directory (or a few); the model can list, read, search, and (if you allow it) write, edit and move files inside it, and cannot reach anything outside it: not with an absolute path, not with .., not through a symlink.

emperor-folder serve --root ~/notes            # read-only, Streamable HTTP on 127.0.0.1:8031/mcp
emperor-folder serve --root ~/notes --writable # also write, edit and move
emperor-folder serve --root ~/notes --stdio    # for desktop clients
emperor-folder serve --root notes=~/notes --root code=~/src/app   # two roots: notes/..., code/...

Why

Giving an agent "the filesystem" is the fastest way to give it your SSH keys. Most file tools check paths with string prefixes and then open them with the full authority of the process; a symlink, a .. the check missed, or a race between check and open, and the model is out.

emperor-folder does not check-then-open. It opens the root once as a cap-std directory handle and does every file operation through that handle with a relative path, so resolution happens under the operating system's (or cap-std's) beneath-the-root rules. There is no code path that opens a file by absolute path.

It is built on emperor-mcp: stateless Streamable HTTP (no session id, load-balancer friendly), output framed so file content reaches the model as data rather than instructions, and a doctor subcommand. It follows Emperor Profile P1.1: no write tool is offered unless the operator switches writing on at start-up, and tool definitions are lean (short descriptions, compact input schemas), so every request carries less of them.

Related MCP server: @modelcontextprotocol/server-filesystem

Install

Release binaries for Linux (x86_64), macOS (Apple Silicon and Intel) and Windows are attached to each GitHub release, with a SHA256SUMS file and build provenance attestations: check a download with sha256sum -c SHA256SUMS --ignore-missing and gh attestation verify <file> --repo yarenty/emperor-folder. From source (Rust 1.88 or newer):

cargo install emperor-folder

Or as a container (stdio, the folder mounted at /data, read-only unless you add --writable):

docker build -t emperor-folder https://github.com/yarenty/emperor-folder.git
docker run -i --rm -v ~/notes:/data emperor-folder                            # read-only
docker run -i --rm -v ~/notes:/data emperor-folder --root /data --writable    # also write, edit, move
docker run -i --rm -v ~/notes:/data -e FOLDER_WRITABLE=true emperor-folder     # the same, from the environment

Run

emperor-folder doctor --root ~/notes   # check first: exits 1 if something is wrong
emperor-folder serve  --root ~/notes

Flag

Default

Meaning

--root [NAME=]DIR

(required)

A folder the server can see. Repeat for several roots; NAME= names one (default: the folder's own name). See Several roots.

--bind ADDR

127.0.0.1:8031

Streamable HTTP address; MCP endpoint at /mcp, liveness at /health.

--writable

off

Offer write, edit and move. Without it they are not listed at all. Also set by the environment variable FOLDER_WRITABLE: true, 1 or yes (also on) turn it on; unset, empty, false, 0 or no (also off) leave it off; anything else is refused.

--stdio

off

Speak MCP over stdin/stdout instead of HTTP. Logs go to stderr.

--hidden

off

Include hidden files and directories in list and search.

--no-ignore

off

Do not honour .gitignore, .ignore and git exclude files.

--max-file-bytes N

1048576

Largest file read returns whole, search opens, stat counts lines in, and write accepts.

--max-results N

200

Most entries list returns and matches search returns.

--max-chars N

50000

Most characters of text one read returns (ceiling 200 000); a longer read stops at a line boundary and says where to continue.

--allowed-host HOST

see below

Accepted Host header, repeatable.

--allowed-origin ORIGIN

none

Accepted Origin header with scheme (https://app.example.com), repeatable. Requests without Origin (non-browser clients) still pass.

RUST_LOG sets the log level (default info).

Connect

Claude Code

claude mcp add --transport http folder http://127.0.0.1:8031/mcp     # server already running
claude mcp add folder -- emperor-folder serve --stdio --root ~/notes  # or let Claude Code start it

With --writable (and several roots) in the stdio form:

claude mcp add folder -- emperor-folder serve --stdio --writable --root notes=$HOME/notes --root code=$HOME/src/app

Claude Desktop (claude_desktop_config.json, stdio):

{
  "mcpServers": {
    "folder": {
      "command": "/usr/local/bin/emperor-folder",
      "args": ["serve", "--stdio", "--root", "/Users/me/notes"]
    }
  }
}

kowalski (config.toml):

[[mcp.servers]]
name = "folder"
transport = "http"
url = "http://127.0.0.1:8031/mcp"

or over stdio:

[[mcp.servers]]
name = "folder"
transport = "stdio"
command = ["emperor-folder", "serve", "--stdio", "--root", "/home/me/notes"]

Any other MCP client that speaks Streamable HTTP or stdio works the same way.

Tools

Seven tools, each a verb. Every path is relative to the root (. or omitted means the root). The three that change files are offered only with --writable; a read-only server lists four.

Tool

Arguments

Returns

Annotations

list

path?, glob?, recursive? (false), max_entries?

Entries with path, kind (file/dir/symlink) and size; truncated when the limit cut it.

read-only, idempotent

read

path, start_line?, end_line? (1-based, inclusive)

The text, with a path lines A-B of N header. PNG, JPEG, GIF and WebP images up to 5 MiB (and --max-file-bytes) come back as MCP image content; other binary files are reported (binary: true, size), never dumped.

read-only, idempotent

search

pattern (regex), path?, glob?, ignore_case? (false)

Matches with path, line number and line (cut at 240 characters); counts of files searched, skipped as binary, skipped as too large.

read-only, idempotent

stat

path

Kind, size, modified time (RFC 3339, UTC), read-only flag; for text files within the size limit, line count; for images, the image type.

read-only, idempotent

write

path, content, mode? (create | overwrite | append, default create)

Path and bytes written. Creates parent directories. create refuses an existing file.

destructive, --writable only

edit

path, old_text, new_text, replace_all? (false)

Path, replacements made, bytes written. old_text must occur exactly once (else the error says how many times it occurs) unless replace_all. Text files only.

destructive, --writable only

move

from, to, overwrite? (false)

The new path. Renames or moves inside the root (or between roots); refuses to replace an existing target unless overwrite.

destructive, --writable only

Every tool sets openWorldHint: false: it touches the named folders and nothing else.

list and search use ignore, ripgrep's walker: .gitignore (even outside a git checkout), .ignore and hidden files are skipped unless --no-ignore / --hidden say otherwise, and symlinks are never followed. Regexes use the regex crate: linear-time matching, no catastrophic backtracking, compiled size capped at 1 MiB. Globs match the path as the tools show it (*.rs matches src/main.rs).

Output. File content from read is framed as a document of unknown authorship (FrameKind::Document); listings, matches and metadata are JSON framed as computed results (FrameKind::Computed). The frame's per-call nonce means a file containing a fake end-of-data marker cannot break out of the block. Refusals come back as tool errors (isError: true) with a message that says what to do instead.

Several roots

--root can be given more than once. With one root nothing changes: paths are relative to it. With several, every path starts with the root's name, and . is a virtual folder whose entries are the roots:

emperor-folder serve --root notes=~/notes --root code=~/src/app
list .                      -> notes, code
read notes/todo.md
search pattern=TODO         -> matches in notes/... and code/...
move  notes/a.md -> code/docs/a.md   (with --writable)

A root without a name is named after its folder (--root ~/notes is notes); two roots with the same name are refused at start-up. In NAME=DIR the name may not contain / or \, so ./a=b is a folder called a=b. Each root is its own cap-std handle: a path, .. or symlink cannot get from one root into another except through move, which names both ends.

Security model

What the model can do: see and read files under the roots that the ignore rules do not hide; with --writable, create, edit and rename files under the roots.

What it cannot do:

  • Leave the root. Absolute paths (/etc/passwd, C:\ on Windows) are refused, and so is any path with a .. component, even one that would stay inside (a/../b). That check gives a clear error; the enforcement underneath is cap-std, which resolves every path beneath the root handle (openat2(RESOLVE_BENEATH) on Linux, component-by-component resolution elsewhere) and refuses any resolution that leaves it.

  • Escape through symlinks. A symlink inside a root that points outside it (also into another root) is refused for read, write, edit, move, list and search alike; a symlink that stays inside works. The walker never follows links, and every path it yields is re-opened through the root handle, so a directory swapped for an escaping link mid-walk is skipped, not read.

  • Write by default. The server starts read-only: write, edit and move are removed from the tool list, and the file operations refuse writes as well, until the operator passes --writable (or sets FOLDER_WRITABLE), as Emperor Profile P1.1 requires. Tool annotations (readOnlyHint, destructiveHint) tell clients which tools change files, so they can ask before running them.

  • Exhaust the host or the context. Whole-file reads stop at --max-file-bytes (line-range reads stream and return at most that much) and at --max-chars characters, search skips larger files, list and search stop at --max-results, binary files other than small images are never returned.

  • Be reached from a web page. On a loopback bind the Host header must be localhost, 127.0.0.1 or ::1 (DNS-rebinding protection). On a non-loopback bind the check is off, as the Emperor Profile expects a validating ingress in front; --allowed-host turns it back on for named hosts, and --allowed-origin refuses browser requests from any other origin.

Not in scope: authentication (put the HTTP endpoint behind your gateway or keep it on loopback), and hard links (a hard link inside the root to a file outside is the same file; do not point the server at a folder where untrusted users can create hard links). Other local processes can still change the folder while the server runs; confinement holds regardless, because every operation re-resolves beneath the root.

doctor

$ emperor-folder doctor --root ~/notes --writable
emperor-folder 0.1.3 doctor
  [ok  ] dir /Users/me/notes  readable (42 entries)
  [ok  ] dir /Users/me/notes  writable
  [ok  ] write access         on: write, edit and move are offered
  [ok  ] roots                notes = /Users/me/notes
  [ok  ] listen address       127.0.0.1:8031 is free
  [ok  ] tool budget          7 of at most 8 tools
  [ok  ] files                1234 visible files under the roots
all good (0 warning(s))

It checks each root exists and can be listed, can be written when --writable is set (a probe file is created and removed), says whether writing is on, the roots open and have distinct names, the listen address is free (skipped with --stdio), the tool count is within the emperor-mcp budget, and counts the files the walker sees (a warning above 100 000: listing and search get slow). Exit code 0 when nothing failed, 1 otherwise, so scripts and container health checks can use it.

Build

cargo fmt --all --check
cargo clippy --all-targets -- -D warnings
cargo test          # unit tests + every tool over real HTTP and stdio, escape attempts included
cargo deny check

Contributing

See CONTRIBUTING.md: run the checks, keep the docs in step, and sign off your commits (DCO, git commit -s).

License

Licensed under either of

at your option.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in emperor-folder by you shall be dual licensed as above, without any additional terms or conditions.

Available Tools

4 tools
listA
Read-onlyIdempotent

List files and directories in a folder. Skips hidden and .gitignored entries. Optional glob filter; recursive for subfolders.

NameRequiredDescriptionDefault
globNoGlob on paths as shown, e.g. "*.md" or "src/**/*.rs".
pathNoDirectory to list; omit or "." for the top.
recursiveNoInclude subdirectories (default false: one level).
max_entriesNoMost entries to return; capped by the server limit.

TDQS

A3.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly, idempotent and non-destructive, so safety is covered. The description adds genuine context beyond them: hidden and .gitignored entries are skipped, glob applies to displayed paths, and max_entries is capped by a server limit — the last point being a behavior an agent cannot see elsewhere.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three short sentences with zero waste, and the core action is front-loaded before the filtering and recursion caveats.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a low-complexity, no-output-schema listing tool, the description covers filtering, recursion, limits and skip rules, which is enough to call it correctly. Only return ordering or output shape is unspecified, a minor gap given the tool's simplicity.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so all four parameters are already documented in the schema. The description only echoes the glob filter and recursion at a high level without adding syntax or format detail beyond what the schema provides; baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('List files and directories in a folder') and adds scope details (hidden/gitignored skipping, glob filter, recursion). It does not explicitly name why an agent would pick this over the sibling 'search', so it stops short of full sibling differentiation.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is only implied: an agent infers this is for enumerating a directory's contents. There is no explicit 'use this instead of search when...' or statement of prerequisites, and the relationship to siblings read/search/stat is left entirely to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

readA
Read-onlyIdempotent

Read a text file, optionally only lines start_line..end_line (1-based, inclusive). Small PNG, JPEG, GIF and WebP images come back as images; other binary files are reported, not returned.

NameRequiredDescriptionDefault
pathYesFile to read.
end_lineNoLast line to return, inclusive (default: end of file).
start_lineNoFirst line to return, 1-based (default 1).

TDQS

A3.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover readOnly, idempotent, and non-destructive behavior, so the bar is lower. The description still earns credit for actionable traits beyond structured data: 1-based inclusive line ranges, images returned as images, and other binaries reported rather than returned. It omits error behavior (missing file, permission failures) and encoding concerns, so not a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two dense sentences with zero waste. The core purpose and line range are front-loaded, and the edge-case behavior for images and binaries follows without filler or repetition of annotation data.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a three-parameter, fully annotated read tool with no output schema, the description covers the important decision points: what is returned for text, images, and other binaries. Missing error conditions and file-size or encoding caveats are real but minor gaps, so it falls just short of fully complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents path, start_line, and end_line including 1-based indexing and defaults. The description mostly restates the range semantics and adds context about non-text inputs rather than deepening parameter behavior, which matches the baseline 3 for high schema coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description gives a specific verb and resource ('Read a text file') and sharpens it with line-slicing and non-text handling. It is clearly distinguishable in spirit from list/search/stat, but it never names those siblings or states the contrast explicitly, which keeps it at 4 rather than 5.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The use case (fetch file contents, optionally a line window) is implied clearly enough to act on. However, there is no explicit guidance on when to prefer this over stat, list, or search, and no stated prerequisites or exclusions, so it remains implied usage rather than real routing guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

statA
Read-onlyIdempotent

Describe a path: kind (file, dir, symlink), size, modified time, line count for text files, image type for images.

NameRequiredDescriptionDefault
pathYesPath to describe; "." for the top.

TDQS

A3.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint, destructiveHint=false and closed-world, so safety is covered. The description adds genuine value by specifying the returned attributes (kind classification, line count for text, image type for images), which is the only place the return contract is documented since no output schema exists. It omits edge behavior such as what happens for a nonexistent path or whether symlinks are followed.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

One dense sentence, front-loaded with the verb and resource, then the returned fields in a compact parenthetical list. No filler, no repetition of the tool name.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a one-parameter read-only inspection tool with full annotation coverage, the description supplies the essential return-field contract. The remaining gap is error/edge behavior (missing paths, symlink traversal, cost of line counting on large files), which an agent may need but is minor relative to overall completeness.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and the single 'path' parameter is documented in the schema including the '"." for the top' convention. The description adds no path semantics (relative vs absolute, symlink resolution) beyond what the schema already provides, so the baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb ('describe') and resource ('a path') and enumerates exactly what is reported: kind, size, modified time, line count, image type. This makes it distinguishable from siblings like read (contents) and list (directory enumeration), though it never names those siblings to draw the contrast explicitly.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is only implied: an agent can infer this is the metadata-inspection tool, but the description never says when to prefer it over read or list, nor any precondition such as the path existing. No exclusions or alternatives are stated.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 4 tool updatesv0.1.3
    • First observedlist
    • First observedread
    • First observedsearch
    • First observedstat

TDQS

A4.2/5.0

Scored across 4 tools

Disambiguation5/5

Each tool has a clearly distinct purpose: list enumerates directory contents, read returns file contents, search finds regex matches, and stat describes metadata. There is little risk of confusing these operations, even though list and search can both traverse folders.

Naming Consistency5/5

All four tools use short, lowercase verb-style names with a consistent convention. The pattern is predictable and readable throughout the set.

Tool Count5/5

Four tools are well-scoped for a folder/filesystem inspection server. Each tool covers a distinct core operation without unnecessary bloat.

Completeness4/5

The surface covers read-only filesystem workflows well: listing, reading, searching, and metadata inspection. If write operations such as create, delete, or move are expected for a folder server, those are missing, but the read-only scope appears intentional.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    MCP server for filesystem operations, enabling reading, writing, editing, moving, and searching files with dynamic directory access control via MCP Roots.
    1,304,784 npm
    -
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables secure filesystem operations (read, write, list, move, search files/directories) through the Model Context Protocol, with dynamic directory access control via MCP Roots.
    -
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables remote filesystem access and command/process control through MCP, with safe-by-default permission profiles that restrict file operations to configured roots and make command execution an explicit opt-in.
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables MCP clients to browse selected folders, search filenames, inspect metadata, and read bounded UTF-8 text files through a read-only, token-authenticated interface.
    1
    MIT