@tanskong/office-assistant 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 ADDED
@@ -0,0 +1,57 @@
1
+ # Office Assistant
2
+
3
+ AI-powered Office automation via MCP protocol.
4
+
5
+ ## Features
6
+
7
+ - **RunPython**: Execute Python code to control Office applications freely
8
+ - **Smart Workspace**: Desktop folder for safe file processing
9
+ - **Full Office Suite**: Excel, Word, PowerPoint, Outlook, Visio, WPS, and more
10
+ - **License Protection**: Machine-bound license activation
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ npx @yourname/office-assistant
16
+ ```
17
+
18
+ ## Activation
19
+
20
+ 1. Set environment variable:
21
+ ```bash
22
+ set OFFICE_ASSISTANT_LICENSE=OFFICE-ASSISTANT-XXXX-XXXX-XXXX-XXXX
23
+ ```
24
+
25
+ 2. Run activation:
26
+ ```bash
27
+ npx @yourname/office-assistant activate
28
+ ```
29
+
30
+ 3. Start the server:
31
+ ```bash
32
+ npx @yourname/office-assistant
33
+ ```
34
+
35
+ ## Disclaimer
36
+
37
+ Please place files in `Desktop/智能办公区/数据` before processing.
38
+ We are not responsible for data loss when operating on original file locations.
39
+
40
+ ## Tools
41
+
42
+ | Tool | Description |
43
+ |------|-------------|
44
+ | `run_python` | Execute Python code (PRIMARY TOOL) |
45
+ | `available_apps` | List installed Office applications |
46
+ | `launch_app` | Launch an Office application |
47
+ | `quit_app` | Quit an Office application |
48
+ | `open_file` | Open an Office file |
49
+ | `save_file` | Save current document |
50
+ | `screenshot` | Capture screenshot |
51
+ | `download` | Download file from URL |
52
+ | `speak` | Text-to-speech |
53
+ | `demonstrate` | Run demonstration |
54
+
55
+ ## License
56
+
57
+ Commercial license required. See activation instructions above.
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Office Assistant - MCP Server Entry Point
4
+ *
5
+ * This script:
6
+ * 1. Checks Python environment
7
+ * 2. Verifies license activation
8
+ * 3. Launches the Python MCP server
9
+ */
10
+
11
+ const { spawn } = require('child_process');
12
+ const path = require('path');
13
+ const fs = require('fs');
14
+
15
+ const PYTHON_SCRIPT = path.join(__dirname, '..', 'src', 'server.py');
16
+
17
+ function findPython() {
18
+ // Try 'python' first, then 'python3'
19
+ const candidates = ['python', 'python3'];
20
+ for (const cmd of candidates) {
21
+ try {
22
+ const result = require('child_process').execSync(`${cmd} --version`, { encoding: 'utf8' });
23
+ console.log(`[INFO] Found Python: ${result.trim()}`);
24
+ return cmd;
25
+ } catch (e) {
26
+ continue;
27
+ }
28
+ }
29
+ console.error('[ERROR] Python not found. Please install Python 3.10+');
30
+ process.exit(1);
31
+ }
32
+
33
+ function main() {
34
+ const python = findPython();
35
+
36
+ console.log('[INFO] Starting Office Assistant MCP Server...');
37
+ console.log(`[INFO] Python script: ${PYTHON_SCRIPT}`);
38
+
39
+ const args = process.argv.slice(2);
40
+ const server = spawn(python, [PYTHON_SCRIPT, ...args], {
41
+ stdio: ['inherit', 'inherit', 'inherit']
42
+ });
43
+
44
+ server.on('close', (code) => {
45
+ process.exit(code);
46
+ });
47
+
48
+ server.on('error', (err) => {
49
+ console.error('[ERROR] Failed to start server:', err.message);
50
+ process.exit(1);
51
+ });
52
+ }
53
+
54
+ main();
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@tanskong/office-assistant",
3
+ "version": "1.0.0",
4
+ "description": "Office Assistant - AI-powered Office automation via MCP protocol with license protection",
5
+ "main": "bin/office-assistant.js",
6
+ "bin": {
7
+ "office-assistant": "bin/office-assistant.js"
8
+ },
9
+ "scripts": {
10
+ "start": "node bin/office-assistant.js",
11
+ "activate": "node bin/office-assistant.js activate",
12
+ "test": "echo \"Error: no test specified\" && exit 1"
13
+ },
14
+ "keywords": [
15
+ "mcp",
16
+ "office",
17
+ "excel",
18
+ "word",
19
+ "powerpoint",
20
+ "automation",
21
+ "ai",
22
+ "microsoft-office",
23
+ "wps"
24
+ ],
25
+ "author": "tanskong",
26
+ "license": "UNLICENSED",
27
+ "private": false,
28
+ "publishConfig": {
29
+ "access": "public",
30
+ "registry": "https://registry.npmjs.org/"
31
+ },
32
+ "engines": {
33
+ "node": ">=18.0.0"
34
+ },
35
+ "os": [
36
+ "win32"
37
+ ],
38
+ "files": [
39
+ "bin/",
40
+ "src/",
41
+ "README.md",
42
+ "requirements.txt"
43
+ ],
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "https://github.com/yourname/office-assistant.git"
47
+ },
48
+ "bugs": {
49
+ "url": "https://github.com/yourname/office-assistant/issues"
50
+ },
51
+ "homepage": "https://github.com/yourname/office-assistant#readme"
52
+ }
@@ -0,0 +1,3 @@
1
+ fastmcp>=2.3.3
2
+ pywin32>=310
3
+ Pillow>=10.0.0
package/src/.dev_mode ADDED
@@ -0,0 +1,2 @@
1
+ # Development mode marker - remove before release
2
+ # This file bypasses license verification for development
File without changes
@@ -0,0 +1,141 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Office Assistant License Activation System
4
+ ==========================================
5
+ 1. User activates with license key
6
+ 2. System captures MAC address and machine fingerprint
7
+ 3. Saves activation record locally
8
+ 4. On every startup, verifies MAC address matches
9
+ """
10
+
11
+ import os
12
+ import sys
13
+ import json
14
+ import hashlib
15
+ import uuid
16
+ import socket
17
+ import subprocess
18
+ from datetime import datetime
19
+
20
+ APP_DATA_DIR = os.path.join(os.environ.get("APPDATA", os.path.dirname(__file__)), "office-assistant")
21
+ ACTIVATION_FILE = os.path.join(APP_DATA_DIR, "activation.dat")
22
+
23
+
24
+ def get_mac_address():
25
+ mac = ':'.join(['{:02x}'.format((uuid.getnode() >> i) & 0xff) for i in range(0, 48, 8)][::-1])
26
+ return mac.upper().replace(':', '')
27
+
28
+
29
+ def get_machine_fingerprint():
30
+ mac = get_mac_address()
31
+ hostname = socket.gethostname()
32
+ try:
33
+ result = subprocess.run(['wmic', 'cpu', 'get', 'ProcessorId'], capture_output=True, text=True)
34
+ cpu_id = result.stdout.strip().split('\n')[-1].strip()
35
+ except:
36
+ cpu_id = 'CPU-UNKNOWN'
37
+ combined = f"{mac}|{hostname}|{cpu_id}"
38
+ return hashlib.sha256(combined.encode()).hexdigest()[:32].upper()
39
+
40
+
41
+ def get_license_key():
42
+ return os.environ.get("OFFICE_ASSISTANT_LICENSE", "")
43
+
44
+
45
+ def validate_key_format(key):
46
+ if not key:
47
+ return False, "No license key provided"
48
+ key = key.strip()
49
+ if not key.startswith("OFFICE-ASSISTANT-"):
50
+ return False, "Invalid key format. Key must start with OFFICE-ASSISTANT-"
51
+ return True, "OK"
52
+
53
+
54
+ def activate(key):
55
+ print("\n" + "=" * 50)
56
+ print(" Office Assistant Activation")
57
+ print("=" * 50)
58
+
59
+ if not key:
60
+ print("\n[ERROR] No license key provided!")
61
+ print("\nPlease set environment variable OFFICE_ASSISTANT_LICENSE")
62
+ print(" Windows: set OFFICE_ASSISTANT_LICENSE=your-key")
63
+ return False
64
+
65
+ valid, msg = validate_key_format(key)
66
+ if not valid:
67
+ print(f"\n[ERROR] Invalid license key: {key}")
68
+ print(f" Error: {msg}")
69
+ return False
70
+
71
+ if os.path.exists(ACTIVATION_FILE):
72
+ print("\n[WARN] Already activated!")
73
+ print(f" Activation file: {ACTIVATION_FILE}")
74
+ print("\n To re-activate, delete activation.dat first.")
75
+ return False
76
+
77
+ os.makedirs(APP_DATA_DIR, exist_ok=True)
78
+
79
+ mac = get_mac_address()
80
+ fingerprint = get_machine_fingerprint()
81
+
82
+ activation_data = {
83
+ "license_key": key.strip(),
84
+ "mac_address": mac,
85
+ "fingerprint": fingerprint,
86
+ "activated_at": datetime.now().isoformat(),
87
+ "hostname": socket.gethostname()
88
+ }
89
+
90
+ with open(ACTIVATION_FILE, 'w') as f:
91
+ json.dump(activation_data, f, indent=2)
92
+
93
+ print("\n[OK] Activation Successful!")
94
+ print(f"\n License Key: {key}")
95
+ print(f" MAC Address: {mac}")
96
+ print(f" Fingerprint: {fingerprint}")
97
+ print(f" Activated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
98
+ print(f"\n Activation file: {ACTIVATION_FILE}")
99
+ print(f"\n This computer is now bound to this license.")
100
+ print("=" * 50)
101
+ return True
102
+
103
+
104
+ def verify_license():
105
+ """Verify license on server startup"""
106
+ dev_marker = os.path.join(os.path.dirname(__file__), ".dev_mode")
107
+ if os.path.exists(dev_marker):
108
+ return True
109
+
110
+ if not os.path.exists(ACTIVATION_FILE):
111
+ print("[ERROR] Office Assistant is not activated. Please run activation first.")
112
+ sys.exit(1)
113
+
114
+ try:
115
+ with open(ACTIVATION_FILE, 'r') as f:
116
+ data = json.load(f)
117
+
118
+ current_mac = get_mac_address()
119
+ current_fingerprint = get_machine_fingerprint()
120
+ stored_mac = data.get("mac_address", "")
121
+ stored_fingerprint = data.get("fingerprint", "")
122
+ stored_key = data.get("license_key", "").strip()
123
+ current_key = os.environ.get("OFFICE_ASSISTANT_LICENSE", "").strip()
124
+
125
+ if current_key != stored_key:
126
+ print("[ERROR] License key mismatch.")
127
+ sys.exit(1)
128
+
129
+ if current_mac != stored_mac or current_fingerprint != stored_fingerprint:
130
+ print("[ERROR] License is bound to another device.")
131
+ sys.exit(1)
132
+
133
+ except Exception as e:
134
+ print(f"[ERROR] License verification failed: {e}")
135
+ sys.exit(1)
136
+
137
+
138
+ if __name__ == "__main__":
139
+ key = get_license_key()
140
+ success = activate(key)
141
+ sys.exit(0 if success else 1)
package/src/officer.py ADDED
@@ -0,0 +1,391 @@
1
+ import winreg
2
+ import pywintypes
3
+ import os
4
+ import sys
5
+ import win32com.client
6
+ from pathlib import Path
7
+
8
+
9
+ class TheOfficer:
10
+ def __init__(self):
11
+ self._excel = None
12
+ self._word = None
13
+ self._outlook = None
14
+ self._visio = None
15
+ self._access = None
16
+ self._project = None
17
+ self._publisher = None
18
+ self._onenote = None
19
+ self._powerpoint = None
20
+ self._ket = None
21
+ self._kwpp = None
22
+ self._kwps = None
23
+
24
+ self._default_folder = "D:\\@OfficeMCP"
25
+ self.Version = "1.0.5"
26
+ self.Speaker = None
27
+ self.ComObjects = {}
28
+ self._printable = False
29
+ self.Data = OfficerData()
30
+
31
+ self.MicrosoftApplications = [
32
+ 'Word', 'Excel', 'PowerPoint',
33
+ 'Visio', 'Access', 'MSProject',
34
+ 'Outlook', 'Publisher', "OneNote", "Kwps", "Ket", "Kwpp"
35
+ ]
36
+
37
+ @property
38
+ def RootFolder(self) -> str:
39
+ if not os.path.exists(self._default_folder):
40
+ os.makedirs(self._default_folder)
41
+ return self._default_folder
42
+
43
+ def GetComObject(self, com_name: str, active: bool = True):
44
+ comobject = self.ComObjects.get(com_name)
45
+ if comobject is None:
46
+ try:
47
+ comobject = win32com.client.GetActiveObject(com_name)
48
+ except Exception:
49
+ comobject = win32com.client.Dispatch(com_name)
50
+ self.ComObjects[com_name] = comobject
51
+ return comobject
52
+
53
+ def Speak(self, text: str = "Hello!, I'm Office Assistant.", volume: int = 80, rate: int = 2) -> bool:
54
+ try:
55
+ if self.Speaker is None:
56
+ self.Speaker = self.GetComObject("SAPI.SPVOICE")
57
+ self.Speaker.Volume = volume
58
+ self.Speaker.Rate = rate
59
+ self.Speaker.Speak(text)
60
+ return True
61
+ except Exception as e:
62
+ print(e)
63
+ return False
64
+
65
+ def DownloadImage(self, url: str, save_path: str = None) -> str:
66
+ import datetime
67
+ if save_path is None:
68
+ now = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
69
+ save_path = f"downloaded_{now}.png"
70
+ return self.DownloadURL(url, save_path)
71
+
72
+ def DownloadURL(self, url: str, save_path: str = None) -> str:
73
+ import urllib.request
74
+ import datetime
75
+ if save_path is None:
76
+ now = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
77
+ save_path = f"downloaded_{now}.png"
78
+ file_path = self.FilePath(save_path)
79
+ try:
80
+ with urllib.request.urlopen(url) as response, open(file_path, 'wb') as out_file:
81
+ out_file.write(response.read())
82
+ return file_path
83
+ except Exception as e:
84
+ print(f'Download failed: {e}')
85
+ return ""
86
+
87
+ def Visible(self, app_name: str, visible=None) -> bool:
88
+ try:
89
+ app = self.Application(app_name)
90
+ if app is None:
91
+ return False
92
+ if visible is not None:
93
+ app.Visible = visible
94
+ return app.Visible
95
+ except Exception as e:
96
+ print(e)
97
+ return False
98
+
99
+ def Quit(self, app_name: str, force: bool = False) -> bool:
100
+ app_name_attr = "_" + app_name.lower()
101
+ if hasattr(self, app_name_attr):
102
+ app = getattr(self, app_name_attr)
103
+ try:
104
+ return self.QuitApplication(app, force)
105
+ except Exception as e:
106
+ print(e)
107
+ return False
108
+
109
+ def QuitApplication(self, app, force: bool = False) -> bool:
110
+ if not force:
111
+ try:
112
+ app.Quit()
113
+ return True
114
+ except Exception as e:
115
+ print(e)
116
+ return False
117
+ import win32process
118
+ import win32api
119
+ import win32con
120
+ hwnd = app.Hwnd
121
+ t, p = win32process.GetWindowThreadProcessId(hwnd)
122
+ try:
123
+ handle = win32api.OpenProcess(win32con.PROCESS_TERMINATE, 0, p)
124
+ if handle:
125
+ win32api.TerminateProcess(handle, 0)
126
+ win32api.CloseHandle(handle)
127
+ return True
128
+ except:
129
+ pass
130
+ return False
131
+
132
+ def Application(self, app_name: str, asNewInstance: bool = False):
133
+ app_name_attr = "_" + app_name.lower()
134
+ if hasattr(self, app_name_attr):
135
+ app = getattr(self, app_name_attr)
136
+ try:
137
+ name = app.Name
138
+ return app
139
+ except Exception:
140
+ pass
141
+ if app_name not in self.MicrosoftApplications:
142
+ return None
143
+ if not self.IsAppAvailable(app_name):
144
+ return None
145
+ app_full_name = app_name + ".Application"
146
+ if asNewInstance:
147
+ app = win32com.client.Dispatch(app_full_name)
148
+ self.__dict__[app_name_attr] = app
149
+ return app
150
+ else:
151
+ try:
152
+ app = win32com.client.GetActiveObject(app_full_name)
153
+ except pywintypes.com_error:
154
+ app = win32com.client.Dispatch(app_full_name)
155
+ self.__dict__[app_name_attr] = app
156
+ return app
157
+
158
+ def RunningApps(self) -> list:
159
+ apps = []
160
+ for prog_id in self.MicrosoftApplications:
161
+ try:
162
+ app = win32com.client.GetActiveObject(prog_id + ".Application")
163
+ if app is not None:
164
+ apps.append(prog_id)
165
+ except (FileNotFoundError, pywintypes.com_error):
166
+ continue
167
+ except Exception as e:
168
+ print(f"[DEBUG] Error verifying {prog_id}: {str(e)}")
169
+ return apps
170
+
171
+ def AvailableApps(self) -> list:
172
+ apps = []
173
+ for prog_id in self.MicrosoftApplications:
174
+ try:
175
+ with winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, prog_id + ".Application"):
176
+ pass
177
+ apps.append(prog_id)
178
+ except (FileNotFoundError, pywintypes.com_error):
179
+ continue
180
+ except Exception as e:
181
+ print(f"[DEBUG] Error verifying {prog_id}: {str(e)}")
182
+ return apps
183
+
184
+ def IsAppAvailable(self, app_name: str) -> bool:
185
+ try:
186
+ if not app_name.endswith(".Application"):
187
+ app_name = app_name + ".Application"
188
+ with winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, app_name):
189
+ pass
190
+ return True
191
+ except Exception:
192
+ return False
193
+
194
+ def Demonstrate(self) -> str:
195
+ if not self.DemonstrateExcel():
196
+ return "There's no Excel installed or something wrong"
197
+ if not self.DemonstratePowerPoint():
198
+ return "There's no PowerPoint installed or something wrong"
199
+ return "Demo succeeded"
200
+
201
+ def DemonstrateExcel(self) -> bool:
202
+ excel = self.Excel
203
+ if excel is None:
204
+ return False
205
+ book = excel.Workbooks.Add()
206
+ sheet = excel.ActiveSheet
207
+ excel.Visible = True
208
+ sheet.Cells(1, 1).Value = "Hello, World From Office Assistant!"
209
+ sheet.Cells(1, 1).Font.Size = 20
210
+ sheet.Cells(1, 1).Font.Bold = True
211
+ sheet.Cells(2, 1).Value = "This is a demonstration of the Office Assistant server."
212
+ sheet.Cells(3, 1).Value = "You can use run_python tool to control all Microsoft Office Applications."
213
+ return True
214
+
215
+ def DemonstratePowerPoint(self) -> bool:
216
+ ppt = self.Application("PowerPoint")
217
+ if ppt is None:
218
+ return False
219
+ ppt.Visible = True
220
+ presentation = ppt.Presentations.Add()
221
+ slide = presentation.Slides.Add(1, 12)
222
+ shape = slide.Shapes.AddTextbox(1, 100, 20, 800, 100)
223
+ shape.TextFrame.TextRange.Text = "Hello, World From Office Assistant!"
224
+ shape.TextFrame.TextRange.Font.Bold = True
225
+ return True
226
+
227
+ def FilePath(self, file_name: str = None, subfolder: str = None) -> str:
228
+ if subfolder is None:
229
+ file_path = os.path.join(self.RootFolder, file_name)
230
+ else:
231
+ file_path = os.path.join(self.RootFolder, subfolder, file_name)
232
+ return file_path
233
+
234
+ def ScreenShot(self, save_path: str = None) -> str:
235
+ import win32gui
236
+ import win32ui
237
+ import win32con
238
+ import win32api
239
+ from PIL import Image
240
+ import datetime
241
+ hdesktop = win32gui.GetDesktopWindow()
242
+ width = win32api.GetSystemMetrics(win32con.SM_CXVIRTUALSCREEN)
243
+ height = win32api.GetSystemMetrics(win32con.SM_CYVIRTUALSCREEN)
244
+ left = win32api.GetSystemMetrics(win32con.SM_XVIRTUALSCREEN)
245
+ top = win32api.GetSystemMetrics(win32con.SM_YVIRTUALSCREEN)
246
+ desktop_dc = win32gui.GetWindowDC(hdesktop)
247
+ img_dc = win32ui.CreateDCFromHandle(desktop_dc)
248
+ mem_dc = img_dc.CreateCompatibleDC()
249
+ screenshot = win32ui.CreateBitmap()
250
+ screenshot.CreateCompatibleBitmap(img_dc, width, height)
251
+ mem_dc.SelectObject(screenshot)
252
+ mem_dc.BitBlt((0, 0), (width, height), img_dc, (left, top), win32con.SRCCOPY)
253
+ bmpinfo = screenshot.GetInfo()
254
+ bmpstr = screenshot.GetBitmapBits(True)
255
+ img = Image.frombuffer(
256
+ 'RGB',
257
+ (bmpinfo['bmWidth'], bmpinfo['bmHeight']),
258
+ bmpstr, 'raw', 'BGRX', 0, 1)
259
+ if save_path is None:
260
+ now = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
261
+ save_path = self.FilePath(f"screenshot_{now}.png")
262
+ else:
263
+ save_path = self.FilePath(save_path)
264
+ img.save(save_path)
265
+ mem_dc.DeleteDC()
266
+ win32gui.DeleteObject(screenshot.GetHandle())
267
+ img_dc.DeleteDC()
268
+ win32gui.ReleaseDC(hdesktop, desktop_dc)
269
+ return save_path
270
+
271
+ @property
272
+ def Kwps(self):
273
+ try:
274
+ name = self._kwps.Name
275
+ return self._kwps
276
+ except Exception:
277
+ self._kwps = self.Application("Kwps", False)
278
+ return self._kwps
279
+
280
+ @property
281
+ def Ket(self):
282
+ try:
283
+ name = self._ket.Name
284
+ return self._ket
285
+ except Exception:
286
+ self._ket = self.Application("Ket", False)
287
+ return self._ket
288
+
289
+ @property
290
+ def Kwpp(self):
291
+ try:
292
+ name = self._kwpp.Name
293
+ return self._kwpp
294
+ except Exception:
295
+ self._kwpp = self.Application("Kwpp", False)
296
+ return self._kwpp
297
+
298
+ @property
299
+ def Excel(self):
300
+ try:
301
+ name = self._excel.Name
302
+ return self._excel
303
+ except Exception:
304
+ self._excel = self.Application("Excel", False)
305
+ return self._excel
306
+
307
+ @property
308
+ def Word(self):
309
+ try:
310
+ name = self._word.Name
311
+ return self._word
312
+ except Exception:
313
+ self._word = self.Application("Word", False)
314
+ return self._word
315
+
316
+ @property
317
+ def PowerPoint(self):
318
+ try:
319
+ name = self._powerpoint.Name
320
+ return self._powerpoint
321
+ except Exception:
322
+ self._powerpoint = self.Application("PowerPoint", False)
323
+ return self._powerpoint
324
+
325
+ @property
326
+ def Visio(self):
327
+ try:
328
+ name = self._visio.Name
329
+ return self._visio
330
+ except Exception:
331
+ self._visio = self.Application("Visio", False)
332
+ return self._visio
333
+
334
+ @property
335
+ def Access(self):
336
+ try:
337
+ name = self._access.Name
338
+ return self._access
339
+ except Exception:
340
+ self._access = self.Application("Access", False)
341
+ return self._access
342
+
343
+ @property
344
+ def Project(self):
345
+ try:
346
+ name = self._project.Name
347
+ return self._project
348
+ except Exception:
349
+ self._project = self.Application("MSProject", False)
350
+ return self._project
351
+
352
+ @property
353
+ def Outlook(self):
354
+ try:
355
+ name = self._outlook.Name
356
+ return self._outlook
357
+ except Exception:
358
+ self._outlook = self.Application("Outlook", False)
359
+ return self._outlook
360
+
361
+ @property
362
+ def Publisher(self):
363
+ try:
364
+ name = self._publisher.Name
365
+ return self._publisher
366
+ except Exception:
367
+ self._publisher = self.Application("Publisher", False)
368
+ return self._publisher
369
+
370
+ @property
371
+ def OneNote(self):
372
+ try:
373
+ name = self._onenote.Name
374
+ return self._onenote
375
+ except Exception:
376
+ self._onenote = self.Application("OneNote", False)
377
+ return self._onenote
378
+
379
+
380
+ class OfficerData:
381
+ def init(self):
382
+ current_attrs = set(vars(self).keys())
383
+ for attr in current_attrs - self._initial_attrs:
384
+ delattr(self, attr)
385
+ self.data = {}
386
+ self.output = ""
387
+ self.helloworld = "Hello World"
388
+
389
+ def __init__(self):
390
+ self._initial_attrs = set(vars(self).keys())
391
+ self.init()
package/src/server.py ADDED
@@ -0,0 +1,267 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Office Assistant MCP Server
4
+ ===========================
5
+ Minimalist design: RunPython as the primary tool.
6
+ Smart Office Workspace on Desktop for data safety.
7
+
8
+ Disclaimer: Please place files in "Desktop/智能办公区/数据" before processing.
9
+ We are not responsible for data loss when operating on original file locations.
10
+ """
11
+
12
+ import sys
13
+ import os
14
+
15
+ from license_manager import verify_license
16
+ verify_license()
17
+
18
+ from fastmcp import FastMCP
19
+ from officer import TheOfficer
20
+ from utils import get_workspace, get_data_dir, get_export_dir, get_download_dir
21
+
22
+ mcp = FastMCP("Office Assistant")
23
+ Officer = TheOfficer()
24
+
25
+ APP_MAP = {
26
+ 'xlsx': 'Excel', 'xls': 'Excel', 'csv': 'Excel',
27
+ 'docx': 'Word', 'doc': 'Word',
28
+ 'pptx': 'PowerPoint', 'ppt': 'PowerPoint',
29
+ }
30
+
31
+
32
+ def _get_active_doc(app, app_name):
33
+ try:
34
+ if app_name.lower() in ['excel', 'ket']:
35
+ return app.ActiveWorkbook
36
+ elif app_name.lower() in ['word', 'kwps']:
37
+ return app.ActiveDocument
38
+ elif app_name.lower() in ['powerpoint', 'kwpp']:
39
+ return app.ActivePresentation
40
+ except:
41
+ return None
42
+
43
+
44
+ def _build_globals(app, doc, app_name):
45
+ base = {
46
+ 'app': app,
47
+ 'doc': doc,
48
+ 'Officer': Officer,
49
+ '__builtins__': __builtins__,
50
+ }
51
+ if app_name.lower() in ['excel', 'ket']:
52
+ base['sheet'] = doc.ActiveSheet if doc else None
53
+ elif app_name.lower() in ['word', 'kwps']:
54
+ base['selection'] = app.Selection
55
+ elif app_name.lower() in ['powerpoint', 'kwpp']:
56
+ base['slide'] = doc.Slides(doc.Slides.Count) if doc else None
57
+ return base
58
+
59
+
60
+ # ========== Application Control Tools ==========
61
+
62
+ @mcp.tool()
63
+ def available_apps() -> list:
64
+ """List installed Microsoft Office applications."""
65
+ return Officer.AvailableApps()
66
+
67
+
68
+ @mcp.tool()
69
+ def launch_app(app_name: str = "Excel", visible: bool = True) -> bool:
70
+ """Launch a Microsoft Office application."""
71
+ try:
72
+ app = Officer.Application(app_name)
73
+ app.Visible = visible
74
+ return True
75
+ except Exception as e:
76
+ print(f"Launch failed: {e}")
77
+ return False
78
+
79
+
80
+ @mcp.tool()
81
+ def quit_app(app_name: str = "Excel", force: bool = False) -> bool:
82
+ """Quit a Microsoft Office application."""
83
+ return Officer.Quit(app_name, force)
84
+
85
+
86
+ # ========== File Operation Tools ==========
87
+
88
+ @mcp.tool()
89
+ def open_file(file_path: str) -> dict:
90
+ """
91
+ Open an Office file.
92
+
93
+ Supported formats:
94
+ - Excel: .xlsx, .xls, .csv
95
+ - Word: .docx, .doc
96
+ - PowerPoint: .pptx, .ppt
97
+
98
+ Tip: Place files in "Desktop/智能办公区/数据" for safe processing.
99
+ """
100
+ ext = file_path.split('.')[-1].lower()
101
+ app_name = APP_MAP.get(ext, 'Excel')
102
+ app = Officer.Application(app_name)
103
+
104
+ if app_name == 'Excel':
105
+ doc = app.Workbooks.Open(file_path)
106
+ elif app_name == 'Word':
107
+ doc = app.Documents.Open(file_path)
108
+ elif app_name == 'PowerPoint':
109
+ doc = app.Presentations.Open(file_path)
110
+
111
+ app.Visible = True
112
+ return {"success": True, "app": app_name, "file": file_path}
113
+
114
+
115
+ @mcp.tool()
116
+ def save_file(file_path: str = None) -> dict:
117
+ """Save the current active document."""
118
+ for app_name in ['Excel', 'Word', 'PowerPoint']:
119
+ try:
120
+ app = Officer.Application(app_name)
121
+ if app_name == 'Excel':
122
+ doc = app.ActiveWorkbook
123
+ elif app_name == 'Word':
124
+ doc = app.ActiveDocument
125
+ else:
126
+ doc = app.ActivePresentation
127
+
128
+ if doc:
129
+ if file_path:
130
+ doc.SaveAs(file_path)
131
+ else:
132
+ doc.Save()
133
+ return {"success": True, "file": doc.FullName}
134
+ except:
135
+ continue
136
+ return {"success": False, "error": "No active document found"}
137
+
138
+
139
+ # ========== Core Tool: RunPython ==========
140
+
141
+ @mcp.tool()
142
+ def run_python(code: str, app_name: str = "Excel") -> dict:
143
+ """
144
+ Execute Python code to control Office applications (PRIMARY TOOL).
145
+
146
+ Available global variables:
147
+ - app: The Office application object
148
+ - doc: The current active document
149
+ - sheet: Active worksheet (Excel)
150
+ - selection: Current selection (Word)
151
+ - slide: Active slide (PowerPoint)
152
+ - Officer: TheOfficer instance for accessing other apps
153
+
154
+ Excel example:
155
+ sheet.Cells(1, 1).Value = "Hello"
156
+ doc.Save()
157
+
158
+ Word example:
159
+ doc.Content.Text = "Hello World"
160
+ doc.Save()
161
+
162
+ PowerPoint example:
163
+ slide.Shapes.AddTextbox(1, 100, 100, 400, 50).TextFrame.TextRange.Text = "Title"
164
+ """
165
+ app = Officer.Application(app_name)
166
+ doc = _get_active_doc(app, app_name)
167
+ globals_dict = _build_globals(app, doc, app_name)
168
+
169
+ try:
170
+ exec(code, globals_dict)
171
+ return {"success": True}
172
+ except Exception as e:
173
+ return {"success": False, "error": str(e)}
174
+
175
+
176
+ # ========== System Tools ==========
177
+
178
+ @mcp.tool()
179
+ def screenshot(save_name: str = None) -> str:
180
+ """Capture a screenshot and save to the export folder."""
181
+ path = Officer.ScreenShot(save_name)
182
+ return path
183
+
184
+
185
+ @mcp.tool()
186
+ def download(url: str, save_name: str = None) -> str:
187
+ """Download a file from URL to the download folder."""
188
+ return Officer.DownloadURL(url, save_name)
189
+
190
+
191
+ @mcp.tool()
192
+ def speak(text: str = "Hello", volume: int = 80, rate: int = 0) -> bool:
193
+ """Text-to-speech. Volume: 0-100, Rate: -10 to 10."""
194
+ return Officer.Speak(text, volume, rate)
195
+
196
+
197
+ @mcp.tool()
198
+ def demonstrate() -> dict:
199
+ """Run a demonstration of Office automation capabilities."""
200
+ try:
201
+ result = Officer.Demonstrate()
202
+ return {"success": True, "output": result}
203
+ except Exception as e:
204
+ return {"success": False, "error": str(e)}
205
+
206
+
207
+ @mcp.resource("resource://instructions")
208
+ def get_instructions() -> str:
209
+ """Get usage instructions and disclaimer."""
210
+ workspace = get_workspace()
211
+ return f"""
212
+ ╔══════════════════════════════════════════════════════════════╗
213
+ ║ Office Assistant - 使用指南 ║
214
+ ╚══════════════════════════════════════════════════════════════╝
215
+
216
+ 【免责声明】
217
+ 请将需要处理的文件复制到「桌面/智能办公区/数据」文件夹后再操作。
218
+ 直接在原文件位置操作导致的数据丢失,本软件概不负责。
219
+
220
+ 工作目录: {workspace}
221
+ - 数据/: 放置源文件
222
+ - 导出/: 截图、PDF等导出文件
223
+ - 下载/: 下载的文件
224
+
225
+ 【主力工具: run_python】
226
+ 执行 Python 代码控制 Office 应用。
227
+
228
+ 可用变量:
229
+ - app: Office 应用对象
230
+ - doc: 当前文档
231
+ - sheet: 当前工作表 (Excel)
232
+ - selection: 当前选区 (Word)
233
+ - slide: 当前幻灯片 (PowerPoint)
234
+ - Officer: 办公助手 (Officer.Excel, Officer.Word 等)
235
+
236
+ 【Excel 示例】
237
+ sheet.Cells(1, 1).Value = "Hello"
238
+ sheet.Cells(1, 1).Font.Bold = True
239
+ doc.Save()
240
+
241
+ 【Word 示例】
242
+ doc.Content.Text = "Hello World"
243
+ doc.Save()
244
+
245
+ 【PowerPoint 示例】
246
+ slide.Shapes.AddTextbox(1, 100, 100, 400, 50).TextFrame.TextRange.Text = "Title"
247
+ """
248
+
249
+
250
+ # ========== Entry Point ==========
251
+
252
+ def run():
253
+ args = sys.argv[1:]
254
+ transport = args[0].lower() if args else "stdio"
255
+
256
+ if transport == "stdio":
257
+ print("Office Assistant started (stdio mode)")
258
+ mcp.run("stdio")
259
+ else:
260
+ host = "127.0.0.1"
261
+ port = 8888
262
+ print(f"Office Assistant started (SSE mode: http://{host}:{port}/sse)")
263
+ mcp.run(transport="sse", host=host, port=port)
264
+
265
+
266
+ if __name__ == "__main__":
267
+ run()
package/src/utils.py ADDED
@@ -0,0 +1,24 @@
1
+ from pathlib import Path
2
+
3
+
4
+ def get_workspace():
5
+ """Get or create the smart office workspace on Desktop"""
6
+ desktop = Path.home() / "Desktop"
7
+ workspace = desktop / "智能办公区"
8
+ workspace.mkdir(exist_ok=True)
9
+ (workspace / "数据").mkdir(exist_ok=True)
10
+ (workspace / "导出").mkdir(exist_ok=True)
11
+ (workspace / "下载").mkdir(exist_ok=True)
12
+ return workspace
13
+
14
+
15
+ def get_data_dir():
16
+ return get_workspace() / "数据"
17
+
18
+
19
+ def get_export_dir():
20
+ return get_workspace() / "导出"
21
+
22
+
23
+ def get_download_dir():
24
+ return get_workspace() / "下载"