ucu-mcp 0.4.3 → 0.5.1
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 +63 -0
- package/README.md +28 -3
- package/dist/src/mcp/server.js +1 -0
- package/dist/src/mcp/tools/element-tools.js +23 -2
- package/dist/src/mcp/tools/index.d.ts +1 -1
- package/dist/src/mcp/tools/index.js +1 -1
- package/dist/src/mcp/tools/keyboard-tools.js +2 -2
- package/dist/src/mcp/tools/screen-tools.js +139 -3
- package/dist/src/platform/base.d.ts +43 -1
- package/dist/src/platform/linux.d.ts +2 -2
- package/dist/src/platform/macos/base.d.ts +3 -1
- package/dist/src/platform/macos/base.js +7 -1
- package/dist/src/platform/macos/element.d.ts +22 -1
- package/dist/src/platform/macos/element.js +377 -13
- package/dist/src/platform/macos/focus.d.ts +10 -0
- package/dist/src/platform/macos/focus.js +10 -0
- package/dist/src/platform/macos/screen.js +21 -18
- package/dist/src/platform/macos/window.js +22 -0
- package/dist/src/platform/windows.d.ts +2 -2
- package/dist/src/safety/guard.js +17 -2
- package/dist/src/utils/input.js +24 -16
- package/package.json +2 -1
- package/skills/ucu-mcp/SKILL.md +100 -0
- package/skills/ucu-mcp/agents/openai.yaml +4 -0
- package/skills/ucu-mcp/references/tool-reference.md +255 -0
- package/skills/ucu-mcp/references/troubleshooting.md +142 -0
- package/skills/ucu-mcp/references/workflows.md +146 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Workflows
|
|
2
|
+
|
|
3
|
+
Common task playbooks. Each shows the preferred tool sequence and the fallback
|
|
4
|
+
path when the primary path is blocked.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 1. Fill a form field
|
|
9
|
+
|
|
10
|
+
**Primary (AX):**
|
|
11
|
+
```
|
|
12
|
+
focus_app("Safari")
|
|
13
|
+
find_element({ text: "Email", role: "AXTextField" })
|
|
14
|
+
→ elementId "Safari/w0/42"
|
|
15
|
+
type_in_element({ elementId: "Safari/w0/42", text: "user@example.com" })
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**Fallback (AX value set, for non-text controls):**
|
|
19
|
+
```
|
|
20
|
+
set_value({ elementId: "...", value: "option" })
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Fallback (coordinates, when AX is opaque):**
|
|
24
|
+
```
|
|
25
|
+
screenshot({})
|
|
26
|
+
ocr({}) → blocks[].text === "Email" → {x, y, width, height}
|
|
27
|
+
click({ x: block.x + block.width/2, y: block.y + block.height/2 })
|
|
28
|
+
type_text({ text: "user@example.com" })
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 2. Operate a menu-bar / tray app (e.g. cc-switch)
|
|
34
|
+
|
|
35
|
+
Tray apps' status items live in `SystemUIServer`, not the app's own window AX
|
|
36
|
+
tree. `focus_app` alone may return `WINDOW_NOT_FOUND`.
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
focus_app("cc-switch") # establishes tray target if status item found
|
|
40
|
+
click_menu_bar_extra({ app: "cc-switch", name: "switch" }) # opens the menu
|
|
41
|
+
# menu is now open — find items inside it:
|
|
42
|
+
find_element({ text: "使用统计", app: "cc-switch" })
|
|
43
|
+
→ elementId
|
|
44
|
+
click_element({ elementId })
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
If the menu's AX tree is opaque (some Tauri/Electron menus):
|
|
48
|
+
```
|
|
49
|
+
click_menu_bar_extra({ app: "cc-switch" })
|
|
50
|
+
screenshot({})
|
|
51
|
+
ocr({}) → locate "使用统计" by text → coordinates
|
|
52
|
+
click({ x, y })
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## 3. Electron / WebView opaque UI
|
|
58
|
+
|
|
59
|
+
Electron/Tauri apps often expose only a near-empty `AXGroup`. The runtime `hint`
|
|
60
|
+
on `find_element` and `list_windows` tells you when this is happening.
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
find_element({ text: "Submit" })
|
|
64
|
+
→ 0 results, hint: "app is likely Electron... screenshot → ocr → click(x,y)"
|
|
65
|
+
|
|
66
|
+
screenshot({})
|
|
67
|
+
ocr({ region: { x, y, width, height } }) # or full screen
|
|
68
|
+
→ blocks[].text === "Submit" → {x, y, width, height}
|
|
69
|
+
click({ x: block.x + block.width/2, y: block.y + block.height/2 })
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
For repeated interaction with a known-opaque app, snapshot once with
|
|
73
|
+
`describe_screen` to plan, then drive by coordinates.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## 4. Vision-degraded environment (image content not visible)
|
|
78
|
+
|
|
79
|
+
When the model cannot see `screenshot` image blocks (relay/downgrade to URLs),
|
|
80
|
+
switch to text-based screen reading:
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
describe_screen({ ocr: true, includeAx: true })
|
|
84
|
+
→ { screen, foregroundWindow, ocr:{blocks}, ax:{elements}, errors }
|
|
85
|
+
|
|
86
|
+
# or, if you also want the image for clients that DO support it:
|
|
87
|
+
screenshot({ describe: true })
|
|
88
|
+
→ [image block, text description block]
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`describe_screen` never throws — OCR and AX each try/catch independently, so a
|
|
92
|
+
Vision failure still returns AX state and vice versa. Check `errors[]` to know
|
|
93
|
+
what was skipped/failed.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## 5. Recover from TARGET_STALE
|
|
98
|
+
|
|
99
|
+
The active window target can go stale (window closed, app restarted, pid
|
|
100
|
+
changed). AX tools throw `TARGET_STALE`.
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
# error response includes hint: "Run focus_app again for the target app..."
|
|
104
|
+
focus_app("Safari") # re-establishes target
|
|
105
|
+
find_element({ text: "Save" }) # retry — cache refetches equivalent nodes
|
|
106
|
+
click_element({ elementId })
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`type_in_element` automatically refetches an equivalent AX node if the original
|
|
110
|
+
`elementId` is stale, so a single retry often succeeds without `focus_app`.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## 6. Verify an action succeeded
|
|
115
|
+
|
|
116
|
+
Always verify after clicks/types — UI may not have updated, or the wrong element
|
|
117
|
+
was hit.
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
click_element({ elementId, captureAfter: true }) # screenshot in response
|
|
121
|
+
# or explicitly:
|
|
122
|
+
screenshot({})
|
|
123
|
+
# or check AX state:
|
|
124
|
+
get_window_state({}) → focusedElement / tree reflects the change
|
|
125
|
+
# or wait for a specific change:
|
|
126
|
+
wait_for_element({ text: "Saved", until: "appear", timeout: 3000 })
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## 7. Multi-step task with error recovery
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
doctor() # verify permissions first
|
|
135
|
+
list_apps()
|
|
136
|
+
focus_app("Notes")
|
|
137
|
+
find_element({ text: "New Note" }) → id
|
|
138
|
+
click_element({ elementId: id, captureAfter: true })
|
|
139
|
+
|
|
140
|
+
# if click_element throws ELEMENT_NOT_FOUND:
|
|
141
|
+
find_element({ text: "New Note" }) → id2 # refetch, id may have changed
|
|
142
|
+
click_element({ elementId: id2 })
|
|
143
|
+
|
|
144
|
+
type_in_element({ elementId: bodyId, text: "Hello" })
|
|
145
|
+
screenshot({}) # confirm content
|
|
146
|
+
```
|