Skip to main content
Glama

简体中文

paddock — Local filesystem workbench (MCP stdio server)

paddock gives agents a complete, constrained set of local file operations: text read/write, directory management, filename search, content search, and metadata queries — all operations are confined to configurable "paddock" directories, and out-of-bounds access is always rejected.

  • Zero runtime dependencies: pure Node.js standard library, no npm install needed

  • MCP protocol: line-delimited JSON-RPC 2.0 over stdio, loadable by any MCP client

  • dsh-ready: ships a Cordis bridge plugin, one-line bundle integration with the harness

  • Read-only mode: --read-only blocks all write operations in one shot

  • Large files & binaries: streaming head/tail, byte slicing, base64 reads, binary sniffing

  • Search: a self-built glob engine (* ** ? {a,b} [abc]) plus regex/fixed-string content search


Quick start

# No dependency installation needed, run directly
node src/entry.js "C:/Users/me/projects"

Once started, the MCP session begins: JSON-RPC messages are read line by line from stdin, written line by line to stdout, and logs go only to stderr.

Connect from any MCP client (example with a standard MCP client config):

{
  "mcpServers": {
    "paddock": {
      "command": "node",
      "args": ["/path/to/fs-mcp/src/entry.js", "C:/Users/me/projects"]
    }
  }
}

You can also run a smoke session directly to verify:

# Write an initialize request to stdin and observe the response
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18"}}' | node src/entry.js "C:/Users/me/projects"

Related MCP server: MCP Local Filesystem Server

Installation

  • As an MCP server: copy this directory, run node src/entry.js <paddock dirs…>, no build step.

  • As a dsh plugin: see docs/INTEGRATION.md.

  • Requirements: Node.js ≥ 18.17 (check with node --version).

Installing in DSH

dsh plugin --profile demo add github:JohnXu22786/fs-mcp
  • demo is a dsh profile: it is created automatically on first use, and the package is added to dsh.profile.bundles;

  • The cordis.patch.yml inside the package defines the paddock/bridge plugin, which starts this MCP server within the dsh process and registers all 16 tools into ctx.tools after the handshake — no manual configuration needed;

  • Removal:

dsh plugin --profile demo remove paddock

Configuration

Configuration is merged as "defaults < config file < environment variables < command-line arguments"; paddock directories are unioned across all three sources.

Command line

node src/entry.js [options] [paddock dirs…]

  --config <path>   config file (defaults to paddock.config.json in the working directory, loaded only if present)
  --read-only       read-only mode: blocks all write operations
  --zones <path>    additional paddock directories (repeatable)
  --verbose         debug logging (stderr)
  --version / --help

Positional arguments are treated as paddock directories. At least one paddock is required, otherwise startup fails (exit code 2).

Config file (paddock.config.json)

{
  "zones": ["C:/Users/me/projects", "C:/Users/me/data"],
  "readOnly": false,
  "limits": {
    "peekBytes": 1048576,
    "readBytes": 16777216,
    "sliceBytes": 65536,
    "grepBytes": 262144,
    "grepHits": 200,
    "findHits": 1000,
    "treeDepth": 6
  },
  "behavior": {
    "includeHidden": false
  }
}

A full example is in examples/paddock.config.example.json.

Environment variables

Variable

Description

PADDOCK_CONFIG

config file path (takes precedence over the default path)

PADDOCK_ZONES

paddock directories: a JSON array string (["/a","/b"]) or a single path

PADDOCK_READ_ONLY

1 / true / yes / on count as enabled

PADDOCK_VERBOSE

same as above (1 / true / yes / on)

limits semantics

Key

Default

Effect

peekBytes

1 MiB

upper bound for pdk_peek full reads (head/tail are not limited by this, but an over-long single line is capped by the internal 8 MiB line-buffer limit)

readBytes

16 MiB

upper bound for pdk_read_bytes base64 reads

sliceBytes

64 KiB

upper bound for a single pdk_slice slice

grepBytes

256 KiB

per-file scan limit for pdk_grep (deep: true can bypass; lines over 1 MiB are skipped wholesale)

grepHits

200

hit-count limit for pdk_grep

findHits

1000

result-count limit for pdk_find

treeDepth

6

default depth limit for pdk_tree

treeEntries

20000

total node limit for pdk_tree (truncated with a truncated marker when exceeded)


Tool interface

16 tools; write operations carry a mutating marker (gated by read-only mode). All path arguments must be absolute paths inside a paddock; relative paths are resolved against the process working directory and validated the same way.

Read-only tools

Tool

Parameters

Description

pdk_zones

—

list paddocks (given paths + resolved real paths)

pdk_ls

path, sortBy?

list directory: entry type (file/dir/link), size, sorting, counts and totals

pdk_tree

path, depth?, exclude?, includeHidden?

recursive directory tree (JSON); overly deep directories are marked cut: true, node-limit overruns are marked truncated

pdk_peek

path, head?, tail?

read text files; head/tail stream the first/last N lines; binary and over-limit files are rejected

pdk_read_many

paths[]

batch text reads (1–64 files); a single file failure is folded without aborting

pdk_slice

path, offset?, length?

read large files by byte range; binary content returned as base64; eof: true when the slice reaches the end of the file

pdk_read_bytes

path

base64 read of binaries + MIME guessing (png/jpg/mp3/…)

pdk_find

path, pattern, exclude?, includeHidden?

recursive filename glob search

pdk_grep

path, needle, mode?, caseSensitive?, glob?, exclude?, includeHidden?, deep?

content search: regex (case-insensitive by default) or fixed string; line numbers + truncated snippets

pdk_meta

path

metadata: type/size/permissions/times; symlinks report their target

Write tools

Tool

Parameters

Description

pdk_write

path, content

write/overwrite (UTF-8), parent directories created automatically

pdk_rewrite

path, edits[], dryRun?

precise replacement: {oldText, newText, all?} applied in order; fails on any miss; dryRun previews -/+ changes; newText is treated literally ($ sequences are never expanded)

pdk_mkdir

path

recursive directory creation, idempotent

pdk_move

source, destination

move/rename; refuses to overwrite an existing destination; cross-device copies + deletes automatically

pdk_copy

source, destination

copy files/directories; symlinks are not followed (links are recreated as-is)

pdk_remove

path, recursive?

delete; non-empty directories need recursive: true; paddock roots can never be deleted

Error model

Tool-level failures do not produce protocol errors; they return structured isError: true results:

{
  "isError": true,
  "content": [{ "type": "text", "text": "[not-found] File does not exist: C:/…" }],
  "structuredContent": { "error": { "code": "not-found", "message": "File does not exist: C:/…" } }
}

Error codes: config readonly validation fence (out of bounds) not-found conflict limit binary wrong-kind io internal (fallback). network / timeout / jsonrpc appear only in the bridge client (the communication layer with the server subprocess). Error messages carry actionable follow-up suggestions (e.g. large files are pointed to pdk_slice).


Security model

  • Paddock boundary: every path passes "paddock" validation before any IO (see docs/SECURITY.md).

  • Symlinks: existing paths are realpath-resolved then validated; new paths validate by resolving the "nearest existing ancestor" and re-checking — links inside a paddock that point outside can never become a springboard.

  • Component-level checks: string-prefix misjudgments such as zone vs zone_extra are impossible; win32 comparisons fold case.

  • Windows hardening: rejects invalid path segments, trailing dots/spaces, and reserved device names (NUL/CON/COM1…).

  • Read-only mode: with --read-only or config readOnly: true, all 6 write tools are gated.

  • Delete protection: paddock root directories cannot be removed by pdk_remove.

  • Large files/binaries: text-read limits, slice reads, base64, NUL-byte sniffing — the model is never slammed with gigantic or binary content.


dsh integration

Pick either of the two ways; details in docs/INTEGRATION.md:

  1. Bundle bridge (recommended, zero dependencies): this package's dsh.bundle.patch points to cordis.patch.yml, and dsh merges the paddock/bridge plugin line into the config tree; the plugin starts the server subprocess inside the harness process and, after the handshake, registers all 16 tools into ctx.tools — model-side tool names look like mcp__paddock__pdk_peek.

  2. Official MCP client: connect directly to node src/entry.js <paddock> with the @deepseek-ai/dsh-mcp-client config line.

Development

npm test        # node --test: 100+ cases (4 symlink cases skip on win32 as they need developer mode)
npm run smoke   # sequential end-to-end smoke test (real subprocess + stdio session, 16 checks)
node src/entry.js --help

Test coverage: paddock escapes/symlink escapes/ancestor-chain protection, read-write round-trips, large-file and binary policies, glob and content search, three-source config merging, protocol engine, end-to-end subprocess sessions.

License

MIT, see LICENSE.

Related MCP Connectors

Related MCP Servers

  • F
    license
    B
    quality
    D
    maintenance
    A lightweight MCP server for basic file operations, enabling reading, writing, and listing files securely via the Model Context Protocol.
    3
    1
    -
  • A
    license
    Not graded
    quality
    C
    maintenance
    MCP server that exposes file operations (list, read, write, delete, unzip) in a sandbox directory, preventing path traversal.
    11 npm
    MIT