Skip to main content
Glama

Filesystem MCP Server

index.ts14.2 kB
#!/usr/bin/env bun import { FastMCP } from "fastmcp"; import fs from "fs/promises"; import path from "path"; import { expandHome, normalizePath, validatePath, } from "./src/utils/path-utils.js"; import { toolSchemas } from "./src/schemas/index.js"; import { toZodParameters } from "./src/utils/typebox-zod.js"; import { handleReadFile, handleReadMultipleFiles, handleCreateFile, handleModifyFile, handleEditFile, handleGetFileInfo, handleMoveFile, handleDeleteFile, handleRenameFile, } from "./src/handlers/file-handlers.js"; import { handleCreateDirectory, handleListDirectory, handleDirectoryTree, handleDeleteDirectory, } from "./src/handlers/directory-handlers.js"; import { handleSearchFiles, handleFindFilesByExtension, handleGetPermissions, handleXmlToJson, handleXmlToJsonString, handleListAllowedDirectories, handleRegexSearchContent, } from "./src/handlers/utility-handlers.js"; import { handleXmlQuery, handleXmlStructure, } from "./src/handlers/xml-handlers.js"; import { handleJsonQuery, handleJsonFilter, handleJsonGetValue, handleJsonTransform, handleJsonStructure, handleJsonSample, handleJsonValidate, handleJsonSearchKv, } from "./src/handlers/json-handlers.js"; // parse command line const args = process.argv.slice(2); const readonlyFlag = args.includes("--readonly"); const noFollowSymlinks = args.includes("--no-follow-symlinks"); const fullAccessFlag = args.includes("--full-access"); const allowCreate = args.includes("--allow-create"); const allowEdit = args.includes("--allow-edit"); const allowMove = args.includes("--allow-move"); const allowDelete = args.includes("--allow-delete"); const allowRename = args.includes("--allow-rename"); const httpFlagIndex = args.indexOf("--http"); const useHttp = httpFlagIndex !== -1; if (useHttp) args.splice(httpFlagIndex, 1); let port = 8080; const portIndex = args.indexOf("--port"); if (portIndex !== -1) { port = parseInt(args[portIndex + 1], 10); args.splice(portIndex, 2); } if (readonlyFlag) args.splice(args.indexOf("--readonly"), 1); if (noFollowSymlinks) args.splice(args.indexOf("--no-follow-symlinks"), 1); if (fullAccessFlag) args.splice(args.indexOf("--full-access"), 1); if (allowCreate) args.splice(args.indexOf("--allow-create"), 1); if (allowEdit) args.splice(args.indexOf("--allow-edit"), 1); if (allowMove) args.splice(args.indexOf("--allow-move"), 1); if (allowDelete) args.splice(args.indexOf("--allow-delete"), 1); if (allowRename) args.splice(args.indexOf("--allow-rename"), 1); const useCwdFlag = args.includes("--cwd"); if (useCwdFlag) { args.splice(args.indexOf("--cwd"), 1); } // If no explicit allowed directories, use cwd if --cwd was passed or no directory was passed at all let allowedDirectories: string[]; if (args.length === 0 || useCwdFlag) { allowedDirectories = [normalizePath(process.cwd())]; } else { allowedDirectories = args.map((dir) => normalizePath(path.resolve(expandHome(dir))), ); } if (!useCwdFlag && args.length === 0) { console.warn( "No allowed directory specified. Using current working directory as root.", ); console.warn( "Usage: mcp-server-filesystem [flags] <allowed-directory> [additional-directories...]\n mcp-server-filesystem --cwd [flags]", ); } // duplicate declaration removed; `allowedDirectories` already defined above const symlinksMap = new Map<string, string>(); await Promise.all( allowedDirectories.map(async (dir) => { try { const stats = await fs.stat(dir); if (!stats.isDirectory()) { console.error(`Error: ${dir} is not a directory`); process.exit(1); } try { const realPath = await fs.realpath(dir); if (realPath !== dir) { const normalizedDir = normalizePath(path.resolve(expandHome(dir))); const normalizedRealPath = normalizePath(realPath); symlinksMap.set(normalizedRealPath, normalizedDir); if (!allowedDirectories.includes(normalizedRealPath)) allowedDirectories.push(normalizedRealPath); await validatePath( normalizedRealPath, allowedDirectories, symlinksMap, noFollowSymlinks, ); } await validatePath( dir, allowedDirectories, symlinksMap, noFollowSymlinks, ); } catch (error) { console.error( `Warning: Could not resolve real path for ${dir}:`, error, ); } } catch (error) { console.error(`Error accessing directory ${dir}:`, error); process.exit(1); } }), ); const permissions = { create: !readonlyFlag && (fullAccessFlag || allowCreate), edit: !readonlyFlag && (fullAccessFlag || allowEdit), move: !readonlyFlag && (fullAccessFlag || allowMove), delete: !readonlyFlag && (fullAccessFlag || allowDelete), rename: !readonlyFlag && (fullAccessFlag || allowRename), fullAccess: !readonlyFlag && fullAccessFlag, }; const server = new FastMCP({ name: "secure-filesystem-server", version: "0.2.0", }); const toolHandlers = { read_file: (a: unknown) => handleReadFile(a, allowedDirectories, symlinksMap, noFollowSymlinks), read_multiple_files: (a: unknown) => handleReadMultipleFiles( a, allowedDirectories, symlinksMap, noFollowSymlinks, ), create_file: (a: unknown) => handleCreateFile( a, permissions, allowedDirectories, symlinksMap, noFollowSymlinks, ), modify_file: (a: unknown) => handleModifyFile( a, permissions, allowedDirectories, symlinksMap, noFollowSymlinks, ), edit_file: (a: unknown) => handleEditFile( a, permissions, allowedDirectories, symlinksMap, noFollowSymlinks, ), create_directory: (a: unknown) => handleCreateDirectory( a, permissions, allowedDirectories, symlinksMap, noFollowSymlinks, ), list_directory: (a: unknown) => handleListDirectory(a, allowedDirectories, symlinksMap, noFollowSymlinks), directory_tree: (a: unknown) => handleDirectoryTree(a, allowedDirectories, symlinksMap, noFollowSymlinks), move_file: (a: unknown) => handleMoveFile( a, permissions, allowedDirectories, symlinksMap, noFollowSymlinks, ), rename_file: (a: unknown) => handleRenameFile( a, permissions, allowedDirectories, symlinksMap, noFollowSymlinks, ), delete_directory: (a: unknown) => handleDeleteDirectory( a, permissions, allowedDirectories, symlinksMap, noFollowSymlinks, ), search_files: (a: unknown) => handleSearchFiles(a, allowedDirectories, symlinksMap, noFollowSymlinks), find_files_by_extension: (a: unknown) => handleFindFilesByExtension( a, allowedDirectories, symlinksMap, noFollowSymlinks, ), get_file_info: (a: unknown) => handleGetFileInfo(a, allowedDirectories, symlinksMap, noFollowSymlinks), list_allowed_directories: (a: unknown) => handleListAllowedDirectories(a, allowedDirectories), get_permissions: (a: unknown) => handleGetPermissions( a, permissions, readonlyFlag, noFollowSymlinks, allowedDirectories, ), xml_query: (a: unknown) => handleXmlQuery(a, allowedDirectories, symlinksMap, noFollowSymlinks), xml_structure: (a: unknown) => handleXmlStructure(a, allowedDirectories, symlinksMap, noFollowSymlinks), xml_to_json: (a: unknown) => handleXmlToJson( a, permissions, allowedDirectories, symlinksMap, noFollowSymlinks, ), xml_to_json_string: (a: unknown) => handleXmlToJsonString(a, allowedDirectories, symlinksMap, noFollowSymlinks), delete_file: (a: unknown) => handleDeleteFile( a, permissions, allowedDirectories, symlinksMap, noFollowSymlinks, ), json_query: (a: unknown) => handleJsonQuery(a, allowedDirectories, symlinksMap, noFollowSymlinks), json_structure: (a: unknown) => handleJsonStructure(a, allowedDirectories, symlinksMap, noFollowSymlinks), json_filter: (a: unknown) => handleJsonFilter(a, allowedDirectories, symlinksMap, noFollowSymlinks), json_get_value: (a: unknown) => handleJsonGetValue(a, allowedDirectories, symlinksMap, noFollowSymlinks), json_transform: (a: unknown) => handleJsonTransform(a, allowedDirectories, symlinksMap, noFollowSymlinks), json_sample: (a: unknown) => handleJsonSample(a, allowedDirectories, symlinksMap, noFollowSymlinks), json_validate: (a: unknown) => handleJsonValidate(a, allowedDirectories, symlinksMap, noFollowSymlinks), json_search_kv: (a: unknown) => handleJsonSearchKv(a, allowedDirectories, symlinksMap, noFollowSymlinks), regex_search_content: (a: unknown) => handleRegexSearchContent( a, allowedDirectories, symlinksMap, noFollowSymlinks, ), } as const; const allTools = [ { name: "read_file", description: "Read file contents" }, { name: "read_multiple_files", description: "Read multiple files" }, { name: "list_directory", description: "List directory contents" }, { name: "directory_tree", description: "Directory tree view" }, { name: "search_files", description: "Search files by name" }, { name: "find_files_by_extension", description: "Find files by extension" }, { name: "get_file_info", description: "Get file metadata" }, { name: "list_allowed_directories", description: "List allowed directories" }, { name: "get_permissions", description: "Get server permissions" }, { name: "create_file", description: "Create a new file" }, { name: "modify_file", description: "Replace file contents" }, { name: "edit_file", description: "Edit part of a file" }, { name: "create_directory", description: "Create a directory" }, { name: "move_file", description: "Move a file" }, { name: "rename_file", description: "Rename a file" }, { name: "delete_directory", description: "Delete a directory" }, { name: "xml_query", description: "Query XML" }, { name: "xml_structure", description: "Analyze XML structure" }, { name: "xml_to_json", description: "Convert XML to JSON" }, { name: "xml_to_json_string", description: "XML to JSON string" }, { name: "delete_file", description: "Delete a file" }, { name: "json_query", description: "Query JSON" }, { name: "json_structure", description: "JSON structure" }, { name: "json_filter", description: "Filter JSON" }, { name: "json_get_value", description: "Get value from JSON" }, { name: "json_transform", description: "Transform JSON" }, { name: "json_sample", description: "Sample JSON data" }, { name: "json_validate", description: "Validate JSON" }, { name: "json_search_kv", description: "Search key/value in JSON" }, { name: "regex_search_content", description: "Search file content with regex", }, ]; const tools = !permissions.fullAccess ? allTools.filter((t) => { if ( [ "read_file", "read_multiple_files", "list_directory", "directory_tree", "search_files", "find_files_by_extension", "get_file_info", "list_allowed_directories", "xml_to_json_string", "get_permissions", "xml_query", "xml_structure", "json_query", "json_filter", "json_get_value", "json_transform", "json_structure", "json_sample", "json_validate", "json_search_kv", "regex_search_content", ].includes(t.name) ) { return true; } if ( permissions.create && ["create_file", "create_directory", "xml_to_json"].includes(t.name) ) return true; if (permissions.edit && ["modify_file", "edit_file"].includes(t.name)) return true; if (permissions.move && t.name === "move_file") return true; if (permissions.rename && t.name === "rename_file") return true; if ( permissions.delete && ["delete_file", "delete_directory"].includes(t.name) ) return true; return false; }) : allTools; for (const tool of tools) { const execute = toolHandlers[tool.name as keyof typeof toolHandlers]; const schema = (toolSchemas as Record<string, any>)[tool.name]; server.addTool({ name: tool.name, description: tool.description, parameters: toZodParameters(schema as any) as any, execute: async (a) => execute(a) as any, }); } async function runServer() { if (useHttp) { await server.start({ transportType: "httpStream", httpStream: { port } }); console.error( `Secure MCP Filesystem Server running on HTTP stream port ${port}`, ); } else { await server.start({ transportType: "stdio" }); console.error("Secure MCP Filesystem Server running on stdio"); } console.error("Allowed directories:", allowedDirectories); const permState = [] as string[]; if (readonlyFlag) { console.error( "Server running in read-only mode (--readonly flag overrides all other permissions)", ); } else if (permissions.fullAccess) { console.error( "Server running with full access (all operations enabled via --full-access)", ); } else { if (permissions.create) permState.push("create"); if (permissions.edit) permState.push("edit"); if (permissions.move) permState.push("move"); if (permissions.rename) permState.push("rename"); if (permissions.delete) permState.push("delete"); if (permState.length === 0) { console.error( "Server running in default read-only mode (use --full-access or specific --allow-* flags to enable write operations)", ); } else { console.error( `Server running with specific permissions enabled: ${permState.join(", ")}`, ); } } if (noFollowSymlinks) { console.error("Server running with symlink following disabled"); } } runServer().catch((error) => { console.error("Fatal error running server:", error); process.exit(1); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/rawr-ai/mcp-filesystem'

If you have feedback or need assistance with the MCP directory API, please join our Discord server