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
text/typescript
/**
* 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');
}
}
};