# 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")
```