@birthday8/doc-mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +137 -0
- package/index.js +159 -0
- package/install.js +96 -0
- package/package.json +32 -0
- package/python/__pycache__/docx_converter.cpython-313.pyc +0 -0
- package/python/docx_converter.py +807 -0
- package/python/requirements.txt +4 -0
- package/python/server.py +320 -0
package/python/server.py
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Doc Creator MCP Server
|
|
4
|
+
文档生成器 - 提供HTML转Word文档的工具服务
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
# MCP imports
|
|
12
|
+
from mcp.server import Server
|
|
13
|
+
from mcp.types import TextContent, Tool
|
|
14
|
+
|
|
15
|
+
# Import the conversion module
|
|
16
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
17
|
+
from docx_converter import convert_html_to_docx as docx_convert
|
|
18
|
+
|
|
19
|
+
# Create MCP server
|
|
20
|
+
app = Server("doc-creator")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@app.list_tools()
|
|
24
|
+
async def list_tools() -> list[Tool]:
|
|
25
|
+
"""列出可用的工具"""
|
|
26
|
+
return [
|
|
27
|
+
Tool(
|
|
28
|
+
name="convert_document",
|
|
29
|
+
description="将HTML文件转换为Word文档(.docx)",
|
|
30
|
+
inputSchema={
|
|
31
|
+
"type": "object",
|
|
32
|
+
"properties": {
|
|
33
|
+
"html_path": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"description": "HTML文件的完整路径"
|
|
36
|
+
},
|
|
37
|
+
"output_path": {
|
|
38
|
+
"type": "string",
|
|
39
|
+
"description": "输出DOCX文件的路径(可选,默认与HTML同目录)"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"required": ["html_path"]
|
|
43
|
+
}
|
|
44
|
+
),
|
|
45
|
+
Tool(
|
|
46
|
+
name="generate_document",
|
|
47
|
+
description="根据HTML内容生成Word文档",
|
|
48
|
+
inputSchema={
|
|
49
|
+
"type": "object",
|
|
50
|
+
"properties": {
|
|
51
|
+
"content": {
|
|
52
|
+
"type": "string",
|
|
53
|
+
"description": "HTML内容(不需要完整的HTML结构,只需要body内的内容)"
|
|
54
|
+
},
|
|
55
|
+
"title": {
|
|
56
|
+
"type": "string",
|
|
57
|
+
"description": "文档标题",
|
|
58
|
+
"default": "文档"
|
|
59
|
+
},
|
|
60
|
+
"output_dir": {
|
|
61
|
+
"type": "string",
|
|
62
|
+
"description": "输出目录(可选,默认使用workspace/documents/)"
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"required": ["content"]
|
|
66
|
+
}
|
|
67
|
+
),
|
|
68
|
+
Tool(
|
|
69
|
+
name="get_html_template",
|
|
70
|
+
description="获取完整的HTML模板,包含CSS样式",
|
|
71
|
+
inputSchema={
|
|
72
|
+
"type": "object",
|
|
73
|
+
"properties": {}
|
|
74
|
+
}
|
|
75
|
+
)
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@app.call_tool()
|
|
80
|
+
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
|
|
81
|
+
"""调用工具"""
|
|
82
|
+
|
|
83
|
+
if name == "convert_document":
|
|
84
|
+
html_path = arguments.get("html_path")
|
|
85
|
+
output_path = arguments.get("output_path")
|
|
86
|
+
|
|
87
|
+
if not html_path:
|
|
88
|
+
return [TextContent(type="text", text="错误:必须提供html_path参数")]
|
|
89
|
+
|
|
90
|
+
if not os.path.exists(html_path):
|
|
91
|
+
return [TextContent(type="text", text=f"错误:HTML文件不存在: {html_path}")]
|
|
92
|
+
|
|
93
|
+
# 如果未指定输出路径,使用相同目录和文件名
|
|
94
|
+
if not output_path:
|
|
95
|
+
base_name = os.path.splitext(html_path)[0]
|
|
96
|
+
output_path = base_name + ".docx"
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
docx_convert(html_path, output_path)
|
|
100
|
+
return [TextContent(
|
|
101
|
+
type="text",
|
|
102
|
+
text=f"✅ 转换成功!\n📄 HTML文件: {html_path}\n📝 Word文件: {output_path}"
|
|
103
|
+
)]
|
|
104
|
+
except Exception as e:
|
|
105
|
+
return [TextContent(type="text", text=f"❌ 转换失败: {str(e)}")]
|
|
106
|
+
|
|
107
|
+
elif name == "generate_document":
|
|
108
|
+
content = arguments.get("content", "")
|
|
109
|
+
title = arguments.get("title", "文档")
|
|
110
|
+
output_dir = arguments.get("output_dir")
|
|
111
|
+
|
|
112
|
+
if not content:
|
|
113
|
+
return [TextContent(type="text", text="错误:必须提供content参数")]
|
|
114
|
+
|
|
115
|
+
# 构建输出目录
|
|
116
|
+
if not output_dir:
|
|
117
|
+
workspace_dir = Path(__file__).parent.parent / "workspace" / "documents"
|
|
118
|
+
from datetime import datetime
|
|
119
|
+
timestamp = datetime.now().strftime("%Y%m%d")
|
|
120
|
+
output_dir = workspace_dir / f"{timestamp}_{title.replace(' ', '_')}"
|
|
121
|
+
|
|
122
|
+
output_dir = Path(output_dir)
|
|
123
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
124
|
+
|
|
125
|
+
# 生成HTML文件
|
|
126
|
+
html_path = output_dir / "document.html"
|
|
127
|
+
docx_path = output_dir / "document.docx"
|
|
128
|
+
|
|
129
|
+
# 构建完整HTML
|
|
130
|
+
html_template = f"""<!DOCTYPE html>
|
|
131
|
+
<html lang="zh-CN">
|
|
132
|
+
<head>
|
|
133
|
+
<meta charset="UTF-8">
|
|
134
|
+
<title>{title}</title>
|
|
135
|
+
<style>
|
|
136
|
+
:root {{
|
|
137
|
+
--default-font: '微软雅黑';
|
|
138
|
+
--default-size: 12pt;
|
|
139
|
+
--default-color: #333;
|
|
140
|
+
--line-height: 1.8;
|
|
141
|
+
}}
|
|
142
|
+
body {{
|
|
143
|
+
font-family: var(--default-font);
|
|
144
|
+
font-size: var(--default-size);
|
|
145
|
+
color: var(--default-color);
|
|
146
|
+
line-height: var(--line-height);
|
|
147
|
+
padding: 20px;
|
|
148
|
+
max-width: 800px;
|
|
149
|
+
margin: 0 auto;
|
|
150
|
+
}}
|
|
151
|
+
h1 {{
|
|
152
|
+
font-family: '微软雅黑';
|
|
153
|
+
font-size: 18pt;
|
|
154
|
+
color: #4a3f6b;
|
|
155
|
+
text-align: center;
|
|
156
|
+
margin-bottom: 20pt;
|
|
157
|
+
}}
|
|
158
|
+
h2 {{
|
|
159
|
+
font-family: '微软雅黑';
|
|
160
|
+
font-size: 16pt;
|
|
161
|
+
color: #5b4e8c;
|
|
162
|
+
border-bottom: 2px solid #667eea;
|
|
163
|
+
padding-bottom: 10px;
|
|
164
|
+
margin-bottom: 15pt;
|
|
165
|
+
}}
|
|
166
|
+
h3 {{
|
|
167
|
+
font-family: '微软雅黑';
|
|
168
|
+
font-size: 14pt;
|
|
169
|
+
color: #6b5b7a;
|
|
170
|
+
margin-bottom: 10pt;
|
|
171
|
+
}}
|
|
172
|
+
p {{
|
|
173
|
+
text-indent: 2em;
|
|
174
|
+
margin-bottom: 10pt;
|
|
175
|
+
}}
|
|
176
|
+
.center {{ text-align: center; }}
|
|
177
|
+
.red {{ color: red; }}
|
|
178
|
+
.blue {{ color: blue; }}
|
|
179
|
+
.green {{ color: green; }}
|
|
180
|
+
.highlight {{ background-color: yellow; }}
|
|
181
|
+
.info {{
|
|
182
|
+
background-color: #e3f2fd;
|
|
183
|
+
padding: 10px;
|
|
184
|
+
border-left: 4px solid #2196F3;
|
|
185
|
+
margin: 10pt 0;
|
|
186
|
+
}}
|
|
187
|
+
.warning {{
|
|
188
|
+
background-color: #fff3cd;
|
|
189
|
+
padding: 10px;
|
|
190
|
+
border-left: 4px solid #ffc107;
|
|
191
|
+
margin: 10pt 0;
|
|
192
|
+
}}
|
|
193
|
+
.success {{
|
|
194
|
+
background-color: #d4edda;
|
|
195
|
+
padding: 10px;
|
|
196
|
+
border-left: 4px solid #28a745;
|
|
197
|
+
margin: 10pt 0;
|
|
198
|
+
}}
|
|
199
|
+
table {{
|
|
200
|
+
width: 100%;
|
|
201
|
+
border-collapse: collapse;
|
|
202
|
+
margin: 20px 0;
|
|
203
|
+
}}
|
|
204
|
+
th, td {{
|
|
205
|
+
border: 1px solid #ddd;
|
|
206
|
+
padding: 12px;
|
|
207
|
+
text-align: center;
|
|
208
|
+
}}
|
|
209
|
+
th {{
|
|
210
|
+
background-color: #667eea;
|
|
211
|
+
color: white;
|
|
212
|
+
}}
|
|
213
|
+
tr:nth-child(even) {{
|
|
214
|
+
background-color: #f9f9f9;
|
|
215
|
+
}}
|
|
216
|
+
</style>
|
|
217
|
+
</head>
|
|
218
|
+
<body>
|
|
219
|
+
<h1>{title}</h1>
|
|
220
|
+
{content}
|
|
221
|
+
</body>
|
|
222
|
+
</html>"""
|
|
223
|
+
|
|
224
|
+
# 保存HTML
|
|
225
|
+
with open(html_path, 'w', encoding='utf-8') as f:
|
|
226
|
+
f.write(html_template)
|
|
227
|
+
|
|
228
|
+
# 转换为DOCX
|
|
229
|
+
try:
|
|
230
|
+
docx_convert(str(html_path), str(docx_path))
|
|
231
|
+
return [TextContent(
|
|
232
|
+
type="text",
|
|
233
|
+
text=f"✅ 文档生成成功!\n📁 输出目录: {output_dir}\n📄 HTML文件: {html_path}\n📝 Word文件: {docx_path}"
|
|
234
|
+
)]
|
|
235
|
+
except Exception as e:
|
|
236
|
+
return [TextContent(type="text", text=f"❌ 转换失败: {str(e)}")]
|
|
237
|
+
|
|
238
|
+
elif name == "get_html_template":
|
|
239
|
+
template = """<!DOCTYPE html>
|
|
240
|
+
<html lang="zh-CN">
|
|
241
|
+
<head>
|
|
242
|
+
<meta charset="UTF-8">
|
|
243
|
+
<title>文档标题</title>
|
|
244
|
+
<style>
|
|
245
|
+
/* 全局配置 */
|
|
246
|
+
:root {
|
|
247
|
+
--default-font: '微软雅黑';
|
|
248
|
+
--default-size: 12pt;
|
|
249
|
+
--default-color: #333;
|
|
250
|
+
--line-height: 1.8;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
body {
|
|
254
|
+
font-family: var(--default-font);
|
|
255
|
+
font-size: var(--default-size);
|
|
256
|
+
color: var(--default-color);
|
|
257
|
+
line-height: var(--line-height);
|
|
258
|
+
padding: 20px;
|
|
259
|
+
max-width: 800px;
|
|
260
|
+
margin: 0 auto;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/* 标题样式 */
|
|
264
|
+
h1 { font-size: 18pt; color: #4a3f6b; text-align: center; }
|
|
265
|
+
h2 { font-size: 16pt; color: #5b4e8c; border-bottom: 2px solid #667eea; }
|
|
266
|
+
h3 { font-size: 14pt; color: #6b5b7a; }
|
|
267
|
+
|
|
268
|
+
/* 段落 */
|
|
269
|
+
p { text-indent: 2em; margin-bottom: 10pt; }
|
|
270
|
+
|
|
271
|
+
/* 文本格式 */
|
|
272
|
+
.red { color: red; }
|
|
273
|
+
.blue { color: blue; }
|
|
274
|
+
.green { color: green; }
|
|
275
|
+
.highlight { background-color: yellow; }
|
|
276
|
+
|
|
277
|
+
/* 提示框 */
|
|
278
|
+
.info { background-color: #e3f2fd; padding: 10px; border-left: 4px solid #2196F3; }
|
|
279
|
+
.warning { background-color: #fff3cd; padding: 10px; border-left: 4px solid #ffc107; }
|
|
280
|
+
.success { background-color: #d4edda; padding: 10px; border-left: 4px solid #28a745; }
|
|
281
|
+
|
|
282
|
+
/* 表格 */
|
|
283
|
+
table { width: 100%; border-collapse: collapse; margin: 20px 0; }
|
|
284
|
+
th, td { border: 1px solid #ddd; padding: 12px; text-align: center; }
|
|
285
|
+
th { background-color: #667eea; color: white; }
|
|
286
|
+
tr:nth-child(even) { background-color: #f9f9f9; }
|
|
287
|
+
</style>
|
|
288
|
+
</head>
|
|
289
|
+
<body>
|
|
290
|
+
<h1>文档标题</h1>
|
|
291
|
+
|
|
292
|
+
<h2>一、章节标题</h2>
|
|
293
|
+
<p>这是正文段落,<strong>支持加粗</strong>、<em>斜体</em>、<span class="red">彩色文字</span>等格式。</p>
|
|
294
|
+
|
|
295
|
+
<div class="info">
|
|
296
|
+
<strong>提示:</strong> 这是信息提示框
|
|
297
|
+
</div>
|
|
298
|
+
</body>
|
|
299
|
+
</html>"""
|
|
300
|
+
return [TextContent(type="text", text=template)]
|
|
301
|
+
|
|
302
|
+
else:
|
|
303
|
+
return [TextContent(type="text", text=f"未知工具: {name}")]
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
async def main():
|
|
307
|
+
"""主函数"""
|
|
308
|
+
from mcp.server.stdio import stdio_server
|
|
309
|
+
|
|
310
|
+
async with stdio_server() as (read_stream, write_stream):
|
|
311
|
+
await app.run(
|
|
312
|
+
read_stream,
|
|
313
|
+
write_stream,
|
|
314
|
+
app.create_initialization_options()
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
if __name__ == "__main__":
|
|
319
|
+
import asyncio
|
|
320
|
+
asyncio.run(main())
|