force_close_workbook_by_path_tool
Force close an Excel workbook by file path without saving changes. Use to terminate unresponsive workbooks or prevent data loss from unsaved modifications.
Instructions
Force close a specific workbook by file path (without saving).
Args:
filepath: Path to the workbook to force close
Returns:
Dictionary with 'closed' (bool) and 'message' (str)
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| filepath | Yes |
Implementation Reference
- src/xlwings_mcp/server.py:206-227 (handler)MCP tool handler: wrapper that resolves the full path and calls the core force_close_workbook_by_path function, also handles exceptions and logging.def force_close_workbook_by_path_tool( filepath: str ) -> Dict[str, Any]: """ Force close a specific workbook by file path (without saving). Args: filepath: Path to the workbook to force close Returns: Dictionary with 'closed' (bool) and 'message' (str) """ try: full_path = get_excel_path(filepath) return force_close_workbook_by_path(full_path) except Exception as e: logger.error(f"Error force closing workbook: {e}") return { "closed": False, "message": f"Failed to force close workbook: {str(e)}" }
- Core helper function implementing the force close logic using pywin32 COM to connect to Excel, find the workbook by path, close it without saving, and optionally quit Excel if no workbooks remain.def force_close_workbook_by_path(filepath: str) -> Dict[str, Any]: """ Force close a specific workbook from any running Excel process. Args: filepath: Absolute path to the workbook to close Returns: Dictionary with 'closed' (bool) and 'message' (str) """ if not PYWIN32_AVAILABLE: return { "closed": False, "message": "Force close not available (pywin32 not installed or not on Windows)" } # Normalize the file path target_path = os.path.abspath(filepath).lower() try: # Initialize COM for this thread pythoncom.CoInitialize() found = False closed = False try: # Try to connect to running Excel instance xl = win32com.client.GetObject(Class="Excel.Application") # Check all open workbooks for wb in xl.Workbooks: try: # Compare full paths (case-insensitive on Windows) wb_path = os.path.abspath(wb.FullName).lower() if wb_path == target_path: logger.info(f"Found workbook to force close: {wb.FullName}") found = True # Force close without saving wb.Close(SaveChanges=False) closed = True logger.info(f"Successfully force closed: {filepath}") break except Exception as e: logger.warning(f"Error checking/closing workbook: {e}") continue # If no workbooks remain, optionally quit Excel if closed and xl.Workbooks.Count == 0: try: xl.Quit() logger.info("Excel application quit (no remaining workbooks)") except: pass except Exception as e: # No Excel instance running or other COM error logger.debug(f"Could not connect to Excel: {e}") return { "closed": False, "message": f"No Excel instance found or cannot connect: {str(e)}" } finally: # Uninitialize COM pythoncom.CoUninitialize() if not found: return { "closed": False, "message": f"Workbook not found in any Excel instance: {filepath}" } return { "closed": closed, "message": f"Successfully force closed workbook: {filepath}" } except Exception as e: logger.error(f"Force close failed for {filepath}: {e}") return { "closed": False, "message": f"Force close failed: {str(e)}" }
- src/xlwings_mcp/server.py:25-206 (registration)Import of the helper function used by the tool handler.from xlwings_mcp.force_close import force_close_workbook_by_path # Get project root directory path for log file path. # When using the stdio transmission method, # relative paths may cause log files to fail to create # due to the client's running location and permission issues, # resulting in the program not being able to run. # Thus using os.path.join(ROOT_DIR, "excel-mcp.log") instead. ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) LOGS_DIR = os.path.join(ROOT_DIR, "logs") os.makedirs(LOGS_DIR, exist_ok=True) LOG_FILE = os.path.join(LOGS_DIR, "excel-mcp.log") # Initialize EXCEL_FILES_PATH variable without assigning a value EXCEL_FILES_PATH = None # xlwings 구현 사용 (openpyxl 마이그레이션 완료) # Configure logging with rotation to prevent infinite log growth from logging.handlers import RotatingFileHandler logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", handlers=[ # Referring to https://github.com/modelcontextprotocol/python-sdk/issues/409#issuecomment-2816831318 # The stdio mode server MUST NOT write anything to its stdout that is not a valid MCP message. # Use RotatingFileHandler to prevent infinite log growth (5MB max, keep 3 backup files) RotatingFileHandler(LOG_FILE, maxBytes=5*1024*1024, backupCount=3) ], ) logger = logging.getLogger("excel-mcp") logger.info("🚀 Excel MCP Server starting - xlwings 모드 활성화") # Error message templates for consistent error reporting ERROR_TEMPLATES = { 'SESSION_NOT_FOUND': "SESSION_NOT_FOUND: Session '{session_id}' not found. It may have expired after {ttl} minutes of inactivity. Use open_workbook() to create a new session.", 'SESSION_TIMEOUT': "SESSION_TIMEOUT: Session '{session_id}' expired at {time}. Create new session with open_workbook()", 'FILE_LOCKED': "FILE_ACCESS_ERROR: '{filepath}' is locked by another process. Use force_close_workbook_by_path() to force close it first.", 'FILE_NOT_FOUND': "FILE_NOT_FOUND: '{filepath}' does not exist. Check the path or create a new workbook with create_workbook().", 'SHEET_NOT_FOUND': "SHEET_NOT_FOUND: Sheet '{sheet_name}' not found in workbook. Available sheets: {sheets}", 'INVALID_RANGE': "INVALID_RANGE: Range '{range}' is not valid. Use format like 'A1' or 'A1:B10'.", 'PARAMETER_MISSING': "PARAMETER_MISSING: Either {param1} or {param2} must be provided.", } # Session validation decorator for DRY principle def get_validated_session(session_id: str): """ Helper function to validate session_id and return session object. Centralizes session validation logic for DRY principle. Args: session_id: Session ID to validate Returns: Session object if valid, error message string if invalid """ if not session_id: return ERROR_TEMPLATES['PARAMETER_MISSING'].format( param1='session_id', param2='valid session' ) session = SESSION_MANAGER.get_session(session_id) if not session: return ERROR_TEMPLATES['SESSION_NOT_FOUND'].format( session_id=session_id, ttl=10 ) return session # Initialize FastMCP server mcp = FastMCP( "excel-mcp", instructions="Excel MCP Server for manipulating Excel files" ) def get_excel_path(filename: str) -> str: """Get full path to Excel file. Args: filename: Name of Excel file Returns: Full path to Excel file """ # If filename is already an absolute path, return it if os.path.isabs(filename): return filename # Check if in SSE mode (EXCEL_FILES_PATH is not None) if EXCEL_FILES_PATH is None: # Must use absolute path raise ValueError(f"Invalid filename: {filename}, must be an absolute path when not in SSE mode") # In SSE mode, if it's a relative path, resolve it based on EXCEL_FILES_PATH return os.path.join(EXCEL_FILES_PATH, filename) # ============================================================================ # SESSION MANAGEMENT TOOLS (NEW) # ============================================================================ @mcp.tool() def open_workbook( filepath: str, visible: bool = False, read_only: bool = False ) -> Dict[str, Any]: """ Open an Excel workbook and create a session. Args: filepath: Path to Excel file visible: Whether to show Excel window (default: False) read_only: Whether to open in read-only mode (default: False) Returns: Dictionary with session_id, filepath, visible, read_only, and sheets """ try: full_path = get_excel_path(filepath) session_id = SESSION_MANAGER.open_workbook(full_path, visible, read_only) # Get session info session = SESSION_MANAGER.get_session(session_id) if not session: raise WorkbookError(f"Failed to create session for {filepath}") return { "session_id": session_id, "filepath": session.filepath, "visible": session.visible, "read_only": session.read_only, "sheets": [sheet.name for sheet in session.workbook.sheets] } except Exception as e: logger.error(f"Error opening workbook: {e}") raise WorkbookError(f"Failed to open workbook: {str(e)}") @mcp.tool() def close_workbook( session_id: str, save: bool = True ) -> str: """ Close a workbook session. Args: session_id: Session ID from open_workbook save: Whether to save changes (default: True) Returns: Success message """ try: success = SESSION_MANAGER.close_workbook(session_id, save) if not success: raise WorkbookError(f"Session {session_id} not found") return f"Workbook session {session_id} closed successfully" except Exception as e: logger.error(f"Error closing workbook: {e}") raise WorkbookError(f"Failed to close workbook: {str(e)}") @mcp.tool() def list_workbooks() -> List[Dict[str, Any]]: """ List all open workbook sessions. Returns: List of session information dictionaries """ try: return SESSION_MANAGER.list_sessions() except Exception as e: logger.error(f"Error listing workbooks: {e}") raise WorkbookError(f"Failed to list workbooks: {str(e)}") @mcp.tool() def force_close_workbook_by_path_tool(