Skip to main content
Glama

Claude Desktop Commander MCP

filesystem-handlers.ts8.69 kB
import { readFile, readMultipleFiles, writeFile, createDirectory, listDirectory, moveFile, getFileInfo, type FileResult, type MultiFileResult } from '../tools/filesystem.js'; import {ServerResult} from '../types.js'; import {withTimeout} from '../utils/withTimeout.js'; import {createErrorResponse} from '../error-handlers.js'; import {configManager} from '../config-manager.js'; import { ReadFileArgsSchema, ReadMultipleFilesArgsSchema, WriteFileArgsSchema, CreateDirectoryArgsSchema, ListDirectoryArgsSchema, MoveFileArgsSchema, GetFileInfoArgsSchema } from '../tools/schemas.js'; /** * Helper function to check if path contains an error */ function isErrorPath(path: string): boolean { return path.startsWith('__ERROR__:'); } /** * Extract error message from error path */ function getErrorFromPath(path: string): string { return path.substring('__ERROR__:'.length).trim(); } /** * Handle read_file command */ export async function handleReadFile(args: unknown): Promise<ServerResult> { const HANDLER_TIMEOUT = 60000; // 60 seconds total operation timeout // Add input validation if (args === null || args === undefined) { return createErrorResponse('No arguments provided for read_file command'); } const readFileOperation = async () => { const parsed = ReadFileArgsSchema.parse(args); // Get the configuration for file read limits const config = await configManager.getConfig(); if (!config) { return createErrorResponse('Configuration not available'); } const defaultLimit = config.fileReadLineLimit ?? 1000; // Use the provided limits or defaults const offset = parsed.offset ?? 0; const length = parsed.length ?? defaultLimit; const fileResult = await readFile(parsed.path, parsed.isUrl, offset, length); if (fileResult.isImage) { // For image files, return as an image content type return { content: [ { type: "text", text: `Image file: ${parsed.path} (${fileResult.mimeType})\n` }, { type: "image", data: fileResult.content, mimeType: fileResult.mimeType } ], }; } else { // For all other files, return as text return { content: [{ type: "text", text: fileResult.content }], }; } }; // Execute with timeout at the handler level const result = await withTimeout( readFileOperation(), HANDLER_TIMEOUT, 'Read file handler operation', null ); if (result == null) { // Handles the impossible case where withTimeout resolves to null instead of throwing throw new Error('Failed to read the file'); } return result; } /** * Handle read_multiple_files command */ export async function handleReadMultipleFiles(args: unknown): Promise<ServerResult> { const parsed = ReadMultipleFilesArgsSchema.parse(args); const fileResults = await readMultipleFiles(parsed.paths); // Create a text summary of all files const textSummary = fileResults.map(result => { if (result.error) { return `${result.path}: Error - ${result.error}`; } else if (result.mimeType) { return `${result.path}: ${result.mimeType} ${result.isImage ? '(image)' : '(text)'}`; } else { return `${result.path}: Unknown type`; } }).join("\n"); // Create content items for each file const contentItems: Array<{type: string, text?: string, data?: string, mimeType?: string}> = []; // Add the text summary contentItems.push({ type: "text", text: textSummary }); // Add each file content for (const result of fileResults) { if (!result.error && result.content !== undefined) { if (result.isImage && result.mimeType) { // For image files, add an image content item contentItems.push({ type: "image", data: result.content, mimeType: result.mimeType }); } else { // For text files, add a text summary contentItems.push({ type: "text", text: `\n--- ${result.path} contents: ---\n${result.content}` }); } } } return { content: contentItems }; } /** * Handle write_file command */ export async function handleWriteFile(args: unknown): Promise<ServerResult> { try { const parsed = WriteFileArgsSchema.parse(args); // Get the line limit from configuration const config = await configManager.getConfig(); const MAX_LINES = config.fileWriteLineLimit ?? 50; // Default to 50 if not set // Strictly enforce line count limit const lines = parsed.content.split('\n'); const lineCount = lines.length; let errorMessage = ""; if (lineCount > MAX_LINES) { errorMessage = `✅ File written successfully! (${lineCount} lines) 💡 Performance tip: For optimal speed, consider chunking files into ≤30 line pieces in future operations.`; } // Pass the mode parameter to writeFile await writeFile(parsed.path, parsed.content, parsed.mode); // Provide more informative message based on mode const modeMessage = parsed.mode === 'append' ? 'appended to' : 'wrote to'; return { content: [{ type: "text", text: `Successfully ${modeMessage} ${parsed.path} (${lineCount} lines) ${errorMessage}` }], }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return createErrorResponse(errorMessage); } } /** * Handle create_directory command */ export async function handleCreateDirectory(args: unknown): Promise<ServerResult> { try { const parsed = CreateDirectoryArgsSchema.parse(args); await createDirectory(parsed.path); return { content: [{ type: "text", text: `Successfully created directory ${parsed.path}` }], }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return createErrorResponse(errorMessage); } } /** * Handle list_directory command */ export async function handleListDirectory(args: unknown): Promise<ServerResult> { try { const startTime = Date.now(); const parsed = ListDirectoryArgsSchema.parse(args); const entries = await listDirectory(parsed.path, parsed.depth); const duration = Date.now() - startTime; const resultText = entries.join('\n'); return { content: [{ type: "text", text: resultText }], }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return createErrorResponse(errorMessage); } } /** * Handle move_file command */ export async function handleMoveFile(args: unknown): Promise<ServerResult> { try { const parsed = MoveFileArgsSchema.parse(args); await moveFile(parsed.source, parsed.destination); return { content: [{ type: "text", text: `Successfully moved ${parsed.source} to ${parsed.destination}` }], }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return createErrorResponse(errorMessage); } } /** * Handle get_file_info command */ export async function handleGetFileInfo(args: unknown): Promise<ServerResult> { try { const parsed = GetFileInfoArgsSchema.parse(args); const info = await getFileInfo(parsed.path); return { content: [{ type: "text", text: Object.entries(info) .map(([key, value]) => `${key}: ${value}`) .join('\n') }], }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return createErrorResponse(errorMessage); } } // The listAllowedDirectories function has been removed // Use get_config to retrieve the allowedDirectories configuration

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/wonderwhy-er/DesktopCommanderMCP'

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