@tanskong/office-assistant 1.0.6 → 1.0.8
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/CHANGELOG.md +26 -0
- package/README.md +4 -10
- package/package.json +2 -1
- package/src/officer.py +29 -21
- package/src/server.py +95 -136
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# 版本历史
|
|
2
|
+
|
|
3
|
+
## 1.0.8 - 2026-05-31
|
|
4
|
+
|
|
5
|
+
### 修复
|
|
6
|
+
- 修复 Word 自动化完全失效问题(app.Selection null 检查)
|
|
7
|
+
- 修复 PowerPoint 无幻灯片时崩溃问题(doc.Slides.Count 检查)
|
|
8
|
+
- 修复 save_file 遍历逻辑错误,添加 app_name 参数支持指定保存目标
|
|
9
|
+
- 修复中文路径不兼容问题,使用 win32api.GetShortPathName 转换
|
|
10
|
+
- 为 run_python 添加 finally 保护,确保 stdout 正确恢复
|
|
11
|
+
- 修复未知扩展名默认用 Excel 打开的问题
|
|
12
|
+
- 为 screenshot_element 添加返回值校验
|
|
13
|
+
- 为全局初始化添加异常捕获
|
|
14
|
+
- 为所有 UIA 工具添加模块可用性检查
|
|
15
|
+
|
|
16
|
+
### 优化
|
|
17
|
+
- 精简 UIA 工具,删除不适合日常办公的工具:
|
|
18
|
+
- screenshot_element
|
|
19
|
+
- find_ui_element
|
|
20
|
+
- get_ui_tree
|
|
21
|
+
- click_ui_element
|
|
22
|
+
- 保留核心 UIA 工具:
|
|
23
|
+
- screenshot(截图)
|
|
24
|
+
- get_window_list(窗口列表)
|
|
25
|
+
- activate_window(激活窗口)
|
|
26
|
+
- 更新 README,反映最新功能状态
|
package/README.md
CHANGED
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
- **RunPython 主力工具**:自由执行 Python 代码控制 Office 应用程序
|
|
8
8
|
- **智能工作区**:桌面文件夹安全处理文件
|
|
9
9
|
- **全 Office 套件支持**:Excel、Word、PowerPoint、Outlook、Visio、WPS 等
|
|
10
|
-
- **UI 自动化引擎**:基于 Windows UI Automation
|
|
10
|
+
- **UI 自动化引擎**:基于 Windows UI Automation 框架的精准截图与窗口管理
|
|
11
11
|
- **许可证保护**:机器绑定的许可证激活机制
|
|
12
|
+
- **中文路径支持**:完美解决中文文件路径问题
|
|
12
13
|
|
|
13
14
|
## 安装
|
|
14
15
|
|
|
@@ -48,18 +49,14 @@ npx @tanskong/office-assistant
|
|
|
48
49
|
| `available_apps` | 列出已安装的 Office 应用程序 |
|
|
49
50
|
| `launch_app` | 启动 Office 应用程序 |
|
|
50
51
|
| `quit_app` | 退出 Office 应用程序 |
|
|
51
|
-
| `open_file` | 打开 Office
|
|
52
|
-
| `save_file` |
|
|
52
|
+
| `open_file` | 打开 Office 文件(支持中文路径) |
|
|
53
|
+
| `save_file` | 保存当前文档(可指定应用) |
|
|
53
54
|
|
|
54
55
|
### UIA 自动化工具
|
|
55
56
|
|
|
56
57
|
| 工具 | 说明 |
|
|
57
58
|
|------|------|
|
|
58
59
|
| `screenshot` | 精准截图(全屏/活动窗口/指定窗口) |
|
|
59
|
-
| `screenshot_element` | 截取指定 UI 元素的截图 |
|
|
60
|
-
| `find_ui_element` | 查找 UI 元素并返回属性信息 |
|
|
61
|
-
| `get_ui_tree` | 获取窗口的 UI 树结构 |
|
|
62
|
-
| `click_ui_element` | 点击指定 UI 元素 |
|
|
63
60
|
| `get_window_list` | 列出所有顶层窗口 |
|
|
64
61
|
| `activate_window` | 激活指定窗口 |
|
|
65
62
|
|
|
@@ -81,9 +78,6 @@ screenshot('active')
|
|
|
81
78
|
|
|
82
79
|
# 截取指定窗口
|
|
83
80
|
screenshot('Excel')
|
|
84
|
-
|
|
85
|
-
# 截取 UI 元素
|
|
86
|
-
screenshot_element('Calculate', 'Button', 'Calculator')
|
|
87
81
|
```
|
|
88
82
|
|
|
89
83
|
## 许可证
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanskong/office-assistant",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "Office Assistant - AI-powered Office automation via MCP protocol with license protection",
|
|
5
5
|
"main": "bin/office-assistant.js",
|
|
6
6
|
"bin": {
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"bin/",
|
|
40
40
|
"src/",
|
|
41
41
|
"README.md",
|
|
42
|
+
"CHANGELOG.md",
|
|
42
43
|
"requirements.txt"
|
|
43
44
|
],
|
|
44
45
|
"repository": {
|
package/src/officer.py
CHANGED
|
@@ -29,7 +29,7 @@ class TheOfficer:
|
|
|
29
29
|
|
|
30
30
|
# 使用用户目录,避免硬编码 D 盘
|
|
31
31
|
self._default_folder = os.path.join(os.path.expanduser("~"), "OfficeMCP_Data")
|
|
32
|
-
self.Version = "1.0.
|
|
32
|
+
self.Version = "1.0.8"
|
|
33
33
|
self.ComObjects = {}
|
|
34
34
|
self._printable = False
|
|
35
35
|
self.Data = OfficerData()
|
|
@@ -229,30 +229,38 @@ class TheOfficer:
|
|
|
229
229
|
return "Demo succeeded"
|
|
230
230
|
|
|
231
231
|
def DemonstrateExcel(self) -> bool:
|
|
232
|
-
|
|
233
|
-
|
|
232
|
+
try:
|
|
233
|
+
excel = self.Application("Excel", asNewInstance=True)
|
|
234
|
+
if excel is None:
|
|
235
|
+
return False
|
|
236
|
+
book = excel.Workbooks.Add()
|
|
237
|
+
sheet = excel.ActiveSheet
|
|
238
|
+
excel.Visible = True
|
|
239
|
+
sheet.Cells(1, 1).Value = "Hello, World From Office Assistant!"
|
|
240
|
+
sheet.Cells(1, 1).Font.Size = 20
|
|
241
|
+
sheet.Cells(1, 1).Font.Bold = True
|
|
242
|
+
sheet.Cells(2, 1).Value = "This is a demonstration of the Office Assistant server."
|
|
243
|
+
sheet.Cells(3, 1).Value = "You can use run_python tool to control all Microsoft Office Applications."
|
|
244
|
+
return True
|
|
245
|
+
except Exception as e:
|
|
246
|
+
print(f"DemonstrateExcel error: {e}")
|
|
234
247
|
return False
|
|
235
|
-
book = excel.Workbooks.Add()
|
|
236
|
-
sheet = excel.ActiveSheet
|
|
237
|
-
excel.Visible = True
|
|
238
|
-
sheet.Cells(1, 1).Value = "Hello, World From Office Assistant!"
|
|
239
|
-
sheet.Cells(1, 1).Font.Size = 20
|
|
240
|
-
sheet.Cells(1, 1).Font.Bold = True
|
|
241
|
-
sheet.Cells(2, 1).Value = "This is a demonstration of the Office Assistant server."
|
|
242
|
-
sheet.Cells(3, 1).Value = "You can use run_python tool to control all Microsoft Office Applications."
|
|
243
|
-
return True
|
|
244
248
|
|
|
245
249
|
def DemonstratePowerPoint(self) -> bool:
|
|
246
|
-
|
|
247
|
-
|
|
250
|
+
try:
|
|
251
|
+
ppt = self.Application("PowerPoint", asNewInstance=True)
|
|
252
|
+
if ppt is None:
|
|
253
|
+
return False
|
|
254
|
+
ppt.Visible = True
|
|
255
|
+
presentation = ppt.Presentations.Add()
|
|
256
|
+
slide = presentation.Slides.Add(1, 12)
|
|
257
|
+
shape = slide.Shapes.AddTextbox(1, 100, 20, 800, 100)
|
|
258
|
+
shape.TextFrame.TextRange.Text = "Hello, World From Office Assistant!"
|
|
259
|
+
shape.TextFrame.TextRange.Font.Bold = True
|
|
260
|
+
return True
|
|
261
|
+
except Exception as e:
|
|
262
|
+
print(f"DemonstratePowerPoint error: {e}")
|
|
248
263
|
return False
|
|
249
|
-
ppt.Visible = True
|
|
250
|
-
presentation = ppt.Presentations.Add()
|
|
251
|
-
slide = presentation.Slides.Add(1, 12)
|
|
252
|
-
shape = slide.Shapes.AddTextbox(1, 100, 20, 800, 100)
|
|
253
|
-
shape.TextFrame.TextRange.Text = "Hello, World From Office Assistant!"
|
|
254
|
-
shape.TextFrame.TextRange.Font.Bold = True
|
|
255
|
-
return True
|
|
256
264
|
|
|
257
265
|
def FilePath(self, file_name: str = None, subfolder: str = None) -> str:
|
|
258
266
|
# 优先使用智能办公区路径
|
package/src/server.py
CHANGED
|
@@ -13,12 +13,19 @@ import sys
|
|
|
13
13
|
import os
|
|
14
14
|
from io import StringIO
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
verify_license
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
try:
|
|
17
|
+
from license_manager import verify_license
|
|
18
|
+
verify_license()
|
|
19
|
+
except Exception as e:
|
|
20
|
+
print(f"[WARNING] License verification failed: {e}")
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
from fastmcp import FastMCP
|
|
24
|
+
from officer import TheOfficer
|
|
25
|
+
from utils import get_workspace, get_data_dir, get_export_dir, get_download_dir
|
|
26
|
+
except Exception as e:
|
|
27
|
+
print(f"[ERROR] Failed to import dependencies: {e}")
|
|
28
|
+
sys.exit(1)
|
|
22
29
|
|
|
23
30
|
mcp = FastMCP("Office Assistant")
|
|
24
31
|
Officer = TheOfficer()
|
|
@@ -52,9 +59,15 @@ def _build_globals(app, doc, app_name):
|
|
|
52
59
|
if app_name.lower() in ['excel', 'ket']:
|
|
53
60
|
base['sheet'] = doc.ActiveSheet if doc else None
|
|
54
61
|
elif app_name.lower() in ['word', 'kwps']:
|
|
55
|
-
base['selection'] = app.Selection
|
|
62
|
+
base['selection'] = app.Selection if app else None
|
|
56
63
|
elif app_name.lower() in ['powerpoint', 'kwpp']:
|
|
57
|
-
base['slide'] =
|
|
64
|
+
base['slide'] = None
|
|
65
|
+
if doc and hasattr(doc, 'Slides'):
|
|
66
|
+
try:
|
|
67
|
+
if doc.Slides.Count > 0:
|
|
68
|
+
base['slide'] = doc.Slides(doc.Slides.Count)
|
|
69
|
+
except Exception:
|
|
70
|
+
pass
|
|
58
71
|
return base
|
|
59
72
|
|
|
60
73
|
|
|
@@ -67,21 +80,29 @@ def available_apps() -> list:
|
|
|
67
80
|
|
|
68
81
|
|
|
69
82
|
@mcp.tool()
|
|
70
|
-
def launch_app(app_name: str = "Excel", visible: bool = True) ->
|
|
83
|
+
def launch_app(app_name: str = "Excel", visible: bool = True) -> dict:
|
|
71
84
|
"""Launch a Microsoft Office application."""
|
|
72
85
|
try:
|
|
73
86
|
app = Officer.Application(app_name)
|
|
74
|
-
app
|
|
75
|
-
|
|
87
|
+
if app is None:
|
|
88
|
+
return {"success": False, "error": f"Failed to start {app_name}"}
|
|
89
|
+
try:
|
|
90
|
+
app.Visible = visible
|
|
91
|
+
except Exception:
|
|
92
|
+
pass
|
|
93
|
+
return {"success": True, "app": app_name, "name": app.Name}
|
|
76
94
|
except Exception as e:
|
|
77
|
-
|
|
78
|
-
return False
|
|
95
|
+
return {"success": False, "error": str(e)}
|
|
79
96
|
|
|
80
97
|
|
|
81
98
|
@mcp.tool()
|
|
82
|
-
def quit_app(app_name: str = "Excel", force: bool = False) ->
|
|
99
|
+
def quit_app(app_name: str = "Excel", force: bool = False) -> dict:
|
|
83
100
|
"""Quit a Microsoft Office application."""
|
|
84
|
-
|
|
101
|
+
try:
|
|
102
|
+
result = Officer.Quit(app_name, force)
|
|
103
|
+
return {"success": result}
|
|
104
|
+
except Exception as e:
|
|
105
|
+
return {"success": False, "error": str(e)}
|
|
85
106
|
|
|
86
107
|
|
|
87
108
|
# ========== File Operation Tools ==========
|
|
@@ -92,54 +113,71 @@ def open_file(file_path: str) -> dict:
|
|
|
92
113
|
Open an Office file.
|
|
93
114
|
|
|
94
115
|
Supported formats:
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
116
|
+
- Excel: .xlsx, .xls, .csv
|
|
117
|
+
- Word: .docx, .doc
|
|
118
|
+
- PowerPoint: .pptx, .ppt
|
|
98
119
|
|
|
99
120
|
Tip: Place files in "Desktop/智能办公区/数据" for safe processing.
|
|
100
|
-
Note: For Chinese file paths, use run_python tool instead.
|
|
101
121
|
"""
|
|
102
122
|
import os
|
|
103
123
|
|
|
104
124
|
# 处理中文路径编码
|
|
105
|
-
# 方法1
|
|
125
|
+
# 方法1:使用绝对路径
|
|
106
126
|
file_path = os.path.abspath(file_path)
|
|
107
127
|
|
|
108
|
-
# 方法2
|
|
128
|
+
# 方法2:确保路径存在
|
|
109
129
|
if not os.path.exists(file_path):
|
|
110
130
|
return {"success": False, "error": f"File not found: {file_path}"}
|
|
111
131
|
|
|
112
132
|
ext = file_path.split('.')[-1].lower()
|
|
113
|
-
|
|
133
|
+
if ext not in APP_MAP:
|
|
134
|
+
return {"success": False, "error": f"Unsupported file format: .{ext}"}
|
|
135
|
+
|
|
136
|
+
app_name = APP_MAP.get(ext)
|
|
114
137
|
app = Officer.Application(app_name)
|
|
115
138
|
|
|
116
139
|
if app is None:
|
|
117
140
|
return {"success": False, "error": f"Failed to start {app_name}"}
|
|
118
141
|
|
|
119
142
|
try:
|
|
143
|
+
# 使用win32api.GetShortPathName解决中文路径问题
|
|
144
|
+
try:
|
|
145
|
+
import win32api
|
|
146
|
+
short_path = win32api.GetShortPathName(file_path)
|
|
147
|
+
open_path = short_path
|
|
148
|
+
except Exception:
|
|
149
|
+
open_path = file_path
|
|
150
|
+
|
|
120
151
|
if app_name == 'Excel':
|
|
121
|
-
|
|
122
|
-
doc = app.Workbooks.Open(file_path)
|
|
152
|
+
doc = app.Workbooks.Open(open_path)
|
|
123
153
|
elif app_name == 'Word':
|
|
124
|
-
doc = app.Documents.Open(
|
|
154
|
+
doc = app.Documents.Open(open_path)
|
|
125
155
|
elif app_name == 'PowerPoint':
|
|
126
|
-
doc = app.Presentations.Open(
|
|
156
|
+
doc = app.Presentations.Open(open_path)
|
|
127
157
|
|
|
128
158
|
app.Visible = True
|
|
129
159
|
return {"success": True, "app": app_name, "file": file_path}
|
|
130
160
|
except Exception as e:
|
|
131
|
-
return {"success": False, "error": str(e)
|
|
161
|
+
return {"success": False, "error": str(e)}
|
|
132
162
|
|
|
133
163
|
|
|
134
164
|
@mcp.tool()
|
|
135
|
-
def save_file(file_path: str = None) -> dict:
|
|
136
|
-
"""Save the current active document.
|
|
137
|
-
|
|
165
|
+
def save_file(file_path: str = None, app_name: str = None) -> dict:
|
|
166
|
+
"""Save the current active document.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
file_path: Optional file path to save as
|
|
170
|
+
app_name: Optional specific app to save (Excel, Word, PowerPoint)
|
|
171
|
+
"""
|
|
172
|
+
apps_to_check = [app_name] if app_name else ['Excel', 'Word', 'PowerPoint']
|
|
173
|
+
for app_name_check in apps_to_check:
|
|
138
174
|
try:
|
|
139
|
-
app = Officer.Application(
|
|
140
|
-
if
|
|
175
|
+
app = Officer.Application(app_name_check)
|
|
176
|
+
if app is None:
|
|
177
|
+
continue
|
|
178
|
+
if app_name_check == 'Excel':
|
|
141
179
|
doc = app.ActiveWorkbook
|
|
142
|
-
elif
|
|
180
|
+
elif app_name_check == 'Word':
|
|
143
181
|
doc = app.ActiveDocument
|
|
144
182
|
else:
|
|
145
183
|
doc = app.ActivePresentation
|
|
@@ -150,7 +188,7 @@ def save_file(file_path: str = None) -> dict:
|
|
|
150
188
|
else:
|
|
151
189
|
doc.Save()
|
|
152
190
|
return {"success": True, "file": doc.FullName}
|
|
153
|
-
except:
|
|
191
|
+
except Exception:
|
|
154
192
|
continue
|
|
155
193
|
return {"success": False, "error": "No active document found"}
|
|
156
194
|
|
|
@@ -163,12 +201,12 @@ def run_python(code: str, app_name: str = "Excel") -> dict:
|
|
|
163
201
|
Execute Python code to control Office applications (PRIMARY TOOL).
|
|
164
202
|
|
|
165
203
|
Available global variables:
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
204
|
+
- app: The Office application object
|
|
205
|
+
- doc: The current active document
|
|
206
|
+
- sheet: Active worksheet (Excel)
|
|
207
|
+
- selection: Current selection (Word)
|
|
208
|
+
- slide: Active slide (PowerPoint)
|
|
209
|
+
- Officer: TheOfficer instance for accessing other apps
|
|
172
210
|
|
|
173
211
|
Excel example:
|
|
174
212
|
sheet.Cells(1, 1).Value = "Hello"
|
|
@@ -192,11 +230,11 @@ def run_python(code: str, app_name: str = "Excel") -> dict:
|
|
|
192
230
|
try:
|
|
193
231
|
exec(code, globals_dict)
|
|
194
232
|
output = buffer.getvalue()
|
|
195
|
-
sys.stdout = old_stdout
|
|
196
233
|
return {"success": True, "output": output or "(无输出)"}
|
|
197
234
|
except Exception as e:
|
|
198
|
-
sys.stdout = old_stdout
|
|
199
235
|
return {"success": False, "error": str(e)}
|
|
236
|
+
finally:
|
|
237
|
+
sys.stdout = old_stdout
|
|
200
238
|
|
|
201
239
|
|
|
202
240
|
# ========== UIA Tools ==========
|
|
@@ -222,102 +260,13 @@ def screenshot(target: str = None, save_name: str = None) -> dict:
|
|
|
222
260
|
return {"success": False, "error": str(e)}
|
|
223
261
|
|
|
224
262
|
|
|
225
|
-
@mcp.tool()
|
|
226
|
-
def screenshot_element(element_name: str, control_type: str = None,
|
|
227
|
-
parent_name: str = None, save_name: str = None) -> dict:
|
|
228
|
-
"""
|
|
229
|
-
Capture screenshot of a specific UI element.
|
|
230
|
-
|
|
231
|
-
Args:
|
|
232
|
-
element_name: Name of the UI element
|
|
233
|
-
control_type: Control type, e.g., 'Button', 'Edit', 'Document'
|
|
234
|
-
parent_name: Parent window name to limit search scope
|
|
235
|
-
save_name: Filename to save
|
|
236
|
-
|
|
237
|
-
Examples:
|
|
238
|
-
screenshot_element('Calculate', 'Button', 'Calculator')
|
|
239
|
-
screenshot_element('Sheet1', 'TabItem', 'Excel')
|
|
240
|
-
"""
|
|
241
|
-
try:
|
|
242
|
-
path = Officer.UIA.screenshot_element(element_name, control_type, parent_name, save_name)
|
|
243
|
-
return {"success": True, "path": path}
|
|
244
|
-
except Exception as e:
|
|
245
|
-
return {"success": False, "error": str(e)}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
@mcp.tool()
|
|
249
|
-
def find_ui_element(name: str, control_type: str = None,
|
|
250
|
-
parent_name: str = None) -> dict:
|
|
251
|
-
"""
|
|
252
|
-
Find a UI element and return its properties.
|
|
253
|
-
|
|
254
|
-
Args:
|
|
255
|
-
name: Element name
|
|
256
|
-
control_type: Control type filter
|
|
257
|
-
parent_name: Parent window name
|
|
258
|
-
|
|
259
|
-
Returns:
|
|
260
|
-
Element properties including name, type, rect, automation_id
|
|
261
|
-
"""
|
|
262
|
-
try:
|
|
263
|
-
element = Officer.UIA.find_element(name, control_type, parent_name)
|
|
264
|
-
if element is None:
|
|
265
|
-
return {"success": False, "error": f"Element not found: {name}"}
|
|
266
|
-
return {
|
|
267
|
-
"success": True,
|
|
268
|
-
"name": element.Name,
|
|
269
|
-
"type": element.ControlTypeName,
|
|
270
|
-
"automation_id": element.AutomationId,
|
|
271
|
-
"class_name": element.ClassName,
|
|
272
|
-
"rect": element.BoundingRectangle,
|
|
273
|
-
"enabled": element.IsEnabled,
|
|
274
|
-
"visible": not element.IsOffscreen
|
|
275
|
-
}
|
|
276
|
-
except Exception as e:
|
|
277
|
-
return {"success": False, "error": str(e)}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
@mcp.tool()
|
|
281
|
-
def get_ui_tree(window_name: str = None, max_depth: int = 3) -> dict:
|
|
282
|
-
"""
|
|
283
|
-
Get the UI automation tree of a window.
|
|
284
|
-
|
|
285
|
-
Args:
|
|
286
|
-
window_name: Window name, None for active window
|
|
287
|
-
max_depth: Maximum traversal depth
|
|
288
|
-
|
|
289
|
-
Returns:
|
|
290
|
-
Hierarchical tree structure of UI elements
|
|
291
|
-
"""
|
|
292
|
-
try:
|
|
293
|
-
tree = Officer.UIA.get_element_tree(window_name, max_depth)
|
|
294
|
-
return {"success": True, "tree": tree}
|
|
295
|
-
except Exception as e:
|
|
296
|
-
return {"success": False, "error": str(e)}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
@mcp.tool()
|
|
300
|
-
def click_ui_element(name: str, control_type: str = None,
|
|
301
|
-
parent_name: str = None) -> dict:
|
|
302
|
-
"""
|
|
303
|
-
Click a UI element.
|
|
304
|
-
|
|
305
|
-
Args:
|
|
306
|
-
name: Element name
|
|
307
|
-
control_type: Control type
|
|
308
|
-
parent_name: Parent window name
|
|
309
|
-
"""
|
|
310
|
-
try:
|
|
311
|
-
result = Officer.UIA.click_element(name, control_type, parent_name)
|
|
312
|
-
return {"success": result}
|
|
313
|
-
except Exception as e:
|
|
314
|
-
return {"success": False, "error": str(e)}
|
|
315
|
-
|
|
316
|
-
|
|
317
263
|
@mcp.tool()
|
|
318
264
|
def get_window_list() -> dict:
|
|
319
265
|
"""List all top-level windows."""
|
|
320
266
|
try:
|
|
267
|
+
if not hasattr(Officer, 'UIA') or not Officer.UIA.available:
|
|
268
|
+
return {"success": False, "error": "UIA automation module not available"}
|
|
269
|
+
|
|
321
270
|
windows = Officer.UIA.get_window_list()
|
|
322
271
|
return {"success": True, "windows": windows}
|
|
323
272
|
except Exception as e:
|
|
@@ -328,6 +277,9 @@ def get_window_list() -> dict:
|
|
|
328
277
|
def activate_window(window_name: str) -> dict:
|
|
329
278
|
"""Activate a window by name."""
|
|
330
279
|
try:
|
|
280
|
+
if not hasattr(Officer, 'UIA') or not Officer.UIA.available:
|
|
281
|
+
return {"success": False, "error": "UIA automation module not available"}
|
|
282
|
+
|
|
331
283
|
result = Officer.UIA.activate_window(window_name)
|
|
332
284
|
return {"success": result}
|
|
333
285
|
except Exception as e:
|
|
@@ -337,9 +289,16 @@ def activate_window(window_name: str) -> dict:
|
|
|
337
289
|
# ========== System Tools ==========
|
|
338
290
|
|
|
339
291
|
@mcp.tool()
|
|
340
|
-
def download(url: str, save_name: str = None) ->
|
|
292
|
+
def download(url: str, save_name: str = None) -> dict:
|
|
341
293
|
"""Download a file from URL to the download folder."""
|
|
342
|
-
|
|
294
|
+
try:
|
|
295
|
+
result = Officer.DownloadURL(url, save_name)
|
|
296
|
+
if result:
|
|
297
|
+
return {"success": True, "file_path": result}
|
|
298
|
+
else:
|
|
299
|
+
return {"success": False, "error": "Download failed or returned empty path"}
|
|
300
|
+
except Exception as e:
|
|
301
|
+
return {"success": False, "error": str(e)}
|
|
343
302
|
|
|
344
303
|
|
|
345
304
|
@mcp.tool()
|