clousd-mcp 0.1.0__tar.gz
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.
- clousd_mcp-0.1.0/PKG-INFO +57 -0
- clousd_mcp-0.1.0/README.md +45 -0
- clousd_mcp-0.1.0/clousd_mcp/__init__.py +2 -0
- clousd_mcp-0.1.0/clousd_mcp/__main__.py +3 -0
- clousd_mcp-0.1.0/clousd_mcp/server.py +224 -0
- clousd_mcp-0.1.0/clousd_mcp.egg-info/PKG-INFO +57 -0
- clousd_mcp-0.1.0/clousd_mcp.egg-info/SOURCES.txt +11 -0
- clousd_mcp-0.1.0/clousd_mcp.egg-info/dependency_links.txt +1 -0
- clousd_mcp-0.1.0/clousd_mcp.egg-info/entry_points.txt +2 -0
- clousd_mcp-0.1.0/clousd_mcp.egg-info/requires.txt +2 -0
- clousd_mcp-0.1.0/clousd_mcp.egg-info/top_level.txt +1 -0
- clousd_mcp-0.1.0/pyproject.toml +22 -0
- clousd_mcp-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: clousd-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server: give an AI agent a real Android phone in the cloud
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://clousd.com/agents/
|
|
7
|
+
Keywords: mcp,android,cloud phone,ai agents,mobile automation
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: mcp>=1.2.0
|
|
11
|
+
Requires-Dist: clousd>=0.1.0
|
|
12
|
+
|
|
13
|
+
# clousd-mcp
|
|
14
|
+
|
|
15
|
+
An MCP server that gives any MCP-capable agent a real Android phone in the cloud: a device modelled on a
|
|
16
|
+
real phone, with Google Play, a network in the country you choose, and saved states to reset to between runs.
|
|
17
|
+
|
|
18
|
+
Early access: ask for a key at [clousd.com/agents](https://clousd.com/agents/#access).
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install "git+https://github.com/Clousd-Android/clousd-sdk#subdirectory=python"
|
|
24
|
+
pip install "git+https://github.com/Clousd-Android/clousd-sdk#subdirectory=mcp"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Any MCP client (Cursor, Windsurf, Zed, a desktop assistant or your own agent) - stdio server, one environment variable:
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"mcpServers": {
|
|
32
|
+
"clousd": {
|
|
33
|
+
"command": "clousd-mcp",
|
|
34
|
+
"env": { "CLOUSD_API_KEY": "cl_live_..." }
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Clients that take a command line instead of JSON: `clousd-mcp` with `CLOUSD_API_KEY` in the environment.
|
|
41
|
+
|
|
42
|
+
## Tools
|
|
43
|
+
|
|
44
|
+
Four tools, on purpose - agents choose better from a few large tools than from a dozen small ones:
|
|
45
|
+
|
|
46
|
+
| Tool | What it does |
|
|
47
|
+
|---|---|
|
|
48
|
+
| `devices` | list the phones this key can use; start or stop one |
|
|
49
|
+
| `observe` | the screen as an image plus every text on it |
|
|
50
|
+
| `act` | `tap`, `swipe`, `scroll`, `tap_text`, `wait_text`, `type`, `key`, `open_app`, `close_app`, `open_url`, `installed` |
|
|
51
|
+
| `snapshots` | list, save, restore (reset between runs) or clone a phone |
|
|
52
|
+
|
|
53
|
+
Screenshots come back 540 px wide (`CLOUSD_SHOT_WIDTH` to change); `tap` and `swipe` take coordinates in that image
|
|
54
|
+
and the server scales them to the phone. `tap_text` is usually the more reliable way to press a button. Typing works in
|
|
55
|
+
any language.
|
|
56
|
+
|
|
57
|
+
Built on the [`clousd`](../python) Python client. MIT license.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# clousd-mcp
|
|
2
|
+
|
|
3
|
+
An MCP server that gives any MCP-capable agent a real Android phone in the cloud: a device modelled on a
|
|
4
|
+
real phone, with Google Play, a network in the country you choose, and saved states to reset to between runs.
|
|
5
|
+
|
|
6
|
+
Early access: ask for a key at [clousd.com/agents](https://clousd.com/agents/#access).
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pip install "git+https://github.com/Clousd-Android/clousd-sdk#subdirectory=python"
|
|
12
|
+
pip install "git+https://github.com/Clousd-Android/clousd-sdk#subdirectory=mcp"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Any MCP client (Cursor, Windsurf, Zed, a desktop assistant or your own agent) - stdio server, one environment variable:
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"mcpServers": {
|
|
20
|
+
"clousd": {
|
|
21
|
+
"command": "clousd-mcp",
|
|
22
|
+
"env": { "CLOUSD_API_KEY": "cl_live_..." }
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Clients that take a command line instead of JSON: `clousd-mcp` with `CLOUSD_API_KEY` in the environment.
|
|
29
|
+
|
|
30
|
+
## Tools
|
|
31
|
+
|
|
32
|
+
Four tools, on purpose - agents choose better from a few large tools than from a dozen small ones:
|
|
33
|
+
|
|
34
|
+
| Tool | What it does |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `devices` | list the phones this key can use; start or stop one |
|
|
37
|
+
| `observe` | the screen as an image plus every text on it |
|
|
38
|
+
| `act` | `tap`, `swipe`, `scroll`, `tap_text`, `wait_text`, `type`, `key`, `open_app`, `close_app`, `open_url`, `installed` |
|
|
39
|
+
| `snapshots` | list, save, restore (reset between runs) or clone a phone |
|
|
40
|
+
|
|
41
|
+
Screenshots come back 540 px wide (`CLOUSD_SHOT_WIDTH` to change); `tap` and `swipe` take coordinates in that image
|
|
42
|
+
and the server scales them to the phone. `tap_text` is usually the more reliable way to press a button. Typing works in
|
|
43
|
+
any language.
|
|
44
|
+
|
|
45
|
+
Built on the [`clousd`](../python) Python client. MIT license.
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"""MCP server for CLOUSD cloud phones: gives any MCP-capable agent a real Android phone to look at and act on.
|
|
2
|
+
|
|
3
|
+
Four tools, on purpose (agents choose better from a few large tools than from a dozen small ones):
|
|
4
|
+
devices - list the phones this key can use, start or stop one
|
|
5
|
+
observe - the screen as an image plus every text on it
|
|
6
|
+
act - tap, swipe, scroll, tap_text, wait_text, type, key, open_app, close_app, open_url, installed
|
|
7
|
+
snapshots - list, save, restore (reset between runs) or clone a phone
|
|
8
|
+
|
|
9
|
+
Screenshots come back `SHOT_WIDTH` px wide (540: ~1200 px long side on a 1080x2400 phone). tap/swipe take coordinates
|
|
10
|
+
in that screenshot and are scaled to the device here, so the model never converts pixels.
|
|
11
|
+
|
|
12
|
+
CLOUSD_API_KEY=cl_live_... clousd-mcp # stdio server
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
from typing import Dict, Optional
|
|
18
|
+
|
|
19
|
+
from clousd import Clousd, ClousdError
|
|
20
|
+
|
|
21
|
+
try: # mcp 2.x: FastMCP became MCPServer
|
|
22
|
+
from mcp.server.mcpserver import Image, MCPServer as _Server
|
|
23
|
+
except ImportError: # mcp 1.x
|
|
24
|
+
from mcp.server.fastmcp import FastMCP as _Server, Image
|
|
25
|
+
|
|
26
|
+
SHOT_WIDTH = int(os.environ.get("CLOUSD_SHOT_WIDTH", "540"))
|
|
27
|
+
|
|
28
|
+
mcp = _Server("clousd", instructions="Real Android phones in the cloud. Call observe before acting; prefer act "
|
|
29
|
+
"tap_text over coordinates; use snapshots restore to start each run from the same state.")
|
|
30
|
+
_client: Optional[Clousd] = None
|
|
31
|
+
_scale: Dict[str, float] = {} # device name → device pixels per screenshot pixel
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def client() -> Clousd:
|
|
35
|
+
global _client
|
|
36
|
+
if _client is None:
|
|
37
|
+
_client = Clousd()
|
|
38
|
+
return _client
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _jpeg_w(b: bytes) -> int:
|
|
42
|
+
"""Width of a JPEG from its SOF marker (the gateway shrinks by an integer factor, so it can differ from the
|
|
43
|
+
requested width: a 720 px screen stays 720 at w=540)."""
|
|
44
|
+
i = 2
|
|
45
|
+
while i + 9 < len(b):
|
|
46
|
+
if b[i] != 0xFF:
|
|
47
|
+
i += 1
|
|
48
|
+
continue
|
|
49
|
+
if 0xC0 <= b[i + 1] <= 0xC3:
|
|
50
|
+
return (b[i + 7] << 8) | b[i + 8]
|
|
51
|
+
i += 2 + ((b[i + 2] << 8) | b[i + 3])
|
|
52
|
+
return SHOT_WIDTH
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _learn_scale(device: str) -> float:
|
|
56
|
+
"""Phone pixels per image pixel, from the real image width - never from the requested one."""
|
|
57
|
+
d = client().device(device)
|
|
58
|
+
try:
|
|
59
|
+
o = d.observe(width=SHOT_WIDTH, ui=False)
|
|
60
|
+
_scale[device] = o["screen"]["w"] / float(o["image"]["w"])
|
|
61
|
+
except ClousdError as e:
|
|
62
|
+
if e.status != 404:
|
|
63
|
+
raise
|
|
64
|
+
w, _ = d.size()
|
|
65
|
+
_scale[device] = w / float(_jpeg_w(d.screenshot(width=SHOT_WIDTH)))
|
|
66
|
+
return _scale[device]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _px(device: str, v: Optional[float]) -> int:
|
|
70
|
+
if v is None:
|
|
71
|
+
raise ValueError("coordinates are required for this action")
|
|
72
|
+
scale = _scale.get(device) or _learn_scale(device)
|
|
73
|
+
return int(round(v * scale))
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@mcp.tool()
|
|
77
|
+
def devices(op: str = "list", device: str = "") -> object:
|
|
78
|
+
"""Phones this API key can use.
|
|
79
|
+
op=list (default): name, state (running / stopped / starting / error), model, Android, network of each phone.
|
|
80
|
+
op=start / op=stop with `device`: start a stopped phone (waits until it has booted, one to three minutes) or stop
|
|
81
|
+
one (a stopped phone keeps its state and costs nothing)."""
|
|
82
|
+
try:
|
|
83
|
+
if op == "list":
|
|
84
|
+
return [d.data for d in client().devices()]
|
|
85
|
+
if op in ("start", "stop") and device:
|
|
86
|
+
d = client().device(device)
|
|
87
|
+
return (d.start() if op == "start" else d.stop()).output or "ok"
|
|
88
|
+
return "error: op is list, start or stop (start/stop need device)"
|
|
89
|
+
except ClousdError as e:
|
|
90
|
+
return f"error: {e}"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _elements(d: dict, scale: float, limit: int = 80) -> str:
|
|
94
|
+
"""Interactive and labelled elements, centres in screenshot pixels: what the model can tap."""
|
|
95
|
+
rows = []
|
|
96
|
+
for n in d.get("ui", []):
|
|
97
|
+
label = n.get("text") or n.get("desc") or n.get("id", "").split("/")[-1]
|
|
98
|
+
if not label and not n.get("click"):
|
|
99
|
+
continue
|
|
100
|
+
b = n.get("b", [0, 0, 0, 0])
|
|
101
|
+
cx, cy = int((b[0] + b[2]) / 2 / scale), int((b[1] + b[3]) / 2 / scale)
|
|
102
|
+
flags = "".join(f for f, k in ((" tap", "click"), (" scroll", "scroll"), (" focused", "focused"), (" off", "disabled")) if n.get(k))
|
|
103
|
+
rows.append(f"- {label[:60]!r} {n.get('class', '')} at ({cx}, {cy}){flags}")
|
|
104
|
+
if len(rows) >= limit:
|
|
105
|
+
break
|
|
106
|
+
return "\n".join(rows)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@mcp.tool()
|
|
110
|
+
def observe(device: str, with_text: bool = True) -> list:
|
|
111
|
+
"""Look at the phone: a fresh screenshot plus (with_text) the app on screen and its elements with centre
|
|
112
|
+
coordinates. Give tap/swipe coordinates in this image's pixels."""
|
|
113
|
+
d = client().device(device)
|
|
114
|
+
try:
|
|
115
|
+
o = d.observe(width=SHOT_WIDTH, ui=with_text)
|
|
116
|
+
except ClousdError as e:
|
|
117
|
+
if e.status != 404:
|
|
118
|
+
return [f"error: {e}"]
|
|
119
|
+
o = None # an older gateway without /observe: screenshot + texts the old way
|
|
120
|
+
if o is None:
|
|
121
|
+
try:
|
|
122
|
+
jpeg = d.screenshot(width=SHOT_WIDTH)
|
|
123
|
+
w, h = d.size()
|
|
124
|
+
except ClousdError as e:
|
|
125
|
+
return [f"error: {e}"]
|
|
126
|
+
iw = _jpeg_w(jpeg)
|
|
127
|
+
_scale[device] = w / float(iw)
|
|
128
|
+
note = f"Screen image {iw}x{int(h / _scale[device])} px (phone {w}x{h})."
|
|
129
|
+
if with_text:
|
|
130
|
+
try:
|
|
131
|
+
note += "\nTexts on screen: " + "; ".join(d.screen_text())
|
|
132
|
+
except ClousdError as e:
|
|
133
|
+
note += f"\nTexts on screen: unavailable ({e.message or e.error})"
|
|
134
|
+
return [Image(data=jpeg, format="jpeg"), note]
|
|
135
|
+
img, scr = o.get("image", {}), o.get("screen", {})
|
|
136
|
+
scale = scr.get("w", 1080) / float(img.get("w") or SHOT_WIDTH)
|
|
137
|
+
_scale[device] = scale
|
|
138
|
+
note = f"Screen image {img.get('w')}x{img.get('h')} px (phone {scr.get('w')}x{scr.get('h')}), observation #{o.get('seq')}."
|
|
139
|
+
if o.get("package"):
|
|
140
|
+
note += f"\nOn screen: {o.get('activity') or o.get('package')}"
|
|
141
|
+
if with_text:
|
|
142
|
+
if o.get("ui_error"):
|
|
143
|
+
note += f"\nElements: unavailable ({o['ui_error']})"
|
|
144
|
+
else:
|
|
145
|
+
note += "\nElements (centre in image pixels):\n" + _elements(o, scale)
|
|
146
|
+
return [Image(data=o["image_bytes"], format="jpeg"), note]
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@mcp.tool()
|
|
150
|
+
def act(device: str, action: str, x: Optional[float] = None, y: Optional[float] = None,
|
|
151
|
+
x2: Optional[float] = None, y2: Optional[float] = None, text: str = "", ms: int = 300,
|
|
152
|
+
timeout: int = 30) -> str:
|
|
153
|
+
"""Do one thing on the phone, then wait until the screen settles. action is one of:
|
|
154
|
+
tap (x, y) · swipe (x, y, x2, y2, ms) · scroll (text=down|up) · tap_text (text: tap the element whose text
|
|
155
|
+
contains it - usually more reliable than coordinates) · wait_text (text, timeout up to 120 s) · type (text into the
|
|
156
|
+
focused field, any language) · key (text=home|back|recents|enter|tab|del|menu|power|volume_up|volume_down) ·
|
|
157
|
+
open_app (text=package, e.g. com.android.chrome) · close_app (text=package) · open_url (text=https://…) ·
|
|
158
|
+
installed (list apps you can open). Coordinates are in the pixels of the last observe image."""
|
|
159
|
+
d = client().device(device)
|
|
160
|
+
try:
|
|
161
|
+
if action == "tap":
|
|
162
|
+
body = {"op": "tap", "x": _px(device, x), "y": _px(device, y)}
|
|
163
|
+
elif action == "swipe":
|
|
164
|
+
body = {"op": "swipe", "x": _px(device, x), "y": _px(device, y), "x2": _px(device, x2), "y2": _px(device, y2), "ms": ms}
|
|
165
|
+
elif action == "scroll":
|
|
166
|
+
body = {"op": "scroll", "text": text or "down"}
|
|
167
|
+
elif action in ("tap_text", "wait_text"):
|
|
168
|
+
body = {"op": action, "text": text}
|
|
169
|
+
if action == "wait_text":
|
|
170
|
+
body["ms"] = max(1, min(int(timeout), 120)) * 1000
|
|
171
|
+
elif action == "type":
|
|
172
|
+
body = {"op": "text", "text": text}
|
|
173
|
+
elif action == "key":
|
|
174
|
+
body = {"op": "key", "key": text}
|
|
175
|
+
elif action in ("open_app", "close_app"):
|
|
176
|
+
body = {"op": action, "package": text}
|
|
177
|
+
elif action == "open_url":
|
|
178
|
+
body = {"op": "url", "url": text}
|
|
179
|
+
elif action == "installed":
|
|
180
|
+
return ", ".join(d.installed())
|
|
181
|
+
else:
|
|
182
|
+
return "error: unknown action - see the tool description"
|
|
183
|
+
op = body.pop("op")
|
|
184
|
+
try:
|
|
185
|
+
r = d.act(op, settle=True, **body)
|
|
186
|
+
out = r.get("output") or "ok"
|
|
187
|
+
if r.get("changed") is False:
|
|
188
|
+
return out + " (the screen did not change - the action probably missed; observe again)"
|
|
189
|
+
return out + ("" if r.get("settled", True) else " (screen still changing)")
|
|
190
|
+
except ClousdError as e:
|
|
191
|
+
if e.status != 404:
|
|
192
|
+
raise
|
|
193
|
+
return d.action(op, **body).get("output") or "ok" # older gateway without /act
|
|
194
|
+
except (ClousdError, ValueError) as e:
|
|
195
|
+
return f"error: {e}"
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@mcp.tool()
|
|
199
|
+
def snapshots(device: str, op: str = "list", snapshot_id: str = "", new_name: str = "") -> object:
|
|
200
|
+
"""Saved states of a phone, for starting every run from the same point.
|
|
201
|
+
op=list (default): id, created, android, current, restorable.
|
|
202
|
+
op=save: save the current state. op=restore with snapshot_id: reset the phone to that state.
|
|
203
|
+
op=clone with snapshot_id and new_name (letters and digits, up to 16): a new phone from that state."""
|
|
204
|
+
d = client().device(device)
|
|
205
|
+
try:
|
|
206
|
+
if op == "list":
|
|
207
|
+
return d.snapshots()
|
|
208
|
+
if op == "save":
|
|
209
|
+
return d.save_snapshot().output or "saved"
|
|
210
|
+
if op == "restore" and snapshot_id:
|
|
211
|
+
return d.restore(snapshot_id).output or "restored"
|
|
212
|
+
if op == "clone" and snapshot_id and new_name:
|
|
213
|
+
return "created " + d.clone(snapshot_id, new_name).name
|
|
214
|
+
return "error: op is list, save, restore (snapshot_id) or clone (snapshot_id, new_name)"
|
|
215
|
+
except ClousdError as e:
|
|
216
|
+
return f"error: {e}"
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def main() -> None:
|
|
220
|
+
mcp.run()
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
if __name__ == "__main__":
|
|
224
|
+
main()
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: clousd-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server: give an AI agent a real Android phone in the cloud
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://clousd.com/agents/
|
|
7
|
+
Keywords: mcp,android,cloud phone,ai agents,mobile automation
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: mcp>=1.2.0
|
|
11
|
+
Requires-Dist: clousd>=0.1.0
|
|
12
|
+
|
|
13
|
+
# clousd-mcp
|
|
14
|
+
|
|
15
|
+
An MCP server that gives any MCP-capable agent a real Android phone in the cloud: a device modelled on a
|
|
16
|
+
real phone, with Google Play, a network in the country you choose, and saved states to reset to between runs.
|
|
17
|
+
|
|
18
|
+
Early access: ask for a key at [clousd.com/agents](https://clousd.com/agents/#access).
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install "git+https://github.com/Clousd-Android/clousd-sdk#subdirectory=python"
|
|
24
|
+
pip install "git+https://github.com/Clousd-Android/clousd-sdk#subdirectory=mcp"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Any MCP client (Cursor, Windsurf, Zed, a desktop assistant or your own agent) - stdio server, one environment variable:
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"mcpServers": {
|
|
32
|
+
"clousd": {
|
|
33
|
+
"command": "clousd-mcp",
|
|
34
|
+
"env": { "CLOUSD_API_KEY": "cl_live_..." }
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Clients that take a command line instead of JSON: `clousd-mcp` with `CLOUSD_API_KEY` in the environment.
|
|
41
|
+
|
|
42
|
+
## Tools
|
|
43
|
+
|
|
44
|
+
Four tools, on purpose - agents choose better from a few large tools than from a dozen small ones:
|
|
45
|
+
|
|
46
|
+
| Tool | What it does |
|
|
47
|
+
|---|---|
|
|
48
|
+
| `devices` | list the phones this key can use; start or stop one |
|
|
49
|
+
| `observe` | the screen as an image plus every text on it |
|
|
50
|
+
| `act` | `tap`, `swipe`, `scroll`, `tap_text`, `wait_text`, `type`, `key`, `open_app`, `close_app`, `open_url`, `installed` |
|
|
51
|
+
| `snapshots` | list, save, restore (reset between runs) or clone a phone |
|
|
52
|
+
|
|
53
|
+
Screenshots come back 540 px wide (`CLOUSD_SHOT_WIDTH` to change); `tap` and `swipe` take coordinates in that image
|
|
54
|
+
and the server scales them to the phone. `tap_text` is usually the more reliable way to press a button. Typing works in
|
|
55
|
+
any language.
|
|
56
|
+
|
|
57
|
+
Built on the [`clousd`](../python) Python client. MIT license.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
clousd_mcp/__init__.py
|
|
4
|
+
clousd_mcp/__main__.py
|
|
5
|
+
clousd_mcp/server.py
|
|
6
|
+
clousd_mcp.egg-info/PKG-INFO
|
|
7
|
+
clousd_mcp.egg-info/SOURCES.txt
|
|
8
|
+
clousd_mcp.egg-info/dependency_links.txt
|
|
9
|
+
clousd_mcp.egg-info/entry_points.txt
|
|
10
|
+
clousd_mcp.egg-info/requires.txt
|
|
11
|
+
clousd_mcp.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
clousd_mcp
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "clousd-mcp"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "MCP server: give an AI agent a real Android phone in the cloud"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
dependencies = ["mcp>=1.2.0", "clousd>=0.1.0"]
|
|
13
|
+
keywords = ["mcp", "android", "cloud phone", "ai agents", "mobile automation"]
|
|
14
|
+
|
|
15
|
+
[project.scripts]
|
|
16
|
+
clousd-mcp = "clousd_mcp.server:main"
|
|
17
|
+
|
|
18
|
+
[project.urls]
|
|
19
|
+
Homepage = "https://clousd.com/agents/"
|
|
20
|
+
|
|
21
|
+
[tool.setuptools]
|
|
22
|
+
packages = ["clousd_mcp"]
|