Skip to main content
Glama
hyunjae-labs

xlwings Excel MCP Server

by hyunjae-labs

get_data_validation_info

Retrieve all data validation rules from an Excel worksheet to identify which cells have validation and what types are applied.

Instructions

Get all data validation rules in a worksheet.

This tool helps identify which cell ranges have validation rules
and what types of validation are applied.

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.
    
Returns:
    JSON string containing all validation rules in the worksheet

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
sheet_nameYes
session_idNo
filepathNo

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Implementation Reference

  • MCP tool registration and handler wrapper. Dispatches to appropriate implementation based on session_id or filepath, supports legacy API.
    @mcp.tool()
    def get_data_validation_info(
        sheet_name: str,
        session_id: Optional[str] = None,
        filepath: Optional[str] = None
    ) -> str:
        """
        Get all data validation rules in a worksheet.
        
        This tool helps identify which cell ranges have validation rules
        and what types of validation are applied.
        
        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.
            
        Returns:
            JSON string containing all validation rules in the worksheet
        """
        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.validation_xlw import get_data_validation_info_xlw_with_wb
                    result = get_data_validation_info_xlw_with_wb(session.workbook, sheet_name)
            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.validation_xlw import get_data_validation_info_xlw
                result = get_data_validation_info_xlw(full_path, sheet_name)
            else:
                return ERROR_TEMPLATES['PARAMETER_MISSING'].format(
                    param1='session_id',
                    param2='filepath'
                )
            
            if "error" in result:
                return f"Error: {result['error']}"
            import json
            return json.dumps(result, indent=2, default=str)
            
        except (ValidationError, DataError) as e:
            return f"Error: {str(e)}"
        except Exception as e:
            logger.error(f"Error getting validation info: {e}")
            raise
  • Primary handler implementation for file-path based access. Opens workbook, scans used range for data validation rules using Excel COM API, extracts details like type, operator, formulas.
    def get_data_validation_info_xlw(
        filepath: str,
        sheet_name: str
    ) -> Dict[str, Any]:
        """
        Get all data validation rules in a worksheet using xlwings.
        
        This tool helps identify which cell ranges have validation rules
        and what types of validation are applied.
        
        Args:
            filepath: Path to Excel file
            sheet_name: Name of worksheet
            
        Returns:
            Dict containing all validation rules in the worksheet
        """
        app = None
        wb = None
    
        # Initialize COM for thread safety (Windows)
        _com_initialize()
    
        try:
            logger.info(f"Getting data validation info for {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]
            
            # Access worksheet COM object for validation
            ws_com = sheet.api
            
            validation_rules = []
            processed_ranges = set()
            
            # Get used range to scan for validation
            try:
                used_range = sheet.used_range
                if used_range:
                    # Iterate through cells to find validation rules
                    # Note: This is more efficient than checking every cell
                    # We'll check representative cells and expand to find full ranges
                    
                    max_row = used_range.last_cell.row
                    max_col = used_range.last_cell.column
                    
                    # Sample cells to check (every 5th cell for efficiency)
                    for row in range(1, max_row + 1, 5):
                        for col in range(1, max_col + 1, 5):
                            try:
                                cell = sheet.range((row, col))
                                cell_address = cell.address.replace('$', '')
                                
                                # Skip if already processed
                                if cell_address in processed_ranges:
                                    continue
                                
                                # Check if cell has validation using COM API
                                cell_com = cell.api
                                validation = cell_com.Validation
                                
                                # Check if validation exists (Type > 0 means validation is present)
                                if hasattr(validation, 'Type') and validation.Type > 0:
                                    # Found validation, now find the full range
                                    validation_info = {
                                        "range": cell_address,
                                        "type": get_validation_type_name(validation.Type),
                                        "operator": None,
                                        "formula1": None,
                                        "formula2": None,
                                        "error_message": None,
                                        "input_message": None,
                                        "show_error": True,
                                        "show_input": True
                                    }
                                    
                                    # Get validation details
                                    try:
                                        if hasattr(validation, 'Operator'):
                                            validation_info["operator"] = get_operator_name(validation.Operator)
                                    except:
                                        pass
                                    
                                    try:
                                        if hasattr(validation, 'Formula1'):
                                            validation_info["formula1"] = str(validation.Formula1)
                                    except:
                                        pass
                                    
                                    try:
                                        if hasattr(validation, 'Formula2'):
                                            validation_info["formula2"] = str(validation.Formula2)
                                    except:
                                        pass
                                    
                                    try:
                                        if hasattr(validation, 'ErrorMessage'):
                                            validation_info["error_message"] = validation.ErrorMessage
                                    except:
                                        pass
                                    
                                    try:
                                        if hasattr(validation, 'InputMessage'):
                                            validation_info["input_message"] = validation.InputMessage
                                    except:
                                        pass
                                    
                                    try:
                                        if hasattr(validation, 'ShowError'):
                                            validation_info["show_error"] = bool(validation.ShowError)
                                    except:
                                        pass
                                    
                                    try:
                                        if hasattr(validation, 'ShowInput'):
                                            validation_info["show_input"] = bool(validation.ShowInput)
                                    except:
                                        pass
                                    
                                    # Try to find the full range with this validation
                                    # by checking adjacent cells
                                    full_range = expand_validation_range(sheet, row, col, validation)
                                    validation_info["range"] = full_range
                                    
                                    # Mark cells as processed
                                    for r in range(row, row + 10):
                                        for c in range(col, col + 10):
                                            processed_ranges.add(f"{chr(64+c)}{r}")
                                    
                                    validation_rules.append(validation_info)
                                    
                            except Exception as e:
                                # Cell might not have validation, continue
                                continue
                    
            except Exception as e:
                logger.warning(f"Error scanning for validation rules: {e}")
            
            # Return validation information
            result = {
                "sheet": sheet_name,
                "validation_count": len(validation_rules),
                "validation_rules": validation_rules
            }
            
            logger.info(f"✅ Found {len(validation_rules)} validation rules in {sheet_name}")
            return result
            
        except Exception as e:
            logger.error(f"Error getting validation info: {e}")
            return {"error": str(e)}
            
        finally:
            if wb:
                wb.close()
            if app:
                app.quit()
  • Session-optimized handler using pre-opened workbook. Identical scanning logic, avoids reopening file.
    def get_data_validation_info_xlw_with_wb(
        wb,
        sheet_name: str
    ) -> Dict[str, Any]:
        """
        Session-based data validation info retrieval using existing workbook object.
        
        Args:
            wb: Workbook object from session
            sheet_name: Name of worksheet
            
        Returns:
            Dict containing all validation rules in the worksheet
        """
        try:
            logger.info(f"🔍 Getting data validation info for {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]
            
            # Access worksheet COM object for validation
            ws_com = sheet.api
            
            validation_rules = []
            processed_ranges = set()
            
            # Get used range to scan for validation
            try:
                used_range = sheet.used_range
                if used_range:
                    # Iterate through cells to find validation rules
                    # Note: This is more efficient than checking every cell
                    # We'll check representative cells and expand to find full ranges
                    
                    max_row = used_range.last_cell.row
                    max_col = used_range.last_cell.column
                    
                    # Sample cells to check (every 5th cell for efficiency)
                    for row in range(1, max_row + 1, 5):
                        for col in range(1, max_col + 1, 5):
                            try:
                                cell = sheet.range((row, col))
                                cell_address = cell.address.replace('$', '')
                                
                                # Skip if already processed
                                if cell_address in processed_ranges:
                                    continue
                                
                                # Check if cell has validation using COM API
                                cell_com = cell.api
                                validation = cell_com.Validation
                                
                                # Check if validation exists (Type > 0 means validation is present)
                                if hasattr(validation, 'Type') and validation.Type > 0:
                                    # Found validation, now find the full range
                                    validation_info = {
                                        "range": cell_address,
                                        "type": get_validation_type_name(validation.Type),
                                        "operator": None,
                                        "formula1": None,
                                        "formula2": None,
                                        "error_message": None,
                                        "input_message": None,
                                        "show_error": True,
                                        "show_input": True
                                    }
                                    
                                    # Get validation details
                                    try:
                                        if hasattr(validation, 'Operator'):
                                            validation_info["operator"] = get_operator_name(validation.Operator)
                                    except:
                                        pass
                                    
                                    try:
                                        if hasattr(validation, 'Formula1'):
                                            validation_info["formula1"] = str(validation.Formula1)
                                    except:
                                        pass
                                    
                                    try:
                                        if hasattr(validation, 'Formula2'):
                                            validation_info["formula2"] = str(validation.Formula2)
                                    except:
                                        pass
                                    
                                    try:
                                        if hasattr(validation, 'ErrorMessage'):
                                            validation_info["error_message"] = validation.ErrorMessage
                                    except:
                                        pass
                                    
                                    try:
                                        if hasattr(validation, 'InputMessage'):
                                            validation_info["input_message"] = validation.InputMessage
                                    except:
                                        pass
                                    
                                    try:
                                        if hasattr(validation, 'ShowError'):
                                            validation_info["show_error"] = bool(validation.ShowError)
                                    except:
                                        pass
                                    
                                    try:
                                        if hasattr(validation, 'ShowInput'):
                                            validation_info["show_input"] = bool(validation.ShowInput)
                                    except:
                                        pass
                                    
                                    # Try to find the full range with this validation
                                    # by checking adjacent cells
                                    full_range = expand_validation_range(sheet, row, col, validation)
                                    validation_info["range"] = full_range
                                    
                                    # Mark cells as processed
                                    for r in range(row, row + 10):
                                        for c in range(col, col + 10):
                                            processed_ranges.add(f"{chr(64+c)}{r}")
                                    
                                    validation_rules.append(validation_info)
                                    
                            except Exception as e:
                                # Cell might not have validation, continue
                                continue
                    
            except Exception as e:
                logger.warning(f"Error scanning for validation rules: {e}")
            
            # Return validation information
            result = {
                "sheet": sheet_name,
                "validation_count": len(validation_rules),
                "validation_rules": validation_rules
            }
            
            logger.info(f"✅ Found {len(validation_rules)} validation rules in {sheet_name}")
            return result
            
        except Exception as e:
            logger.error(f"Error getting validation info: {e}")
            return {"error": str(e)}
  • Helper function to map Excel validation type integers to human-readable names.
    def get_validation_type_name(type_value: int) -> str:
        """Convert Excel validation type constant to readable name."""
        validation_types = {
            0: "None",
            1: "Whole Number",
            2: "Decimal",
            3: "List",
            4: "Date",
            5: "Time",
            6: "Text Length",
            7: "Custom"
        }
        return validation_types.get(type_value, f"Unknown ({type_value})")
  • Helper function to map Excel validation operator integers to human-readable names.
    def get_operator_name(operator_value: int) -> str:
        """Convert Excel validation operator constant to readable name."""
        operators = {
            1: "Between",
            2: "Not Between",
            3: "Equal",
            4: "Not Equal",
            5: "Greater",
            6: "Less",
            7: "Greater or Equal",
            8: "Less or Equal"
        }
        return operators.get(operator_value, f"Unknown ({operator_value})")

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed5 schema fields changed
    • addedInput schema / properties / filepath / anyOf
      Added value: +[
      +  {
      +    "type": "string"
      +  },
      +  {
      +    "type": "null"
      +  }
      +]
    • addedInput schema / properties / filepath / default
      Added value: +null
    • removedInput schema / properties / filepath / type
      Removed value: -"string"
    • addedInput schema / properties / session_id
      Added value: +{
      +  "anyOf": [
      +    {
      +      "type": "string"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "default": null,
      +  "title": "Session Id"
      +}
    • changedInput schema / required
      Previous value: -[
      -  "filepath",
      -  "sheet_name"
      -]New value: +[
      +  "sheet_name"
      +]
  2. First observed

TDQS

A3.6/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description must disclose all behavioral traits. It only states that the tool returns a JSON string of validation rules, but does not mention side effects (e.g., read-only, no modifications), required permissions, or error conditions. This leaves significant gaps for a read operation.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is reasonably concise, but the Args section redundantly lists parameters already in the schema. The core purpose is front-loaded. Some words could be trimmed without loss.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's simplicity (3 parameters, output schema exists), the description covers the essential purpose and key parameter guidance. The return type is stated, though the output schema itself provides more detail. Missing behavioral transparency is the main gap.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate. It adds value by noting that session_id is preferred for better performance and filepath is deprecated, which goes beyond the schema's type information. However, it does not describe the format of sheet_name or any constraints.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states 'Get all data validation rules in a worksheet' with additional context about identifying ranges and validation types. This verb+resource combination is specific and distinct from sibling tools like validate_excel_range.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage by explaining what the tool does, but does not explicitly state when to use it over alternatives like validate_excel_range or validate_formula_syntax. No exclusions or context for when-not-to-use are provided.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.