Skip to main content
Glama

GemForge-Gemini-Tools-MCP

fileops.ts8.36 kB
/** * Handler for gemini_fileops tool * * This file contains the implementation of the gemini_fileops tool, * which performs efficient operations on files using appropriate Gemini models. */ import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; import { FileopsArgs } from '../interfaces/tool-args.js'; import { TOOL_NAMES } from '../config/constants.js'; import { buildFileopsRequest } from '../utils/request-builder.js'; import { executeRequest } from '../utils/api-executor.js'; import { fileExists, isLargeFile, getFileTypeCategory, concatenateFiles } from '../utils/file-handler.js'; import { formatResponse } from '../utils/response-formatter.js'; import { selectToolModel } from '../utils/model-selector.js'; import path from 'path'; /** * Handle Gemini file operations request * @param request - MCP request * @returns MCP response */ export async function handleFileops(request: any) { try { // Validate required parameters if (!request.params.arguments || !request.params.arguments.file_path) { throw new McpError( ErrorCode.InvalidParams, 'file_path parameter is required' ); } // Extract arguments as FileopsArgs const args = request.params.arguments as FileopsArgs; const { file_path, instruction, operation, use_large_context_model = false, model_id } = args; // Validate file_path if (typeof file_path !== 'string' && !Array.isArray(file_path)) { throw new McpError( ErrorCode.InvalidParams, 'file_path must be a string or array of strings' ); } // Handle file path validation for both single files and arrays if (Array.isArray(file_path)) { console.error(`[handleFileops] Checking if files exist: ${JSON.stringify(file_path)}`); // Validate each file in the array const validatedPaths = []; for (const path of file_path) { console.error(`[handleFileops] Checking file: ${path}`); const exists = await fileExists(path); if (!exists) { throw new McpError( ErrorCode.InvalidParams, `File not found: ${path}` ); } validatedPaths.push(path); } console.error(`[handleFileops] All files exist and are accessible`); // If we have multiple files, create a temporary file with their contents if (validatedPaths.length > 1) { console.error(`[handleFileops] Processing multiple files: ${validatedPaths.length}`); try { // Concatenate all files into a single temporary file const tempFilePath = await concatenateFiles(validatedPaths); console.error(`[handleFileops] Created temporary file with concatenated content: ${tempFilePath}`); // Update the file_path to use the temporary file args.file_path = tempFilePath; // Add a note about the original files if (!args.instruction) { args.instruction = ""; } args.instruction = `This is a combined file containing the contents of ${validatedPaths.length} files: ${validatedPaths.map(p => path.basename(p)).join(", ")}.\n\n${args.instruction}`; } catch (error) { console.error(`[handleFileops] Error concatenating files:`, error); throw new McpError( ErrorCode.InternalError, `Error processing multiple files: ${error instanceof Error ? error.message : String(error)}` ); } } } else { // Single file path console.error(`[handleFileops] Checking if file exists: ${file_path}`); const exists = await fileExists(file_path); if (!exists) { throw new McpError( ErrorCode.InvalidParams, `File not found: ${file_path}` ); } } // Determine file type for logging (use first file if array) const firstFilePath = Array.isArray(file_path) ? file_path[0] : file_path; const fileType = getFileTypeCategory(firstFilePath); console.error(`[handleFileops] File type: ${fileType}`); // Select the model using the tool-specific model selector const targetModelId = selectToolModel(TOOL_NAMES.GEM_FILEOPS, args); console.error(`[handleFileops] Selected model: ${targetModelId}`); // Build request using the new builder function const internalRequest = await buildFileopsRequest(args); console.error(`[handleFileops] Request built for model: ${targetModelId}`); // Explicitly load file content using preparePartsWithFiles try { const { preparePartsWithFiles } = await import('../utils/file-handler.js'); // Get the instruction text from the first user message let instructionText = ''; const userMsgIndex = internalRequest.contents.findIndex((c: any) => c.role === 'user'); if (userMsgIndex >= 0 && internalRequest.contents[userMsgIndex].parts.length > 0) { instructionText = internalRequest.contents[userMsgIndex].parts[0].text || ''; } console.error(`[handleFileops] Explicitly loading file content for: ${typeof file_path === 'string' ? file_path : JSON.stringify(file_path)}`); const parts = await preparePartsWithFiles(file_path, instructionText); console.error(`[handleFileops] Successfully loaded ${parts.length} parts`); // Replace the parts in the user message with our explicitly loaded parts if (userMsgIndex >= 0) { // Need to cast parts to any to avoid TypeScript errors internalRequest.contents[userMsgIndex].parts = parts as any; console.error(`[handleFileops] Replaced parts in user message at index ${userMsgIndex}`); } else if (internalRequest.contents.length > 0) { // If no user message found but we have contents, replace the first message internalRequest.contents[0].parts = parts as any; console.error(`[handleFileops] Replaced parts in first message`); } } catch (error) { console.error(`[handleFileops] Error explicitly loading file content:`, error); // Continue with the original request if there's an error } try { // Execute request const { response, rawSdkResponse } = await executeRequest(targetModelId, internalRequest); console.error(`[handleFileops] Got response from ${targetModelId}`); // Format response return formatResponse(response, targetModelId, { operation: TOOL_NAMES.GEM_FILEOPS, processingType: operation || 'analyze', withFile: true, fileType: fileType, isImageFile: fileType === 'image', customFormat: { usedLargeContext: use_large_context_model === true } }, rawSdkResponse); } catch (apiError) { console.error('[handleFileops] API error:', apiError); if (apiError instanceof McpError) { throw apiError; } // For large files, try falling back to 1.5 model if not already using it if (isLargeFile(file_path) && targetModelId !== 'gemini-1.5-pro') { console.error('[handleFileops] Large file detected, retrying with gemini-1.5-pro...'); // Create a new args object with large context model flag set to true const fallbackArgs: FileopsArgs = { ...args, use_large_context_model: true }; // Build a new request with the fallback model const fallbackRequest = buildFileopsRequest(fallbackArgs); fallbackRequest.modelId = 'gemini-1.5-pro'; const { response, rawSdkResponse } = await executeRequest('gemini-1.5-pro', fallbackRequest); return formatResponse(response, 'gemini-1.5-pro', { operation: TOOL_NAMES.GEM_FILEOPS, processingType: operation || 'analyze', withFile: true, fileType: fileType, isImageFile: fileType === 'image', customFormat: { usedLargeContext: true, fallbackModel: true } }, rawSdkResponse); } throw apiError; } } catch (error) { console.error('Error in fileops handler:', error); if (error instanceof McpError) { throw error; } return { content: [ { type: 'text', text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}` } ], isError: true }; } }

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/PV-Bhat/GemForge-MCP'

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