Skip to main content
Glama
ishayoyo

Excel MCP Server

by ishayoyo

read_file

Read CSV or Excel files with chunking support for large datasets. Specify sheet names, row offsets, and limits to extract data efficiently.

Instructions

Read an entire CSV or Excel file with optional chunking for large files

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
filePathYesPath to the CSV or Excel file
sheetNoSheet name for Excel files (optional, defaults to first sheet)
offsetNoStarting row index for chunked reading (0-based, optional)
limitNoMaximum number of rows to return (optional, enables chunking)

Implementation Reference

  • The primary handler implementation for the 'read_file' MCP tool. Handles file reading, chunking with offset/limit, metadata generation, warnings, and large file suggestions.
    async readFile(args: ToolArgs): Promise<ToolResponse> {
      try {
        if (!args.filePath) {
          return {
            content: [
              {
                type: 'text',
                text: JSON.stringify({
                  success: false,
                  error: 'Missing required parameter: filePath',
                }, null, 2),
              },
            ],
          };
        }
    
        const { filePath, sheet, offset, limit } = args;
        const result = await readFileContentWithWarnings(filePath, sheet);
    
        // Store original data info for metadata
        const totalRows = result.data.length;
        const totalColumns = result.data[0]?.length || 0;
        const headers = totalRows > 0 ? result.data[0] : [];
    
        // Apply chunking if offset or limit are specified
        let chunkedData = result.data;
        let chunkMetadata = null;
    
        if (offset !== undefined || limit !== undefined) {
          const requestedOffset = offset || 0;
          const requestedLimit = limit || (totalRows - requestedOffset);
    
          // Validate chunk boundaries
          const { validOffset, validLimit } = validateChunkBoundaries(result.data, requestedOffset, requestedLimit);
    
          const endRow = validOffset + validLimit;
          chunkedData = result.data.slice(validOffset, endRow);
    
          chunkMetadata = {
            offset: validOffset,
            limit: validLimit,
            totalRows,
            returnedRows: chunkedData.length,
            hasMore: endRow < totalRows,
            nextOffset: endRow < totalRows ? endRow : null,
            note: validOffset > 0 && totalRows > 0 ? "This chunk doesn't include headers. Consider including row 0 for headers." : undefined,
          };
        }
    
        const response: any = {
          success: true,
          data: chunkedData,
          rowCount: chunkedData.length,
          columnCount: totalColumns,
          headers: headers,
        };
    
        // Add chunk metadata if chunking was used
        if (chunkMetadata) {
          response.chunkInfo = chunkMetadata;
        }
    
        // Include warnings if they exist
        if (result.warnings) {
          response.warnings = result.warnings;
        }
    
        // Add suggestion for large files
        if (!chunkMetadata && totalRows > 10000) {
          response.suggestion = `Large file detected (${totalRows} rows). Consider using offset/limit parameters for chunked reading to avoid token limits.`;
        }
    
        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify(response, null, 2),
            },
          ],
        };
      } catch (error) {
        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify({
                success: false,
                error: error instanceof Error ? error.message : 'Unknown error occurred',
              }, null, 2),
            },
          ],
        };
      }
    }
  • src/index.ts:1199-1200 (registration)
    MCP server registration mapping the tool name 'read_file' to the DataOperationsHandler.readFile method in the CallToolRequestSchema handler.
    case 'read_file':
      return await this.dataOpsHandler.readFile(toolArgs);
  • Tool schema definition including name, description, and input schema validation for the 'read_file' tool, provided in ListToolsRequestSchema response.
      name: 'read_file',
      description: 'Read an entire CSV or Excel file with optional chunking for large files',
      inputSchema: {
        type: 'object',
        properties: {
          filePath: {
            type: 'string',
            description: 'Path to the CSV or Excel file',
          },
          sheet: {
            type: 'string',
            description: 'Sheet name for Excel files (optional, defaults to first sheet)',
          },
          offset: {
            type: 'number',
            description: 'Starting row index for chunked reading (0-based, optional)',
          },
          limit: {
            type: 'number',
            description: 'Maximum number of rows to return (optional, enables chunking)',
          },
        },
        required: ['filePath'],
      },
    },
  • Core helper function that performs the actual file reading for CSV and Excel files, generates warnings for data quality issues, and returns parsed data. Called by the read_file handler.
    export async function readFileContentWithWarnings(filePath: string, sheet?: string): Promise<FileReadResult> {
      const ext = path.extname(filePath).toLowerCase();
      const absolutePath = path.resolve(filePath);
      const warnings: string[] = [];
    
      try {
        await fs.access(absolutePath);
      } catch {
        throw new Error(`File not found: ${filePath}`);
      }
    
      if (ext === '.csv') {
        const content = await fs.readFile(absolutePath, 'utf-8');
    
        // Check for binary content that might cause issues
        if (content.includes('\u0000') || content.includes('\uFFFD')) {
          throw new Error('File appears to contain binary data and cannot be read as CSV');
        }
    
        try {
          const parsed = csv.parse(content, {
            skip_empty_lines: true,
            relax_quotes: true,
            relax_column_count: true,
          });
    
          // Additional validation - ensure we have some valid data
          if (parsed.length === 0) {
            throw new Error('empty file: No valid CSV data found in file');
          }
    
          // Check for malformed CSV issues and add warnings
          if (parsed.length > 1) {
            const expectedColumns = parsed[0].length;
            let inconsistentRows = 0;
    
            for (let i = 1; i < parsed.length; i++) {
              if (parsed[i].length !== expectedColumns) {
                inconsistentRows++;
              }
            }
    
            if (inconsistentRows > 0) {
              warnings.push(`CSV file has ${inconsistentRows} rows with inconsistent column count`);
            }
          }
    
          // Check for potential data quality issues
          const emptyRows = parsed.filter((row: any[]) => row.every((cell: any) => cell === '')).length;
          if (emptyRows > 0) {
            warnings.push(`Found ${emptyRows} completely empty rows`);
          }
    
          return { data: parsed, warnings: warnings.length > 0 ? warnings : undefined };
        } catch (parseError) {
          throw new Error(`Failed to parse CSV file: ${parseError instanceof Error ? parseError.message : 'Unknown parsing error'}`);
        }
      } else if (ext === '.xlsx' || ext === '.xls') {
        const workbook = new ExcelJS.Workbook();
        await workbook.xlsx.readFile(absolutePath);
        const sheetName = sheet || workbook.worksheets[0]?.name;
        const worksheet = workbook.getWorksheet(sheetName);
    
        if (!worksheet) {
          throw new Error(`Sheet "${sheetName}" not found in workbook`);
        }
    
        const data: any[][] = [];
        worksheet.eachRow((row, rowNumber) => {
          const rowData: any[] = [];
          row.eachCell((cell, colNumber) => {
            rowData[colNumber - 1] = cell.value || '';
          });
          data.push(rowData);
        });
    
        return { data, warnings: warnings.length > 0 ? warnings : undefined };
      } else {
        throw new Error('Unsupported file format. Please use .csv, .xlsx, or .xls files.');
      }
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden for behavioral disclosure. It mentions chunking for large files, which is useful, but doesn't address critical aspects like error handling (e.g., what happens if file doesn't exist), performance characteristics, memory usage, or output format details. For a file reading tool with zero annotation coverage, this leaves significant behavioral gaps.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is perfectly concise - a single sentence that communicates the core functionality with no wasted words. It's front-loaded with the main purpose and includes the key additional feature (chunking) efficiently.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's moderate complexity (file reading with chunking options), 100% schema coverage for parameters, but no annotations and no output schema, the description is minimally adequate. It covers what the tool does but lacks important context about behavior, error conditions, and output format that would help an agent use it effectively.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The description mentions 'optional chunking for large files' which relates to the offset and limit parameters, but doesn't add meaningful semantics beyond what the 100% schema coverage already provides. The schema descriptions thoroughly document each parameter's purpose and optionality, so the description adds minimal value here, meeting the baseline expectation.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose with a specific verb ('Read') and resource ('CSV or Excel file'), and mentions optional chunking for large files. However, it doesn't explicitly differentiate from sibling tools like 'read_file_chunked' or 'get_file_info', which reduces clarity about when to choose this specific tool.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives. With multiple sibling tools like 'read_file_chunked', 'get_file_info', and 'bulk_aggregate_multi_files', there's no indication of when this tool is preferred or what distinguishes it from similar file reading operations.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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/ishayoyo/excel-mcp'

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