Skip to main content
Glama
hyunjae-labs

xlwings Excel MCP Server

by hyunjae-labs

get_merged_cells

Identify merged cell ranges in Excel worksheets to understand spreadsheet structure and formatting. Specify the worksheet name and session ID for efficient retrieval.

Instructions

Get merged cells in a worksheet.

Args:
    sheet_name: Name of worksheet
    session_id: Session ID from open_workbook (preferred)
    filepath: Path to Excel file (legacy, deprecated)
    
Note: Use session_id for better performance. filepath parameter is deprecated.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
sheet_nameYes
session_idNo
filepathNo

Implementation Reference

  • Primary MCP tool handler and registration for 'get_merged_cells'. Dispatches to session-based or legacy filepath-based implementations using xlwings, handles errors and JSON serialization.
    @mcp.tool()
    def get_merged_cells(
        sheet_name: str,
        session_id: Optional[str] = None,
        filepath: Optional[str] = None
    ) -> str:
        """
        Get merged cells in a worksheet.
        
        Args:
            sheet_name: Name of worksheet
            session_id: Session ID from open_workbook (preferred)
            filepath: Path to Excel file (legacy, deprecated)
            
        Note: Use session_id for better performance. filepath parameter is deprecated.
        """
        try:
            # Support both new (session_id) and old (filepath) API
            if session_id:
                # New API: use session
                session = SESSION_MANAGER.get_session(session_id)
                if not session:
                    return ERROR_TEMPLATES['SESSION_NOT_FOUND'].format(
                        session_id=session_id, 
                        ttl=10  # Default TTL is 10 minutes (600 seconds)
                    )
                
                with session.lock:
                    from xlwings_mcp.xlwings_impl.range_xlw import get_merged_cells_xlw_with_wb
                    result = get_merged_cells_xlw_with_wb(session.workbook, sheet_name)
                    if "error" in result:
                        return f"Error: {result['error']}"
                    import json
                    return json.dumps(result, indent=2, default=str)
            elif filepath:
                # Legacy API: backwards compatibility
                logger.warning("Using deprecated filepath parameter. Please use session_id instead.")
                full_path = get_excel_path(filepath)
                from xlwings_mcp.xlwings_impl.range_xlw import get_merged_cells_xlw
                result = get_merged_cells_xlw(full_path, sheet_name)
                if "error" in result:
                    return f"Error: {result['error']}"
                import json
                return json.dumps(result, indent=2, default=str)
            else:
                return ERROR_TEMPLATES['PARAMETER_MISSING'].format(
                    param1='session_id',
                    param2='filepath'
                )
            
        except (ValidationError, SheetError) as e:
            return f"Error: {str(e)}"
        except Exception as e:
            logger.error(f"Error getting merged cells: {e}")
            raise
  • Core implementation for legacy filepath-based get_merged_cells. Opens workbook, scans used range cell-by-cell using COM API to detect and extract merged cell ranges with details.
    def get_merged_cells_xlw(filepath: str, sheet_name: str) -> Dict[str, Any]:
        """
        Get all merged cell ranges in a worksheet using xlwings.
        
        Args:
            filepath: Path to Excel file
            sheet_name: Name of worksheet
            
        Returns:
            Dict with list of merged ranges or error
        """
        app = None
        wb = None
    
        # Initialize COM for thread safety (Windows)
        _com_initialize()
    
        try:
            logger.info(f"Getting merged cells in {sheet_name}")
            
            # Check if file exists
            if not os.path.exists(filepath):
                return {"error": f"File not found: {filepath}"}
            
            # Open Excel app and workbook
            app = xw.App(visible=False, add_book=False)
            wb = app.books.open(filepath)
            
            # Check if sheet exists
            sheet_names = [s.name for s in wb.sheets]
            if sheet_name not in sheet_names:
                return {"error": f"Sheet '{sheet_name}' not found"}
            
            sheet = wb.sheets[sheet_name]
            
            # Get all merged ranges
            merged_ranges = []
            
            # Use a simpler approach with COM API
            try:
                # Access the worksheet's COM object directly
                ws_com = sheet.api
                
                # Check if there are any merged cells in the sheet
                used_range = sheet.used_range
                if used_range:
                    # Track processed merged areas to avoid duplicates
                    processed_areas = set()
                    
                    # Get dimensions of used range
                    max_row = used_range.last_cell.row
                    max_col = used_range.last_cell.column
                    
                    # Iterate through the used range
                    for row in range(1, max_row + 1):
                        for col in range(1, max_col + 1):
                            try:
                                # Get the cell using COM API
                                cell_com = ws_com.Cells(row, col)
                                
                                # Check if this cell is part of a merged range
                                if cell_com.MergeCells:
                                    # Get the merge area
                                    merge_area = cell_com.MergeArea
                                    merge_address = merge_area.Address.replace("$", "")
                                    
                                    # Skip if we've already processed this merged area
                                    if merge_address in processed_areas:
                                        continue
                                    
                                    # Add to processed areas
                                    processed_areas.add(merge_address)
                                    
                                    # Get details about the merged range
                                    first_row = merge_area.Row
                                    first_col = merge_area.Column
                                    row_count = merge_area.Rows.Count
                                    col_count = merge_area.Columns.Count
                                    
                                    # Calculate last cell
                                    last_row = first_row + row_count - 1
                                    last_col = first_col + col_count - 1
                                    
                                    # Create cell addresses
                                    def get_column_letter(col_idx):
                                        """Convert column index to Excel column string"""
                                        result = ""
                                        while col_idx > 0:
                                            col_idx -= 1
                                            result = chr(col_idx % 26 + ord('A')) + result
                                            col_idx //= 26
                                        return result
                                    
                                    start_addr = f"{get_column_letter(first_col)}{first_row}"
                                    end_addr = f"{get_column_letter(last_col)}{last_row}"
                                    
                                    merged_ranges.append({
                                        "range": merge_address,
                                        "start": start_addr,
                                        "end": end_addr,
                                        "rows": row_count,
                                        "columns": col_count
                                    })
                            except:
                                # Skip cells that cause errors
                                continue
            except Exception as e:
                logger.warning(f"Could not get merged cells: {e}")
            
            logger.info(f"βœ… Found {len(merged_ranges)} merged ranges")
            return {
                "merged_ranges": merged_ranges,
                "count": len(merged_ranges),
                "sheet": sheet_name
            }
            
        except Exception as e:
            logger.error(f"❌ Error getting merged cells: {str(e)}")
            return {"error": str(e)}
            
        finally:
            if wb:
                wb.close()
            if app:
                app.quit()
  • Efficient session-based helper for get_merged_cells using existing open workbook. Avoids reopening files; identical COM-based scanning logic.
    def get_merged_cells_xlw_with_wb(wb, sheet_name: str) -> Dict[str, Any]:
        """
        Session-based merged cells retrieval using existing workbook object.
        
        Args:
            wb: Workbook object from session
            sheet_name: Name of worksheet
            
        Returns:
            Dict with list of merged ranges or error
        """
        try:
            logger.info(f"πŸ“Š Getting merged cells in {sheet_name}")
            
            # Check if sheet exists
            sheet_names = [s.name for s in wb.sheets]
            if sheet_name not in sheet_names:
                return {"error": f"Sheet '{sheet_name}' not found"}
            
            sheet = wb.sheets[sheet_name]
            
            # Get all merged ranges
            merged_ranges = []
            
            # Use a simpler approach with COM API
            try:
                # Access the worksheet's COM object directly
                ws_com = sheet.api
                
                # Check if there are any merged cells in the sheet
                used_range = sheet.used_range
                if used_range:
                    # Track processed merged areas to avoid duplicates
                    processed_areas = set()
                    
                    # Get dimensions of used range
                    max_row = used_range.last_cell.row
                    max_col = used_range.last_cell.column
                    
                    # Iterate through the used range
                    for row in range(1, max_row + 1):
                        for col in range(1, max_col + 1):
                            try:
                                # Get the cell using COM API
                                cell_com = ws_com.Cells(row, col)
                                
                                # Check if this cell is part of a merged range
                                if cell_com.MergeCells:
                                    # Get the merge area
                                    merge_area = cell_com.MergeArea
                                    merge_address = merge_area.Address.replace("$", "")
                                    
                                    # Skip if we've already processed this merged area
                                    if merge_address in processed_areas:
                                        continue
                                    
                                    # Add to processed areas
                                    processed_areas.add(merge_address)
                                    
                                    # Get details about the merged range
                                    first_row = merge_area.Row
                                    first_col = merge_area.Column
                                    row_count = merge_area.Rows.Count
                                    col_count = merge_area.Columns.Count
                                    
                                    # Calculate last cell
                                    last_row = first_row + row_count - 1
                                    last_col = first_col + col_count - 1
                                    
                                    # Create cell addresses
                                    def get_column_letter(col_idx):
                                        """Convert column index to Excel column string"""
                                        result = ""
                                        while col_idx > 0:
                                            col_idx -= 1
                                            result = chr(col_idx % 26 + ord('A')) + result
                                            col_idx //= 26
                                        return result
                                    
                                    start_addr = f"{get_column_letter(first_col)}{first_row}"
                                    end_addr = f"{get_column_letter(last_col)}{last_row}"
                                    
                                    merged_ranges.append({
                                        "range": merge_address,
                                        "start": start_addr,
                                        "end": end_addr,
                                        "rows": row_count,
                                        "columns": col_count
                                    })
                            except:
                                # Skip cells that cause errors
                                continue
            except Exception as e:
                logger.warning(f"Could not get merged cells: {e}")
            
            logger.info(f"βœ… Found {len(merged_ranges)} merged ranges")
            return {
                "merged_ranges": merged_ranges,
                "count": len(merged_ranges),
                "sheet": sheet_name
            }
            
        except Exception as e:
            logger.error(f"❌ Error getting merged cells: {str(e)}")
            return {"error": str(e)}
  • Wrapper function in sheet.py that calls the xlwings implementation and converts result to string.
    def get_merged_ranges(filepath: str, sheet_name: str) -> str:
        """Get merged cells in a worksheet."""
        result = get_merged_cells_xlw(filepath, sheet_name)
        if "error" in result:
            raise SheetError(result["error"])
        return str(result)

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/hyunjae-labs/xlwings-mcp-server'

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