Skip to main content
Glama
MartinSchlott

BetterMCPFileServer

index.ts6.54 kB
#!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import { zodToJsonSchema } from "zod-to-json-schema"; import { validateDirectories, parseAliasArgs, pathAliases } from "./pathUtils.js"; import { writeFile, readFileContent, editFile, manageFile } from "./tools/fileTools.js"; import { manageFolder } from "./tools/folderTools.js"; import { searchFilesAndFolders, SearchFilesAndFoldersSchema } from "./tools/searchTools.js"; import { ToolInput, WriteFileSchema, ReadFileContentSchema, EditFileSchema, ManageFileSchema, ManageFolderSchema, } from "./schemas.js"; // Command line argument parsing const args = process.argv.slice(2); if (args.length === 0) { console.error("Usage: mcp-file-server <alias>:<allowed-directory> [<alias2>:<directory2>...]"); process.exit(1); } // Initialize path aliases await parseAliasArgs(args).then(aliases => { // Copy all aliases to the pathAliases array pathAliases.push(...aliases); }); // Also create an allowedDirectories array for backwards compatibility const allowedDirectories = pathAliases.map(pa => pa.normalizedPath); // Validate that all directories exist and are accessible await validateDirectories(args); // Server setup const server = new Server( { name: "mcp-file-server", version: "1.0.0", }, { capabilities: { tools: {}, }, }, ); // List available tools server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "writeFile", description: "Create or update a file at the specified path with the given content.", inputSchema: zodToJsonSchema(WriteFileSchema) as ToolInput, }, { name: "readFileContent", description: "Retrieve the content of a specified file.", inputSchema: zodToJsonSchema(ReadFileContentSchema) as ToolInput, }, { name: "editFile", description: "Make targeted changes to specific text portions within a file without rewriting the entire content.", inputSchema: zodToJsonSchema(EditFileSchema) as ToolInput, }, { name: "manageFile", description: "Perform actions like move, rename, copy, or delete a file.", inputSchema: zodToJsonSchema(ManageFileSchema) as ToolInput, }, { name: "manageFolder", description: "Perform actions like create, rename, or delete a folder.", inputSchema: zodToJsonSchema(ManageFolderSchema) as ToolInput, }, { name: "searchFilesAndFolders", description: "Search for matching files and folders using glob patterns. Results always include path and type fields. For simple directory listing, use pattern '*'. Only set includeMetadata to true when file size or timestamps are needed - this adds size, created, and modified fields.", inputSchema: zodToJsonSchema(SearchFilesAndFoldersSchema) as ToolInput, }, ], }; }); // Tool implementations server.setRequestHandler(CallToolRequestSchema, async (request) => { try { const { name, arguments: args } = request.params; switch (name) { case "writeFile": { const parsed = WriteFileSchema.safeParse(args); if (!parsed.success) { throw new Error(`Invalid arguments for writeFile: ${parsed.error}`); } const result = await writeFile(parsed.data, allowedDirectories); return { content: [{ type: "text", text: result }], }; } case "readFileContent": { const parsed = ReadFileContentSchema.safeParse(args); if (!parsed.success) { throw new Error(`Invalid arguments for readFileContent: ${parsed.error}`); } const content = await readFileContent(parsed.data, allowedDirectories); return { content: [{ type: "text", text: content }], }; } case "editFile": { const parsed = EditFileSchema.safeParse(args); if (!parsed.success) { throw new Error(`Invalid arguments for editFile: ${parsed.error}`); } const result = await editFile(parsed.data, allowedDirectories); return { content: [{ type: "text", text: result }], }; } case "manageFile": { const parsed = ManageFileSchema.safeParse(args); if (!parsed.success) { throw new Error(`Invalid arguments for manageFile: ${parsed.error}`); } const result = await manageFile(parsed.data, allowedDirectories); return { content: [{ type: "text", text: result }], }; } case "manageFolder": { const parsed = ManageFolderSchema.safeParse(args); if (!parsed.success) { throw new Error(`Invalid arguments for manageFolder: ${parsed.error}`); } const result = await manageFolder(parsed.data, allowedDirectories); return { content: [{ type: "text", text: result }], }; } case "searchFilesAndFolders": { const parsed = SearchFilesAndFoldersSchema.safeParse(args); if (!parsed.success) { throw new Error(`Invalid arguments for searchFilesAndFolders: ${parsed.error}`); } const validResults = await searchFilesAndFolders(parsed.data, allowedDirectories); return { content: [{ type: "text", text: JSON.stringify(validResults, null, 2) }], }; } default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [{ type: "text", text: `Error: ${errorMessage}` }], isError: true, }; } }); // Start server async function runServer() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("MCP File Server running on stdio"); if (pathAliases.length > 0) { console.error("Configured aliases:"); pathAliases.forEach(pa => { console.error(` ${pa.alias} => ${pa.fullPath}`); }); } else { console.error("Allowed directories:", allowedDirectories); } } runServer().catch((error) => { console.error("Fatal error running server:", error); process.exit(1); });

Latest Blog Posts

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/MartinSchlott/BetterMCPFileServer'

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