Skip to main content
Glama
hyunjae-labs

xlwings Excel MCP Server

by hyunjae-labs

get_merged_cells

Retrieve merged cell ranges in an Excel worksheet by specifying the filepath and sheet name, enabling easy extraction and manipulation of structured data.

Instructions

Get merged cells in a worksheet.

Input Schema

NameRequiredDescriptionDefault
filepathYes
sheet_nameYes

Input Schema (JSON Schema)

{ "properties": { "filepath": { "title": "Filepath", "type": "string" }, "sheet_name": { "title": "Sheet Name", "type": "string" } }, "required": [ "filepath", "sheet_name" ], "title": "get_merged_cellsArguments", "type": "object" }

Implementation Reference

  • MCP tool handler for 'get_merged_cells'. Supports session-based (preferred) and legacy filepath access. Calls appropriate xlwings_impl helper, serializes result to JSON string.
    @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 mode. Opens workbook with xlwings, iterates over used range using COM API to detect merged cells, extracts range details (address, start/end, rows/columns), returns structured dict.
    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()
  • Session-optimized helper using existing workbook object. Identical scanning logic to legacy version but avoids reopening file for better performance in session mode.
    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)}
  • MCP tool registration via FastMCP decorator @mcp.tool() on the handler function.
    @mcp.tool()

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