Skip to main content
Glama

filesystem-mcp

by sylphxltd
copy-items.ts6.11 kB
// src/handlers/copyItems.ts import { promises as fs } from 'node:fs'; import path from 'node:path'; import { z } from 'zod'; import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; import { resolvePath, PROJECT_ROOT } from '../utils/path-utils.js'; // --- Types --- import type { McpToolResponse } from '../types/mcp-types.js'; export const CopyOperationSchema = z .object({ source: z.string().describe('Relative path of the source.'), destination: z.string().describe('Relative path of the destination.'), }) .strict(); export const CopyItemsArgsSchema = z .object({ operations: z .array(CopyOperationSchema) .min(1, { message: 'Operations array cannot be empty' }) .describe('Array of {source, destination} objects.'), }) .strict(); type CopyItemsArgs = z.infer<typeof CopyItemsArgsSchema>; type CopyOperation = z.infer<typeof CopyOperationSchema>; // Export or define locally if needed interface CopyResult { source: string; destination: string; success: boolean; error?: string; } // --- Parameter Interfaces --- interface HandleCopyErrorParams { error: unknown; sourceRelative: string; destinationRelative: string; sourceOutput: string; destOutput: string; } interface ProcessSingleCopyParams { op: CopyOperation; } // --- Helper Functions --- /** Parses and validates the input arguments. */ function parseAndValidateArgs(args: unknown): CopyItemsArgs { try { return CopyItemsArgsSchema.parse(args); } catch (error) { if (error instanceof z.ZodError) { throw new McpError( ErrorCode.InvalidParams, `Invalid arguments: ${error.errors.map((e) => `${e.path.join('.')} (${e.message})`).join(', ')}`, ); } throw new McpError(ErrorCode.InvalidParams, 'Argument validation failed'); } } /** Handles errors during the copy operation for a single item. */ function handleCopyError(params: HandleCopyErrorParams): CopyResult { const { error, sourceRelative, destinationRelative, sourceOutput, destOutput } = params; let errorMessage = 'An unknown error occurred during copy.'; let errorCode: string | undefined = undefined; if (error && typeof error === 'object' && 'code' in error && typeof error.code === 'string') { errorCode = error.code; } if (error instanceof McpError) { errorMessage = error.message; } else if (error instanceof Error) { errorMessage = `Failed to copy item: ${error.message}`; } if (errorCode === 'ENOENT') { errorMessage = `Source path not found: ${sourceRelative}`; } else if (errorCode === 'EPERM' || errorCode === 'EACCES') { errorMessage = `Permission denied copying '${sourceRelative}' to '${destinationRelative}'.`; } return { source: sourceOutput, destination: destOutput, success: false, error: errorMessage, }; } /** Processes a single copy operation. */ async function processSingleCopyOperation(params: ProcessSingleCopyParams): Promise<CopyResult> { const { op } = params; const sourceRelative = op.source; const destinationRelative = op.destination; const sourceOutput = sourceRelative.replaceAll('\\', '/'); const destOutput = destinationRelative.replaceAll('\\', '/'); let sourceAbsolute = ''; // Initialize for potential use in error message try { sourceAbsolute = resolvePath(sourceRelative); const destinationAbsolute = resolvePath(destinationRelative); if (sourceAbsolute === PROJECT_ROOT) { return { source: sourceOutput, destination: destOutput, success: false, error: 'Copying the project root is not allowed.', }; } // Ensure parent directory of destination exists const destDir = path.dirname(destinationAbsolute); await fs.mkdir(destDir, { recursive: true }); // Perform the copy (recursive for directories) await fs.cp(sourceAbsolute, destinationAbsolute, { recursive: true, errorOnExist: false, // Overwrite existing files/dirs force: true, // Ensure overwrite }); return { source: sourceOutput, destination: destOutput, success: true }; } catch (error: unknown) { return handleCopyError({ // Pass object error, sourceRelative, destinationRelative, sourceOutput, destOutput, }); } } /** Processes results from Promise.allSettled. */ function processSettledResults( results: PromiseSettledResult<CopyResult>[], originalOps: CopyOperation[], ): CopyResult[] { return results.map((result, index) => { const op = originalOps[index]; const sourceOutput = (op?.source ?? 'unknown').replaceAll('\\', '/'); const destOutput = (op?.destination ?? 'unknown').replaceAll('\\', '/'); return result.status === 'fulfilled' ? result.value : { source: sourceOutput, destination: destOutput, success: false, error: `Unexpected error during processing: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`, }; }); } /** Main handler function */ const handleCopyItemsFunc = async (args: unknown): Promise<McpToolResponse> => { const { operations } = parseAndValidateArgs(args); const copyPromises = operations.map((op) => processSingleCopyOperation({ op })); const settledResults = await Promise.allSettled(copyPromises); const outputResults = processSettledResults(settledResults, operations); // Sort results based on the original order const originalIndexMap = new Map(operations.map((op, i) => [op.source.replaceAll('\\', '/'), i])); outputResults.sort((a, b) => { const indexA = originalIndexMap.get(a.source) ?? Infinity; const indexB = originalIndexMap.get(b.source) ?? Infinity; return indexA - indexB; }); return { content: [{ type: 'text', text: JSON.stringify(outputResults, undefined, 2) }], }; }; // Export the complete tool definition export const copyItemsToolDefinition = { name: 'copy_items', description: 'Copy multiple specified files/directories.', inputSchema: CopyItemsArgsSchema, handler: handleCopyItemsFunc, };

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/sylphxltd/filesystem-mcp'

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