Read-only MCP Wrapper
The fastest way to give an AI assistant a project's knowledge (docs, notes, specs, exported data) is a small MCP server that can list, search and read files, and can do nothing else. Most hand-rolled servers drift into risk: a "helpful" write tool, a path that escapes the folder through .. or a symlink, or logs printed to stdout that corrupt the protocol. This skill generates a server that is read-only by construction, from the tested template in references/server-template.mjs.
Required Inputs
Ask for these if not provided:
- The folder to serve, and roughly how many files and how large
- File types: Markdown, JSON, or both (other types are excluded by default)
- The client: Claude Code, Claude Desktop, Cursor, or another MCP client
- Runtime: Node is the default; ask if the user needs Python instead
- Anything inside the folder that must never be served (drafts, private notes), to exclude by path
Output Structure
1. Server code
A complete file based on references/server-template.mjs, adapted to the inputs:
- stdio transport: newline-delimited JSON-RPC 2.0 on stdin and stdout; all logging on stderr
- handles
initialize, ping, tools/list and tools/call; ignores notifications; returns -32601 for anything else
- the served root resolved once with
realpath; every requested path resolved and checked to stay inside it, so .. and symlinks cannot escape
- an allow-list of extensions and a per-file size cap
- exclusions from the inputs applied in the file walk
2. Tool definitions
A table and the JSON schemas:
Errors are returned as tool results with isError: true and a message that never includes the absolute path.
3. Client configuration
The exact snippet for the named client, with an absolute path placeholder, for example for Claude Code:
claude mcp add my-docs -- node /absolute/path/server.mjs /absolute/path/to/folder
and the mcpServers JSON block for Claude Desktop or Cursor.
4. Safety section
A short statement the user can paste into their README, confirming:
- the server exposes exactly three tools, all read-only
- there is no code that writes, deletes, renames or moves files
- there is no network code: no HTTP client, no sockets, no child processes
- paths cannot escape the served folder, including through symlinks
- only allow-listed file types under the size cap are returned
Then the commands that prove it: a grep over the server for write, delete, network and process APIs that should return nothing, and the test below.
5. Test script
Piped JSON-RPC requests that check: initialize succeeds; the three tools are listed; a document can be listed, searched and read; ../ traversal is refused; a symlink pointing outside is refused; a disallowed extension is refused; an unknown tool returns an error.
Quality Checks
Anti-Patterns
- "Just one write tool." The moment an agent can write, the safety story changes; build a separate server for that.
console.log for debugging. It corrupts the stdio protocol; log to stderr.
- Checking paths with string prefixes only.
startsWith(root) without realpath lets symlinks escape.
- Serving everything in the folder. Allow-list file types; keys and
.env files live in the same trees as docs.
Example Trigger Phrases
- "Make an MCP server so Claude can read my Obsidian notes, read-only."
- "Expose this docs folder to Cursor as MCP tools."
- "Build a safe MCP server over a folder of JSON files."
- "I want my agent to search our specs but never change them."