Skip to main content
Glama
hyunjae-labs

xlwings Excel MCP Server

by hyunjae-labs

read_data_from_excel

Extract data from Excel worksheets with cell metadata and validation rules for analysis or processing in corporate environments.

Instructions

Read data from Excel worksheet with cell metadata including validation rules.

Args:
    session_id: Session ID from open_workbook (required)
    sheet_name: Name of worksheet
    start_cell: Starting cell (default A1)
    end_cell: Ending cell (optional, auto-expands if not provided)
    preview_only: Whether to return preview only

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
session_idYes
sheet_nameYes
start_cellNo
end_cellNo
preview_onlyNo

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Implementation Reference

  • MCP tool handler for 'read_data_from_excel'. Validates the session, locks it for thread-safety, imports and calls the xlwings implementation function.
    def read_data_from_excel(
        session_id: str,
        sheet_name: str,
        start_cell: Optional[str] = None,
        end_cell: Optional[str] = None,
        preview_only: bool = False
    ) -> str:
        """
        Read data from Excel worksheet with cell metadata including validation rules.
        
        Args:
            session_id: Session ID from open_workbook (required)
            sheet_name: Name of worksheet
            start_cell: Starting cell (default A1)
            end_cell: Ending cell (optional, auto-expands if not provided)
            preview_only: Whether to return preview only
        """
        try:
            # Validate session using centralized helper
            session = get_validated_session(session_id)
            if isinstance(session, str):  # Error message returned
                return session
                
            with session.lock:
                from xlwings_mcp.xlwings_impl.data_xlw import read_data_from_excel_xlw_with_wb
                return read_data_from_excel_xlw_with_wb(session.workbook, sheet_name, start_cell, end_cell, preview_only)
            
        except (ValidationError, DataError) as e:
            return f"Error: {str(e)}"
        except Exception as e:
            logger.error(f"Error reading data: {e}")
            raise
  • Core helper function that performs the actual Excel data reading using xlwings. Handles range expansion, data extraction, and JSON serialization with cell metadata.
    def read_data_from_excel_xlw_with_wb(
        wb,
        sheet_name: str,
        start_cell: str = "A1",
        end_cell: Optional[str] = None,
        preview_only: bool = False
    ) -> str:
        """xlwings 세션 기반 데이터 읽기
        
        Args:
            wb: 워크북 객체 (세션에서 전달)
            sheet_name: 시트명
            start_cell: 시작 셀 (기본값: A1)
            end_cell: 종료 셀 (선택사항, 자동 확장)
            preview_only: 미리보기 모드 (현재 미사용)
            
        Returns:
            JSON 형식의 문자열 - 셀 메타데이터와 함께 구조화된 데이터
        """
        try:
            # 시트 존재 확인
            if sheet_name not in [s.name for s in wb.sheets]:
                return json.dumps({"error": f"Sheet '{sheet_name}' not found"}, indent=2)
            
            ws = wb.sheets[sheet_name]
            
            # Set default start_cell if not provided
            if not start_cell:
                # Find first non-empty cell or default to A1
                used_range = ws.used_range
                if used_range:
                    start_cell = used_range.address.split(":")[0].replace("$", "")
                else:
                    start_cell = "A1"
            
            # 범위 결정
            if end_cell:
                # 명시적 범위 사용
                data_range = ws.range(f"{start_cell}:{end_cell}")
            else:
                # 시작 셀부터 자동 확장
                try:
                    data_range = ws.range(start_cell).expand()
                except Exception:
                    # 빈 시트이거나 단일 셀인 경우
                    data_range = ws.range(start_cell)
            
            # 데이터 읽기
            values = data_range.value
            
            # 결과 구조 생성
            result = {
                "range": str(data_range.address),
                "sheet_name": sheet_name,
                "cells": []
            }
            
            # 셀별 데이터 변환
            if values is None:
                # 단일 빈 셀
                result["cells"].append({
                    "address": data_range.address,
                    "value": None,
                    "row": data_range.row,
                    "column": data_range.column
                })
            elif isinstance(values, list):
                # 다차원 배열
                for i, row in enumerate(values):
                    if isinstance(row, list):
                        for j, val in enumerate(row):
                            cell_range = data_range.offset(i, j).resize(1, 1)
                            result["cells"].append({
                                "address": cell_range.address,
                                "value": val,
                                "row": cell_range.row,
                                "column": cell_range.column
                            })
                    else:
                        # 단일 열의 경우
                        cell_range = data_range.offset(i, 0).resize(1, 1)
                        result["cells"].append({
                            "address": cell_range.address,
                            "value": row,
                            "row": cell_range.row,
                            "column": cell_range.column
                        })
            else:
                # 단일 값
                result["cells"].append({
                    "address": data_range.address,
                    "value": values,
                    "row": data_range.row,
                    "column": data_range.column
                })
            
            return json.dumps(result, indent=2, default=str, ensure_ascii=False)
            
        except Exception as e:
            logger.error(f"xlwings 데이터 읽기 실패: {e}")
            return json.dumps({"error": f"Failed to read data: {str(e)}"}, indent=2)
  • The @mcp.tool() decorator registers this function as an MCP tool named 'read_data_from_excel'.
    def read_data_from_excel(
  • Legacy helper function for reading data without pre-opened workbook (opens Excel app and workbook).
    def read_data_from_excel_xlw(
        filepath: str,
        sheet_name: str,
        start_cell: str = "A1",
        end_cell: Optional[str] = None,
        preview_only: bool = False
    ) -> str:
        """xlwings를 사용한 데이터 읽기
    
        Args:
            filepath: Excel 파일 경로
            sheet_name: 시트명
            start_cell: 시작 셀 (기본값: A1)
            end_cell: 종료 셀 (선택사항, 자동 확장)
            preview_only: 미리보기 모드 (현재 미사용)
    
        Returns:
            JSON 형식의 문자열 - 셀 메타데이터와 함께 구조화된 데이터
        """
        app = None
        wb = None
    
        # Initialize COM for thread safety (Windows)
        _com_initialize()
    
        try:
            # Excel 앱 시작 (백그라운드에서)
            app = xw.App(visible=False, add_book=False)
            
            # 파일 경로 검증
            if not os.path.exists(filepath):
                return json.dumps({"error": f"File not found: {filepath}"}, indent=2)
            
            # 워크북 열기
            wb = app.books.open(filepath)
            
            # 시트 존재 확인
            if sheet_name not in [s.name for s in wb.sheets]:
                return json.dumps({"error": f"Sheet '{sheet_name}' not found"}, indent=2)
            
            ws = wb.sheets[sheet_name]
            
            # Set default start_cell if not provided
            if not start_cell:
                # Find first non-empty cell or default to A1
                used_range = ws.used_range
                if used_range:
                    start_cell = used_range.address.split(":")[0].replace("$", "")
                else:
                    start_cell = "A1"
            
            # 범위 결정
            if end_cell:
                # 명시적 범위 사용
                data_range = ws.range(f"{start_cell}:{end_cell}")
            else:
                # 시작 셀부터 자동 확장
                try:
                    data_range = ws.range(start_cell).expand()
                except Exception:
                    # 빈 시트이거나 단일 셀인 경우
                    data_range = ws.range(start_cell)
            
            # 데이터 읽기
            values = data_range.value
            
            # 결과 구조 생성
            result = {
                "range": str(data_range.address),
                "sheet_name": sheet_name,
                "cells": []
            }
            
            # 셀별 데이터 변환
            if values is None:
                # 단일 빈 셀
                result["cells"].append({
                    "address": data_range.address,
                    "value": None,
                    "row": data_range.row,
                    "column": data_range.column
                })
            elif isinstance(values, list):
                # 다차원 배열
                for i, row in enumerate(values):
                    if isinstance(row, list):
                        for j, val in enumerate(row):
                            cell_range = data_range.offset(i, j).resize(1, 1)
                            result["cells"].append({
                                "address": cell_range.address,
                                "value": val,
                                "row": cell_range.row,
                                "column": cell_range.column
                            })
                    else:
                        # 단일 열의 경우
                        cell_range = data_range.offset(i, 0).resize(1, 1)
                        result["cells"].append({
                            "address": cell_range.address,
                            "value": row,
                            "row": cell_range.row,
                            "column": cell_range.column
                        })
            else:
                # 단일 값
                result["cells"].append({
                    "address": data_range.address,
                    "value": values,
                    "row": data_range.row,
                    "column": data_range.column
                })
            
            return json.dumps(result, indent=2, default=str, ensure_ascii=False)
            
        except Exception as e:
            logger.error(f"xlwings 데이터 읽기 실패: {e}")
            return json.dumps({"error": f"Failed to read data: {str(e)}"}, indent=2)
            
        finally:
            # 리소스 정리
            if wb:
                try:
                    wb.close()
                except Exception as e:
                    logger.warning(f"워크북 닫기 실패: {e}")
            
            if app:
                try:
                    app.quit()
                except Exception as e:
                    logger.warning(f"Excel 앱 종료 실패: {e}")

Schema Changelog

Changes observed during successful MCP inspections.

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

TDQS

A4/5.0
Behavior3/5

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

No annotations are provided, so the description carries full burden. It states the tool reads data without mutation, but lacks details on side effects, concurrency behavior, or performance implications. Basic transparency is present but incomplete.

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

Conciseness5/5

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

The description is concise with two introductory sentences and a well-structured parameter list. No redundant information, and each part contributes to understanding the tool's usage.

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 has an output schema, the description does not need to detail return values. However, it could be more complete by explaining what cell metadata includes or how preview_only affects the output. Overall, it sufficiently covers the main aspects.

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 coverage is 0%, so the description adds value by explaining each parameter's role, including defaults (start_cell=A1), auto-expansion for end_cell, and the preview_only boolean. This goes beyond the schema's type-only information.

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 the tool reads data from an Excel worksheet with cell metadata including validation rules. This distinguishes it from sibling tools like get_data_validation_info or validate_excel_range by focusing on data reading with metadata.

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 through parameter descriptions but does not explicitly state when to use this tool versus alternatives like validate_excel_range or get_workbook_metadata. No exclusion criteria or when-not-to-use guidance is provided.

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