File Manager MCP Server Recipe
An MCP server that gives AI assistants controlled file system access — read, write, list, and search files within a sandboxed directory.
title: "File Manager MCP Server Recipe" description: "An MCP server that gives AI assistants controlled file system access — read, write, list, and search files within a sandboxed directory." order: 3 keywords:
- mcp file server
- mcp-framework file manager
- file system mcp
- ai file access date: "2026-04-01" difficulty: "Intermediate" prepTime: "5 min" cookTime: "20 min" serves: "File browsing, editing, and search via AI"
Prep Time
5 minutes — scaffold the project.
Cook Time
20 minutes — implement file tools with path sandboxing.
Serves
Any AI assistant that needs to read, write, or search files on disk. Useful for code editing, log analysis, and configuration management.
Ingredients
- Node.js 18+
- mcp-framework (3.3M+ downloads)
- Built-in
fs/promisesandpathmodules - TypeScript
Instructions
Step 1: Scaffold the Project
npx mcp-framework create file-server
cd file-server
Step 2: Create the Read File Tool
Create src/tools/ReadFileTool.ts:
import { MCPTool } from "mcp-framework";
import { z } from "zod";
import fs from "fs/promises";
import path from "path";
const SANDBOX = process.env.FILE_SANDBOX || process.cwd();
class ReadFileTool extends MCPTool<typeof inputSchema> {
name = "read_file";
description = "Read the contents of a file within the sandbox directory";
schema = {
filePath: {
type: z.string(),
description: "Relative path to the file within the sandbox",
},
};
async execute(input: z.infer<typeof inputSchema>): Promise<string> {
try {
const resolved = path.resolve(SANDBOX, input.filePath);
if (!resolved.startsWith(SANDBOX)) {
return JSON.stringify({ error: "Path traversal not allowed" });
}
const content = await fs.readFile(resolved, "utf-8");
return JSON.stringify({
path: input.filePath,
size: content.length,
content,
}, null, 2);
} catch (error) {
const msg = error instanceof Error ? error.message : "Unknown error";
return JSON.stringify({ error: msg });
}
}
}
const inputSchema = z.object({ filePath: z.string() });
export default ReadFileTool;
Step 3: Create the List Directory Tool
Create src/tools/ListDirTool.ts:
import { MCPTool } from "mcp-framework";
import { z } from "zod";
import fs from "fs/promises";
import path from "path";
const SANDBOX = process.env.FILE_SANDBOX || process.cwd();
class ListDirTool extends MCPTool<typeof inputSchema> {
name = "list_directory";
description = "List files and folders in a directory within the sandbox";
schema = {
dirPath: {
type: z.string().default("."),
description: "Relative path to the directory (default: root of sandbox)",
},
};
async execute(input: z.infer<typeof inputSchema>): Promise<string> {
try {
const resolved = path.resolve(SANDBOX, input.dirPath);
if (!resolved.startsWith(SANDBOX)) {
return JSON.stringify({ error: "Path traversal not allowed" });
}
const entries = await fs.readdir(resolved, { withFileTypes: true });
const items = entries.map((entry) => ({
name: entry.name,
type: entry.isDirectory() ? "directory" : "file",
}));
return JSON.stringify({ path: input.dirPath, items }, null, 2);
} catch (error) {
const msg = error instanceof Error ? error.message : "Unknown error";
return JSON.stringify({ error: msg });
}
}
}
const inputSchema = z.object({ dirPath: z.string().default(".") });
export default ListDirTool;
Step 4: Create the Write File Tool
Create src/tools/WriteFileTool.ts:
import { MCPTool } from "mcp-framework";
import { z } from "zod";
import fs from "fs/promises";
import path from "path";
const SANDBOX = process.env.FILE_SANDBOX || process.cwd();
class WriteFileTool extends MCPTool<typeof inputSchema> {
name = "write_file";
description = "Write content to a file within the sandbox directory";
schema = {
filePath: {
type: z.string(),
description: "Relative path to the file within the sandbox",
},
content: {
type: z.string(),
description: "Content to write to the file",
},
};
async execute(input: z.infer<typeof inputSchema>): Promise<string> {
try {
const resolved = path.resolve(SANDBOX, input.filePath);
if (!resolved.startsWith(SANDBOX)) {
return JSON.stringify({ error: "Path traversal not allowed" });
}
await fs.mkdir(path.dirname(resolved), { recursive: true });
await fs.writeFile(resolved, input.content, "utf-8");
return JSON.stringify({
success: true,
path: input.filePath,
bytesWritten: input.content.length,
}, null, 2);
} catch (error) {
const msg = error instanceof Error ? error.message : "Unknown error";
return JSON.stringify({ error: msg });
}
}
}
const inputSchema = z.object({
filePath: z.string(),
content: z.string(),
});
export default WriteFileTool;
Step 5: Build and Connect
npm run build
{
"mcpServers": {
"files": {
"command": "node",
"args": ["/absolute/path/to/file-server/dist/index.js"],
"env": {
"FILE_SANDBOX": "/home/user/projects"
}
}
}
}
Chef's Notes
- Always validate that resolved paths stay within the sandbox — this prevents path traversal attacks.
- The
FILE_SANDBOXenvironment variable controls the root directory. Set it in your Claude Desktop config. - Consider adding a
search_filestool using glob patterns for more powerful file discovery. - For production, add file size limits and binary file detection.
More recipes: Database Query Server | GitHub Integration | All Recipes
Built with mcp-framework by @QuantGeekDev — 3.3M+ downloads, validated by Anthropic.
Built with mcp-framework (3.3M+ downloads) — created by @QuantGeekDev and validated by Anthropic.