Skip to main content
Glama

Spire.XLS MCP Server

by eiceblue
server.py30.2 kB
import json import logging import sys import os import functools from typing import Any, List, Dict, Optional from mcp.server.fastmcp import FastMCP # Import exceptions from spire_xls_mcp.utils.exceptions import ( ExcelMCPError, ValidationError, WorkbookError, SheetError, DataError, FormattingError, CalculationError, PivotError, ChartError, ConversionError ) # Import core functions from spire_xls_mcp.core.calculations import apply_formula as apply_formula_impl from spire_xls_mcp.core.chart import create_chart_in_sheet as create_chart_impl from spire_xls_mcp.core.conversion import convert_workbook as convert_workbook_impl from spire_xls_mcp.core.data import read_excel_range, write_data from spire_xls_mcp.core.formatting import format_range as format_range_func from spire_xls_mcp.core.json_operations import export_to_json as export_json_impl, import_from_json as import_json_impl from spire_xls_mcp.core.pivot import create_pivot_table as create_pivot_table_impl from spire_xls_mcp.core.sheet import ( copy_sheet, delete_sheet, rename_sheet, merge_range, unmerge_range, copy_range_operation, delete_range as delete_range_operation, apply_autofilter as apply_autofilter_impl, get_shape_image_base64 as get_shape_img_b64 ) from spire_xls_mcp.core.validation import validate_range_in_sheet_operation as validate_range_impl from spire_xls_mcp.core.workbook import get_workbook_info, create_workbook as create_workbook_impl, create_sheet as create_worksheet_impl # Configure logging logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", handlers=[ logging.StreamHandler(sys.stdout), logging.FileHandler("spire-xls-mcp.log") ], force=True ) logger = logging.getLogger("spire-xls-mcp") # Get Excel files path from environment or use default EXCEL_FILES_PATH = os.environ.get("EXCEL_FILES_PATH", "./excel_files") # Initialize FastMCP server mcp = FastMCP( "spire-xls-mcp", version="0.1.1", description="Spire.Xls MCP Server for manipulating Excel files", dependencies=["Spire.Xls.Free>=14.12.4"], env_vars={ "EXCEL_FILES_PATH": { "description": "Path to Excel files directory", "required": False, "default": EXCEL_FILES_PATH } } ) def get_excel_path(filename: str) -> str: """Get full path to Excel file. Args: filename: Name of Excel file Returns: Full path to Excel file """ if not filename or os.path.isabs(filename): return filename return os.path.join(EXCEL_FILES_PATH, filename) def tool_exception_handler(func): """A decorator to handle exceptions for all tool functions.""" @functools.wraps(func) def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except ExcelMCPError as e: logger.error(f"Error in tool '{func.__name__}': {e}") return f"Error: {str(e)}" except Exception as e: logger.error(f"An unexpected error occurred in tool '{func.__name__}': {e}") raise return wrapper @mcp.tool() @tool_exception_handler def apply_formula( filepath: str, sheet_name: str, cell: str, formula: str, ) -> str: """ Applies an Excel formula to a specified cell with verification. Parameters: filepath (str): Path to the Excel file sheet_name (str): Name of the worksheet cell (str): Cell reference where formula will be applied (e.g., "A1") formula (str): Excel formula to apply (must include "=" prefix) Returns: str: Success message confirming formula application """ full_path = get_excel_path(filepath) result = apply_formula_impl(full_path, sheet_name, cell, formula) return json.dumps(result) @mcp.tool() @tool_exception_handler def format_range( filepath: str, sheet_name: str, cell_range: str, bold: bool = False, italic: bool = False, underline: bool = False, font_size: int = None, font_color: str = None, bg_color: str = None, border_style: str = None, border_color: str = None, number_format: str = None, alignment: str = None, wrap_text: bool = False, merge_cells: bool = False, protection: Dict[str, Any] = None, conditional_format: Dict[str, Any] = None ) -> str: """ Applies formatting to a range of cells. Parameters: filepath (str): Path to the Excel file sheet_name (str): Name of the worksheet cell_range (str): Range of cells to format (e.g., "A1:C5") bold (bool, optional): Whether to apply bold formatting italic (bool, optional): Whether to apply italic formatting underline (bool, optional): Whether to apply underline formatting font_size (int, optional): Font size to apply font_color (str, optional): Font color as hex code (e.g., "#FF0000") bg_color (str, optional): Background color as hex code (e.g., "#FFFF00") border_style (str, optional): Border style (thin, medium, thick, double) border_color (str, optional): Border color as hex code (e.g., "#000000") number_format (str, optional): Excel number format code alignment (str, optional): Text alignment (left, center, right, justify) wrap_text (bool, optional): Whether to enable text wrapping merge_cells (bool, optional): Whether to merge the cells in the range protection (dict, optional): Cell protection settings dict with keys 'locked' and/or 'hidden' conditional_format (dict, optional): Conditional formatting rules dict with keys: - "type": Condition type, supported values: - "cell"/"单元格值": Cell value condition - "text"/"文本": Contains text condition - "date"/"日期": Date condition - "time_period"/"时间段": Time period condition - "average"/"平均值": Average condition - "duplicate"/"重复值": Duplicate values condition - "unique"/"唯一值": Unique values condition - "formula"/"公式": Formula condition - "top10"/"前10项": Top/Bottom items condition - "data_bar"/"数据条": Data bar condition - "color_scale"/"色阶": Color scale condition - "icon_set"/"图标集": Icon set condition - "criteria"/"operator": Comparison operator, supported values: - "greater"/"gt"/">"/"大于": Greater than - "greater_or_equal"/">="/"ge"/"大于等于": Greater than or equal - "less"/"lt"/"<"/"小于": Less than - "less_or_equal"/"<="/"le"/"小于等于": Less than or equal - "equal"/"eq"/"="/"/"等于": Equal - "not_equal"/"ne"/"!="/"<>"/"不等于": Not equal - "value"/"first_formula": First value/formula for comparison - "value2"/"second_formula": Second value/formula for comparison (optional) - "format": Dict with formatting to apply when condition is met: - "font_color": Hex color code for font - "bg_color": Hex color code for background Returns: str: Success message confirming formatting was applied """ full_path = get_excel_path(filepath) result = format_range_func( filepath=full_path, sheet_name=sheet_name, cell_range=cell_range, bold=bold, italic=italic, underline=underline, font_size=font_size, font_color=font_color, bg_color=bg_color, border_style=border_style, border_color=border_color, number_format=number_format, alignment=alignment, wrap_text=wrap_text, merge_cells=merge_cells, protection=protection, conditional_format=conditional_format ) return json.dumps(result) @mcp.tool() @tool_exception_handler def read_data_from_excel( filepath: str, sheet_name: str, cell_range: str, preview_only: bool = False ) -> str: """ Reads data from an Excel worksheet. Returns data in column-first format where cells can be accessed as data[column_letter][row_number]. Each cell contains detailed information including value, formula, style properties, etc. Parameters: filepath (str): Path to the Excel file sheet_name (str): Name of the worksheet to read from cell_range (str): Range of cells to read (e.g., "A1:D10") preview_only (bool, optional): If True, returns only preview data without full styling info Returns: dict: Column-first nested dictionary with cell data """ full_path = get_excel_path(filepath) result = read_excel_range(full_path, sheet_name, cell_range, preview_only) if not result: return "No data found in specified range" return json.dumps(result) @mcp.tool() @tool_exception_handler def write_data_to_excel( filepath: str, sheet_name: str, data: List[List], start_cell: str = "A1", ) -> str: """ Writes data to an Excel worksheet. Parameters: filepath (str): Path to the Excel file sheet_name (str): Name of the worksheet to write to data (list): List of lists containing data to write (rows of data) start_cell (str, optional): Cell to start writing from, default is "A1" Returns: str: Success message confirming data was written """ full_path = get_excel_path(filepath) result = write_data(full_path, sheet_name, data, start_cell) return json.dumps(result) @mcp.tool() @tool_exception_handler def create_workbook(filepath: str, sheet_name: str = None) -> str: """ Creates a new Excel workbook. Parameters: filepath (str): Path where the new workbook will be saved sheet_name (str, optional): Name for the initial worksheet. If not provided, default name will be used. Returns: str: Success message with the created workbook path """ full_path = get_excel_path(filepath) result = create_workbook_impl(full_path, sheet_name) return json.dumps(result) @mcp.tool() @tool_exception_handler def create_worksheet(filepath: str, sheet_name: str) -> str: """ Creates a new worksheet in an existing workbook. Parameters: filepath (str): Path to the Excel file sheet_name (str): Name for the new worksheet Returns: str: Success message confirming sheet creation """ full_path = get_excel_path(filepath) result = create_worksheet_impl(full_path, sheet_name) return json.dumps(result) @mcp.tool() @tool_exception_handler def create_chart( input_filepath: str, output_filepath: Optional[str], data_sheet_name: Optional[str], chart_sheet_name: str, data_range: str, chart_type: str, target_cell: str, chart_options: Optional[Dict[str, Any]] = None ) -> str: """ Creates a chart in a worksheet. Parameters: input_filepath (str): Path to the input Excel file. output_filepath (str,optional): Path to the output Excel file,default use input_filepath. data_sheet_name (str,optional): Name of the worksheet containing the data,default use chart_sheet_name. chart_sheet_name (str): Name of the worksheet where the chart will be inserted. data_range (str): Range of cells containing data for the chart (e.g., "A1:B10"). This is used for charts with a single data source and is ignored if `series` is provided in `chart_options`. chart_type (str): Type of chart to create ("column", "line", "pie", "bar", "scatter", "doughnut", "area", "waterfall", "column_stacked", "column_100_percent_stacked", "bubble", "funnel", "treemap", "sunburst", "histogram", "box_and_whisker"). target_cell (str): Cell where the top-left corner of the chart will be positioned (e.g., "D5"). chart_options (dict, optional): Dictionary with chart options. Supported keys include: - title (str): Chart title. - axis_titles (dict): A dictionary for axis titles, e.g., `{"x_axis_title": "X-Axis", "y_axis_title": "Y-Axis"}`. - series (list[dict]): A list of data series for multi-series charts. Each dict can have `name`, `values`, and `category_labels`. - category_labels (str): A common range for category labels for all series. - bubbles (str): range of the bubble chart. - style (dict): Chart style settings: - legend_position: "right", "left", "top", "bottom". - has_legend: bool. - has_data_labels: bool. - width: Chart width in pixels. - height: Chart height in pixels. Returns: str: Success message, e.g., "Chart created successfully". """ input_full_path = get_excel_path(input_filepath) output_full_path = get_excel_path(output_filepath) if output_filepath else input_full_path if data_sheet_name is None: data_sheet_name = chart_sheet_name result = create_chart_impl( input_filepath=input_full_path, output_filepath=output_full_path, data_sheet_name=data_sheet_name, chart_sheet_name=chart_sheet_name, data_range=data_range, chart_type=chart_type, target_cell=target_cell, chart_options=chart_options ) return json.dumps(result) @mcp.tool() @tool_exception_handler def create_pivot_table( input_filepath: str, output_filepath: str, data_sheet_name: str, pivot_sheet_name: str, pivot_name: str, data_range: str, locate_range: str, rows: List[str], values: dict[str, str], columns: List[str] = None, agg_func: str = "sum" ) -> str: """ Creates a pivot table in a worksheet. Parameters: input_filepath (str): Path to the input Excel file. output_filepath (str): Path to the output Excel file. data_sheet_name (str): Name of the worksheet containing the source data. pivot_sheet_name (str): Name of the worksheet where the pivot table will be inserted. pivot_name (str): Name for the pivot table data_range (str): Range containing source data (e.g., "A1:D10") locate_range (str): Range where the pivot table will be placed rows (list): List of field names to use as row labels values (dict): Dictionary mapping field names to aggregation functions Key: Field name Value: Aggregation function ("sum", "count", "average", "max", "min",) columns (list, optional): List of field names to use as column labels agg_func (str, optional): Default aggregation function ("sum", "count", "average","min", "max".) Returns: str: Success message confirming pivot table creation """ input_full_path = get_excel_path(input_filepath) output_full_path = get_excel_path(output_filepath) result = create_pivot_table_impl( input_filepath=input_full_path, output_filepath=output_full_path, data_sheet_name=data_sheet_name, pivot_sheet_name=pivot_sheet_name, pivot_name=pivot_name, data_range=data_range, locate_range=locate_range, rows=rows, values=values, columns=columns or [], agg_func=agg_func ) return json.dumps(result) @mcp.tool() @tool_exception_handler def copy_worksheet( filepath: str, source_sheet: str, target_sheet: str, target_filepath: str = None ) -> str: """ Copies a worksheet within the same workbook or to another workbook. Parameters: filepath (str): Path to the source Excel file source_sheet (str): Name of the worksheet to copy target_sheet (str): Name for the new worksheet copy target_filepath (str, optional): Path to the target Excel file if copying to another workbook Returns: str: Success message confirming sheet was copied """ full_path = get_excel_path(filepath) target_path = get_excel_path(target_filepath) if target_filepath else full_path result = copy_sheet(full_path, source_sheet, target_sheet, target_path) return json.dumps(result) @mcp.tool() @tool_exception_handler def delete_worksheet( filepath: str, sheet_name: str ) -> str: """ Deletes a worksheet from an Excel workbook. Parameters: filepath (str): Path to the Excel file sheet_name (str): Name of the worksheet to delete Returns: str: Success message confirming worksheet deletion """ full_path = get_excel_path(filepath) result = delete_sheet(full_path, sheet_name) return json.dumps(result) @mcp.tool() @tool_exception_handler def rename_worksheet( filepath: str, old_name: str, new_name: str ) -> str: """ Renames a worksheet in an Excel workbook. Parameters: filepath (str): Path to the Excel file old_name (str): Current name of the worksheet new_name (str): New name to assign to the worksheet Returns: str: Success message confirming the rename operation """ full_path = get_excel_path(filepath) result = rename_sheet(full_path, old_name, new_name) return json.dumps(result) @mcp.tool() @tool_exception_handler def get_workbook_metadata( filepath: str, include_ranges: bool = False ) -> str: """ Gets metadata about an Excel workbook including sheets, ranges, and file information. Parameters: filepath (str): Path to the Excel file include_ranges (bool, optional): Whether to include data about used ranges for each sheet Returns: dict: Dictionary containing workbook metadata: - filename: Name of the Excel file - sheets: List of worksheet names - size: File size in bytes - modified: Last modification timestamp - used_ranges: Dictionary mapping sheet names to their used data ranges (if include_ranges=True) """ full_path = get_excel_path(filepath) result = get_workbook_info(full_path, include_ranges=include_ranges) return json.dumps(result) @mcp.tool() @tool_exception_handler def merge_cells(filepath: str, sheet_name: str, cell_range_list: List[str]) -> str: """ Merges multiple cell ranges in a worksheet. Parameters: filepath (str): Path to the Excel file sheet_name (str): Name of the worksheet cell_range_list (list): List of cell ranges to merge (e.g., ["A1:C1", "A2:B2"]) Returns: str: Success message confirming cells were merged """ full_path = get_excel_path(filepath) result = merge_range(full_path, sheet_name, cell_range_list) return json.dumps(result) @mcp.tool() @tool_exception_handler def unmerge_cells(filepath: str, sheet_name: str, cell_range: str) -> str: """ Unmerges a range of previously merged cells. Parameters: filepath (str): Path to the Excel file sheet_name (str): Name of the worksheet cell_range (str): Range of cells to unmerge (e.g., "A1:C1") Returns: str: Success message confirming cells were unmerged """ full_path = get_excel_path(filepath) result = unmerge_range(full_path, sheet_name, cell_range) return json.dumps(result) @mcp.tool() @tool_exception_handler def copy_range( filepath: str, sheet_name: str, source_range: str, target_range: str, target_sheet: str = None, target_filepath: str = None ) -> str: """ Copies a range of cells to another location within the same workbook or to another workbook. Parameters: filepath (str): Path to the source Excel file sheet_name (str): Name of the source worksheet source_range (str): Range of cells to copy (e.g., "A1:C5") target_range (str): Target range where cells will be copied target_sheet (str, optional): Name of the target worksheet if different from source target_filepath (str, optional): Path to the target Excel file if copying to another workbook Returns: str: Success message confirming range was copied """ full_path = get_excel_path(filepath) target_path = get_excel_path(target_filepath) if target_filepath else full_path result = copy_range_operation( full_path, sheet_name, source_range, target_range, target_sheet, target_path ) return json.dumps(result) @mcp.tool() @tool_exception_handler def delete_range( filepath: str, sheet_name: str, cell_range: str, shift_direction: str = "up" ) -> str: """ Deletes a range of cells and shifts remaining cells. Parameters: filepath (str): Path to the Excel file sheet_name (str): Name of the worksheet cell_range (str): Range of cells to delete (e.g., "A1:C5") shift_direction (str, optional): Direction to shift remaining cells ("up" or "left", default "up") Returns: str: Success message describing the deletion and shift operation """ full_path = get_excel_path(filepath) result = delete_range_operation( full_path, sheet_name, cell_range, shift_direction ) return json.dumps(result) @mcp.tool() @tool_exception_handler def apply_autofilter( filepath: str, sheet_name: str, cell_range: str, filter_criteria: Dict[int, Dict[str, Any]] = None ) -> str: """ Applies autofilter to a range of cells and optionally sets filter criteria. Parameters: filepath (str): Path to the Excel file sheet_name (str): Name of the worksheet cell_range (str): Range to apply autofilter (e.g., "A1:D10") filter_criteria (dict, optional): Dictionary of filter criteria Key: Column index (0-based) Value: Dictionary with filter settings: "type": "value", "top10", "custom" "values": List of values for "value" type "operator": "<", ">", "=", ">=", "<=", "<>" for "custom" type "criteria": Criteria value for "custom" type "operator2": Second operator for "custom" type when using AND/OR conditions "criteria2": Second criteria value for "custom" type when using AND/OR conditions "is_and": Boolean indicating whether to use AND (True) or OR (False) for dual conditions "percent": True/False for "top10" type "count": Count for "top10" type "bottom": True/False for "top10" type Returns: str: Success or error message """ full_path = get_excel_path(filepath) result = apply_autofilter_impl( full_path, sheet_name, cell_range, filter_criteria ) return json.dumps(result) @mcp.tool() @tool_exception_handler def validate_excel_range( filepath: str, sheet_name: str, cell_range: str ) -> str: """ Validates if a cell range exists and is properly formatted. Parameters: filepath (str): Path to the Excel file sheet_name (str): Name of the worksheet cell_range (str): Range to validate (e.g., "A1:D10") Returns: str: Validation result including details about the actual data range in the sheet """ full_path = get_excel_path(filepath) result = validate_range_impl(full_path, sheet_name, cell_range) return json.dumps(result) @mcp.tool() @tool_exception_handler def export_to_json( filepath: str, sheet_name: str, cell_range: str, output_filepath: str, include_headers: bool = True, options: Dict[str, Any] = None ) -> str: """ Exports Excel worksheet data to a JSON file. Parameters: filepath (str): Path to the Excel file sheet_name (str): Name of the worksheet cell_range (str): Cell range to export (e.g., "A1:D10") output_filepath (str): Path to the output JSON file include_headers (bool, optional): Whether to use the first row as headers (default True) options (dict, optional): Additional options including: - pretty_print: Whether to format JSON with indentation (default True) - date_format: Format for date values (default ISO format) - encoding: File encoding (default "utf-8") - array_format: Use array format when include_headers is False (default False) Returns: str: Success message with the path to the created JSON file """ full_path = get_excel_path(filepath) output_path = get_excel_path(output_filepath) result = export_json_impl( full_path, sheet_name, cell_range, output_path, include_headers, options ) return json.dumps(result) @mcp.tool() @tool_exception_handler def import_from_json( json_filepath: str, excel_filepath: str, sheet_name: str, start_cell: str = "A1", create_sheet: bool = False, options: Dict[str, Any] = None ) -> str: """ Imports data from a JSON file to an Excel worksheet. Parameters: json_filepath (str): Path to the JSON file excel_filepath (str): Path to the Excel file sheet_name (str): Name of the target worksheet create_sheet (bool, optional): Whether to create the sheet if it doesn't exist start_cell (str, optional): Cell to start importing data (default "A1") options (dict, optional): Additional options: - encoding: File encoding (default "utf-8") - include_headers: Add header row for object arrays (default True) - date_format: Date format string for date values Returns: str: Success message with the path to the updated Excel file """ json_path = get_excel_path(json_filepath) excel_path = get_excel_path(excel_filepath) result = import_json_impl( json_path, excel_path, sheet_name, start_cell, create_sheet, options ) return json.dumps(result) @mcp.tool() @tool_exception_handler def convert_excel( filepath: str, output_filepath: str, format_type: str, options: Dict[str, Any] = None, sheet_name: str = None, cell_range: str = None ) -> str: """ Converts Excel file to different formats. Supported formats: - pdf: Convert to PDF document - csv: Convert to CSV text file (requires sheet_name) - txt: Convert to text file (requires sheet_name) - html: Convert to HTML document - image: Convert to image file (png, jpg) - xlsx/xls: Convert between Excel formats - xml: Convert to XML format Parameters: filepath (str): Path to the Excel file format_type (str): Target format type (pdf, csv, txt, html, image, xlsx, xls, xml) output_filepath (str): Path for the output file sheet_name (str, optional): Name of the worksheet (required for some formats) cell_range (str, optional): Range to convert (if not entire sheet) options (dict, optional): Format-specific options: For PDF: - orientation: "portrait" or "landscape" - paper_size: "a4", "letter", etc. - fit_to_page: true/false For CSV/TXT: - delimiter: Character to use as delimiter (default ",") - encoding: File encoding (default "utf-8") For HTML: - image_embedded: true/false - image_locationType: Controls image position mode For Image: - image_type: "png", "jpg", "original" Returns: str: Success message or error description """ full_path = get_excel_path(filepath) output_path = get_excel_path(output_filepath) result = convert_workbook_impl( filepath=full_path, output_filepath=output_path, format_type=format_type, options=options, sheet_name=sheet_name, cell_range=cell_range ) return json.dumps(result) @mcp.tool() @tool_exception_handler def get_shape_image_base64( filepath: str, sheet_name: str, shape_name: str = None, shape_index: int = None ) -> str: """ Gets the image of a Shape in Excel as a base64 string. temp support PrstGeom Shapes and Pictures Parameters: filepath (str): Path to the Excel file sheet_name (str): Name of the worksheet containing the shape shape_name (str, optional): Name of the shape to export shape_index (int, optional): Index of the shape in the worksheet (0-based) Returns: str: Base64 string representation of the shape image Note: Either shape_name or shape_index must be provided. If the worksheet has no shapes or the specified shape doesn't exist, an error will be returned. """ full_path = get_excel_path(filepath) result = get_shape_img_b64( full_path, sheet_name, shape_name, shape_index ) return json.dumps(result) async def run_server(): """Run the Spire.Xls MCP Server.""" try: logger.info(f"Starting Spire.Xls MCP Server (files directory: {EXCEL_FILES_PATH})") await mcp.run_sse_async() except KeyboardInterrupt: logger.info("Server stopped by user") await mcp.shutdown() except Exception as e: logger.error(f"Server failed: {e}") raise finally: logger.info("Server shutdown complete")

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/eiceblue/spire-xls-mcp-server'

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