#
tokens: 9689/50000 9/9 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .DS_Store
├── .gitignore
├── .python-version
├── chroma.sqlite3
├── common.py
├── pyproject.toml
├── README.md
├── server.py
└── word_mcp_server.egg-info
    ├── dependency_links.txt
    ├── PKG-INFO
    ├── requires.txt
    ├── SOURCES.txt
    └── top_level.txt
```

# Files

--------------------------------------------------------------------------------
/.python-version:
--------------------------------------------------------------------------------

```
1 | 3.12
2 | 
```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
1 | .qodo
2 | 
```

--------------------------------------------------------------------------------
/word_mcp_server.egg-info/dependency_links.txt:
--------------------------------------------------------------------------------

```
1 | 
2 | 
```

--------------------------------------------------------------------------------
/word_mcp_server.egg-info/top_level.txt:
--------------------------------------------------------------------------------

```
1 | common
2 | server
3 | 
```

--------------------------------------------------------------------------------
/word_mcp_server.egg-info/requires.txt:
--------------------------------------------------------------------------------

```
1 | python-docx
2 | langchain-mcp-adapters
3 | opencv-python
4 | numpy
5 | 
```

--------------------------------------------------------------------------------
/word_mcp_server.egg-info/SOURCES.txt:
--------------------------------------------------------------------------------

```
1 | README.md
2 | common.py
3 | pyproject.toml
4 | server.py
5 | word_mcp_server.egg-info/PKG-INFO
6 | word_mcp_server.egg-info/SOURCES.txt
7 | word_mcp_server.egg-info/dependency_links.txt
8 | word_mcp_server.egg-info/requires.txt
9 | word_mcp_server.egg-info/top_level.txt
```

--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------

```toml
 1 | [project]
 2 | name = "word-mcp-server"
 3 | version = "0.1.1"
 4 | description = "Add your description here"
 5 | readme = "README.md"
 6 | requires-python = ">=3.12"
 7 | dependencies = [
 8 |     "python-docx",
 9 |     "langchain-mcp-adapters",
10 |     "opencv-python",
11 |     "numpy",
12 | ]
13 | 
14 | [build-system]
15 | requires = ["setuptools>=61.0"]
16 | build-backend = "setuptools.build_meta"
17 | 
18 | [tool.setuptools]
19 | py-modules = ["server", "common"]
20 | 
```

--------------------------------------------------------------------------------
/common.py:
--------------------------------------------------------------------------------

```python
 1 | from docx.enum.text import WD_COLOR_INDEX
 2 | 
 3 | def color_paragraph(paragraph, color:str):
 4 |     # Get the style of the paragraph
 5 |     if color == 'black':
 6 |         color_element = WD_COLOR_INDEX.BLACK
 7 |     elif color == 'blue':
 8 |         color_element = WD_COLOR_INDEX.BLUE
 9 |     elif color == 'green':
10 |         color_element = WD_COLOR_INDEX.BRIGHT_GREEN
11 |     elif color == 'dark blue':
12 |         color_element = WD_COLOR_INDEX.DARK_BLUE
13 |     elif color == 'dark red':
14 |         color_element = WD_COLOR_INDEX.DARK_RED
15 |     elif color == 'dark yellow':
16 |         color_element = WD_COLOR_INDEX.DARK_YELLOW
17 |     elif color == 'dark green':
18 |         color_element = WD_COLOR_INDEX.GREEN
19 |     elif color == 'pink':
20 |         color_element = WD_COLOR_INDEX.PINK
21 |     elif color == 'red':
22 |         color_element = WD_COLOR_INDEX.PINK
23 |     elif color == 'white':
24 |         color_element = WD_COLOR_INDEX.WHITE
25 |     elif color == 'teal':
26 |         color_element = WD_COLOR_INDEX.TEAL
27 |     elif color == 'yellow':
28 |         color_element = WD_COLOR_INDEX.YELLOW
29 |     elif color == 'violet':
30 |         color_element = WD_COLOR_INDEX.VIOLET
31 |     elif color == 'gray25':
32 |         color_element = WD_COLOR_INDEX.GRAY_25
33 |     elif color == 'gray50':
34 |         color_element = WD_COLOR_INDEX.GRAY_50
35 |     
36 |     return color_element
```

--------------------------------------------------------------------------------
/server.py:
--------------------------------------------------------------------------------

```python
  1 | # math_server.py
  2 | from docx import Document
  3 | from docx.enum.section import WD_SECTION_START
  4 | from docx.section import Section
  5 | from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
  6 | from mcp.server.fastmcp import FastMCP
  7 | import cv2
  8 | import numpy as np
  9 | from io import BytesIO
 10 | import os
 11 | import json
 12 | from typing import Dict, List, Any, Optional
 13 | from docx.shared import Inches, Pt
 14 | from docx.enum.style import WD_STYLE_TYPE
 15 | 
 16 | mcp = FastMCP("Word MCP Server", "1.0")
 17 | 
 18 | document = Document()
 19 | 
 20 | # Khởi tạo cấu trúc dữ liệu cho Resources và Prompts
 21 | RESOURCES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "resources")
 22 | PROMPTS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "prompts")
 23 | 
 24 | # Tạo thư mục resources và prompts nếu chưa tồn tại
 25 | os.makedirs(RESOURCES_DIR, exist_ok=True)
 26 | os.makedirs(PROMPTS_DIR, exist_ok=True)
 27 | 
 28 | # Dictionary lưu trữ tài nguyên và prompt trong bộ nhớ
 29 | resources_cache = {}
 30 | 
 31 | 
 32 | @mcp.tool()
 33 | def save_file(filename: str):
 34 |     """
 35 |     Save file to disk
 36 |     - filename: path where the file should be saved (including filename)
 37 |     """
 38 |     try:
 39 |         # Check if the filename has a .docx extension
 40 |         if not filename.endswith('.docx'):
 41 |             filename = f"{filename}.docx"
 42 |             
 43 |         # If no directory specified, save to resources directory
 44 |         if not os.path.dirname(filename):
 45 |             filename = os.path.join(RESOURCES_DIR, filename)
 46 |             
 47 |         # Ensure the directory exists
 48 |         directory = os.path.dirname(filename)
 49 |         if directory and not os.path.exists(directory):
 50 |             os.makedirs(directory, exist_ok=True)
 51 |             
 52 |         # Debug info
 53 |         print(f"Attempting to save document to: {filename}")
 54 |             
 55 |         # Save the document
 56 |         document.save(filename)
 57 |         
 58 |         # Also save to resources for reference
 59 |         resource_id = os.path.basename(filename)
 60 |         save_resource(resource_id, resource_id)
 61 |         
 62 |         return f"File saved successfully to: {filename}"
 63 |     except Exception as e:
 64 |         error_msg = f"Error saving file: {str(e)}"
 65 |         print(error_msg)  # Debug print
 66 |         return error_msg
 67 | 
 68 | @mcp.tool()
 69 | def add_heading(content: str, level: int):
 70 |     """
 71 |     Add heading to the document
 72 |         - Content: nội dung title hoặc heading
 73 |         - Level: bậc của heading (0, 1, 2, ...). Số càng nhỏ font chữ càng lớn.
 74 |     
 75 |     """
 76 |     document.add_heading(content, level)
 77 | 
 78 | @mcp.tool()
 79 | def add_paragraph(
 80 |     content: str,
 81 |     style: str = "Normal",
 82 |     font_size: int = 12,
 83 |     bold: bool = False,
 84 |     italic: bool = False,
 85 |     alignment: WD_PARAGRAPH_ALIGNMENT = WD_PARAGRAPH_ALIGNMENT.LEFT,
 86 | ):
 87 |     """
 88 |     Add paragraph to the document
 89 |         - Content: nội dung paragraph
 90 |     """
 91 |     p = document.add_paragraph(content)
 92 |     p.style = style
 93 |     p.alignment = alignment
 94 |     run = p.runs[0]
 95 |     run.font.size = Pt(font_size)
 96 |     run.font.bold = bold
 97 |     run.font.italic = italic
 98 |     return p
 99 | 
100 | 
101 | @mcp.tool()
102 | def update_paragraph(
103 |     p,
104 |     content: str = None,
105 |     style: str = "Normal",
106 |     font_size: int = 12,
107 |     bold: bool = False,
108 |     italic: bool = False,
109 |     color: str = None,
110 |     alignment: WD_PARAGRAPH_ALIGNMENT = WD_PARAGRAPH_ALIGNMENT.LEFT,
111 | ):
112 |     """
113 |     Update paragraph
114 |         - Content: nội dung paragraph
115 |         - Style: style of paragraph
116 |         - Font size: font size of paragraph
117 |         - Bold: bold or not
118 |         - Italic: italic or not
119 |         - Color: màu chữ (black, blue, green, dark blue, dark red, dark yellow, 
120 |           dark green, pink, red, white, teal, yellow, violet, gray25, gray50)
121 |         - Alignment: căn lề (LEFT, RIGHT, CENTER, JUSTIFY)
122 |     """
123 |     p.style = style
124 |     p.alignment = alignment
125 |     
126 |     run = p.runs[0]
127 |     run.font.size = Pt(font_size)
128 |     run.font.bold = bold
129 |     run.font.italic = italic
130 |     
131 |     if color is not None:
132 |         from common import color_paragraph
133 |         color_element = color_paragraph(p, color)
134 |         run.font.color.val = color_element
135 | 
136 |     if content is not None:
137 |         new_run = p.add_run(content)
138 |         new_run.font.size = Pt(font_size)
139 |         new_run.font.bold = bold
140 |         new_run.font.italic = italic
141 |         if color is not None:
142 |             new_run.font.color.val = color_element
143 |             
144 |     return p 
145 | 
146 | 
147 | @mcp.tool()
148 | def add_section(section = WD_SECTION_START.NEW_PAGE) -> Section:
149 |     """
150 |     Add section to the document
151 |     """
152 |     return document.add_section(section)
153 | 
154 | @mcp.tool()
155 | def set_number_of_columns(section, cols):
156 |     """
157 |     Set number of columns for a section
158 |         - Section: section to set columns
159 |         - Cols: number of columns
160 |     """
161 |     section._sectPr.xpath("./w:cols")[0].set("{http://schemas.openxmlformats.org/wordprocessingml/2006/main}num", str(cols))
162 | 
163 | @mcp.tool()
164 | def add_run_to_paragraph(
165 |     p,
166 |     content: str,
167 |     bold: bool = False,
168 |     italic: bool = False,
169 |     underline: bool = False,
170 |     color: str = None,
171 |     highlight: str = None
172 | ):
173 |     """
174 |     Thêm câu vào đoạn văn đã được khởi tạo
175 |         - p: paragraph đã được khởi tạo
176 |         - content: nội dung cần thêm
177 |         - bold: in đậm hoặc không
178 |         - italic: in nghiêng hoặc không
179 |         - underline: gạch chân hoặc không
180 |         - color: màu chữ (black, blue, green, dark blue, dark red, dark yellow,
181 |           dark green, pink, red, white, teal, yellow, violet, gray25, gray50)
182 |         - highlight: màu nền highlight cho chữ
183 |     """
184 |     sentence_element = p.add_run(str(content))
185 |     sentence_element.bold = bold
186 |     sentence_element.italic = italic
187 |     sentence_element.underline = underline
188 |     
189 |     if color is not None:
190 |         from common import color_paragraph
191 |         color_element = color_paragraph(p, color)
192 |         sentence_element.font.color.val = color_element
193 |     
194 |     if highlight is not None:
195 |         from docx.enum.text import WD_COLOR_INDEX
196 |         
197 |         if highlight == 'black':
198 |             color_element = WD_COLOR_INDEX.BLACK
199 |         elif highlight == 'blue':
200 |             color_element = WD_COLOR_INDEX.BLUE
201 |         elif highlight == 'green':
202 |             color_element = WD_COLOR_INDEX.BRIGHT_GREEN
203 |         elif highlight == 'dark blue':
204 |             color_element = WD_COLOR_INDEX.DARK_BLUE
205 |         elif highlight == 'dark red':
206 |             color_element = WD_COLOR_INDEX.DARK_RED
207 |         elif highlight == 'dark yellow':
208 |             color_element = WD_COLOR_INDEX.DARK_YELLOW
209 |         elif highlight == 'dark green':
210 |             color_element = WD_COLOR_INDEX.GREEN
211 |         elif highlight == 'pink':
212 |             color_element = WD_COLOR_INDEX.PINK
213 |         elif highlight == 'red':
214 |             color_element = WD_COLOR_INDEX.RED
215 |         elif highlight == 'white':
216 |             color_element = WD_COLOR_INDEX.WHITE
217 |         elif highlight == 'teal':
218 |             color_element = WD_COLOR_INDEX.TEAL
219 |         elif highlight == 'yellow':
220 |             color_element = WD_COLOR_INDEX.YELLOW
221 |         elif highlight == 'violet':
222 |             color_element = WD_COLOR_INDEX.VIOLET
223 |         elif highlight == 'gray25':
224 |             color_element = WD_COLOR_INDEX.GRAY_25
225 |         elif highlight == 'gray50':
226 |             color_element = WD_COLOR_INDEX.GRAY_50
227 |         
228 |         style = document.styles.add_style(f"highlight_style_{highlight}", WD_STYLE_TYPE.CHARACTER)
229 |         style.font.highlight_color = color_element
230 |         sentence_element.style = style
231 |     
232 |     return sentence_element
233 | 
234 | @mcp.tool()
235 | def add_picture(image_path_or_stream, width: float = 5.0):
236 |     """
237 |     Thêm hình ảnh vào tài liệu
238 |         - image_path_or_stream: đường dẫn đến file ảnh hoặc ảnh dạng ma trận
239 |         - width: chiều rộng của ảnh (tính bằng inch)
240 |     """
241 |     if isinstance(image_path_or_stream, str):
242 |         img = cv2.imread(image_path_or_stream)
243 |     else:
244 |         img = np.array(image_path_or_stream)
245 | 
246 |     is_success, im_buf_arr = cv2.imencode(".jpg", img)
247 |     byte_im = im_buf_arr.tobytes()
248 |     stream = BytesIO(byte_im)
249 |     return document.add_picture(stream, width=Inches(width))
250 | 
251 | @mcp.tool()
252 | def create_new_document():
253 |     """
254 |     Tạo một tài liệu mới, loại bỏ tài liệu hiện tại
255 |     """
256 |     global document
257 |     document = Document()
258 |     return "Đã tạo tài liệu mới"
259 | 
260 | @mcp.tool()
261 | def open_document(filepath: str):
262 |     """
263 |     Mở một tài liệu docx có sẵn
264 |         - filepath: đường dẫn đến file docx cần mở
265 |     """
266 |     global document
267 |     try:
268 |         document = Document(filepath)
269 |         return f"Đã mở tài liệu từ {filepath}"
270 |     except Exception as e:
271 |         return f"Lỗi khi mở tài liệu: {str(e)}"
272 | 
273 | @mcp.tool()
274 | def add_table(rows: int, cols: int, style: str = "Table Grid"):
275 |     """
276 |     Thêm bảng vào tài liệu
277 |         - rows: số hàng
278 |         - cols: số cột
279 |         - style: kiểu bảng
280 |     """
281 |     table = document.add_table(rows=rows, cols=cols)
282 |     table.style = style
283 |     return table
284 | 
285 | @mcp.tool()
286 | def create_table(rows: int, cols: int, style: str = "Table Grid", headers: List[str] = None):
287 |     """
288 |     Tạo bảng với số hàng và cột chỉ định, có thể thêm tiêu đề
289 |         - rows: số hàng (không bao gồm hàng tiêu đề)
290 |         - cols: số cột
291 |         - style: kiểu bảng ("Table Grid", "Light Grid", "Light Shading", etc.)
292 |         - headers: danh sách các tiêu đề cột (độ dài bằng số cột)
293 |     """
294 |     try:
295 |         # Nếu có headers, thêm 1 hàng cho tiêu đề
296 |         actual_rows = rows
297 |         if headers:
298 |             if len(headers) != cols:
299 |                 return f"Lỗi: Số lượng tiêu đề ({len(headers)}) khác với số cột ({cols})"
300 |             actual_rows = rows + 1
301 |             
302 |         # Tạo bảng
303 |         table = document.add_table(rows=actual_rows, cols=cols)
304 |         table.style = style
305 |         
306 |         # Thêm tiêu đề nếu có
307 |         if headers:
308 |             for i, header in enumerate(headers):
309 |                 cell = table.cell(0, i)
310 |                 cell.text = header
311 |                 # Định dạng tiêu đề (in đậm, căn giữa)
312 |                 for paragraph in cell.paragraphs:
313 |                     for run in paragraph.runs:
314 |                         run.bold = True
315 |         
316 |         # Trả về thông tin của bảng để có thể sử dụng sau này
317 |         table_info = {
318 |             "table_object": table,
319 |             "rows": actual_rows,
320 |             "cols": cols,
321 |             "has_headers": bool(headers)
322 |         }
323 |         
324 |         # Lưu thông tin bảng vào resources để có thể truy cập sau này
325 |         table_id = f"table_{id(table)}"
326 |         save_resource(table_id, table_info)
327 |         
328 |         return {
329 |             "table_id": table_id,
330 |             "table_object": table,
331 |             "message": f"Đã tạo bảng với {actual_rows} hàng và {cols} cột"
332 |         }
333 |     except Exception as e:
334 |         error_msg = f"Lỗi khi tạo bảng: {str(e)}"
335 |         print(error_msg)
336 |         return error_msg
337 | 
338 | @mcp.tool()
339 | def update_cell(table, row: int, col: int, content: str):
340 |     """
341 |     Cập nhật nội dung cho một ô trong bảng
342 |         - table: bảng cần cập nhật (đối tượng Table hoặc mô tả chuỗi)
343 |         - row: chỉ số hàng
344 |         - col: chỉ số cột
345 |         - content: nội dung cần cập nhật
346 |     """
347 |     try:
348 |         # Print debug info
349 |         print(f"Updating cell: row={row}, col={col}, table type={type(table)}")
350 |         
351 |         # Xử lý trường hợp table là chuỗi
352 |         if isinstance(table, str):
353 |             print(f"Table is a string: {table}")
354 |             # Sử dụng bảng cuối cùng được thêm vào document
355 |             if not document.tables:
356 |                 return "Không tìm thấy bảng nào trong tài liệu"
357 |                 
358 |             print(f"Using last table in document. Total tables: {len(document.tables)}")
359 |             real_table = document.tables[-1]
360 |         else:
361 |             real_table = table
362 |         
363 |         # Truy cập vào ô theo cách được khuyến nghị trong tài liệu
364 |         try:
365 |             if row >= len(real_table.rows):
366 |                 return f"Lỗi: Chỉ số hàng {row} vượt quá số hàng trong bảng ({len(real_table.rows)})"
367 |                 
368 |             row_cells = real_table.rows[row].cells
369 |             
370 |             if col >= len(row_cells):
371 |                 return f"Lỗi: Chỉ số cột {col} vượt quá số cột trong hàng ({len(row_cells)})"
372 |                 
373 |             cell = row_cells[col]
374 |             
375 |             # Xóa nội dung hiện tại
376 |             cell.text = ""
377 |             
378 |             # Thêm nội dung mới
379 |             paragraph = cell.paragraphs[0]
380 |             run = paragraph.add_run(content)
381 |             run.font.size = Pt(10)  # Kích thước font mặc định
382 |             
383 |             return cell
384 |         except Exception as cell_error:
385 |             error_msg = f"Lỗi khi truy cập ô: {str(cell_error)}"
386 |             print(error_msg)
387 |             return error_msg
388 |     except Exception as e:
389 |         error_msg = f"Lỗi khi cập nhật ô: {str(e)}"
390 |         print(error_msg)
391 |         import traceback
392 |         print(traceback.format_exc())
393 |         return error_msg
394 | 
395 | @mcp.tool()
396 | def add_page_break():
397 |     """
398 |     Thêm ngắt trang
399 |     """
400 |     document.add_page_break()
401 |     return "Đã thêm ngắt trang"
402 | 
403 | @mcp.tool()
404 | def fill_table_cell(table, row: int, col: int, content: str, bold: bool = False, alignment = None, font_size: int = None):
405 |     """
406 |     Điền nội dung vào ô trong bảng với định dạng
407 |         - table: đối tượng bảng hoặc table_id (chuỗi)
408 |         - row: chỉ số hàng
409 |         - col: chỉ số cột
410 |         - content: nội dung cần thêm
411 |         - bold: in đậm hay không
412 |         - alignment: căn lề (LEFT, RIGHT, CENTER)
413 |         - font_size: kích thước font
414 |     """
415 |     try:
416 |         # Print debug info
417 |         print(f"Filling cell: row={row}, col={col}, content={content}, table type={type(table)}")
418 |         
419 |         # Xử lý trường hợp table là chuỗi (table_id)
420 |         if isinstance(table, str):
421 |             print(f"Table is a string ID: {table}")
422 |             
423 |             # Trường hợp 1: Đây là table_id từ hàm create_table
424 |             if table.startswith('table_'):
425 |                 table_info = get_resource(table)
426 |                 if isinstance(table_info, dict) and "table_object" in table_info:
427 |                     real_table = table_info["table_object"]
428 |                 else:
429 |                     # Trường hợp 2: Sử dụng bảng cuối cùng trong tài liệu
430 |                     if not document.tables:
431 |                         return "Không tìm thấy bảng nào trong tài liệu"
432 |                     real_table = document.tables[-1]
433 |             else:
434 |                 # Trường hợp 3: Sử dụng bảng cuối cùng trong tài liệu
435 |                 if not document.tables:
436 |                     return "Không tìm thấy bảng nào trong tài liệu"
437 |                 real_table = document.tables[-1]
438 |         else:
439 |             real_table = table
440 |         
441 |         # Kiểm tra có đủ hàng và cột không
442 |         if row >= len(real_table.rows):
443 |             return f"Lỗi: Chỉ số hàng {row} vượt quá số hàng trong bảng ({len(real_table.rows)})"
444 |             
445 |         if col >= len(real_table.rows[row].cells):
446 |             return f"Lỗi: Chỉ số cột {col} vượt quá số cột trong hàng ({len(real_table.rows[row].cells)})"
447 |             
448 |         # Truy cập ô
449 |         cell = real_table.rows[row].cells[col]
450 |         
451 |         # Clear existing content
452 |         for paragraph in cell.paragraphs:
453 |             paragraph.clear()
454 |         
455 |         # Add new content
456 |         paragraph = cell.paragraphs[0] if cell.paragraphs else cell.add_paragraph()
457 |         run = paragraph.add_run(content)
458 |         
459 |         # Set formatting
460 |         if bold:
461 |             run.bold = True
462 |         
463 |         if font_size:
464 |             run.font.size = Pt(font_size)
465 |         
466 |         if alignment:
467 |             if alignment == "LEFT":
468 |                 paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.LEFT
469 |             elif alignment == "RIGHT":
470 |                 paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.RIGHT
471 |             elif alignment == "CENTER":
472 |                 paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
473 |             elif alignment == "JUSTIFY":
474 |                 paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.JUSTIFY
475 |         
476 |         return paragraph
477 |     except Exception as e:
478 |         error_msg = f"Lỗi khi điền nội dung vào ô: {str(e)}"
479 |         print(error_msg)
480 |         import traceback
481 |         print(traceback.format_exc())
482 |         return error_msg
483 | 
484 | @mcp.tool()
485 | def add_table_row(table, data: List[str], is_header: bool = False):
486 |     """
487 |     Thêm một hàng vào bảng với dữ liệu được cung cấp
488 |         - table: đối tượng bảng
489 |         - data: danh sách dữ liệu cho từng ô
490 |         - is_header: có phải hàng tiêu đề không
491 |     """
492 |     try:
493 |         # Print debug info
494 |         print(f"Adding row to table: data={data}, is_header={is_header}")
495 |         
496 |         # Add a new row
497 |         row = table.add_row()
498 |         
499 |         # Fill the cells
500 |         for i, content in enumerate(data):
501 |             if i < len(table.columns):
502 |                 cell = row.cells[i]
503 |                 paragraph = cell.paragraphs[0] if cell.paragraphs else cell.add_paragraph()
504 |                 run = paragraph.add_run(content)
505 |                 
506 |                 if is_header:
507 |                     run.bold = True
508 |                     paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
509 |         
510 |         return row
511 |     except Exception as e:
512 |         error_msg = f"Lỗi khi thêm hàng vào bảng: {str(e)}"
513 |         print(error_msg)
514 |         return error_msg
515 | 
516 | @mcp.tool()
517 | def create_simple_table_with_data(headers: List[str], data: List[List[str]], style: str = "Table Grid"):
518 |     """
519 |     Tạo một bảng hoàn chỉnh với dữ liệu
520 |         - headers: danh sách tiêu đề cột
521 |         - data: danh sách các hàng dữ liệu
522 |         - style: kiểu bảng
523 |     """
524 |     try:
525 |         if not headers or not data:
526 |             return "Lỗi: headers hoặc data không được để trống"
527 |             
528 |         cols = len(headers)
529 |         
530 |         # Kiểm tra xem các hàng dữ liệu có đúng số cột không
531 |         for i, row in enumerate(data):
532 |             if len(row) != cols:
533 |                 return f"Lỗi: Hàng {i} có {len(row)} cột nhưng cần {cols} cột"
534 |         
535 |         # Tạo bảng với số hàng bằng số lượng dữ liệu + 1 hàng tiêu đề
536 |         table = document.add_table(rows=1, cols=cols)  # Start with 1 row for headers
537 |         table.style = style
538 |         
539 |         # Điền tiêu đề
540 |         for i, header in enumerate(headers):
541 |             fill_table_cell(table, 0, i, header, bold=True, alignment="CENTER")
542 |         
543 |         # Điền dữ liệu
544 |         for row_data in data:
545 |             row = table.add_row()
546 |             for i, cell_data in enumerate(row_data):
547 |                 fill_table_cell(table, len(table.rows)-1, i, cell_data)
548 |         
549 |         return table
550 |     except Exception as e:
551 |         error_msg = f"Lỗi khi tạo bảng với dữ liệu: {str(e)}"
552 |         print(error_msg)
553 |         return error_msg
554 | 
555 | # PHẦN RESOURCES - quản lý tài nguyên
556 | @mcp.tool()
557 | def save_resource(resource_id: str, content: Any) -> str:
558 |     """
559 |     Lưu tài nguyên vào bộ nhớ và hệ thống file
560 |     
561 |     - resource_id: định danh duy nhất cho tài nguyên
562 |     - 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)
563 |     
564 |     Trả về: Thông báo kết quả
565 |     """
566 |     try:
567 |         # Print debug info
568 |         print(f"Saving resource: '{resource_id}', content type: {type(content)}")
569 |         
570 |         # Lưu vào cache bộ nhớ
571 |         resources_cache[resource_id] = content
572 |         
573 |         # Lưu vào file
574 |         resource_path = os.path.join(RESOURCES_DIR, f"{resource_id}.json")
575 |         
576 |         # If content is a file path and exists
577 |         if isinstance(content, str) and os.path.exists(content) and not os.path.isdir(content):
578 |             try:
579 |                 # If it's a docx file, we'll just store the reference
580 |                 if content.endswith('.docx'):
581 |                     with open(resource_path, 'w', encoding='utf-8') as f:
582 |                         json.dump({"content": content, "type": "docx_file"}, f, ensure_ascii=False, indent=2)
583 |                 else:
584 |                     # For other file types, we might want to store the content directly
585 |                     with open(resource_path, 'w', encoding='utf-8') as f:
586 |                         json.dump({"content": content, "type": "file_path"}, f, ensure_ascii=False, indent=2)
587 |             except Exception as file_error:
588 |                 print(f"Error handling file content: {file_error}")
589 |                 raise
590 |         else:
591 |             # Regular content (string, dict, etc.)
592 |             with open(resource_path, 'w', encoding='utf-8') as f:
593 |                 if isinstance(content, (dict, list)):
594 |                     json.dump(content, f, ensure_ascii=False, indent=2)
595 |                 else:
596 |                     json.dump({"content": str(content)}, f, ensure_ascii=False, indent=2)
597 |         
598 |         return f"Đã lưu tài nguyên '{resource_id}' thành công"
599 |     except Exception as e:
600 |         error_msg = f"Lỗi khi lưu tài nguyên: {str(e)}"
601 |         print(error_msg)  # Debug print
602 |         return error_msg
603 | 
604 | @mcp.tool()
605 | def get_resource(resource_id: str) -> Any:
606 |     """
607 |     Lấy tài nguyên từ bộ nhớ cache hoặc file hệ thống
608 |     
609 |     - resource_id: định danh của tài nguyên cần lấy
610 |     
611 |     Trả về: Nội dung của tài nguyên hoặc thông báo lỗi
612 |     """
613 |     # Kiểm tra trong cache
614 |     if resource_id in resources_cache:
615 |         return resources_cache[resource_id]
616 |     
617 |     # Không có trong cache, thử đọc từ file
618 |     resource_path = os.path.join(RESOURCES_DIR, f"{resource_id}.json")
619 |     try:
620 |         if os.path.exists(resource_path):
621 |             with open(resource_path, 'r', encoding='utf-8') as f:
622 |                 data = json.load(f)
623 |                 # Nếu dữ liệu được lưu dưới dạng đơn giản, lấy trường content
624 |                 if isinstance(data, dict) and len(data) == 1 and "content" in data:
625 |                     content = data["content"]
626 |                 else:
627 |                     content = data
628 |                 
629 |                 # Cập nhật vào cache
630 |                 resources_cache[resource_id] = content
631 |                 return content
632 |         else:
633 |             return f"Không tìm thấy tài nguyên '{resource_id}'"
634 |     except Exception as e:
635 |         return f"Lỗi khi đọc tài nguyên: {str(e)}"
636 | 
637 | @mcp.tool()
638 | def list_resources() -> List[str]:
639 |     """
640 |     Liệt kê danh sách tất cả tài nguyên có sẵn
641 |     
642 |     Trả về: Danh sách các định danh tài nguyên
643 |     """
644 |     # Lấy danh sách từ thư mục
645 |     resources = []
646 |     if os.path.exists(RESOURCES_DIR):
647 |         for filename in os.listdir(RESOURCES_DIR):
648 |             if filename.endswith('.json'):
649 |                 resources.append(filename[:-5])  # Bỏ phần mở rộng .json
650 |     
651 |     return resources
652 | 
653 | @mcp.tool()
654 | def delete_resource(resource_id: str) -> str:
655 |     """
656 |     Xóa tài nguyên
657 |     
658 |     - resource_id: định danh của tài nguyên cần xóa
659 |     
660 |     Trả về: Thông báo kết quả
661 |     """
662 |     # Xóa khỏi cache
663 |     if resource_id in resources_cache:
664 |         del resources_cache[resource_id]
665 |     
666 |     # Xóa file
667 |     resource_path = os.path.join(RESOURCES_DIR, f"{resource_id}.json")
668 |     try:
669 |         if os.path.exists(resource_path):
670 |             os.remove(resource_path)
671 |             return f"Đã xóa tài nguyên '{resource_id}' thành công"
672 |         else:
673 |             return f"Không tìm thấy tài nguyên '{resource_id}'"
674 |     except Exception as e:
675 |         return f"Lỗi khi xóa tài nguyên: {str(e)}"
676 | 
677 | # PHẦN PROMPT - quản lý templates và prompts
678 | @mcp.tool()
679 | def save_prompt(prompt_id: str, template: str, description: str = "", metadata: Dict = None) -> str:
680 |     """
681 |     Lưu prompt template
682 |     
683 |     - prompt_id: định danh duy nhất cho prompt
684 |     - template: nội dung mẫu của prompt, có thể chứa biến dạng {variable_name}
685 |     - description: mô tả về mục đích và cách sử dụng của prompt
686 |     - metadata: thông tin bổ sung về prompt (tags, tác giả, v.v.)
687 |     
688 |     Trả về: Thông báo kết quả
689 |     """
690 |     prompt_data = {
691 |         "template": template,
692 |         "description": description,
693 |         "metadata": metadata or {},
694 |         "created_at": str(import_datetime_and_get_now())
695 |     }
696 |     
697 |     # Lưu vào file
698 |     prompt_path = os.path.join(PROMPTS_DIR, f"{prompt_id}.json")
699 |     try:
700 |         with open(prompt_path, 'w', encoding='utf-8') as f:
701 |             json.dump(prompt_data, f, ensure_ascii=False, indent=2)
702 |         return f"Đã lưu prompt '{prompt_id}' thành công"
703 |     except Exception as e:
704 |         return f"Lỗi khi lưu prompt: {str(e)}"
705 | 
706 | @mcp.tool()
707 | def get_prompt(prompt_id: str) -> Dict:
708 |     """
709 |     Lấy thông tin về một prompt
710 |     
711 |     - prompt_id: định danh của prompt cần lấy
712 |     
713 |     Trả về: Thông tin đầy đủ về prompt hoặc thông báo lỗi
714 |     """
715 |     prompt_path = os.path.join(PROMPTS_DIR, f"{prompt_id}.json")
716 |     try:
717 |         if os.path.exists(prompt_path):
718 |             with open(prompt_path, 'r', encoding='utf-8') as f:
719 |                 return json.load(f)
720 |         else:
721 |             return {"error": f"Không tìm thấy prompt '{prompt_id}'"}
722 |     except Exception as e:
723 |         return {"error": f"Lỗi khi đọc prompt: {str(e)}"}
724 | 
725 | @mcp.tool()
726 | def list_prompts() -> List[str]:
727 |     """
728 |     Liệt kê danh sách tất cả prompts có sẵn
729 |     
730 |     Trả về: Danh sách các định danh prompt
731 |     """
732 |     prompts = []
733 |     if os.path.exists(PROMPTS_DIR):
734 |         for filename in os.listdir(PROMPTS_DIR):
735 |             if filename.endswith('.json'):
736 |                 prompts.append(filename[:-5])  # Bỏ phần mở rộng .json
737 |     
738 |     return prompts
739 | 
740 | @mcp.tool()
741 | def delete_prompt(prompt_id: str) -> str:
742 |     """
743 |     Xóa prompt
744 |     
745 |     - prompt_id: định danh của prompt cần xóa
746 |     
747 |     Trả về: Thông báo kết quả
748 |     """
749 |     prompt_path = os.path.join(PROMPTS_DIR, f"{prompt_id}.json")
750 |     try:
751 |         if os.path.exists(prompt_path):
752 |             os.remove(prompt_path)
753 |             return f"Đã xóa prompt '{prompt_id}' thành công"
754 |         else:
755 |             return f"Không tìm thấy prompt '{prompt_id}'"
756 |     except Exception as e:
757 |         return f"Lỗi khi xóa prompt: {str(e)}"
758 | 
759 | @mcp.tool()
760 | def render_prompt(prompt_id: str, variables: Dict = None) -> str:
761 |     """
762 |     Render một prompt với các biến được cung cấp
763 |     
764 |     - prompt_id: định danh của prompt
765 |     - variables: từ điển chứa các giá trị thay thế cho các biến trong template
766 |     
767 |     Trả về: Prompt đã được render với các biến được thay thế
768 |     """
769 |     prompt_info = get_prompt(prompt_id)
770 |     
771 |     if "error" in prompt_info:
772 |         return prompt_info["error"]
773 |     
774 |     template = prompt_info.get("template", "")
775 |     
776 |     # Thực hiện thay thế biến
777 |     if variables:
778 |         try:
779 |             # Sử dụng format string của Python
780 |             for key, value in variables.items():
781 |                 placeholder = "{" + key + "}"
782 |                 template = template.replace(placeholder, str(value))
783 |         except Exception as e:
784 |             return f"Lỗi khi render prompt: {str(e)}"
785 |     
786 |     return template
787 | 
788 | def import_datetime_and_get_now():
789 |     """Helper function to get current datetime"""
790 |     from datetime import datetime
791 |     return datetime.now()
792 | 
793 | if __name__ == "__main__":
794 |     print("Server đang khởi động...")
795 |     mcp.run(transport="stdio")
```