from datetime import datetime, timezone
from typing import List, Dict, Any, Optional
import json
from mcp.server.fastmcp import FastMCP
from excel_ops import ExcelManager
# Initialize MCP server
mcp = FastMCP("Excel-Tools")
@mcp.tool()
def get_excel_sheet_names(filepath: str) -> List[str]:
"""
Get the list of worksheet names from an Excel file.
Args:
filepath: Absolute path to the Excel file.
"""
try:
return ExcelManager.get_sheet_names(filepath)
except Exception as e:
return [f"Error: {str(e)}"]
@mcp.tool()
def read_excel_sheet(filepath: str, sheet_name: str) -> List[Dict[str, Any]]:
"""
Read data from a specific worksheet in an Excel file.
Returns a list of records (dictionaries).
Args:
filepath: Absolute path to the Excel file.
sheet_name: Name of the sheet to read.
"""
try:
return ExcelManager.read_sheet(filepath, sheet_name)
except Exception as e:
return [{"Error": str(e)}]
@mcp.tool()
def read_all_excel_sheets(filepath: str) -> Dict[str, List[Dict[str, Any]]]:
"""
Read all worksheets from an Excel file.
Returns a dictionary where keys are sheet names and values are lists of records.
Args:
filepath: Absolute path to the Excel file.
"""
try:
return ExcelManager.read_sheet(filepath, sheet_name=None)
except Exception as e:
return {"Error": str(e)}
@mcp.tool()
def create_excel_file(filepath: str, data: str, sheet_name: str = "Sheet1") -> str:
"""
Create a new Excel file with the provided data.
Args:
filepath: Target path for the new Excel file.
data: JSON string representing a list of dictionaries (records).
sheet_name: Name of the initial sheet (default: "Sheet1").
"""
try:
# Parse JSON string back to list of dicts
records = json.loads(data)
if not isinstance(records, list):
return "Error: Data must be a JSON list of objects."
ExcelManager.create_file(filepath, records, sheet_name)
return f"Successfully created {filepath} with sheet '{sheet_name}'"
except Exception as e:
return f"Error creating file: {str(e)}"
@mcp.tool()
def add_excel_sheet(filepath: str, data: str, sheet_name: str) -> str:
"""
Add a new worksheet to an existing Excel file (or append if file exists).
Args:
filepath: Path to the existing Excel file.
data: JSON string representing a list of dictionaries.
sheet_name: Name of the new sheet.
"""
try:
records = json.loads(data)
if not isinstance(records, list):
return "Error: Data must be a JSON list of objects."
ExcelManager.add_sheet(filepath, records, sheet_name)
return f"Successfully added sheet '{sheet_name}' to {filepath}"
except Exception as e:
return f"Error adding sheet: {str(e)}"
@mcp.tool()
def analyze_excel_structure(filepath: str, output_path: Optional[str] = None) -> str:
"""
Analyze the structure of an Excel file (columns, types, row counts).
Optionally exports the structure to a JSON file.
Args:
filepath: Path to the Excel file to analyze.
output_path: (Optional) Path to save the analysis JSON.
"""
try:
if output_path:
ExcelManager.export_structure(filepath, output_path)
return f"Analysis saved to {output_path}"
else:
structure = ExcelManager.analyze_structure(filepath)
return json.dumps(structure, indent=2, ensure_ascii=False)
except Exception as e:
return f"Error analyzing file: {str(e)}"
@mcp.tool()
def set_excel_dimensions(
filepath: str,
sheet_names: Optional[List[str]] = None,
column_widths: Optional[Dict[str, float]] = None,
row_heights: Optional[Dict[str, float]] = None,
) -> str:
"""Set column widths/row heights across one or more sheets.
Args:
filepath: Absolute path to the Excel file.
sheet_names: List of sheet names; null means all sheets.
column_widths: Mapping of column letter/index to width, e.g. {"A": 20, "C": 12}.
row_heights: Mapping of row index (as string) to height, e.g. {"1": 25, "2": 18}.
"""
try:
return ExcelManager.set_dimensions(filepath, sheet_names, column_widths, row_heights)
except Exception as e:
return f"Error: {str(e)}"
@mcp.tool()
def merge_excel_cells(
filepath: str,
ranges: List[str],
sheet_names: Optional[List[str]] = None,
unmerge: bool = False,
) -> str:
"""Merge or unmerge cells (e.g. ['A1:C1'])."""
try:
return ExcelManager.merge_cells(filepath, sheet_names, ranges, unmerge)
except Exception as e:
return f"Error: {str(e)}"
@mcp.tool()
def set_excel_font(
filepath: str,
targets: List[str],
sheet_names: Optional[List[str]] = None,
name: Optional[str] = None,
size: Optional[float] = None,
bold: Optional[bool] = None,
italic: Optional[bool] = None,
underline: Optional[str] = None,
color: Optional[str] = None,
) -> str:
"""Set font style for cells/ranges. targets supports single address or ranges like 'A1:C3'."""
try:
return ExcelManager.set_font(filepath, sheet_names, targets, name, size, bold, italic, underline, color)
except Exception as e:
return f"Error: {str(e)}"
@mcp.tool()
def set_excel_fill(
filepath: str,
targets: List[str],
sheet_names: Optional[List[str]] = None,
fill_color: Optional[str] = None,
pattern: str = "solid",
clear: bool = False,
) -> str:
"""Set/clear cell background fill."""
try:
return ExcelManager.set_fill(filepath, sheet_names, targets, fill_color, pattern, clear)
except Exception as e:
return f"Error: {str(e)}"
@mcp.tool()
def set_excel_number_format(
filepath: str,
targets: List[str],
number_format: str,
sheet_names: Optional[List[str]] = None,
) -> str:
"""Set number format (e.g. '0.00', 'yyyy-mm-dd', '@')."""
try:
return ExcelManager.set_number_format(filepath, sheet_names, targets, number_format)
except Exception as e:
return f"Error: {str(e)}"
@mcp.tool()
def set_excel_border(
filepath: str,
targets: List[str],
sheet_names: Optional[List[str]] = None,
style: str = "thin",
color: Optional[str] = None,
sides: Optional[List[str]] = None,
remove: bool = False,
) -> str:
"""Add or remove borders. sides defaults to ['left','right','top','bottom']."""
try:
return ExcelManager.set_border(filepath, sheet_names, targets, style, color, sides, remove)
except Exception as e:
return f"Error: {str(e)}"
@mcp.tool()
def set_excel_alignment(
filepath: str,
targets: List[str],
sheet_names: Optional[List[str]] = None,
horizontal: Optional[str] = None,
vertical: Optional[str] = None,
wrap_text: Optional[bool] = None,
text_rotation: Optional[int] = None,
) -> str:
"""Set alignment (horizontal/vertical/wrap/text_rotation)."""
try:
return ExcelManager.set_alignment(filepath, sheet_names, targets, horizontal, vertical, wrap_text, text_rotation)
except Exception as e:
return f"Error: {str(e)}"
@mcp.tool()
def apply_excel_operations(filepath: str, operations: str) -> str:
"""Apply multiple operations in one shot.
Args:
filepath: Absolute path to the Excel file.
operations: JSON string of operation list. Each item must include 'op'.
"""
try:
ops = json.loads(operations)
if not isinstance(ops, list):
return "Error: operations must be a JSON array"
return ExcelManager.apply_operations(filepath, ops)
except Exception as e:
return f"Error: {str(e)}"
@mcp.tool()
def freeze_excel_panes(
filepath: str,
cell: Optional[str] = None,
sheet_names: Optional[List[str]] = None,
) -> str:
"""Freeze panes at a cell (e.g. 'B2'). Use cell=null to clear."""
try:
return ExcelManager.freeze_panes(filepath, sheet_names, cell)
except Exception as e:
return f"Error: {str(e)}"
@mcp.tool()
def set_excel_auto_filter(
filepath: str,
ref: Optional[str] = None,
sheet_names: Optional[List[str]] = None,
clear: bool = False,
) -> str:
"""Set/clear auto-filter range like 'A1:D200'."""
try:
return ExcelManager.set_auto_filter(filepath, sheet_names, ref, clear)
except Exception as e:
return f"Error: {str(e)}"
@mcp.tool()
def protect_excel_sheet(
filepath: str,
protect: bool = True,
sheet_names: Optional[List[str]] = None,
password: Optional[str] = None,
) -> str:
"""Enable/disable worksheet protection. Optionally set password."""
try:
return ExcelManager.set_sheet_protection(filepath, sheet_names, protect, password)
except Exception as e:
return f"Error: {str(e)}"
@mcp.tool()
def set_excel_hidden(
filepath: str,
sheet_names: Optional[List[str]] = None,
hide_rows: Optional[List[int]] = None,
hide_cols: Optional[List[str]] = None,
hidden: bool = True,
) -> str:
"""Hide/unhide rows and columns."""
try:
return ExcelManager.set_hidden(filepath, sheet_names, hide_rows, hide_cols, hidden)
except Exception as e:
return f"Error: {str(e)}"
@mcp.tool()
def set_excel_conditional_formatting(
filepath: str,
ranges: List[str],
rule: Optional[str] = None,
sheet_names: Optional[List[str]] = None,
clear: bool = False,
) -> str:
"""Add or clear conditional formatting.
Args:
ranges: Target ranges like ['A2:A100']
rule: JSON string describing a rule (required unless clear=true)
"""
try:
rule_obj = None
if rule is not None:
rule_obj = json.loads(rule)
return ExcelManager.add_conditional_formatting(filepath, sheet_names, ranges, rule_obj, clear)
except Exception as e:
return f"Error: {str(e)}"
def main():
mcp.run(transport="stdio")
if __name__ == "__main__":
main()