@tanskong/office-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.
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { spawn } = require('child_process');
4
+ const path = require('path');
5
+ const fs = require('fs');
6
+
7
+ const scriptPath = path.join(__dirname, '..', 'src', 'office_server.py');
8
+
9
+ if (!fs.existsSync(scriptPath)) {
10
+ console.error('Error: office_server.py not found');
11
+ process.exit(1);
12
+ }
13
+
14
+ const python = process.platform === 'win32' ? 'python' : 'python3';
15
+
16
+ const proc = spawn(python, [scriptPath], {
17
+ cwd: path.join(__dirname, '..', 'src'),
18
+ stdio: 'inherit'
19
+ });
20
+
21
+ proc.on('close', (code) => {
22
+ process.exit(code);
23
+ });
package/package.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "@tanskong/office-mcp",
3
+ "version": "1.0.0",
4
+ "description": "Excel实时办公助手 MCP服务器",
5
+ "bin": {
6
+ "office-mcp": "./bin/office-mcp.js"
7
+ },
8
+ "scripts": {
9
+ "start": "node bin/office-mcp.js"
10
+ },
11
+ "engines": {
12
+ "node": ">=16"
13
+ },
14
+ "keywords": ["mcp", "excel", "office", "trae"],
15
+ "author": "",
16
+ "license": "MIT"
17
+ }
@@ -0,0 +1,995 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Office MCP Server - Real-time Excel Control
4
+ 使用FastMCP协议,支持实时读写Excel数据
5
+ """
6
+
7
+ import json
8
+ import sys
9
+ import os
10
+ from pathlib import Path
11
+
12
+ import importlib.metadata
13
+ orig_version = importlib.metadata.version
14
+ importlib.metadata.version = lambda name: "3.1.1" if name == "fastmcp" else orig_version(name)
15
+
16
+ try:
17
+ from fastmcp import FastMCP
18
+ FASTMCP_AVAILABLE = True
19
+ except Exception as e:
20
+ FASTMCP_AVAILABLE = False
21
+ print(f"ERROR: Failed to import fastmcp: {e}", file=sys.stderr)
22
+ sys.exit(1)
23
+
24
+ MCP = FastMCP("Office Server")
25
+
26
+ def run_mcp():
27
+ MCP.run(transport="stdio")
28
+
29
+ def show_window(icon, item):
30
+ import win32api
31
+ import win32con
32
+ guide_text = """
33
+ 📖 快速指南:
34
+ • 双击运行 exe → 托盘出现绿色图标 → 在 Trae 中发送指令 → Excel 自动操作!
35
+ • 配置 Trae(只需一次):打开 Trae → 设置 → MCP 配置 → 添加服务器 → 填入文件夹路径
36
+ • 重要原则:✅ 用 IDE 模式,不是 Solo 模式 ✅ Excel在打开状态也能使用,实时互动 ✅ 绿色图标亮着表示 MCP 在运行
37
+ • 常用指令:帮我读取表格 / 在第二行插入一行 / 帮我把 A1 单元格标红
38
+ • 配置模板:command: python, args: ["你的路径\\office_server.py"], cwd: "你的路径"
39
+ • 完整说明请看《学员使用手册.md》
40
+ """
41
+ win32api.MessageBox(0, f"Office MCP Server v2.1\n\n服务器正在运行中...\n\n关闭此窗口可停止服务器{guide_text}", "MCP Server", win32con.MB_OK)
42
+
43
+ # Excel COM interface
44
+ def get_excel_app():
45
+ """获取已打开的Excel应用实例"""
46
+ import win32com.client
47
+ import pythoncom
48
+ pythoncom.CoInitialize()
49
+
50
+ # Dispatch会连接到已打开的Excel,如果没有则新建
51
+ app = win32com.client.Dispatch("Excel.Application")
52
+ app.Visible = True
53
+ app.DisplayAlerts = False
54
+ return app
55
+
56
+ def get_active_workbook(app):
57
+ """获取当前活动工作簿"""
58
+ try:
59
+ # Excel: Workbooks, WPS: Workbooks (same)
60
+ wb = app.ActiveWorkbook
61
+ except:
62
+ try:
63
+ # WPS可能用这个
64
+ wb = app.ActiveWorkbook
65
+ except:
66
+ raise Exception("无法获取活动工作簿,请确保Excel/WPS已打开文件")
67
+
68
+ if wb is None:
69
+ raise Exception("No active workbook. Please open an Excel file first.")
70
+ return wb
71
+
72
+ # ==================== VBA 相关工具 ====================
73
+
74
+ @MCP.tool()
75
+ def read_vba_code(module_name: str = None):
76
+ """
77
+ 读取VBA代码
78
+ 参数: module_name - 可选,模块名称。不指定则读取所有模块
79
+ 返回: VBA代码内容
80
+ """
81
+ try:
82
+ app = get_excel_app()
83
+ wb = get_active_workbook(app)
84
+ vba_project = wb.VBProject
85
+
86
+ if module_name:
87
+ for component in vba_project.VBComponents:
88
+ if component.Name == module_name:
89
+ code = component.CodeModule.Lines(1, component.CodeModule.CountOfLines)
90
+ return {"success": True, "module": module_name, "code": code}
91
+ return {"success": False, "error": f"Module '{module_name}' not found"}
92
+ else:
93
+ modules = {}
94
+ for component in vba_project.VBComponents:
95
+ if component.Type in [1, 2, 3]: # StdModule, ClassModule, MSForm
96
+ name = component.Name
97
+ code = component.CodeModule.Lines(1, component.CodeModule.CountOfLines)
98
+ modules[name] = code
99
+ return {"success": True, "modules": modules}
100
+ except Exception as e:
101
+ return {"success": False, "error": str(e)}
102
+
103
+ @MCP.tool()
104
+ def write_vba_code(module_name: str, code: str):
105
+ """
106
+ 写入VBA代码
107
+ 参数: module_name - 模块名称, code - VBA代码内容
108
+ 返回: 操作结果
109
+ """
110
+ try:
111
+ app = get_excel_app()
112
+ wb = get_active_workbook(app)
113
+ vba_project = wb.VBProject
114
+
115
+ target_component = None
116
+ for component in vba_project.VBComponents:
117
+ if component.Name == module_name:
118
+ target_component = component
119
+ break
120
+
121
+ if target_component is None:
122
+ target_component = vba_project.VBComponents.Add(1) # vbext_ct_StdModule
123
+ target_component.Name = module_name
124
+
125
+ code_module = target_component.CodeModule
126
+ if code_module.CountOfLines > 0:
127
+ code_module.DeleteLines(1, code_module.CountOfLines)
128
+
129
+ code_module.AddFromString(code)
130
+
131
+ return {"success": True, "message": f"VBA code written to module '{module_name}'"}
132
+ except Exception as e:
133
+ return {"success": False, "error": str(e)}
134
+
135
+ @MCP.tool()
136
+ def list_vba_modules():
137
+ """
138
+ 列出所有VBA模块
139
+ 返回: 模块列表
140
+ """
141
+ try:
142
+ app = get_excel_app()
143
+ wb = get_active_workbook(app)
144
+ vba_project = wb.VBProject
145
+
146
+ modules = []
147
+ for component in vba_project.VBComponents:
148
+ module_info = {
149
+ "name": component.Name,
150
+ "type": component.Type,
151
+ "lines": component.CodeModule.CountOfLines
152
+ }
153
+ modules.append(module_info)
154
+
155
+ return {"success": True, "modules": modules}
156
+ except Exception as e:
157
+ return {"success": False, "error": str(e)}
158
+
159
+ @MCP.tool()
160
+ def execute_vba_macro(macro_name: str):
161
+ """
162
+ 运行VBA宏
163
+ 参数: macro_name - 宏名称
164
+ 返回: 执行结果
165
+ """
166
+ try:
167
+ app = get_excel_app()
168
+ wb = get_active_workbook(app)
169
+
170
+ if "." in macro_name:
171
+ wb.Application.Run(macro_name)
172
+ else:
173
+ wb.Application.Run(macro_name)
174
+
175
+ return {"success": True, "message": f"Macro '{macro_name}' executed successfully"}
176
+ except Exception as e:
177
+ return {"success": False, "error": str(e)}
178
+
179
+ # ==================== Excel 数据操作工具 ====================
180
+
181
+ @MCP.tool()
182
+ def get_excel_data(sheet_name: str = None, range_address: str = None):
183
+ """
184
+ 获取Excel数据
185
+ 参数: sheet_name - 工作表名称(可选), range_address - 范围如"A1:B10"(可选)
186
+ 返回: 表格数据
187
+ """
188
+ try:
189
+ app = get_excel_app()
190
+ wb = get_active_workbook(app)
191
+
192
+ if sheet_name:
193
+ ws = wb.Worksheets(sheet_name)
194
+ else:
195
+ ws = wb.ActiveSheet
196
+
197
+ if range_address:
198
+ data = ws.Range(range_address).Value
199
+ else:
200
+ data = ws.UsedRange.Value
201
+
202
+ return {"success": True, "data": data, "sheet": ws.Name}
203
+ except Exception as e:
204
+ return {"success": False, "error": str(e)}
205
+
206
+ @MCP.tool()
207
+ def set_excel_data(sheet_name: str, range_address: str, data):
208
+ """
209
+ 设置Excel数据
210
+ 参数: sheet_name - 工作表名称, range_address - 范围如"A1", data - 要写入的数据
211
+ 返回: 操作结果
212
+ """
213
+ try:
214
+ app = get_excel_app()
215
+ wb = get_active_workbook(app)
216
+
217
+ if sheet_name:
218
+ ws = wb.Worksheets(sheet_name)
219
+ else:
220
+ ws = wb.ActiveSheet
221
+
222
+ ws.Range(range_address).Value = data
223
+ wb.Save()
224
+
225
+ return {"success": True, "message": f"Data written to {sheet_name}!{range_address}"}
226
+ except Exception as e:
227
+ return {"success": False, "error": str(e)}
228
+
229
+ @MCP.tool()
230
+ def get_workbook_info():
231
+ """
232
+ 获取工作簿信息
233
+ 返回: 工作簿名称、路径、工作表列表
234
+ """
235
+ try:
236
+ app = get_excel_app()
237
+ wb = get_active_workbook(app)
238
+
239
+ sheets = []
240
+ for ws in wb.Worksheets:
241
+ sheets.append({
242
+ "name": ws.Name,
243
+ "rows": ws.UsedRange.Rows.Count,
244
+ "columns": ws.UsedRange.Columns.Count
245
+ })
246
+
247
+ return {
248
+ "success": True,
249
+ "workbook": {
250
+ "name": wb.Name,
251
+ "path": wb.FullName,
252
+ "sheets": sheets
253
+ }
254
+ }
255
+ except Exception as e:
256
+ return {"success": False, "error": str(e)}
257
+
258
+ @MCP.tool()
259
+ def create_formula(sheet_name: str, cell: str, formula: str):
260
+ """
261
+ 创建Excel公式
262
+ 参数: sheet_name - 工作表名称, cell - 单元格如"A1", formula - 公式如"=SUM(B1:B10)"
263
+ 返回: 操作结果
264
+ """
265
+ try:
266
+ app = get_excel_app()
267
+ wb = get_active_workbook(app)
268
+
269
+ if sheet_name:
270
+ ws = wb.Worksheets(sheet_name)
271
+ else:
272
+ ws = wb.ActiveSheet
273
+
274
+ ws.Range(cell).Formula = formula
275
+ wb.Save()
276
+
277
+ return {"success": True, "message": f"Formula '{formula}' set at {cell}"}
278
+ except Exception as e:
279
+ return {"success": False, "error": str(e)}
280
+
281
+ @MCP.tool()
282
+ def add_new_sheet(sheet_name: str):
283
+ """
284
+ 添加新工作表
285
+ 参数: sheet_name - 新工作表名称
286
+ 返回: 操作结果
287
+ """
288
+ try:
289
+ app = get_excel_app()
290
+ wb = get_active_workbook(app)
291
+
292
+ ws = wb.Worksheets.Add()
293
+ ws.Name = sheet_name
294
+ wb.Save()
295
+
296
+ return {"success": True, "message": f"Sheet '{sheet_name}' created"}
297
+ except Exception as e:
298
+ return {"success": False, "error": str(e)}
299
+
300
+ @MCP.tool()
301
+ def delete_sheet(sheet_name: str):
302
+ """
303
+ 删除工作表
304
+ 参数: sheet_name - 要删除的工作表名称
305
+ 返回: 操作结果
306
+ """
307
+ try:
308
+ app = get_excel_app()
309
+ wb = get_active_workbook(app)
310
+
311
+ wb.Worksheets(sheet_name).Delete()
312
+ wb.Save()
313
+
314
+ return {"success": True, "message": f"Sheet '{sheet_name}' deleted"}
315
+ except Exception as e:
316
+ return {"success": False, "error": str(e)}
317
+
318
+ @MCP.tool()
319
+ def read_cell(sheet_name: str, cell: str):
320
+ """
321
+ 读取单元格
322
+ 参数: sheet_name - 工作表名称, cell - 单元格如"A1"
323
+ 返回: 单元格值
324
+ """
325
+ try:
326
+ app = get_excel_app()
327
+ wb = get_active_workbook(app)
328
+
329
+ if sheet_name:
330
+ ws = wb.Worksheets(sheet_name)
331
+ else:
332
+ ws = wb.ActiveSheet
333
+
334
+ value = ws.Range(cell).Value
335
+ formula = ws.Range(cell).Formula
336
+
337
+ return {"success": True, "cell": cell, "value": value, "formula": formula}
338
+ except Exception as e:
339
+ return {"success": False, "error": str(e)}
340
+
341
+ @MCP.tool()
342
+ def write_cell(sheet_name: str, cell: str, value):
343
+ """
344
+ 写入单元格
345
+ 参数: sheet_name - 工作表名称, cell - 单元格如"A1", value - 要写入的值
346
+ 返回: 操作结果
347
+ """
348
+ try:
349
+ app = get_excel_app()
350
+ wb = get_active_workbook(app)
351
+
352
+ if sheet_name:
353
+ ws = wb.Worksheets(sheet_name)
354
+ else:
355
+ ws = wb.ActiveSheet
356
+
357
+ ws.Range(cell).Value = value
358
+
359
+ return {"success": True, "message": f"Value '{value}' written to {cell}"}
360
+ except Exception as e:
361
+ return {"success": False, "error": str(e)}
362
+
363
+ @MCP.tool()
364
+ def save_workbook():
365
+ """
366
+ 保存当前工作簿
367
+ 返回: 操作结果
368
+ """
369
+ try:
370
+ app = get_excel_app()
371
+ wb = get_active_workbook(app)
372
+ wb.Save()
373
+ return {"success": True, "message": "Workbook saved"}
374
+ except Exception as e:
375
+ return {"success": False, "error": str(e)}
376
+
377
+ @MCP.tool()
378
+ def set_cell_color(sheet_name: str, cell: str, color: str):
379
+ """
380
+ 设置单元格背景颜色
381
+ 参数: sheet_name - 工作表名称, cell - 单元格如"A1", color - 颜色如"red"或"#FF0000"
382
+ 返回: 操作结果
383
+ """
384
+ try:
385
+ app = get_excel_app()
386
+ wb = get_active_workbook(app)
387
+
388
+ if sheet_name:
389
+ ws = wb.Worksheets(sheet_name)
390
+ else:
391
+ ws = wb.ActiveSheet
392
+
393
+ color_map = {
394
+ "red": 3, "green": 4, "blue": 5, "yellow": 6,
395
+ "purple": 7, "cyan": 8, "magenta": 13,
396
+ "orange": 46, "gray": 15, "white": 2, "black": 1
397
+ }
398
+
399
+ if color.startswith("#"):
400
+ ws.Range(cell).Interior.Color = color
401
+ elif color.lower() in color_map:
402
+ ws.Range(cell).Interior.ColorIndex = color_map[color.lower()]
403
+ else:
404
+ ws.Range(cell).Interior.Color = color
405
+
406
+ wb.Save()
407
+ return {"success": True, "message": f"Cell {cell} color set to {color}"}
408
+ except Exception as e:
409
+ return {"success": False, "error": str(e)}
410
+
411
+ @MCP.tool()
412
+ def set_cell_font_color(sheet_name: str, cell: str, color: str):
413
+ """
414
+ 设置单元格字体颜色
415
+ 参数: sheet_name - 工作表名称, cell - 单元格如"A1", color - 颜色如"red"或"#FF0000"
416
+ 返回: 操作结果
417
+ """
418
+ try:
419
+ app = get_excel_app()
420
+ wb = get_active_workbook(app)
421
+
422
+ if sheet_name:
423
+ ws = wb.Worksheets(sheet_name)
424
+ else:
425
+ ws = wb.ActiveSheet
426
+
427
+ if color.startswith("#"):
428
+ ws.Range(cell).Font.Color = color
429
+ else:
430
+ color_map = {
431
+ "red": 255, "green": 65280, "blue": 16711680,
432
+ "yellow": 65535, "black": 0, "white": 16777215
433
+ }
434
+ ws.Range(cell).Font.Color = color_map.get(color.lower(), 0)
435
+
436
+ wb.Save()
437
+ return {"success": True, "message": f"Cell {cell} font color set to {color}"}
438
+ except Exception as e:
439
+ return {"success": False, "error": str(e)}
440
+
441
+ @MCP.tool()
442
+ def set_cell_bold(sheet_name: str, cell: str, bold: bool = True):
443
+ """
444
+ 设置单元格字体粗体
445
+ 参数: sheet_name - 工作表名称, cell - 单元格如"A1", bold - 是否粗体
446
+ 返回: 操作结果
447
+ """
448
+ try:
449
+ app = get_excel_app()
450
+ wb = get_active_workbook(app)
451
+
452
+ if sheet_name:
453
+ ws = wb.Worksheets(sheet_name)
454
+ else:
455
+ ws = wb.ActiveSheet
456
+
457
+ ws.Range(cell).Font.Bold = bold
458
+
459
+ wb.Save()
460
+ return {"success": True, "message": f"Cell {cell} bold set to {bold}"}
461
+ except Exception as e:
462
+ return {"success": False, "error": str(e)}
463
+
464
+ @MCP.tool()
465
+ def extract_images_from_sheet(sheet_name: str = None, output_folder: str = None):
466
+ """
467
+ 提取Excel中的图片
468
+ 参数: sheet_name - 工作表名称(可选), output_folder - 输出文件夹(可选,默认当前目录)
469
+ 返回: 提取的图片列表
470
+ """
471
+ try:
472
+ import os
473
+ import uuid
474
+
475
+ app = get_excel_app()
476
+ wb = get_active_workbook(app)
477
+
478
+ if sheet_name:
479
+ ws = wb.Worksheets(sheet_name)
480
+ else:
481
+ ws = wb.ActiveSheet
482
+
483
+ if output_folder is None:
484
+ output_folder = os.path.dirname(wb.FullName)
485
+ if not output_folder:
486
+ output_folder = os.getcwd()
487
+
488
+ if not os.path.exists(output_folder):
489
+ os.makedirs(output_folder)
490
+
491
+ extracted = []
492
+ shapes = ws.Shapes
493
+
494
+ for i in range(1, shapes.Count + 1):
495
+ shape = shapes.Item(i)
496
+ if shape.Type == 11 or shape.Type == 13: # msoPicture or linked picture
497
+ name = f"image_{uuid.uuid4().hex[:8]}"
498
+ try:
499
+ shape.CopyPicture(1, 2) # xlScreen, xlBitmap
500
+
501
+ if app.ActiveSheet.Paste().Count > 0:
502
+ img_path = os.path.join(output_folder, f"{name}.png")
503
+ app.ActiveChart.Export(img_path)
504
+ extracted.append({"name": name, "path": img_path})
505
+ except Exception as e:
506
+ pass
507
+
508
+ if extracted:
509
+ return {"success": True, "images": extracted, "count": len(extracted)}
510
+ else:
511
+ return {"success": False, "error": "No images found in sheet"}
512
+ except Exception as e:
513
+ return {"success": False, "error": str(e)}
514
+
515
+ @MCP.tool()
516
+ def list_sheet_images(sheet_name: str = None):
517
+ """
518
+ 列出工作表中的图片
519
+ 参数: sheet_name - 工作表名称(可选)
520
+ 返回: 图片列表
521
+ """
522
+ try:
523
+ app = get_excel_app()
524
+ wb = get_active_workbook(app)
525
+
526
+ if sheet_name:
527
+ ws = wb.Worksheets(sheet_name)
528
+ else:
529
+ ws = wb.ActiveSheet
530
+
531
+ images = []
532
+ shapes = ws.Shapes
533
+
534
+ for i in range(1, shapes.Count + 1):
535
+ shape = shapes.Item(i)
536
+ if shape.Type == 11 or shape.Type == 13: # msoPicture or linked picture
537
+ images.append({
538
+ "name": shape.Name,
539
+ "type": shape.Type,
540
+ "left": shape.Left,
541
+ "top": shape.Top,
542
+ "width": shape.Width,
543
+ "height": shape.Height
544
+ })
545
+
546
+ return {"success": True, "images": images, "count": len(images)}
547
+ except Exception as e:
548
+ return {"success": False, "error": str(e)}
549
+
550
+ @MCP.tool()
551
+ def extract_images_from_excel(workbook_path: str = None, output_folder: str = None):
552
+ """
553
+ 从Excel内部提取图片(更稳定的方式)
554
+ 参数: workbook_path - 工作簿路径(可选,默认当前工作簿), output_folder - 输出文件夹(可选,默认当前目录)
555
+ 返回: 提取的图片列表
556
+ """
557
+ try:
558
+ import zipfile
559
+ import shutil
560
+ import tempfile
561
+
562
+ app = get_excel_app()
563
+ wb = get_active_workbook(app)
564
+
565
+ if workbook_path is None:
566
+ workbook_path = wb.FullName
567
+
568
+ if output_folder is None:
569
+ output_folder = os.path.dirname(workbook_path)
570
+ if not output_folder:
571
+ output_folder = os.getcwd()
572
+
573
+ output_folder = os.path.join(output_folder, "extracted_images")
574
+ os.makedirs(output_folder, exist_ok=True)
575
+
576
+ temp_dir = tempfile.mkdtemp()
577
+ try:
578
+ with zipfile.ZipFile(workbook_path, 'r') as zip_ref:
579
+ zip_ref.extractall(temp_dir)
580
+
581
+ media_dir = os.path.join(temp_dir, 'xl', 'media')
582
+ if not os.path.exists(media_dir):
583
+ return {"success": False, "error": "No media folder found in Excel"}
584
+
585
+ images = []
586
+ for filename in os.listdir(media_dir):
587
+ if filename.lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.bmp')):
588
+ src = os.path.join(media_dir, filename)
589
+ dst = os.path.join(output_folder, filename)
590
+ shutil.copy2(src, dst)
591
+ images.append({
592
+ "name": filename,
593
+ "path": dst
594
+ })
595
+
596
+ return {"success": True, "images": images, "count": len(images), "output_folder": output_folder}
597
+ finally:
598
+ shutil.rmtree(temp_dir, ignore_errors=True)
599
+ except Exception as e:
600
+ return {"success": False, "error": str(e)}
601
+
602
+ @MCP.tool()
603
+ def delete_sheet_image(sheet_name: str, image_index: int = None, image_name: str = None):
604
+ """
605
+ 删除工作表中的图片
606
+ 参数: sheet_name - 工作表名称, image_index - 图片索引(从1开始), image_name - 图片名称(二选一)
607
+ 返回: 操作结果
608
+ """
609
+ try:
610
+ app = get_excel_app()
611
+ wb = get_active_workbook(app)
612
+ ws = wb.Worksheets(sheet_name)
613
+
614
+ shapes = ws.Shapes
615
+ target_shape = None
616
+
617
+ if image_name:
618
+ for i in range(1, shapes.Count + 1):
619
+ shape = shapes.Item(i)
620
+ if shape.Name == image_name:
621
+ target_shape = shape
622
+ break
623
+ elif image_index:
624
+ if 1 <= image_index <= shapes.Count:
625
+ target_shape = shapes.Item(image_index)
626
+
627
+ if target_shape:
628
+ target_shape.Delete()
629
+ wb.Save()
630
+ return {"success": True, "message": f"Deleted image: {target_shape.Name}"}
631
+ else:
632
+ return {"success": False, "error": "Image not found"}
633
+ except Exception as e:
634
+ return {"success": False, "error": str(e)}
635
+
636
+ # ==================== 批量操作工具 ====================
637
+
638
+ @MCP.tool()
639
+ def batch_write_cells(sheet_name: str, cells: list):
640
+ """
641
+ 批量写入多个单元格
642
+ 参数: sheet_name - 工作表名称, cells - 单元格列表,格式如[{"cell": "A1", "value": "值1"}, {"cell": "B1", "value": "值2"}]
643
+ 返回: 操作结果
644
+ """
645
+ try:
646
+ app = get_excel_app()
647
+ wb = get_active_workbook(app)
648
+
649
+ if sheet_name:
650
+ ws = wb.Worksheets(sheet_name)
651
+ else:
652
+ ws = wb.ActiveSheet
653
+
654
+ success_count = 0
655
+ for item in cells:
656
+ cell = item.get("cell")
657
+ value = item.get("value")
658
+ if cell and value is not None:
659
+ ws.Range(cell).Value = value
660
+ success_count += 1
661
+
662
+ wb.Save()
663
+ return {"success": True, "message": f"Written {success_count} cells"}
664
+ except Exception as e:
665
+ return {"success": False, "error": str(e)}
666
+
667
+ @MCP.tool()
668
+ def batch_set_colors(sheet_name: str, cells: list):
669
+ """
670
+ 批量设置单元格颜色
671
+ 参数: sheet_name - 工作表名称, cells - 单元格列表,格式如[{"cell": "A1", "color": "red"}, {"cell": "B1", "color": "yellow"}]
672
+ 返回: 操作结果
673
+ """
674
+ try:
675
+ app = get_excel_app()
676
+ wb = get_active_workbook(app)
677
+
678
+ if sheet_name:
679
+ ws = wb.Worksheets(sheet_name)
680
+ else:
681
+ ws = wb.ActiveSheet
682
+
683
+ color_map = {
684
+ "red": 3, "green": 4, "blue": 5, "yellow": 6,
685
+ "purple": 7, "cyan": 8, "magenta": 13,
686
+ "orange": 46, "gray": 15, "white": 2, "black": 1
687
+ }
688
+
689
+ success_count = 0
690
+ for item in cells:
691
+ cell = item.get("cell")
692
+ color = item.get("color")
693
+ if cell and color:
694
+ if color.startswith("#"):
695
+ ws.Range(cell).Interior.Color = color
696
+ elif color.lower() in color_map:
697
+ ws.Range(cell).Interior.ColorIndex = color_map[color.lower()]
698
+ else:
699
+ ws.Range(cell).Interior.Color = color
700
+ success_count += 1
701
+
702
+ wb.Save()
703
+ return {"success": True, "message": f"Colored {success_count} cells"}
704
+ except Exception as e:
705
+ return {"success": False, "error": str(e)}
706
+
707
+ @MCP.tool()
708
+ def batch_write_rows(sheet_name: str, start_row: int, data: list):
709
+ """
710
+ 批量写入多行数据
711
+ 参数: sheet_name - 工作表名称, start_row - 起始行号, data - 数据列表,如[["A1","B1","C1"], ["A2","B2","C2"]]
712
+ 返回: 操作结果
713
+ """
714
+ try:
715
+ app = get_excel_app()
716
+ wb = get_active_workbook(app)
717
+
718
+ if sheet_name:
719
+ ws = wb.Worksheets(sheet_name)
720
+ else:
721
+ ws = wb.ActiveSheet
722
+
723
+ for i, row_data in enumerate(data):
724
+ row_num = start_row + i
725
+ for j, value in enumerate(row_data):
726
+ col_letter = chr(65 + j) # A, B, C...
727
+ ws.Range(f"{col_letter}{row_num}").Value = value
728
+
729
+ wb.Save()
730
+ return {"success": True, "message": f"Written {len(data)} rows"}
731
+ except Exception as e:
732
+ return {"success": False, "error": str(e)}
733
+
734
+ @MCP.tool()
735
+ def batch_create_formulas(sheet_name: str, formulas: list):
736
+ """
737
+ 批量创建公式
738
+ 参数: sheet_name - 工作表名称, formulas - 公式列表,格式如[{"cell": "C1", "formula": "=A1+B1"}, {"cell": "C2", "formula": "=A2+B2"}]
739
+ 返回: 操作结果
740
+ """
741
+ try:
742
+ app = get_excel_app()
743
+ wb = get_active_workbook(app)
744
+
745
+ if sheet_name:
746
+ ws = wb.Worksheets(sheet_name)
747
+ else:
748
+ ws = wb.ActiveSheet
749
+
750
+ success_count = 0
751
+ for item in formulas:
752
+ cell = item.get("cell")
753
+ formula = item.get("formula")
754
+ if cell and formula:
755
+ ws.Range(cell).Formula = formula
756
+ success_count += 1
757
+
758
+ wb.Save()
759
+ return {"success": True, "message": f"Created {success_count} formulas"}
760
+ except Exception as e:
761
+ return {"success": False, "error": str(e)}
762
+
763
+ @MCP.tool()
764
+ def insert_rows(sheet_name: str, row: int, count: int = 1):
765
+ """
766
+ 插入行
767
+ 参数: sheet_name - 工作表名称, row - 插入位置, count - 插入行数
768
+ 返回: 操作结果
769
+ """
770
+ try:
771
+ app = get_excel_app()
772
+ wb = get_active_workbook(app)
773
+
774
+ if sheet_name:
775
+ ws = wb.Worksheets(sheet_name)
776
+ else:
777
+ ws = wb.ActiveSheet
778
+
779
+ ws.Rows(f"{row}:{row + count - 1}").Insert(-4161) # xlShiftDown
780
+
781
+ wb.Save()
782
+ return {"success": True, "message": f"Inserted {count} rows at row {row}"}
783
+ except Exception as e:
784
+ return {"success": False, "error": str(e)}
785
+
786
+ @MCP.tool()
787
+ def delete_rows(sheet_name: str, row: int, count: int = 1):
788
+ """
789
+ 删除行
790
+ 参数: sheet_name - 工作表名称, row - 删除起始行, count - 删除行数
791
+ 返回: 操作结果
792
+ """
793
+ try:
794
+ app = get_excel_app()
795
+ wb = get_active_workbook(app)
796
+
797
+ if sheet_name:
798
+ ws = wb.Worksheets(sheet_name)
799
+ else:
800
+ ws = wb.ActiveSheet
801
+
802
+ ws.Rows(f"{row}:{row + count - 1}").Delete()
803
+
804
+ wb.Save()
805
+ return {"success": True, "message": f"Deleted {count} rows from row {row}"}
806
+ except Exception as e:
807
+ return {"success": False, "error": str(e)}
808
+
809
+ @MCP.tool()
810
+ def insert_columns(sheet_name: str, column: str, count: int = 1):
811
+ """
812
+ 插入列
813
+ 参数: sheet_name - 工作表名称, column - 列字母如"A", count - 插入列数
814
+ 返回: 操作结果
815
+ """
816
+ try:
817
+ app = get_excel_app()
818
+ wb = get_active_workbook(app)
819
+
820
+ if sheet_name:
821
+ ws = wb.Worksheets(sheet_name)
822
+ else:
823
+ ws = wb.ActiveSheet
824
+
825
+ ws.Columns(f"{column}:{chr(ord(column) + count - 1)}").Insert(-4161) # xlShiftRight
826
+
827
+ wb.Save()
828
+ return {"success": True, "message": f"Inserted {count} columns at {column}"}
829
+ except Exception as e:
830
+ return {"success": False, "error": str(e)}
831
+
832
+ @MCP.tool()
833
+ def delete_columns(sheet_name: str, column: str, count: int = 1):
834
+ """
835
+ 删除列
836
+ 参数: sheet_name - 工作表名称, column - 列字母如"A", count - 删除列数
837
+ 返回: 操作结果
838
+ """
839
+ try:
840
+ app = get_excel_app()
841
+ wb = get_active_workbook(app)
842
+
843
+ if sheet_name:
844
+ ws = wb.Worksheets(sheet_name)
845
+ else:
846
+ ws = wb.ActiveSheet
847
+
848
+ ws.Columns(f"{column}:{chr(ord(column) + count - 1)}").Delete()
849
+
850
+ wb.Save()
851
+ return {"success": True, "message": f"Deleted {count} columns from {column}"}
852
+ except Exception as e:
853
+ return {"success": False, "error": str(e)}
854
+
855
+ @MCP.tool()
856
+ def set_column_width(sheet_name: str, column: str, width: float):
857
+ """
858
+ 设置列宽
859
+ 参数: sheet_name - 工作表名称, column - 列字母如"A", width - 宽度
860
+ 返回: 操作结果
861
+ """
862
+ try:
863
+ app = get_excel_app()
864
+ wb = get_active_workbook(app)
865
+
866
+ if sheet_name:
867
+ ws = wb.Worksheets(sheet_name)
868
+ else:
869
+ ws = wb.ActiveSheet
870
+
871
+ ws.Columns(column).ColumnWidth = width
872
+
873
+ wb.Save()
874
+ return {"success": True, "message": f"Column {column} width set to {width}"}
875
+ except Exception as e:
876
+ return {"success": False, "error": str(e)}
877
+
878
+ @MCP.tool()
879
+ def set_row_height(sheet_name: str, row: int, height: float):
880
+ """
881
+ 设置行高
882
+ 参数: sheet_name - 工作表名称, row - 行号, height - 高度
883
+ 返回: 操作结果
884
+ """
885
+ try:
886
+ app = get_excel_app()
887
+ wb = get_active_workbook(app)
888
+
889
+ if sheet_name:
890
+ ws = wb.Worksheets(sheet_name)
891
+ else:
892
+ ws = wb.ActiveSheet
893
+
894
+ ws.Rows(row).RowHeight = height
895
+
896
+ wb.Save()
897
+ return {"success": True, "message": f"Row {row} height set to {height}"}
898
+ except Exception as e:
899
+ return {"success": False, "error": str(e)}
900
+
901
+ @MCP.tool()
902
+ def merge_cells(sheet_name: str, range_address: str):
903
+ """
904
+ 合并单元格
905
+ 参数: sheet_name - 工作表名称, range_address - 范围如"A1:D1"
906
+ 返回: 操作结果
907
+ """
908
+ try:
909
+ app = get_excel_app()
910
+ wb = get_active_workbook(app)
911
+
912
+ if sheet_name:
913
+ ws = wb.Worksheets(sheet_name)
914
+ else:
915
+ ws = wb.ActiveSheet
916
+
917
+ ws.Range(range_address).Merge()
918
+
919
+ wb.Save()
920
+ return {"success": True, "message": f"Merged cells {range_address}"}
921
+ except Exception as e:
922
+ return {"success": False, "error": str(e)}
923
+
924
+ @MCP.tool()
925
+ def unmerge_cells(sheet_name: str, range_address: str):
926
+ """
927
+ 取消合并单元格
928
+ 参数: sheet_name - 工作表名称, range_address - 范围如"A1:D1"
929
+ 返回: 操作结果
930
+ """
931
+ try:
932
+ app = get_excel_app()
933
+ wb = get_active_workbook(app)
934
+
935
+ if sheet_name:
936
+ ws = wb.Worksheets(sheet_name)
937
+ else:
938
+ ws = wb.ActiveSheet
939
+
940
+ ws.Range(range_address).UnMerge()
941
+
942
+ wb.Save()
943
+ return {"success": True, "message": f"Unmerged cells {range_address}"}
944
+ except Exception as e:
945
+ return {"success": False, "error": str(e)}
946
+
947
+ if __name__ == "__main__":
948
+ import threading
949
+ import time
950
+
951
+ def run_mcp():
952
+ MCP.run()
953
+
954
+ def show_window(icon, item):
955
+ import win32api
956
+ import win32con
957
+ guide_path = Path(__file__).parent / "快速指南.md"
958
+ guide_text = ""
959
+ if guide_path.exists():
960
+ guide_text = "\n\n📖 快速指南:\n" + guide_path.read_text(encoding="utf-8").split("---")[0] if "---" in guide_path.read_text(encoding="utf-8") else ""
961
+ win32api.MessageBox(0, f"Office MCP Server v2.1\n\n服务器正在运行中...\n\n关闭此窗口可停止服务器{guide_text}", "MCP Server", win32con.MB_OK)
962
+
963
+ def quit_app(icon, item):
964
+ import os
965
+ os._exit(0)
966
+
967
+ try:
968
+ import pystray
969
+ from PIL import Image, ImageDraw
970
+
971
+ width = 64
972
+ height = 64
973
+ image = Image.new('RGB', (width, height), color='#4CAF50')
974
+ draw = ImageDraw.Draw(image)
975
+ draw.rectangle([8, 8, 56, 56], fill='white', outline='#4CAF50', width=3)
976
+ draw.text((20, 22), 'MCP', fill='#4CAF50')
977
+
978
+ menu = pystray.Menu(
979
+ pystray.MenuItem("MCP服务器运行中", show_window, default=True),
980
+ pystray.MenuItem("退出", quit_app)
981
+ )
982
+
983
+ icon = pystray.Icon("office_mcp", image, "Office MCP Server", menu)
984
+
985
+ mcp_thread = threading.Thread(target=run_mcp, daemon=True)
986
+ mcp_thread.start()
987
+
988
+ print("Office MCP Server v2.1 已启动!(托盘图标运行)")
989
+ print("双击托盘图标查看状态,点击退出关闭服务器")
990
+
991
+ icon.run()
992
+ except Exception as e:
993
+ print(f"托盘模式启动失败: {e}")
994
+ print("使用备用模式...")
995
+ run_mcp()
@@ -0,0 +1,154 @@
1
+ # Office 智能体配置指南
2
+
3
+ ## 智能体名称
4
+ **Excel实时办公助手**
5
+
6
+ ## 智能体描述
7
+
8
+ 你是一个专业的Excel办公助手,专注于帮助用户高效完成日常办公任务。
9
+
10
+ ## 核心能力
11
+
12
+ 你有一个强大的MCP工具集(Office MCP),可以:
13
+ - ✅ 直接操作Excel数据
14
+ - ✅ 提取Excel中的图片
15
+
16
+ ## ⚠️ 重要原则
17
+
18
+ 1. **首先确认工作簿**:每次操作前,调用 get_workbook_info
19
+
20
+ 2. **优先用MCP工具**:
21
+ - 读取数据:用 get_excel_data、read_cell
22
+ - 写入数据:用 batch_write_cells(批量)、batch_write_rows(批量行)
23
+ - 设置颜色:用 batch_set_colors(批量)
24
+ - 设置字体:用 set_cell_font_color、set_cell_bold
25
+ - 创建公式:用 batch_create_formulas(批量)
26
+ - 管理工作表:用 add_new_sheet、delete_sheet
27
+ - 插入/删除:用 insert_rows、delete_rows、insert_columns、delete_columns
28
+ - 行列调整:用 set_column_width、set_row_height
29
+ - 合并单元格:用 merge_cells、unmerge_cells
30
+ - 提取图片:用 extract_images_from_sheet(优先)或从Excel内部提取
31
+ - 删除图片:用 delete_sheet_image
32
+ - 保存文件:用 save_workbook
33
+
34
+ 3. **遇到困难时要想办法解决**:发散思维,寻找可用的解决方案!
35
+ - 优先尝试用MCP工具
36
+ - MCP没有的功能 → 尝试用Python脚本解决
37
+ - 不要直接说"做不了"
38
+
39
+ 4. **只有这两种情况才写VBA**:
40
+ - 用户明确说"帮我写一个VBA宏"
41
+ - 用户明确说"生成VBA代码"
42
+
43
+ 5. **绝对原则**:不能关闭或重新打开用户当前打开的Excel文件
44
+
45
+ 6. **清理临时文件**:用完Python脚本后,必须删除生成的临时文件(如 .py、.txt、.json 等),用户指定保存的除外!
46
+
47
+ ## MCP可用工具
48
+
49
+ ### 数据读写
50
+ - get_excel_data: 获取Excel数据
51
+ - set_excel_data: 设置Excel数据
52
+ - read_cell: 读取单个单元格
53
+ - write_cell: 写入单个单元格
54
+
55
+ ### 批量操作(更快!)
56
+ - batch_write_cells: 批量写入多个单元格
57
+ - batch_write_rows: 批量写入多行数据
58
+ - batch_set_colors: 批量设置单元格颜色
59
+ - batch_create_formulas: 批量创建公式
60
+
61
+ ### 行列操作
62
+ - insert_rows: 插入行
63
+ - delete_rows: 删除行
64
+ - insert_columns: 插入列
65
+ - delete_columns: 删除列
66
+
67
+ ### 行列调整
68
+ - set_column_width: 设置列宽
69
+ - set_row_height: 设置行高
70
+
71
+ ### 单元格操作
72
+ - merge_cells: 合并单元格
73
+ - unmerge_cells: 取消合并单元格
74
+
75
+ ### 格式设置
76
+ - set_cell_color: 设置单元格背景颜色
77
+ - set_cell_font_color: 设置单元格字体颜色
78
+ - set_cell_bold: 设置单元格字体粗体
79
+
80
+ ### 公式和计算
81
+ - create_formula: 创建公式
82
+
83
+ ### 工作表管理
84
+ - add_new_sheet: 添加新工作表
85
+ - delete_sheet: 删除工作表
86
+ - get_workbook_info: 获取工作簿信息
87
+
88
+ ### 图片处理(重要!)
89
+ - extract_images_from_sheet: 提取图片到文件(优先)
90
+ - extract_images_from_excel: 从Excel内部提取图片(更稳定)
91
+ - list_sheet_images: 列出图片信息
92
+ - delete_sheet_image: 删除图片
93
+
94
+ ### VBA相关
95
+ - write_vba_code: 写入VBA代码
96
+ - read_vba_code: 读取VBA代码
97
+ - execute_vba_macro: 运行VBA宏
98
+
99
+ ### 文件操作
100
+ - save_workbook: 保存工作簿
101
+
102
+ ## 常见任务示例
103
+
104
+ ### 批量写入数据
105
+ 用户:把10行数据写入Excel
106
+ ```
107
+ 用 batch_write_rows(sheet_name="Sheet1", start_row=2, data=[[...], [...], ...])
108
+ ```
109
+
110
+ ### 批量标红
111
+ 用户:把年龄小于18的单元格标红
112
+ ```
113
+ 用 batch_set_colors(sheet_name="Sheet1", cells=[{"cell": "B2", "color": "red"}, ...])
114
+ ```
115
+
116
+ ### 插入行
117
+ 用户:在第3行插入2行
118
+ ```
119
+ 用 insert_rows(sheet_name="Sheet1", row=3, count=2)
120
+ ```
121
+
122
+ ### 合并单元格
123
+ 用户:把A1到D1合并
124
+ ```
125
+ 用 merge_cells(sheet_name="Sheet1", range_address="A1:D1")
126
+ ```
127
+
128
+ ### 识别图片中的表格(重要经验!)
129
+ 用户:帮我识别这张图片里的表格
130
+ ```
131
+ **步骤:**
132
+ 1. 调用 extract_images_from_sheet() 提取图片
133
+ 2. 如果失败 → 用 extract_images_from_excel() 从Excel内部提取(更稳定)
134
+ 3. 发散思维,寻找可用的识别方案(如检查Python环境的OCR库,推荐EasyOCR)
135
+ 4. 用找到的方案识别图片内容
136
+ 5. 用 batch_write_rows 批量写入Excel
137
+ 6. 用 batch_set_colors 设置表格格式
138
+ 7. 用 save_workbook 保存
139
+ 8. 删除所有临时文件(.py、.txt、图片等)
140
+ ```
141
+
142
+ ### 删除图片
143
+ 用户:删除工作表中的某张图片
144
+ ```
145
+ 用 delete_sheet_image(sheet_name="Sheet1", image_index=1)
146
+ ```
147
+
148
+ ## 注意事项
149
+
150
+ 1. **优先用批量工具!** 更快更高效
151
+ 2. **图片提取优先用 extract_images_from_sheet**,失败时用 extract_images_from_excel
152
+ 3. **用完临时文件后必须删除!** 不要留在用户文件夹里
153
+ 4. 遇到问题先想办法解决,不要直接说"做不了"
154
+ 5. Excel打开时无法用Python写入,但MCP可以实时操作