Skip to main content
Glama

Word MCP Server

server.py28.1 kB
# math_server.py from docx import Document from docx.enum.section import WD_SECTION_START from docx.section import Section from docx.enum.text import WD_PARAGRAPH_ALIGNMENT from mcp.server.fastmcp import FastMCP import cv2 import numpy as np from io import BytesIO import os import json from typing import Dict, List, Any, Optional from docx.shared import Inches, Pt from docx.enum.style import WD_STYLE_TYPE mcp = FastMCP("Word MCP Server", "1.0") document = Document() # Khởi tạo cấu trúc dữ liệu cho Resources và Prompts RESOURCES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "resources") PROMPTS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "prompts") # Tạo thư mục resources và prompts nếu chưa tồn tại os.makedirs(RESOURCES_DIR, exist_ok=True) os.makedirs(PROMPTS_DIR, exist_ok=True) # Dictionary lưu trữ tài nguyên và prompt trong bộ nhớ resources_cache = {} @mcp.tool() def save_file(filename: str): """ Save file to disk - filename: path where the file should be saved (including filename) """ try: # Check if the filename has a .docx extension if not filename.endswith('.docx'): filename = f"{filename}.docx" # If no directory specified, save to resources directory if not os.path.dirname(filename): filename = os.path.join(RESOURCES_DIR, filename) # Ensure the directory exists directory = os.path.dirname(filename) if directory and not os.path.exists(directory): os.makedirs(directory, exist_ok=True) # Debug info print(f"Attempting to save document to: {filename}") # Save the document document.save(filename) # Also save to resources for reference resource_id = os.path.basename(filename) save_resource(resource_id, resource_id) return f"File saved successfully to: {filename}" except Exception as e: error_msg = f"Error saving file: {str(e)}" print(error_msg) # Debug print return error_msg @mcp.tool() def add_heading(content: str, level: int): """ Add heading to the document - Content: nội dung title hoặc heading - Level: bậc của heading (0, 1, 2, ...). Số càng nhỏ font chữ càng lớn. """ document.add_heading(content, level) @mcp.tool() def add_paragraph( content: str, style: str = "Normal", font_size: int = 12, bold: bool = False, italic: bool = False, alignment: WD_PARAGRAPH_ALIGNMENT = WD_PARAGRAPH_ALIGNMENT.LEFT, ): """ Add paragraph to the document - Content: nội dung paragraph """ p = document.add_paragraph(content) p.style = style p.alignment = alignment run = p.runs[0] run.font.size = Pt(font_size) run.font.bold = bold run.font.italic = italic return p @mcp.tool() def update_paragraph( p, content: str = None, style: str = "Normal", font_size: int = 12, bold: bool = False, italic: bool = False, color: str = None, alignment: WD_PARAGRAPH_ALIGNMENT = WD_PARAGRAPH_ALIGNMENT.LEFT, ): """ Update paragraph - Content: nội dung paragraph - Style: style of paragraph - Font size: font size of paragraph - Bold: bold or not - Italic: italic or not - Color: màu chữ (black, blue, green, dark blue, dark red, dark yellow, dark green, pink, red, white, teal, yellow, violet, gray25, gray50) - Alignment: căn lề (LEFT, RIGHT, CENTER, JUSTIFY) """ p.style = style p.alignment = alignment run = p.runs[0] run.font.size = Pt(font_size) run.font.bold = bold run.font.italic = italic if color is not None: from common import color_paragraph color_element = color_paragraph(p, color) run.font.color.val = color_element if content is not None: new_run = p.add_run(content) new_run.font.size = Pt(font_size) new_run.font.bold = bold new_run.font.italic = italic if color is not None: new_run.font.color.val = color_element return p @mcp.tool() def add_section(section = WD_SECTION_START.NEW_PAGE) -> Section: """ Add section to the document """ return document.add_section(section) @mcp.tool() def set_number_of_columns(section, cols): """ Set number of columns for a section - Section: section to set columns - Cols: number of columns """ section._sectPr.xpath("./w:cols")[0].set("{http://schemas.openxmlformats.org/wordprocessingml/2006/main}num", str(cols)) @mcp.tool() def add_run_to_paragraph( p, content: str, bold: bool = False, italic: bool = False, underline: bool = False, color: str = None, highlight: str = None ): """ Thêm câu vào đoạn văn đã được khởi tạo - p: paragraph đã được khởi tạo - content: nội dung cần thêm - bold: in đậm hoặc không - italic: in nghiêng hoặc không - underline: gạch chân hoặc không - color: màu chữ (black, blue, green, dark blue, dark red, dark yellow, dark green, pink, red, white, teal, yellow, violet, gray25, gray50) - highlight: màu nền highlight cho chữ """ sentence_element = p.add_run(str(content)) sentence_element.bold = bold sentence_element.italic = italic sentence_element.underline = underline if color is not None: from common import color_paragraph color_element = color_paragraph(p, color) sentence_element.font.color.val = color_element if highlight is not None: from docx.enum.text import WD_COLOR_INDEX if highlight == 'black': color_element = WD_COLOR_INDEX.BLACK elif highlight == 'blue': color_element = WD_COLOR_INDEX.BLUE elif highlight == 'green': color_element = WD_COLOR_INDEX.BRIGHT_GREEN elif highlight == 'dark blue': color_element = WD_COLOR_INDEX.DARK_BLUE elif highlight == 'dark red': color_element = WD_COLOR_INDEX.DARK_RED elif highlight == 'dark yellow': color_element = WD_COLOR_INDEX.DARK_YELLOW elif highlight == 'dark green': color_element = WD_COLOR_INDEX.GREEN elif highlight == 'pink': color_element = WD_COLOR_INDEX.PINK elif highlight == 'red': color_element = WD_COLOR_INDEX.RED elif highlight == 'white': color_element = WD_COLOR_INDEX.WHITE elif highlight == 'teal': color_element = WD_COLOR_INDEX.TEAL elif highlight == 'yellow': color_element = WD_COLOR_INDEX.YELLOW elif highlight == 'violet': color_element = WD_COLOR_INDEX.VIOLET elif highlight == 'gray25': color_element = WD_COLOR_INDEX.GRAY_25 elif highlight == 'gray50': color_element = WD_COLOR_INDEX.GRAY_50 style = document.styles.add_style(f"highlight_style_{highlight}", WD_STYLE_TYPE.CHARACTER) style.font.highlight_color = color_element sentence_element.style = style return sentence_element @mcp.tool() def add_picture(image_path_or_stream, width: float = 5.0): """ Thêm hình ảnh vào tài liệu - image_path_or_stream: đường dẫn đến file ảnh hoặc ảnh dạng ma trận - width: chiều rộng của ảnh (tính bằng inch) """ if isinstance(image_path_or_stream, str): img = cv2.imread(image_path_or_stream) else: img = np.array(image_path_or_stream) is_success, im_buf_arr = cv2.imencode(".jpg", img) byte_im = im_buf_arr.tobytes() stream = BytesIO(byte_im) return document.add_picture(stream, width=Inches(width)) @mcp.tool() def create_new_document(): """ Tạo một tài liệu mới, loại bỏ tài liệu hiện tại """ global document document = Document() return "Đã tạo tài liệu mới" @mcp.tool() def open_document(filepath: str): """ Mở một tài liệu docx có sẵn - filepath: đường dẫn đến file docx cần mở """ global document try: document = Document(filepath) return f"Đã mở tài liệu từ {filepath}" except Exception as e: return f"Lỗi khi mở tài liệu: {str(e)}" @mcp.tool() def add_table(rows: int, cols: int, style: str = "Table Grid"): """ Thêm bảng vào tài liệu - rows: số hàng - cols: số cột - style: kiểu bảng """ table = document.add_table(rows=rows, cols=cols) table.style = style return table @mcp.tool() def create_table(rows: int, cols: int, style: str = "Table Grid", headers: List[str] = None): """ Tạo bảng với số hàng và cột chỉ định, có thể thêm tiêu đề - rows: số hàng (không bao gồm hàng tiêu đề) - cols: số cột - style: kiểu bảng ("Table Grid", "Light Grid", "Light Shading", etc.) - headers: danh sách các tiêu đề cột (độ dài bằng số cột) """ try: # Nếu có headers, thêm 1 hàng cho tiêu đề actual_rows = rows if headers: if len(headers) != cols: return f"Lỗi: Số lượng tiêu đề ({len(headers)}) khác với số cột ({cols})" actual_rows = rows + 1 # Tạo bảng table = document.add_table(rows=actual_rows, cols=cols) table.style = style # Thêm tiêu đề nếu có if headers: for i, header in enumerate(headers): cell = table.cell(0, i) cell.text = header # Định dạng tiêu đề (in đậm, căn giữa) for paragraph in cell.paragraphs: for run in paragraph.runs: run.bold = True # Trả về thông tin của bảng để có thể sử dụng sau này table_info = { "table_object": table, "rows": actual_rows, "cols": cols, "has_headers": bool(headers) } # Lưu thông tin bảng vào resources để có thể truy cập sau này table_id = f"table_{id(table)}" save_resource(table_id, table_info) return { "table_id": table_id, "table_object": table, "message": f"Đã tạo bảng với {actual_rows} hàng và {cols} cột" } except Exception as e: error_msg = f"Lỗi khi tạo bảng: {str(e)}" print(error_msg) return error_msg @mcp.tool() def update_cell(table, row: int, col: int, content: str): """ Cập nhật nội dung cho một ô trong bảng - table: bảng cần cập nhật (đối tượng Table hoặc mô tả chuỗi) - row: chỉ số hàng - col: chỉ số cột - content: nội dung cần cập nhật """ try: # Print debug info print(f"Updating cell: row={row}, col={col}, table type={type(table)}") # Xử lý trường hợp table là chuỗi if isinstance(table, str): print(f"Table is a string: {table}") # Sử dụng bảng cuối cùng được thêm vào document if not document.tables: return "Không tìm thấy bảng nào trong tài liệu" print(f"Using last table in document. Total tables: {len(document.tables)}") real_table = document.tables[-1] else: real_table = table # Truy cập vào ô theo cách được khuyến nghị trong tài liệu try: if row >= len(real_table.rows): return f"Lỗi: Chỉ số hàng {row} vượt quá số hàng trong bảng ({len(real_table.rows)})" row_cells = real_table.rows[row].cells if col >= len(row_cells): return f"Lỗi: Chỉ số cột {col} vượt quá số cột trong hàng ({len(row_cells)})" cell = row_cells[col] # Xóa nội dung hiện tại cell.text = "" # Thêm nội dung mới paragraph = cell.paragraphs[0] run = paragraph.add_run(content) run.font.size = Pt(10) # Kích thước font mặc định return cell except Exception as cell_error: error_msg = f"Lỗi khi truy cập ô: {str(cell_error)}" print(error_msg) return error_msg except Exception as e: error_msg = f"Lỗi khi cập nhật ô: {str(e)}" print(error_msg) import traceback print(traceback.format_exc()) return error_msg @mcp.tool() def add_page_break(): """ Thêm ngắt trang """ document.add_page_break() return "Đã thêm ngắt trang" @mcp.tool() def fill_table_cell(table, row: int, col: int, content: str, bold: bool = False, alignment = None, font_size: int = None): """ Điền nội dung vào ô trong bảng với định dạng - table: đối tượng bảng hoặc table_id (chuỗi) - row: chỉ số hàng - col: chỉ số cột - content: nội dung cần thêm - bold: in đậm hay không - alignment: căn lề (LEFT, RIGHT, CENTER) - font_size: kích thước font """ try: # Print debug info print(f"Filling cell: row={row}, col={col}, content={content}, table type={type(table)}") # Xử lý trường hợp table là chuỗi (table_id) if isinstance(table, str): print(f"Table is a string ID: {table}") # Trường hợp 1: Đây là table_id từ hàm create_table if table.startswith('table_'): table_info = get_resource(table) if isinstance(table_info, dict) and "table_object" in table_info: real_table = table_info["table_object"] else: # Trường hợp 2: Sử dụng bảng cuối cùng trong tài liệu if not document.tables: return "Không tìm thấy bảng nào trong tài liệu" real_table = document.tables[-1] else: # Trường hợp 3: Sử dụng bảng cuối cùng trong tài liệu if not document.tables: return "Không tìm thấy bảng nào trong tài liệu" real_table = document.tables[-1] else: real_table = table # Kiểm tra có đủ hàng và cột không if row >= len(real_table.rows): return f"Lỗi: Chỉ số hàng {row} vượt quá số hàng trong bảng ({len(real_table.rows)})" if col >= len(real_table.rows[row].cells): return f"Lỗi: Chỉ số cột {col} vượt quá số cột trong hàng ({len(real_table.rows[row].cells)})" # Truy cập ô cell = real_table.rows[row].cells[col] # Clear existing content for paragraph in cell.paragraphs: paragraph.clear() # Add new content paragraph = cell.paragraphs[0] if cell.paragraphs else cell.add_paragraph() run = paragraph.add_run(content) # Set formatting if bold: run.bold = True if font_size: run.font.size = Pt(font_size) if alignment: if alignment == "LEFT": paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.LEFT elif alignment == "RIGHT": paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.RIGHT elif alignment == "CENTER": paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER elif alignment == "JUSTIFY": paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.JUSTIFY return paragraph except Exception as e: error_msg = f"Lỗi khi điền nội dung vào ô: {str(e)}" print(error_msg) import traceback print(traceback.format_exc()) return error_msg @mcp.tool() def add_table_row(table, data: List[str], is_header: bool = False): """ Thêm một hàng vào bảng với dữ liệu được cung cấp - table: đối tượng bảng - data: danh sách dữ liệu cho từng ô - is_header: có phải hàng tiêu đề không """ try: # Print debug info print(f"Adding row to table: data={data}, is_header={is_header}") # Add a new row row = table.add_row() # Fill the cells for i, content in enumerate(data): if i < len(table.columns): cell = row.cells[i] paragraph = cell.paragraphs[0] if cell.paragraphs else cell.add_paragraph() run = paragraph.add_run(content) if is_header: run.bold = True paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER return row except Exception as e: error_msg = f"Lỗi khi thêm hàng vào bảng: {str(e)}" print(error_msg) return error_msg @mcp.tool() def create_simple_table_with_data(headers: List[str], data: List[List[str]], style: str = "Table Grid"): """ Tạo một bảng hoàn chỉnh với dữ liệu - headers: danh sách tiêu đề cột - data: danh sách các hàng dữ liệu - style: kiểu bảng """ try: if not headers or not data: return "Lỗi: headers hoặc data không được để trống" cols = len(headers) # Kiểm tra xem các hàng dữ liệu có đúng số cột không for i, row in enumerate(data): if len(row) != cols: return f"Lỗi: Hàng {i} có {len(row)} cột nhưng cần {cols} cột" # Tạo bảng với số hàng bằng số lượng dữ liệu + 1 hàng tiêu đề table = document.add_table(rows=1, cols=cols) # Start with 1 row for headers table.style = style # Điền tiêu đề for i, header in enumerate(headers): fill_table_cell(table, 0, i, header, bold=True, alignment="CENTER") # Điền dữ liệu for row_data in data: row = table.add_row() for i, cell_data in enumerate(row_data): fill_table_cell(table, len(table.rows)-1, i, cell_data) return table except Exception as e: error_msg = f"Lỗi khi tạo bảng với dữ liệu: {str(e)}" print(error_msg) return error_msg # PHẦN RESOURCES - quản lý tài nguyên @mcp.tool() def save_resource(resource_id: str, content: Any) -> str: """ Lưu tài nguyên vào bộ nhớ và hệ thống file - resource_id: định danh duy nhất cho tài nguyên - content: nội dung của tài nguyên (chuỗi văn bản, đường dẫn đến file, hoặc dữ liệu JSON) Trả về: Thông báo kết quả """ try: # Print debug info print(f"Saving resource: '{resource_id}', content type: {type(content)}") # Lưu vào cache bộ nhớ resources_cache[resource_id] = content # Lưu vào file resource_path = os.path.join(RESOURCES_DIR, f"{resource_id}.json") # If content is a file path and exists if isinstance(content, str) and os.path.exists(content) and not os.path.isdir(content): try: # If it's a docx file, we'll just store the reference if content.endswith('.docx'): with open(resource_path, 'w', encoding='utf-8') as f: json.dump({"content": content, "type": "docx_file"}, f, ensure_ascii=False, indent=2) else: # For other file types, we might want to store the content directly with open(resource_path, 'w', encoding='utf-8') as f: json.dump({"content": content, "type": "file_path"}, f, ensure_ascii=False, indent=2) except Exception as file_error: print(f"Error handling file content: {file_error}") raise else: # Regular content (string, dict, etc.) with open(resource_path, 'w', encoding='utf-8') as f: if isinstance(content, (dict, list)): json.dump(content, f, ensure_ascii=False, indent=2) else: json.dump({"content": str(content)}, f, ensure_ascii=False, indent=2) return f"Đã lưu tài nguyên '{resource_id}' thành công" except Exception as e: error_msg = f"Lỗi khi lưu tài nguyên: {str(e)}" print(error_msg) # Debug print return error_msg @mcp.tool() def get_resource(resource_id: str) -> Any: """ Lấy tài nguyên từ bộ nhớ cache hoặc file hệ thống - resource_id: định danh của tài nguyên cần lấy Trả về: Nội dung của tài nguyên hoặc thông báo lỗi """ # Kiểm tra trong cache if resource_id in resources_cache: return resources_cache[resource_id] # Không có trong cache, thử đọc từ file resource_path = os.path.join(RESOURCES_DIR, f"{resource_id}.json") try: if os.path.exists(resource_path): with open(resource_path, 'r', encoding='utf-8') as f: data = json.load(f) # Nếu dữ liệu được lưu dưới dạng đơn giản, lấy trường content if isinstance(data, dict) and len(data) == 1 and "content" in data: content = data["content"] else: content = data # Cập nhật vào cache resources_cache[resource_id] = content return content else: return f"Không tìm thấy tài nguyên '{resource_id}'" except Exception as e: return f"Lỗi khi đọc tài nguyên: {str(e)}" @mcp.tool() def list_resources() -> List[str]: """ Liệt kê danh sách tất cả tài nguyên có sẵn Trả về: Danh sách các định danh tài nguyên """ # Lấy danh sách từ thư mục resources = [] if os.path.exists(RESOURCES_DIR): for filename in os.listdir(RESOURCES_DIR): if filename.endswith('.json'): resources.append(filename[:-5]) # Bỏ phần mở rộng .json return resources @mcp.tool() def delete_resource(resource_id: str) -> str: """ Xóa tài nguyên - resource_id: định danh của tài nguyên cần xóa Trả về: Thông báo kết quả """ # Xóa khỏi cache if resource_id in resources_cache: del resources_cache[resource_id] # Xóa file resource_path = os.path.join(RESOURCES_DIR, f"{resource_id}.json") try: if os.path.exists(resource_path): os.remove(resource_path) return f"Đã xóa tài nguyên '{resource_id}' thành công" else: return f"Không tìm thấy tài nguyên '{resource_id}'" except Exception as e: return f"Lỗi khi xóa tài nguyên: {str(e)}" # PHẦN PROMPT - quản lý templates và prompts @mcp.tool() def save_prompt(prompt_id: str, template: str, description: str = "", metadata: Dict = None) -> str: """ Lưu prompt template - prompt_id: định danh duy nhất cho prompt - template: nội dung mẫu của prompt, có thể chứa biến dạng {variable_name} - description: mô tả về mục đích và cách sử dụng của prompt - metadata: thông tin bổ sung về prompt (tags, tác giả, v.v.) Trả về: Thông báo kết quả """ prompt_data = { "template": template, "description": description, "metadata": metadata or {}, "created_at": str(import_datetime_and_get_now()) } # Lưu vào file prompt_path = os.path.join(PROMPTS_DIR, f"{prompt_id}.json") try: with open(prompt_path, 'w', encoding='utf-8') as f: json.dump(prompt_data, f, ensure_ascii=False, indent=2) return f"Đã lưu prompt '{prompt_id}' thành công" except Exception as e: return f"Lỗi khi lưu prompt: {str(e)}" @mcp.tool() def get_prompt(prompt_id: str) -> Dict: """ Lấy thông tin về một prompt - prompt_id: định danh của prompt cần lấy Trả về: Thông tin đầy đủ về prompt hoặc thông báo lỗi """ prompt_path = os.path.join(PROMPTS_DIR, f"{prompt_id}.json") try: if os.path.exists(prompt_path): with open(prompt_path, 'r', encoding='utf-8') as f: return json.load(f) else: return {"error": f"Không tìm thấy prompt '{prompt_id}'"} except Exception as e: return {"error": f"Lỗi khi đọc prompt: {str(e)}"} @mcp.tool() def list_prompts() -> List[str]: """ Liệt kê danh sách tất cả prompts có sẵn Trả về: Danh sách các định danh prompt """ prompts = [] if os.path.exists(PROMPTS_DIR): for filename in os.listdir(PROMPTS_DIR): if filename.endswith('.json'): prompts.append(filename[:-5]) # Bỏ phần mở rộng .json return prompts @mcp.tool() def delete_prompt(prompt_id: str) -> str: """ Xóa prompt - prompt_id: định danh của prompt cần xóa Trả về: Thông báo kết quả """ prompt_path = os.path.join(PROMPTS_DIR, f"{prompt_id}.json") try: if os.path.exists(prompt_path): os.remove(prompt_path) return f"Đã xóa prompt '{prompt_id}' thành công" else: return f"Không tìm thấy prompt '{prompt_id}'" except Exception as e: return f"Lỗi khi xóa prompt: {str(e)}" @mcp.tool() def render_prompt(prompt_id: str, variables: Dict = None) -> str: """ Render một prompt với các biến được cung cấp - prompt_id: định danh của prompt - variables: từ điển chứa các giá trị thay thế cho các biến trong template Trả về: Prompt đã được render với các biến được thay thế """ prompt_info = get_prompt(prompt_id) if "error" in prompt_info: return prompt_info["error"] template = prompt_info.get("template", "") # Thực hiện thay thế biến if variables: try: # Sử dụng format string của Python for key, value in variables.items(): placeholder = "{" + key + "}" template = template.replace(placeholder, str(value)) except Exception as e: return f"Lỗi khi render prompt: {str(e)}" return template def import_datetime_and_get_now(): """Helper function to get current datetime""" from datetime import datetime return datetime.now() if __name__ == "__main__": print("Server đang khởi động...") mcp.run(transport="stdio")

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/cuongpham2107/word-mcp-server'

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