Skip to main content
Glama

Advanced HWP MCP Server

by skerishKang
advanced_hwp_server.py23.7 kB
#!/usr/bin/env python3 """ Advanced HWP MCP Server 고도화된 한글 MCP 서버 - 한글의 모든 기능을 제어할 수 있는 MCP 서버 """ import logging import sys import os from typing import Optional, Dict, Any, List, Tuple import json try: from mcp.server.fastmcp import FastMCP import win32com.client import pythoncom import win32api import win32con except ImportError as e: print(f"필수 패키지가 설치되지 않음: {e}") print("다음 명령어로 패키지를 설치하세요:") print("pip install mcp fastmcp pywin32") sys.exit(1) # 로깅 설정 logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler('hwp_mcp.log'), logging.StreamHandler() ] ) logger = logging.getLogger(__name__) # MCP 서버 초기화 mcp = FastMCP("Advanced HWP Server") class AdvancedHwpController: """고급 한글 컨트롤러 클래스""" def __init__(self): """한글 COM 객체 초기화""" self.hwp = None self.is_initialized = False self.current_document = None def initialize(self): """한글 COM 객체 초기화""" try: pythoncom.CoInitialize() # 한글 프로그램이 설치되어 있는지 확인 try: self.hwp = win32com.client.gencache.EnsureDispatch("HWPFrame.HwpObject") except: # gencache가 실패하면 일반 Dispatch 사용 self.hwp = win32com.client.Dispatch("HWPFrame.HwpObject") # 한글 창을 보이게 설정 if self.hwp.XHwpWindows.Count > 0: self.hwp.XHwpWindows.Item(0).Visible = True self.is_initialized = True logger.info("한글 COM 객체 초기화 완료") return True except Exception as e: logger.error(f"한글 COM 객체 초기화 실패: {e}") return False def __del__(self): """리소스 정리""" try: if self.is_initialized: pythoncom.CoUninitialize() except: pass def check_initialization(self): """초기화 상태 확인""" if not self.is_initialized: if not self.initialize(): raise Exception("한글 프로그램이 설치되지 않았거나 초기화할 수 없습니다.") return True # 전역 컨트롤러 인스턴스 hwp_controller = AdvancedHwpController() @mcp.tool() def initialize_hwp() -> str: """한글 프로그램을 초기화합니다.""" try: if hwp_controller.initialize(): return "한글 프로그램 초기화 성공" else: return "한글 프로그램 초기화 실패" except Exception as e: logger.error(f"초기화 중 오류: {e}") return f"초기화 실패: {e}" @mcp.tool() def create_document() -> str: """새 한글 문서를 생성합니다.""" try: hwp_controller.check_initialization() # 새 문서 생성 hwp_controller.hwp.HAction.Run("FileNew") hwp_controller.current_document = "new_document" logger.info("새 문서 생성 완료") return "새 문서가 생성되었습니다." except Exception as e: logger.error(f"문서 생성 실패: {e}") return f"문서 생성 실패: {e}" @mcp.tool() def open_document(file_path: str) -> str: """지정된 경로의 한글 문서를 엽니다.""" try: hwp_controller.check_initialization() if not os.path.exists(file_path): return f"파일을 찾을 수 없습니다: {file_path}" # 문서 열기 act = hwp_controller.hwp.CreateAction("FileOpen") pset = act.CreateSet() pset.SetItem("filename", file_path) act.Execute(pset) hwp_controller.current_document = file_path logger.info(f"문서 열기 완료: {file_path}") return f"문서를 열었습니다: {file_path}" except Exception as e: logger.error(f"문서 열기 실패: {e}") return f"문서 열기 실패: {e}" @mcp.tool() def save_document(file_path: Optional[str] = None) -> str: """현재 문서를 저장합니다.""" try: hwp_controller.check_initialization() if file_path: # 다른 이름으로 저장 act = hwp_controller.hwp.CreateAction("FileSaveAs") pset = act.CreateSet() pset.SetItem("filename", file_path) pset.SetItem("format", "HWP") act.Execute(pset) hwp_controller.current_document = file_path logger.info(f"문서 저장 완료: {file_path}") return f"문서를 저장했습니다: {file_path}" else: # 현재 문서 저장 hwp_controller.hwp.HAction.Run("FileSave") logger.info("문서 저장 완료") return "문서를 저장했습니다." except Exception as e: logger.error(f"문서 저장 실패: {e}") return f"문서 저장 실패: {e}" @mcp.tool() def insert_text(text: str, position: str = "current") -> str: """텍스트를 삽입합니다.""" try: hwp_controller.check_initialization() if position == "current": # 현재 위치에 텍스트 삽입 act = hwp_controller.hwp.CreateAction("InsertText") pset = act.CreateSet() pset.SetItem("Text", text) act.Execute(pset) logger.info(f"텍스트 삽입 완료: {text[:50]}...") return f"텍스트를 삽입했습니다: {text[:50]}..." except Exception as e: logger.error(f"텍스트 삽입 실패: {e}") return f"텍스트 삽입 실패: {e}" @mcp.tool() def insert_text_at_position(text: str, x: int = 0, y: int = 0) -> str: """지정된 좌표에 텍스트를 삽입합니다.""" try: hwp_controller.check_initialization() # 좌표로 커서 이동 hwp_controller.hwp.SetPosBySet(x, y) # 텍스트 삽입 act = hwp_controller.hwp.CreateAction("InsertText") pset = act.CreateSet() pset.SetItem("Text", text) act.Execute(pset) logger.info(f"위치 ({x}, {y})에 텍스트 삽입 완료") return f"위치 ({x}, {y})에 텍스트 '{text}'를 삽입했습니다." except Exception as e: logger.error(f"위치별 텍스트 삽입 실패: {e}") return f"위치별 텍스트 삽입 실패: {e}" @mcp.tool() def apply_font_format(font_name: str = "맑은 고딕", font_size: int = 11, bold: bool = False, italic: bool = False, underline: bool = False, color: str = "black") -> str: """선택된 텍스트에 글꼴 서식을 적용합니다.""" try: hwp_controller.check_initialization() # 색상 변환 color_map = { "black": 0x000000, "red": 0xFF0000, "blue": 0x0000FF, "green": 0x00FF00, "yellow": 0xFFFF00, "purple": 0xFF00FF, "cyan": 0x00FFFF } color_value = color_map.get(color.lower(), 0x000000) # 글꼴 서식 적용 act = hwp_controller.hwp.CreateAction("CharShape") pset = act.CreateSet() pset.SetItem("FaceNameHangul", font_name) pset.SetItem("FaceNameLatin", font_name) pset.SetItem("FaceNameHanja", font_name) pset.SetItem("FaceNameJapanese", font_name) pset.SetItem("FaceNameOther", font_name) pset.SetItem("FaceNameSymbol", font_name) pset.SetItem("FaceNameUser", font_name) pset.SetItem("Height", font_size * 100) # 한글에서는 포인트 * 100 pset.SetItem("Bold", bold) pset.SetItem("Italic", italic) pset.SetItem("Underline", underline) pset.SetItem("TextColor", color_value) act.Execute(pset) logger.info(f"글꼴 서식 적용 완료: {font_name}, {font_size}pt") return f"글꼴 서식을 적용했습니다: {font_name}, {font_size}pt" except Exception as e: logger.error(f"글꼴 서식 적용 실패: {e}") return f"글꼴 서식 적용 실패: {e}" @mcp.tool() def select_text_range(start_pos: int, end_pos: int) -> str: """지정된 범위의 텍스트를 선택합니다.""" try: hwp_controller.check_initialization() # 시작 위치로 이동 hwp_controller.hwp.SetPos(start_pos) # 드래그하여 텍스트 선택 hwp_controller.hwp.MovePos(2, end_pos - start_pos, 1) # 2: 오른쪽, 1: 선택 logger.info(f"텍스트 선택 완료: {start_pos} ~ {end_pos}") return f"텍스트를 선택했습니다: 위치 {start_pos} ~ {end_pos}" except Exception as e: logger.error(f"텍스트 선택 실패: {e}") return f"텍스트 선택 실패: {e}" @mcp.tool() def find_and_replace(find_text: str, replace_text: str, replace_all: bool = False) -> str: """텍스트를 찾아서 바꿉니다.""" try: hwp_controller.check_initialization() # 찾기/바꾸기 실행 act = hwp_controller.hwp.CreateAction("Replace") pset = act.CreateSet() pset.SetItem("FindString", find_text) pset.SetItem("ReplaceString", replace_text) pset.SetItem("ReplaceMode", 1 if replace_all else 0) # 0: 찾기, 1: 모두 바꾸기 result = act.Execute(pset) if result: logger.info(f"찾기/바꾸기 완료: '{find_text}' → '{replace_text}'") return f"'{find_text}'를 '{replace_text}'로 {'모두 ' if replace_all else ''}바꾸었습니다." else: return f"'{find_text}'를 찾을 수 없습니다." except Exception as e: logger.error(f"찾기/바꾸기 실패: {e}") return f"찾기/바꾸기 실패: {e}" @mcp.tool() def create_table(rows: int, cols: int, border: bool = True) -> str: """표를 생성합니다.""" try: hwp_controller.check_initialization() # 표 생성 act = hwp_controller.hwp.CreateAction("TableCreate") pset = act.CreateSet() pset.SetItem("Rows", rows) pset.SetItem("Cols", cols) pset.SetItem("WidthType", 2) # 2: 문서 너비에 맞춤 pset.SetItem("HeightType", 0) # 0: 자동 pset.SetItem("CreateItemArray", [0, 1, 0]) # 기본 설정 act.Execute(pset) logger.info(f"표 생성 완료: {rows}행 {cols}열") return f"{rows}행 {cols}열 표를 생성했습니다." except Exception as e: logger.error(f"표 생성 실패: {e}") return f"표 생성 실패: {e}" @mcp.tool() def set_page_margins(top: int = 20, bottom: int = 20, left: int = 20, right: int = 20) -> str: """페이지 여백을 설정합니다. (단위: mm)""" try: hwp_controller.check_initialization() # 페이지 설정 act = hwp_controller.hwp.CreateAction("PageSetup") pset = act.CreateSet() pset.SetItem("TopMargin", top * 100) # 한글에서는 mm * 100 pset.SetItem("BottomMargin", bottom * 100) pset.SetItem("LeftMargin", left * 100) pset.SetItem("RightMargin", right * 100) act.Execute(pset) logger.info(f"페이지 여백 설정 완료: 상{top} 하{bottom} 좌{left} 우{right}mm") return f"페이지 여백을 설정했습니다: 상{top} 하{bottom} 좌{left} 우{right}mm" except Exception as e: logger.error(f"페이지 여백 설정 실패: {e}") return f"페이지 여백 설정 실패: {e}" @mcp.tool() def get_document_info() -> str: """현재 문서의 정보를 조회합니다.""" try: hwp_controller.check_initialization() # 문서 정보 수집 (실제 API에 맞게 수정) try: page_count = hwp_controller.hwp.PageCount # 속성으로 접근 except: page_count = "Unknown" try: current_pos = hwp_controller.hwp.GetPos() except: current_pos = "Unknown" # 현재 페이지는 계산으로 구하기 try: # ListCount를 사용해서 전체 리스트 정보 확인 list_count = getattr(hwp_controller.hwp, 'ListCount', 0) except: list_count = "Unknown" info = { "page_count": page_count, "current_pos": current_pos, "list_count": list_count, "document_name": hwp_controller.current_document or "새 문서" } result = f"""문서 정보: - 문서명: {info['document_name']} - 총 페이지 수: {info['page_count']} - 현재 커서 위치: {info['current_pos']} - 리스트 개수: {info['list_count']}""" logger.info("문서 정보 조회 완료") return result except Exception as e: logger.error(f"문서 정보 조회 실패: {e}") return f"문서 정보 조회 실패: {e}" @mcp.tool() def set_paragraph_format(align: str = "left", left_indent: int = 0, right_indent: int = 0, line_spacing: float = 1.0) -> str: """문단 서식을 설정합니다.""" try: hwp_controller.check_initialization() # 정렬 방식 변환 align_map = { "left": 0, "center": 1, "right": 2, "justify": 3, "distribute": 4 } align_value = align_map.get(align.lower(), 0) # 문단 서식 설정 act = hwp_controller.hwp.CreateAction("ParagraphShape") pset = act.CreateSet() pset.SetItem("Align", align_value) pset.SetItem("IndentLeft", left_indent * 100) # mm * 100 pset.SetItem("IndentRight", right_indent * 100) pset.SetItem("LineSpacing", int(line_spacing * 100)) act.Execute(pset) logger.info(f"문단 서식 설정 완료: {align} 정렬, 줄간격 {line_spacing}") return f"문단 서식을 설정했습니다: {align} 정렬, 줄간격 {line_spacing}" except Exception as e: logger.error(f"문단 서식 설정 실패: {e}") return f"문단 서식 설정 실패: {e}" @mcp.tool() def set_page_size(width: int = 210, height: int = 297, orientation: str = "portrait") -> str: """용지 크기와 방향을 설정합니다. (단위: mm)""" try: hwp_controller.check_initialization() # 용지 방향 처리 if orientation.lower() == "landscape": width, height = height, width # 페이지 설정 act = hwp_controller.hwp.CreateAction("PageSetup") pset = act.CreateSet() pset.SetItem("Width", width * 100) # mm * 100 pset.SetItem("Height", height * 100) pset.SetItem("Orientation", 1 if orientation.lower() == "landscape" else 0) act.Execute(pset) logger.info(f"용지 설정 완료: {width}x{height}mm, {orientation}") return f"용지를 설정했습니다: {width}x{height}mm, {orientation}" except Exception as e: logger.error(f"용지 설정 실패: {e}") return f"용지 설정 실패: {e}" @mcp.tool() def insert_image(image_path: str, x: int = 0, y: int = 0, width: int = 100, height: int = 100) -> str: """이미지를 삽입합니다.""" try: hwp_controller.check_initialization() if not os.path.exists(image_path): return f"이미지 파일을 찾을 수 없습니다: {image_path}" # 이미지 삽입 act = hwp_controller.hwp.CreateAction("InsertPicture") pset = act.CreateSet() pset.SetItem("Path", image_path) pset.SetItem("Embedded", True) pset.SetItem("sizeoption", 3) # 사용자 정의 크기 pset.SetItem("Width", width * 100) # mm * 100 pset.SetItem("Height", height * 100) act.Execute(pset) logger.info(f"이미지 삽입 완료: {image_path}") return f"이미지를 삽입했습니다: {image_path}" except Exception as e: logger.error(f"이미지 삽입 실패: {e}") return f"이미지 삽입 실패: {e}" @mcp.tool() def insert_shape(shape_type: str, x: int = 0, y: int = 0, width: int = 50, height: int = 50) -> str: """도형을 삽입합니다.""" try: hwp_controller.check_initialization() # 도형 종류 맵핑 shape_map = { "rectangle": 1, "ellipse": 2, "line": 3, "arrow": 4, "textbox": 5 } shape_value = shape_map.get(shape_type.lower(), 1) # 도형 삽입 act = hwp_controller.hwp.CreateAction("DrawObjDialog") pset = act.CreateSet() pset.SetItem("ShapeType", shape_value) pset.SetItem("TreatAsChar", False) act.Execute(pset) logger.info(f"도형 삽입 완료: {shape_type}") return f"도형을 삽입했습니다: {shape_type}" except Exception as e: logger.error(f"도형 삽입 실패: {e}") return f"도형 삽입 실패: {e}" @mcp.tool() def insert_header_footer(text: str, is_header: bool = True, position: str = "center") -> str: """머리글 또는 바닥글을 삽입합니다.""" try: hwp_controller.check_initialization() # 머리글/바닥글 편집 모드 진입 if is_header: hwp_controller.hwp.HAction.Run("HeaderFooterEdit") else: hwp_controller.hwp.HAction.Run("HeaderFooterEdit") # 텍스트 삽입 act = hwp_controller.hwp.CreateAction("InsertText") pset = act.CreateSet() pset.SetItem("Text", text) act.Execute(pset) # 편집 모드 종료 hwp_controller.hwp.HAction.Run("CloseEx") logger.info(f"{'머리글' if is_header else '바닥글'} 삽입 완료") return f"{'머리글' if is_header else '바닥글'}을 삽입했습니다: {text}" except Exception as e: logger.error(f"머리글/바닥글 삽입 실패: {e}") return f"머리글/바닥글 삽입 실패: {e}" @mcp.tool() def insert_page_break() -> str: """페이지 나누기를 삽입합니다.""" try: hwp_controller.check_initialization() # 페이지 나누기 삽입 act = hwp_controller.hwp.CreateAction("BreakPage") act.Execute() logger.info("페이지 나누기 삽입 완료") return "페이지 나누기를 삽입했습니다." except Exception as e: logger.error(f"페이지 나누기 삽입 실패: {e}") return f"페이지 나누기 삽입 실패: {e}" @mcp.tool() def merge_table_cells(start_row: int, start_col: int, end_row: int, end_col: int) -> str: """표의 셀을 병합합니다.""" try: hwp_controller.check_initialization() # 셀 범위 선택 hwp_controller.hwp.TableCellBlock(start_row, start_col, end_row, end_col) # 셀 병합 act = hwp_controller.hwp.CreateAction("TableMergeCell") act.Execute() logger.info(f"셀 병합 완료: ({start_row},{start_col}) ~ ({end_row},{end_col})") return f"셀을 병합했습니다: ({start_row},{start_col}) ~ ({end_row},{end_col})" except Exception as e: logger.error(f"셀 병합 실패: {e}") return f"셀 병합 실패: {e}" @mcp.tool() def insert_hyperlink(text: str, url: str) -> str: """하이퍼링크를 삽입합니다.""" try: hwp_controller.check_initialization() # 하이퍼링크 삽입 act = hwp_controller.hwp.CreateAction("InsertHyperlink") pset = act.CreateSet() pset.SetItem("Text", text) pset.SetItem("URL", url) act.Execute(pset) logger.info(f"하이퍼링크 삽입 완료: {text} -> {url}") return f"하이퍼링크를 삽입했습니다: {text} -> {url}" except Exception as e: logger.error(f"하이퍼링크 삽입 실패: {e}") return f"하이퍼링크 삽입 실패: {e}" @mcp.tool() def create_table_of_contents() -> str: """목차를 생성합니다.""" try: hwp_controller.check_initialization() # 목차 삽입 act = hwp_controller.hwp.CreateAction("InsertTableOfContents") pset = act.CreateSet() pset.SetItem("AutoUpdate", True) pset.SetItem("ShowPageNum", True) act.Execute(pset) logger.info("목차 생성 완료") return "목차를 생성했습니다." except Exception as e: logger.error(f"목차 생성 실패: {e}") return f"목차 생성 실패: {e}" @mcp.tool() def apply_heading_style(level: int, text: str) -> str: """제목 스타일을 적용합니다.""" try: hwp_controller.check_initialization() # 텍스트 삽입 act = hwp_controller.hwp.CreateAction("InsertText") pset = act.CreateSet() pset.SetItem("Text", text) act.Execute(pset) # 제목 스타일 적용 style_name = f"제목 {level}" act = hwp_controller.hwp.CreateAction("StyleApply") pset = act.CreateSet() pset.SetItem("StyleName", style_name) act.Execute(pset) logger.info(f"제목 스타일 적용 완료: {style_name}") return f"제목 스타일을 적용했습니다: {style_name}" except Exception as e: logger.error(f"제목 스타일 적용 실패: {e}") return f"제목 스타일 적용 실패: {e}" @mcp.tool() def export_to_pdf(output_path: str) -> str: """현재 문서를 PDF로 내보냅니다.""" try: hwp_controller.check_initialization() # PDF 내보내기 act = hwp_controller.hwp.CreateAction("FileSaveAsPdf") pset = act.CreateSet() pset.SetItem("filename", output_path) pset.SetItem("Format", "PDF") act.Execute(pset) logger.info(f"PDF 내보내기 완료: {output_path}") return f"PDF로 내보냈습니다: {output_path}" except Exception as e: logger.error(f"PDF 내보내기 실패: {e}") return f"PDF 내보내기 실패: {e}" def main(): """메인 함수""" try: logger.info("Advanced HWP MCP Server 시작") # 한글 프로그램 초기화 if not hwp_controller.initialize(): logger.error("한글 프로그램 초기화 실패") return # MCP 서버 실행 mcp.run() except Exception as e: logger.error(f"서버 실행 중 오류: {e}") sys.exit(1) if __name__ == "__main__": main()

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/skerishKang/MCP_HWP_Limone'

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