UNPKG

aspose.cells.mcp

Version:

Excel MCP - AI-powered Excel automation server for Excel AI, Excel Formula MCP, spreadsheets MCP, and Excel automation using Aspose.Cells for Node.js

315 lines (275 loc) 9.17 kB
/** * Get Workbook Info Service * MCP service for retrieving comprehensive Excel workbook metadata */ import AsposeCells from 'aspose.cells.node'; import fs from 'fs'; import path from 'path'; import { getAbsolutePath, handleExcelFileError } from '../core/utils.js'; interface GetWorkbookInfoArgs { filepath: string; includePreview?: boolean; previewColumns?: number; } interface SheetPreview { headers: any[]; firstRows: any[][]; middleSample: string; lastRow: any[]; } interface SheetInfo { name: string; index: number; visible: boolean; protected: boolean; rows: number; cols: number; usedRange: string; nonEmptyCells: number; hasFormulas: boolean; hasPivotTables: boolean; hasCharts: number; preview?: SheetPreview; } interface WorkbookInfo { filename: string; path: string; size: number; sizeReadable: string; modified: string; created: string; author: string; lastModifiedBy: string; application: string; company: string; sheetCount: number; hasVBA: boolean; sheets: SheetInfo[]; namedRanges?: string[]; externalLinks: number; } interface ServiceResponse { content: Array<{ type: 'text'; text: string; }>; [key: string]: unknown; } // Helper functions const formatFileSize = (bytes: number): string => { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; }; const getUsedRange = (cells: any): string => { const maxRow = cells.getMaxRow(); const maxCol = cells.getMaxColumn(); if (maxRow === -1 || maxCol === -1) return ''; const startCell = AsposeCells.CellsHelper.cellIndexToName(0, 0); const endCell = AsposeCells.CellsHelper.cellIndexToName(maxRow, maxCol); return `${startCell}:${endCell}`; }; const getNonEmptyCellCount = (cells: any): number => { let count = 0; const maxRow = cells.getMaxRow(); const maxCol = cells.getMaxColumn(); if (maxRow === -1 || maxCol === -1) return 0; for (let row = 0; row <= maxRow; row++) { for (let col = 0; col <= maxCol; col++) { const cell = cells.get(row, col); if (cell && !cell.isNull()) { count++; } } } return count; }; const hasFormulas = (cells: any): boolean => { const maxRow = cells.getMaxRow(); const maxCol = cells.getMaxColumn(); if (maxRow === -1 || maxCol === -1) return false; for (let row = 0; row <= Math.min(maxRow, 100); row++) { for (let col = 0; col <= Math.min(maxCol, 50); col++) { const cell = cells.get(row, col); if (cell && cell.getFormula && cell.getFormula()) { return true; } } } return false; }; const hasPivotTables = (sheet: any): boolean => { const pivotTables = sheet.getPivotTables(); return pivotTables && pivotTables.getCount() > 0; }; const getChartsCount = (sheet: any): number => { const charts = sheet.getCharts(); return charts ? charts.getCount() : 0; }; const getSheetPreview = (cells: any, maxColumns: number): SheetPreview => { const maxRow = cells.getMaxRow(); const maxCol = Math.min(cells.getMaxColumn(), maxColumns - 1); if (maxRow === -1 || maxCol === -1) { return { headers: [], firstRows: [], middleSample: "No data", lastRow: [] }; } const preview: SheetPreview = { headers: [], firstRows: [], middleSample: "", lastRow: [] }; // Get headers (first row) for (let col = 0; col <= maxCol; col++) { const cell = cells.get(0, col); preview.headers.push(cell ? cell.getValue() : ''); } // Get first 2 rows of data (skip header) for (let row = 1; row <= Math.min(2, maxRow); row++) { const rowData: any[] = []; for (let col = 0; col <= maxCol; col++) { const cell = cells.get(row, col); rowData.push(cell ? cell.getValue() : ''); } preview.firstRows.push(rowData); } // Middle sample info if (maxRow > 3) { preview.middleSample = `... ${maxRow - 2} rows ...`; } // Get last row if (maxRow > 2) { const lastRowData: any[] = []; for (let col = 0; col <= maxCol; col++) { const cell = cells.get(maxRow, col); lastRowData.push(cell ? cell.getValue() : ''); } preview.lastRow = lastRowData; } return preview; }; const getSheetProtectionStatus = (sheet: any): boolean => { const protection = sheet.getProtection(); if (protection && typeof protection.isProtected === 'function') { return protection.isProtected(); } // Alternative check return protection && protection.isProtected === true; }; const getSheetVisibility = (sheet: any): boolean => { const visibilityType = sheet.getVisibilityType(); return visibilityType === AsposeCells.VisibilityType.Visible; }; export const getWorkbookInfoService = { // Tool definition definition: { name: 'get_workbook_info', description: 'Get comprehensive metadata about Excel workbook including sheets, with optional data preview', inputSchema: { type: 'object', properties: { filepath: { type: 'string', description: 'Path to the Excel file', }, includePreview: { type: 'boolean', description: 'Include data preview (first 2 rows, last row, and sample from middle)', default: false, }, previewColumns: { type: 'integer', description: 'Maximum number of columns to include in preview', default: 10, minimum: 1, maximum: 50, }, }, required: ['filepath'], }, }, // Implementation async handler(args: GetWorkbookInfoArgs): Promise<ServiceResponse> { let fullPath: string = ''; try { fullPath = getAbsolutePath(args.filepath); if (!fs.existsSync(fullPath)) { throw new Error(`File not found: ${fullPath}`); } const workbook = new AsposeCells.Workbook(fullPath); const worksheets = workbook.getWorksheets(); const fileStats = fs.statSync(fullPath); // Get document properties const builtInProps = workbook.getBuiltInDocumentProperties(); const customProps = workbook.getCustomDocumentProperties(); const info: WorkbookInfo = { filename: path.basename(fullPath), path: fullPath, size: fileStats.size, sizeReadable: formatFileSize(fileStats.size), modified: fileStats.mtime.toISOString(), created: fileStats.birthtime ? fileStats.birthtime.toISOString() : fileStats.mtime.toISOString(), author: builtInProps.getAuthor() || 'Unknown', lastModifiedBy: builtInProps.getLastSavedBy() || 'Unknown', application: builtInProps.getNameOfApplication() || 'Unknown', company: builtInProps.getCompany() || 'Unknown', sheetCount: worksheets.getCount(), hasVBA: workbook.getVbaProject() ? true : false, sheets: [], externalLinks: 0 }; // Add named ranges if any const names = workbook.getWorksheets().getNames(); if (names && names.getCount() > 0) { info.namedRanges = []; for (let i = 0; i < names.getCount(); i++) { const name = names.get(i); info.namedRanges.push(name.getFullText() || name.toString()); } } // Add external links count const externalLinks = workbook.getWorksheets().getExternalLinks(); info.externalLinks = externalLinks ? externalLinks.getCount() : 0; // Process each worksheet for (let i = 0; i < worksheets.getCount(); i++) { const sheet = worksheets.get(i); const cells = sheet.getCells(); const sheetInfo: SheetInfo = { name: sheet.getName(), index: i, visible: getSheetVisibility(sheet), protected: getSheetProtectionStatus(sheet), rows: cells.getMaxRow() + 1, cols: cells.getMaxColumn() + 1, usedRange: getUsedRange(cells), nonEmptyCells: getNonEmptyCellCount(cells), hasFormulas: hasFormulas(cells), hasPivotTables: hasPivotTables(sheet), hasCharts: getChartsCount(sheet), }; // Add preview if requested if (args.includePreview) { sheetInfo.preview = getSheetPreview(cells, args.previewColumns || 10); } info.sheets.push(sheetInfo); } return { content: [ { type: 'text', text: JSON.stringify(info, null, 2), }, ], }; } catch (error) { const err = error instanceof Error ? error : new Error(String(error)); handleExcelFileError(err, fullPath || args.filepath, args.filepath, 'getting workbook info'); } } };