uiautomator2-mcp-server 0.1.2__py3-none-any.whl → 0.2.0__py3-none-any.whl
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.
- u2mcp/.gitignore +1 -1
- u2mcp/__init__.py +1 -2
- u2mcp/__main__.py +193 -82
- u2mcp/background.py +19 -0
- u2mcp/health.py +67 -0
- u2mcp/helpers.py +222 -0
- u2mcp/mcp.py +172 -61
- u2mcp/middlewares.py +40 -0
- u2mcp/tools/__init__.py +8 -3
- u2mcp/tools/action.py +143 -169
- u2mcp/tools/app.py +232 -231
- u2mcp/tools/clipboard.py +35 -0
- u2mcp/tools/device.py +307 -293
- u2mcp/tools/element.py +267 -0
- u2mcp/tools/input.py +47 -0
- u2mcp/tools/misc.py +17 -0
- u2mcp/tools/scrcpy.py +142 -0
- u2mcp/{_version.py → version.py} +34 -34
- uiautomator2_mcp_server-0.2.0.dist-info/METADATA +738 -0
- uiautomator2_mcp_server-0.2.0.dist-info/RECORD +25 -0
- {uiautomator2_mcp_server-0.1.2.dist-info → uiautomator2_mcp_server-0.2.0.dist-info}/WHEEL +1 -1
- {uiautomator2_mcp_server-0.1.2.dist-info → uiautomator2_mcp_server-0.2.0.dist-info}/entry_points.txt +1 -0
- uiautomator2_mcp_server-0.2.0.dist-info/licenses/LICENSE +190 -0
- uiautomator2_mcp_server-0.1.2.dist-info/METADATA +0 -113
- uiautomator2_mcp_server-0.1.2.dist-info/RECORD +0 -16
- uiautomator2_mcp_server-0.1.2.dist-info/licenses/LICENSE +0 -620
- {uiautomator2_mcp_server-0.1.2.dist-info → uiautomator2_mcp_server-0.2.0.dist-info}/top_level.txt +0 -0
u2mcp/tools/app.py
CHANGED
|
@@ -1,231 +1,232 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
from
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from anyio import to_thread
|
|
6
|
+
|
|
7
|
+
from ..mcp import mcp
|
|
8
|
+
from .device import get_device
|
|
9
|
+
|
|
10
|
+
__all__ = (
|
|
11
|
+
"app_install",
|
|
12
|
+
"app_uninstall",
|
|
13
|
+
"app_uninstall_all",
|
|
14
|
+
"app_start",
|
|
15
|
+
"app_stop",
|
|
16
|
+
"app_stop_all",
|
|
17
|
+
"app_clear",
|
|
18
|
+
"app_info",
|
|
19
|
+
"app_current",
|
|
20
|
+
"app_list",
|
|
21
|
+
"app_list_running",
|
|
22
|
+
"app_auto_grant_permissions",
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@mcp.tool("app_install", tags={"app:manage"})
|
|
27
|
+
async def app_install(serial: str, data: str):
|
|
28
|
+
"""Install app
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
serial (str): Android device serialno
|
|
32
|
+
data (str): APK file path or url
|
|
33
|
+
"""
|
|
34
|
+
async with get_device(serial) as device:
|
|
35
|
+
await to_thread.run_sync(device.app_install, data)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@mcp.tool("app_uninstall", tags={"app:manage"})
|
|
39
|
+
async def app_uninstall(serial: str, package_name: str) -> bool:
|
|
40
|
+
"""Uninstall an app
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
serial (str): Android device serialno
|
|
44
|
+
package_name (str): package name
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
bool: success
|
|
48
|
+
"""
|
|
49
|
+
async with get_device(serial) as device:
|
|
50
|
+
return await to_thread.run_sync(device.app_uninstall, package_name)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@mcp.tool("app_uninstall_all", tags={"app:manage"})
|
|
54
|
+
async def app_uninstall_all(serial: str, excludes: list[str] | None = None) -> list[str]:
|
|
55
|
+
"""Uninstall all apps
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
serial (str): Android device serialno
|
|
59
|
+
excludes (list[str] | None): packages that do not want to uninstall
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
list[str]: list of uninstalled apps
|
|
63
|
+
"""
|
|
64
|
+
async with get_device(serial) as device:
|
|
65
|
+
return await to_thread.run_sync(device.app_uninstall_all, excludes or [])
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@mcp.tool("app_start", tags={"app:lifecycle"})
|
|
69
|
+
async def app_start(
|
|
70
|
+
serial: str,
|
|
71
|
+
package_name: str,
|
|
72
|
+
activity: str | None = None,
|
|
73
|
+
wait: bool = False,
|
|
74
|
+
stop: bool = False,
|
|
75
|
+
):
|
|
76
|
+
"""Launch application
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
serial (str): Android device serialno
|
|
80
|
+
package_name (str): package name
|
|
81
|
+
activity (str): app activity
|
|
82
|
+
stop (bool): Stop app before starting the activity. (require activity)
|
|
83
|
+
wait (bool): wait until app started. default False
|
|
84
|
+
"""
|
|
85
|
+
async with get_device(serial) as device:
|
|
86
|
+
await to_thread.run_sync(device.app_start, package_name, activity, wait, stop)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@mcp.tool("app_wait", tags={"app:lifecycle"})
|
|
90
|
+
async def app_wait(serial: str, package_name: str, timeout: float = 20.0, front=False):
|
|
91
|
+
"""Wait until app launched
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
serial (str): Android device serialno
|
|
95
|
+
package_name (str): package name
|
|
96
|
+
timeout (float): maximum wait time seconds
|
|
97
|
+
front (bool): wait until app is current app
|
|
98
|
+
"""
|
|
99
|
+
async with get_device(serial) as device:
|
|
100
|
+
if not await to_thread.run_sync(device.app_wait, package_name, timeout, front):
|
|
101
|
+
raise RuntimeError(f"Failed to wait App {package_name} to launch")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@mcp.tool("app_stop", tags={"app:lifecycle"})
|
|
105
|
+
async def app_stop(serial: str, package_name: str):
|
|
106
|
+
"""Stop one application
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
serial (str): Android device serialno
|
|
110
|
+
package_name (str): package name
|
|
111
|
+
"""
|
|
112
|
+
async with get_device(serial) as device:
|
|
113
|
+
await to_thread.run_sync(device.app_stop, package_name)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@mcp.tool("app_stop_all", tags={"app:lifecycle"})
|
|
117
|
+
async def app_stop_all(serial: str, excludes: list[str] | None = None) -> list[str]:
|
|
118
|
+
"""Stop all third party applications
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
excludes (list): apps that do now want to kill
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
list[str]: a list of killed apps
|
|
125
|
+
"""
|
|
126
|
+
async with get_device(serial) as device:
|
|
127
|
+
return await to_thread.run_sync(device.app_stop_all, excludes or [])
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@mcp.tool("app_clear", tags={"app:config"})
|
|
131
|
+
async def app_clear(serial: str, package_name: str):
|
|
132
|
+
"""Stop and clear app data: pm clear
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
serial (str): Android device serialno
|
|
136
|
+
package_name (str): package name
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
bool: success
|
|
140
|
+
"""
|
|
141
|
+
async with get_device(serial) as device:
|
|
142
|
+
await to_thread.run_sync(device.app_clear, package_name)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@mcp.tool("app_info", tags={"app:info"})
|
|
146
|
+
async def app_info(serial: str, package_name: str) -> dict[str, Any]:
|
|
147
|
+
"""
|
|
148
|
+
Get app info
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
serial (str): Android device serialno
|
|
152
|
+
package_name (str): package name
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
dict[str,Any]: app info
|
|
156
|
+
|
|
157
|
+
Example:
|
|
158
|
+
{"versionName": "1.1.7", "versionCode": 1001007}
|
|
159
|
+
"""
|
|
160
|
+
async with get_device(serial) as device:
|
|
161
|
+
return await to_thread.run_sync(device.app_info, package_name)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@mcp.tool("app_current", tags={"app:info"})
|
|
165
|
+
async def app_current(serial: str) -> dict[str, Any]:
|
|
166
|
+
"""
|
|
167
|
+
Get current app info
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
serial (str): Android device serialno
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
dict[str,Any]: running app info
|
|
174
|
+
"""
|
|
175
|
+
async with get_device(serial) as device:
|
|
176
|
+
return await to_thread.run_sync(device.app_current)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@mcp.tool("app_list", tags={"app:info"})
|
|
180
|
+
async def app_list(serial: str, filter: str = "") -> list[str]:
|
|
181
|
+
"""
|
|
182
|
+
List installed app package names
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
serial (str): Android device serialno
|
|
186
|
+
filter (str): [-f] [-d] [-e] [-s] [-3] [-i] [-u] [--user USER_ID] [FILTER]
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
list[str]: list of apps by filter
|
|
190
|
+
"""
|
|
191
|
+
async with get_device(serial) as device:
|
|
192
|
+
return await to_thread.run_sync(device.app_list, filter.strip())
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@mcp.tool("app_list_running", tags={"app:info"})
|
|
196
|
+
async def app_list_running(serial: str) -> list[str]:
|
|
197
|
+
"""
|
|
198
|
+
List running apps
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
serial (str): Android device serialno
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
list[str]: list of running apps
|
|
205
|
+
"""
|
|
206
|
+
async with get_device(serial) as device:
|
|
207
|
+
return await to_thread.run_sync(device.app_list_running)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@mcp.tool("app_auto_grant_permissions", tags={"app:config"})
|
|
211
|
+
async def app_auto_grant_permissions(serial: str, package_name: str):
|
|
212
|
+
"""auto grant permissions
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
serial (str): Android device serialno
|
|
216
|
+
package_name (str): package name
|
|
217
|
+
|
|
218
|
+
Help of "adb shell pm":
|
|
219
|
+
grant [--user USER_ID] PACKAGE PERMISSION
|
|
220
|
+
revoke [--user USER_ID] PACKAGE PERMISSION
|
|
221
|
+
These commands either grant or revoke permissions to apps. The permissions
|
|
222
|
+
must be declared as used in the app's manifest, be runtime permissions
|
|
223
|
+
(protection level dangerous), and the app targeting SDK greater than Lollipop MR1 (API level 22).
|
|
224
|
+
|
|
225
|
+
Help of "Android official pm" see <https://developer.android.com/tools/adb#pm>
|
|
226
|
+
Grant a permission to an app. On devices running Android 6.0 (API level 23) and higher,
|
|
227
|
+
the permission can be any permission declared in the app manifest.
|
|
228
|
+
On devices running Android 5.1 (API level 22) and lower,
|
|
229
|
+
must be an optional permission defined by the app.
|
|
230
|
+
"""
|
|
231
|
+
async with get_device(serial) as device:
|
|
232
|
+
await to_thread.run_sync(device.app_auto_grant_permissions, package_name)
|
u2mcp/tools/clipboard.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from anyio import to_thread
|
|
4
|
+
|
|
5
|
+
from ..mcp import mcp
|
|
6
|
+
from .device import get_device
|
|
7
|
+
|
|
8
|
+
__all__ = ("read_clipboard", "write_clipboard")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@mcp.tool("read_clipboard", tags={"clipboard:read"})
|
|
12
|
+
async def read_clipboard(serial: str) -> str | None:
|
|
13
|
+
"""Read clipboard from device
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
serial (str): Android device serialno
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
str: The actual text in the clip.
|
|
20
|
+
None: If there is no text in the clip.
|
|
21
|
+
"""
|
|
22
|
+
async with get_device(serial) as device:
|
|
23
|
+
return await to_thread.run_sync(lambda: device.clipboard)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@mcp.tool("write_clipboard", tags={"clipboard:write"})
|
|
27
|
+
async def write_clipboard(serial: str, text: str):
|
|
28
|
+
"""Write clipboard to device
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
serial (str): Android device serialno
|
|
32
|
+
text: The actual text in the clip.
|
|
33
|
+
"""
|
|
34
|
+
async with get_device(serial) as device:
|
|
35
|
+
await to_thread.run_sync(device.set_clipboard, text)
|