phonectl-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.
@@ -0,0 +1,45 @@
1
+ # Publish to PyPI when a version tag (v0.1.0, ...) is pushed. Uses PyPI trusted publishing:
2
+ # no token is stored; PyPI trusts this workflow in this repo, in the "pypi" environment.
3
+ name: release
4
+
5
+ on:
6
+ push:
7
+ tags: ["v*"]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: macos-latest # the package imports macOS-only frameworks (Vision via pyobjc)
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - uses: astral-sh/setup-uv@v6
15
+ - run: uv sync
16
+ - run: uv run pytest -q
17
+
18
+ build:
19
+ needs: test
20
+ runs-on: ubuntu-latest
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+ - uses: astral-sh/setup-uv@v6
24
+ - name: Check the tag matches the version in pyproject.toml
25
+ run: |
26
+ version=$(grep -m1 '^version = ' pyproject.toml | cut -d'"' -f2)
27
+ test "v$version" = "$GITHUB_REF_NAME" || { echo "tag $GITHUB_REF_NAME != v$version"; exit 1; }
28
+ - run: uv build
29
+ - uses: actions/upload-artifact@v4
30
+ with:
31
+ name: dist
32
+ path: dist/
33
+
34
+ publish:
35
+ needs: build
36
+ runs-on: ubuntu-latest
37
+ environment: pypi
38
+ permissions:
39
+ id-token: write # for trusted publishing
40
+ steps:
41
+ - uses: actions/download-artifact@v4
42
+ with:
43
+ name: dist
44
+ path: dist/
45
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,15 @@
1
+ name: test
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: macos-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: astral-sh/setup-uv@v6
14
+ - run: uv sync
15
+ - run: uv run pytest -q
@@ -0,0 +1,9 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ dist/
5
+ build/
6
+ .DS_Store
7
+ .pytest_cache/
8
+ # personal notes for Claude Code, not part of the project
9
+ CLAUDE.local.md
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 jiefeng-ai
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,143 @@
1
+ Metadata-Version: 2.5
2
+ Name: phonectl-mcp
3
+ Version: 0.1.0
4
+ Summary: MCP server that lets AI agents see and drive a real iPhone over USB: screenshots, on-screen text, taps, swipes, typing, apps.
5
+ Project-URL: Homepage, https://github.com/jiefeng-ai/phonectl-mcp
6
+ Project-URL: Issues, https://github.com/jiefeng-ai/phonectl-mcp/issues
7
+ Author: jiefeng-ai
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: agent,automation,ios,iphone,mcp,model-context-protocol
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: MacOS X
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: MacOS
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Software Development :: Testing
18
+ Requires-Python: >=3.13
19
+ Requires-Dist: mcp<3,>=2.3.0
20
+ Requires-Dist: pillow>=13.0.0
21
+ Requires-Dist: pymobiledevice3==11.50.0
22
+ Requires-Dist: pyobjc-framework-vision>=13.0
23
+ Description-Content-Type: text/markdown
24
+
25
+ # phonectl-mcp
26
+
27
+ [中文](https://github.com/jiefeng-ai/phonectl-mcp/blob/main/README.zh-CN.md)
28
+
29
+ An MCP server that lets AI agents see and drive a real iPhone over a USB cable: screenshots, the text on screen, taps, swipes, typing, hardware buttons, apps and the clipboard. **macOS only.**
30
+
31
+ <p align="center"><img src="https://raw.githubusercontent.com/jiefeng-ai/phonectl-mcp/main/docs/demo.gif" width="358" alt="A script applies to one job in the BOSS Zhipin app through phonectl-mcp, reading the screen with read_text and tapping"></p>
32
+ <p align="center"><sub>A script applying to one job in a recruiting app through phonectl-mcp: <code>read_text</code> finds the buttons, <code>tap</code> presses them. Personal details are pixelated; 2× speed.</sub></p>
33
+
34
+ Nothing is installed on the phone and nothing is jailbroken. It uses the developer services iOS already exposes to a trusted Mac (the same ones Xcode uses), and the touch and keyboard input path that iOS 27 opened over USB.
35
+
36
+ ## Requirements
37
+
38
+ - A Mac. The USB tunnel and the on-screen text recognition (Apple Vision) are macOS-only.
39
+ - An iPhone on **iOS 27 or later**. Taps, swipes and typing need iOS 27. Screenshots and app control may also work on iOS 17–26, but this is untested.
40
+ - A USB data cable (a charge-only cable won't work).
41
+ - On the phone: **Developer Mode** turned on, and this Mac trusted. `phonectl doctor` walks you through both.
42
+ - [uv](https://docs.astral.sh/uv/).
43
+
44
+ Tested on an iPhone 17 Pro with iOS 27.2 and macOS 27.
45
+
46
+ ## Install
47
+
48
+ ```sh
49
+ uv tool install phonectl-mcp
50
+ phonectl install-service
51
+ phonectl doctor
52
+ ```
53
+
54
+ - `install-service` runs the server at login as a launchd job. It listens on `http://127.0.0.1:47321/mcp` and creates an access token in `~/.config/phonectl/token`. It also prints the client setup below.
55
+ - `doctor` checks every link from the cable to the server and says what to do about anything that fails. `phonectl doctor --fix` mounts the developer disk image if it is missing.
56
+
57
+ ### Connect a client
58
+
59
+ Claude Code:
60
+
61
+ ```sh
62
+ claude mcp add --scope user --transport http phone http://127.0.0.1:47321/mcp \
63
+ --header "Authorization: Bearer $(cat ~/.config/phonectl/token)"
64
+ ```
65
+
66
+ Any other client that speaks streamable HTTP needs two things: the URL above, and the header `Authorization: Bearer <token>`.
67
+
68
+ ### Why one server for every client
69
+
70
+ The phone keeps a single control connection to the Mac. If two programs each open their own, they keep knocking each other off. So one resident server owns the phone, and every agent and script connects to that server. Your own scripts can call the same tools with any MCP client library.
71
+
72
+ This only makes the connection safe to share. The server does not stop two agents from tapping the screen at the same time, so let one drive at a time.
73
+
74
+ ## Tools
75
+
76
+ Coordinates are iOS points, which match the pixels of the screenshot the server returns (402×874 on an iPhone 17 Pro).
77
+
78
+ | Tool | What it does |
79
+ | --- | --- |
80
+ | `screenshot(native=False)` | Captures the screen. `native=True` returns the full-resolution PNG, for scripts. |
81
+ | `read_text()` | Returns the text on screen with positions in points, as JSON. Much cheaper than an image when an agent or script only needs to find a button. |
82
+ | `tap(x, y)`, `long_press(x, y, duration_ms)` | Touch. |
83
+ | `swipe(x1, y1, x2, y2, duration_ms)` | Drag. To scroll down, swipe upward. |
84
+ | `type_text(text)` | Types into the focused field. ASCII is typed key by key. Other text (Chinese, emoji) is pasted through the clipboard, which replaces what was on it. |
85
+ | `press_key(key)` | Presses a key or chord, such as `enter`, `backspace` or `cmd+a`. |
86
+ | `press_button(name)` | Presses a hardware button: `home`, `lock`, `volume-up`, `volume-down`, `mute` or `siri`. |
87
+ | `launch_app(bundle_id, restart=False)`, `list_apps(query)` | Opens apps and finds their bundle ids. |
88
+ | `get_clipboard()`, `set_clipboard(text)` | Reads and writes the phone's clipboard. |
89
+ | `status()` | Reports the server and the phone link. |
90
+
91
+ The tools that act on the phone (`tap`, `long_press`, `swipe`, `type_text`, `press_key`, `press_button`, `launch_app`) return a screenshot taken afterwards; pass `screenshot=False` to skip it. Taps are not confirmed by the phone, so use that screenshot to see whether a tap landed.
92
+
93
+ ## Settings
94
+
95
+ Set these environment variables before running `phonectl install-service`; it copies them into the service.
96
+
97
+ | Variable | Default | Meaning |
98
+ | --- | --- | --- |
99
+ | `PHONECTL_UDID` | first USB iPhone | Which phone to use when more than one is connected. |
100
+ | `PHONECTL_PORT` | `47321` | HTTP port. The server only listens on 127.0.0.1. |
101
+ | `PHONECTL_TOKEN_FILE` | `~/.config/phonectl/token` | Where the access token is kept. |
102
+ | `PHONECTL_SCALE` | `3` | Pixels per point. Use 2 on an iPhone SE, XR or 11. It only sets the screenshot size; taps stay correct either way. |
103
+ | `PHONECTL_OCR_LANGS` | `zh-Hans,en-US` | Languages for `read_text`, in priority order. |
104
+ | `PHONECTL_SETTLE` | `0.6` | Seconds to wait before the screenshot that follows an action. |
105
+ | `PHONECTL_TOUCH_IDLE` | `30` | Seconds without input after which the input stream to the phone is closed. The next input reopens it, which takes about 0.6 s. |
106
+ | `PHONECTL_CALL_TIMEOUT` | `15` | Seconds to wait for the phone before a call fails, on top of the time a gesture takes. |
107
+ | `PHONECTL_LOG` | `WARNING` | Log level (`INFO` for the installed service). Logs are in `~/Library/Logs/phonectl*.log`. |
108
+
109
+ Other commands: `phonectl restart` restarts the server (for example after upgrading with `uv tool upgrade phonectl-mcp`), and `phonectl uninstall-service` removes it. Running `phonectl-mcp` with no arguments speaks stdio for a single client; this is meant for debugging.
110
+
111
+ ## Troubleshooting
112
+
113
+ Run `phonectl doctor` first. It tells you which link is broken.
114
+
115
+ | Symptom | Fix |
116
+ | --- | --- |
117
+ | No iPhone found | Use a data cable, unlock the phone, then replug. |
118
+ | "Mac is not trusted" | Unlock the phone, replug, and tap **Trust**. |
119
+ | Developer Mode switch is missing | Open Xcode once with the phone connected. The switch then appears under Settings › Privacy & Security. |
120
+ | Screenshots fail | Run `phonectl doctor --fix` to mount the developer disk image. |
121
+ | Taps do nothing | Taps need iOS 27 or later. Also make sure the screen is on and unlocked. |
122
+ | Clients cannot connect | Run `phonectl restart` and check `~/Library/Logs/phonectl.error.log`. |
123
+
124
+ ## Security
125
+
126
+ - The server listens only on `127.0.0.1` and rejects any request without the token.
127
+ - An agent connected to it can do anything you could do with your finger: send messages, buy things, delete data. Give it the narrowest instructions you can, and watch the screen while it works.
128
+ - You use it at your own risk. Automating third-party apps may break their terms of service.
129
+
130
+ ## Development
131
+
132
+ ```sh
133
+ git clone https://github.com/jiefeng-ai/phonectl-mcp
134
+ cd phonectl-mcp
135
+ uv sync
136
+ uv run pytest
137
+ ```
138
+
139
+ To run your working copy as the service, run `uv run phonectl install-service` from the clone.
140
+
141
+ ## License
142
+
143
+ MIT
@@ -0,0 +1,119 @@
1
+ # phonectl-mcp
2
+
3
+ [中文](https://github.com/jiefeng-ai/phonectl-mcp/blob/main/README.zh-CN.md)
4
+
5
+ An MCP server that lets AI agents see and drive a real iPhone over a USB cable: screenshots, the text on screen, taps, swipes, typing, hardware buttons, apps and the clipboard. **macOS only.**
6
+
7
+ <p align="center"><img src="https://raw.githubusercontent.com/jiefeng-ai/phonectl-mcp/main/docs/demo.gif" width="358" alt="A script applies to one job in the BOSS Zhipin app through phonectl-mcp, reading the screen with read_text and tapping"></p>
8
+ <p align="center"><sub>A script applying to one job in a recruiting app through phonectl-mcp: <code>read_text</code> finds the buttons, <code>tap</code> presses them. Personal details are pixelated; 2× speed.</sub></p>
9
+
10
+ Nothing is installed on the phone and nothing is jailbroken. It uses the developer services iOS already exposes to a trusted Mac (the same ones Xcode uses), and the touch and keyboard input path that iOS 27 opened over USB.
11
+
12
+ ## Requirements
13
+
14
+ - A Mac. The USB tunnel and the on-screen text recognition (Apple Vision) are macOS-only.
15
+ - An iPhone on **iOS 27 or later**. Taps, swipes and typing need iOS 27. Screenshots and app control may also work on iOS 17–26, but this is untested.
16
+ - A USB data cable (a charge-only cable won't work).
17
+ - On the phone: **Developer Mode** turned on, and this Mac trusted. `phonectl doctor` walks you through both.
18
+ - [uv](https://docs.astral.sh/uv/).
19
+
20
+ Tested on an iPhone 17 Pro with iOS 27.2 and macOS 27.
21
+
22
+ ## Install
23
+
24
+ ```sh
25
+ uv tool install phonectl-mcp
26
+ phonectl install-service
27
+ phonectl doctor
28
+ ```
29
+
30
+ - `install-service` runs the server at login as a launchd job. It listens on `http://127.0.0.1:47321/mcp` and creates an access token in `~/.config/phonectl/token`. It also prints the client setup below.
31
+ - `doctor` checks every link from the cable to the server and says what to do about anything that fails. `phonectl doctor --fix` mounts the developer disk image if it is missing.
32
+
33
+ ### Connect a client
34
+
35
+ Claude Code:
36
+
37
+ ```sh
38
+ claude mcp add --scope user --transport http phone http://127.0.0.1:47321/mcp \
39
+ --header "Authorization: Bearer $(cat ~/.config/phonectl/token)"
40
+ ```
41
+
42
+ Any other client that speaks streamable HTTP needs two things: the URL above, and the header `Authorization: Bearer <token>`.
43
+
44
+ ### Why one server for every client
45
+
46
+ The phone keeps a single control connection to the Mac. If two programs each open their own, they keep knocking each other off. So one resident server owns the phone, and every agent and script connects to that server. Your own scripts can call the same tools with any MCP client library.
47
+
48
+ This only makes the connection safe to share. The server does not stop two agents from tapping the screen at the same time, so let one drive at a time.
49
+
50
+ ## Tools
51
+
52
+ Coordinates are iOS points, which match the pixels of the screenshot the server returns (402×874 on an iPhone 17 Pro).
53
+
54
+ | Tool | What it does |
55
+ | --- | --- |
56
+ | `screenshot(native=False)` | Captures the screen. `native=True` returns the full-resolution PNG, for scripts. |
57
+ | `read_text()` | Returns the text on screen with positions in points, as JSON. Much cheaper than an image when an agent or script only needs to find a button. |
58
+ | `tap(x, y)`, `long_press(x, y, duration_ms)` | Touch. |
59
+ | `swipe(x1, y1, x2, y2, duration_ms)` | Drag. To scroll down, swipe upward. |
60
+ | `type_text(text)` | Types into the focused field. ASCII is typed key by key. Other text (Chinese, emoji) is pasted through the clipboard, which replaces what was on it. |
61
+ | `press_key(key)` | Presses a key or chord, such as `enter`, `backspace` or `cmd+a`. |
62
+ | `press_button(name)` | Presses a hardware button: `home`, `lock`, `volume-up`, `volume-down`, `mute` or `siri`. |
63
+ | `launch_app(bundle_id, restart=False)`, `list_apps(query)` | Opens apps and finds their bundle ids. |
64
+ | `get_clipboard()`, `set_clipboard(text)` | Reads and writes the phone's clipboard. |
65
+ | `status()` | Reports the server and the phone link. |
66
+
67
+ The tools that act on the phone (`tap`, `long_press`, `swipe`, `type_text`, `press_key`, `press_button`, `launch_app`) return a screenshot taken afterwards; pass `screenshot=False` to skip it. Taps are not confirmed by the phone, so use that screenshot to see whether a tap landed.
68
+
69
+ ## Settings
70
+
71
+ Set these environment variables before running `phonectl install-service`; it copies them into the service.
72
+
73
+ | Variable | Default | Meaning |
74
+ | --- | --- | --- |
75
+ | `PHONECTL_UDID` | first USB iPhone | Which phone to use when more than one is connected. |
76
+ | `PHONECTL_PORT` | `47321` | HTTP port. The server only listens on 127.0.0.1. |
77
+ | `PHONECTL_TOKEN_FILE` | `~/.config/phonectl/token` | Where the access token is kept. |
78
+ | `PHONECTL_SCALE` | `3` | Pixels per point. Use 2 on an iPhone SE, XR or 11. It only sets the screenshot size; taps stay correct either way. |
79
+ | `PHONECTL_OCR_LANGS` | `zh-Hans,en-US` | Languages for `read_text`, in priority order. |
80
+ | `PHONECTL_SETTLE` | `0.6` | Seconds to wait before the screenshot that follows an action. |
81
+ | `PHONECTL_TOUCH_IDLE` | `30` | Seconds without input after which the input stream to the phone is closed. The next input reopens it, which takes about 0.6 s. |
82
+ | `PHONECTL_CALL_TIMEOUT` | `15` | Seconds to wait for the phone before a call fails, on top of the time a gesture takes. |
83
+ | `PHONECTL_LOG` | `WARNING` | Log level (`INFO` for the installed service). Logs are in `~/Library/Logs/phonectl*.log`. |
84
+
85
+ Other commands: `phonectl restart` restarts the server (for example after upgrading with `uv tool upgrade phonectl-mcp`), and `phonectl uninstall-service` removes it. Running `phonectl-mcp` with no arguments speaks stdio for a single client; this is meant for debugging.
86
+
87
+ ## Troubleshooting
88
+
89
+ Run `phonectl doctor` first. It tells you which link is broken.
90
+
91
+ | Symptom | Fix |
92
+ | --- | --- |
93
+ | No iPhone found | Use a data cable, unlock the phone, then replug. |
94
+ | "Mac is not trusted" | Unlock the phone, replug, and tap **Trust**. |
95
+ | Developer Mode switch is missing | Open Xcode once with the phone connected. The switch then appears under Settings › Privacy & Security. |
96
+ | Screenshots fail | Run `phonectl doctor --fix` to mount the developer disk image. |
97
+ | Taps do nothing | Taps need iOS 27 or later. Also make sure the screen is on and unlocked. |
98
+ | Clients cannot connect | Run `phonectl restart` and check `~/Library/Logs/phonectl.error.log`. |
99
+
100
+ ## Security
101
+
102
+ - The server listens only on `127.0.0.1` and rejects any request without the token.
103
+ - An agent connected to it can do anything you could do with your finger: send messages, buy things, delete data. Give it the narrowest instructions you can, and watch the screen while it works.
104
+ - You use it at your own risk. Automating third-party apps may break their terms of service.
105
+
106
+ ## Development
107
+
108
+ ```sh
109
+ git clone https://github.com/jiefeng-ai/phonectl-mcp
110
+ cd phonectl-mcp
111
+ uv sync
112
+ uv run pytest
113
+ ```
114
+
115
+ To run your working copy as the service, run `uv run phonectl install-service` from the clone.
116
+
117
+ ## License
118
+
119
+ MIT
@@ -0,0 +1,122 @@
1
+ # phonectl-mcp
2
+
3
+ [English](https://github.com/jiefeng-ai/phonectl-mcp/blob/main/README.md)
4
+
5
+ 一个 MCP 服务:让 AI agent 通过 USB 线看见并操作一台真实的 iPhone,能截图、读屏幕文字、点按、滑动、打字、按硬件按钮、启动 App、读写剪贴板。**仅支持 macOS。**
6
+
7
+ <p align="center"><img src="https://raw.githubusercontent.com/jiefeng-ai/phonectl-mcp/main/docs/demo.gif" width="358" alt="脚本通过 phonectl-mcp 在 BOSS 直聘里投递一个岗位:用 read_text 读屏、用 tap 点按"></p>
8
+ <p align="center"><sub>脚本通过 phonectl-mcp 在招聘 App 里投递一个岗位:<code>read_text</code> 找按钮,<code>tap</code> 点下去。个人信息已打码,2 倍速。</sub></p>
9
+
10
+ 手机上不用装任何东西,也不用越狱。它用的是 iOS 对已信任 Mac 开放的开发者服务(Xcode 用的就是这套),点按和打字走 iOS 27 开放的 USB 输入通道。
11
+
12
+ ## 需要什么
13
+
14
+ - **一台 Mac**:USB 隧道和读屏文字识别(Apple Vision)都只有 macOS 上有。
15
+ - **iOS 27 或以上的 iPhone**:点按、滑动、打字需要 iOS 27。iOS 17–26 上截图和启动 App 可能能用,但没有测试过。
16
+ - **一根能传数据的 USB 线**:只能充电的线不行。
17
+ - **手机打开「开发者模式」,并信任这台 Mac**:这两步 `phonectl doctor` 会一步步告诉你怎么做。
18
+ - **[uv](https://docs.astral.sh/uv/)**。
19
+
20
+ 已在 iPhone 17 Pro(iOS 27.2)+ macOS 27 上测试。
21
+
22
+ ## 安装
23
+
24
+ ```sh
25
+ uv tool install phonectl-mcp
26
+ phonectl install-service
27
+ phonectl doctor
28
+ ```
29
+
30
+ - `install-service`:把服务注册为开机自启(launchd),监听 `http://127.0.0.1:47321/mcp`;在 `~/.config/phonectl/token` 生成访问令牌,并打印下面的客户端配置。
31
+ - `doctor`:从数据线到服务,逐段检查连接,哪一步不通就告诉你怎么修。缺开发者磁盘镜像时,加 `--fix` 会自动装上。
32
+
33
+ ### 接入客户端
34
+
35
+ Claude Code:
36
+
37
+ ```sh
38
+ claude mcp add --scope user --transport http phone http://127.0.0.1:47321/mcp \
39
+ --header "Authorization: Bearer $(cat ~/.config/phonectl/token)"
40
+ ```
41
+
42
+ 其他支持 streamable HTTP 的客户端,填上面的地址,再加请求头 `Authorization: Bearer <令牌>` 即可。
43
+
44
+ ### 为什么全机只跑一份服务
45
+
46
+ 手机和 Mac 之间同一时间只保留一条控制连接,两个程序各连各的会互相踢掉对方。所以由一份常驻服务独占手机,所有 agent 和脚本都连这份服务。你自己写的脚本,也可以用任意 MCP 客户端库调用同样的工具。
47
+
48
+ 注意,这只解决连接共用的问题,服务管不了两个 agent 同时点屏幕。同一时间请只让一方操作。
49
+
50
+ ## 工具
51
+
52
+ 坐标单位是 iOS 的 point,和服务返回的截图像素一一对应(iPhone 17 Pro 是 402×874)。
53
+
54
+ | 工具 | 作用 |
55
+ | --- | --- |
56
+ | `screenshot(native=False)` | 截图。`native=True` 返回原分辨率 PNG,给脚本用 |
57
+ | `read_text()` | 以 JSON 返回屏幕上的文字和位置(单位 point)。只需要找按钮时,比看图省得多 |
58
+ | `tap(x, y)`、`long_press(x, y, duration_ms)` | 点按、长按 |
59
+ | `swipe(x1, y1, x2, y2, duration_ms)` | 滑动。要往下翻,就向上滑 |
60
+ | `type_text(text)` | 往当前输入框打字。ASCII 字符逐键输入;中文和 emoji 经剪贴板粘贴,会覆盖剪贴板原有内容 |
61
+ | `press_key(key)` | 按键或组合键,如 `enter`、`backspace`、`cmd+a` |
62
+ | `press_button(name)` | 按硬件按钮:`home`、`lock`、`volume-up`、`volume-down`、`mute`、`siri` |
63
+ | `launch_app(bundle_id, restart=False)`、`list_apps(query)` | 启动 App,查 App 的 bundle id |
64
+ | `get_clipboard()`、`set_clipboard(text)` | 读写手机剪贴板 |
65
+ | `status()` | 报告服务状态和手机连接 |
66
+
67
+ 操作手机的工具(`tap`、`long_press`、`swipe`、`type_text`、`press_key`、`press_button`、`launch_app`)操作完会返回一张截图,传 `screenshot=False` 可以不要。手机不会确认点按是否生效,请看这张截图判断点没点中。
68
+
69
+ ## 配置
70
+
71
+ 在运行 `phonectl install-service` 之前设好下面的环境变量,安装时会写进服务配置。
72
+
73
+ | 变量 | 默认值 | 含义 |
74
+ | --- | --- | --- |
75
+ | `PHONECTL_UDID` | 第一台 USB 连接的 iPhone | 连了多台手机时,指定用哪一台 |
76
+ | `PHONECTL_PORT` | `47321` | HTTP 端口,只监听 127.0.0.1 |
77
+ | `PHONECTL_TOKEN_FILE` | `~/.config/phonectl/token` | 令牌文件的位置 |
78
+ | `PHONECTL_SCALE` | `3` | 每个 point 对应几个像素。iPhone SE、XR、11 填 2。它只影响截图大小,设错了也不会点错位置 |
79
+ | `PHONECTL_OCR_LANGS` | `zh-Hans,en-US` | `read_text` 的识别语言,按优先级排列 |
80
+ | `PHONECTL_SETTLE` | `0.6` | 操作后等多少秒再截图 |
81
+ | `PHONECTL_TOUCH_IDLE` | `30` | 多少秒没有输入后关闭到手机的输入通道。下次输入会重新打开,约 0.6 秒 |
82
+ | `PHONECTL_CALL_TIMEOUT` | `15` | 等手机响应多少秒算失败(不含手势本身的耗时) |
83
+ | `PHONECTL_LOG` | `WARNING` | 日志级别(安装成服务时是 `INFO`)。日志在 `~/Library/Logs/phonectl*.log` |
84
+
85
+ 其他命令:
86
+ - `phonectl restart`:重启服务,比如用 `uv tool upgrade phonectl-mcp` 升级之后;
87
+ - `phonectl uninstall-service`:卸载服务;
88
+ - 直接运行 `phonectl-mcp`(不带参数):以 stdio 方式服务单个客户端,用于调试。
89
+
90
+ ## 排查问题
91
+
92
+ 先跑 `phonectl doctor`,它会告诉你断在哪一段。
93
+
94
+ | 现象 | 处理 |
95
+ | --- | --- |
96
+ | 找不到 iPhone | 换一根能传数据的线,解锁手机后重插 |
97
+ | 提示 Mac 未被信任 | 解锁手机,重插线,在手机上点「信任」 |
98
+ | 设置里找不到「开发者模式」 | 手机连着 Mac 时打开一次 Xcode,之后「设置 › 隐私与安全性」里会出现这个开关 |
99
+ | 截图失败 | 运行 `phonectl doctor --fix`,装上开发者磁盘镜像 |
100
+ | 点了没反应 | 点按需要 iOS 27 及以上;另外确认屏幕亮着、没锁屏 |
101
+ | 客户端连不上 | 运行 `phonectl restart`,再看 `~/Library/Logs/phonectl.error.log` |
102
+
103
+ ## 安全
104
+
105
+ - 服务只监听 `127.0.0.1`,不带令牌的请求一律拒绝。
106
+ - 接入的 agent 能做你用手指能做的任何事:发消息、付款、删数据。请给它尽量明确、范围小的指令,并在它操作时看着屏幕。
107
+ - 使用风险自负。用它自动操作第三方 App,可能违反这些 App 的用户协议。
108
+
109
+ ## 开发
110
+
111
+ ```sh
112
+ git clone https://github.com/jiefeng-ai/phonectl-mcp
113
+ cd phonectl-mcp
114
+ uv sync
115
+ uv run pytest
116
+ ```
117
+
118
+ 想让服务直接运行你本地的代码,在仓库目录里执行 `uv run phonectl install-service`。
119
+
120
+ ## 协议
121
+
122
+ MIT
@@ -0,0 +1,46 @@
1
+ [project]
2
+ name = "phonectl-mcp"
3
+ version = "0.1.0"
4
+ description = "MCP server that lets AI agents see and drive a real iPhone over USB: screenshots, on-screen text, taps, swipes, typing, apps."
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ authors = [{ name = "jiefeng-ai" }]
9
+ requires-python = ">=3.13"
10
+ keywords = ["mcp", "iphone", "ios", "automation", "agent", "model-context-protocol"]
11
+ classifiers = [
12
+ "Development Status :: 3 - Alpha",
13
+ "Environment :: MacOS X",
14
+ "Intended Audience :: Developers",
15
+ "Operating System :: MacOS",
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3.13",
18
+ "Topic :: Software Development :: Testing",
19
+ ]
20
+ dependencies = [
21
+ "mcp>=2.3.0,<3",
22
+ "pillow>=13.0.0",
23
+ "pymobiledevice3==11.50.0",
24
+ "pyobjc-framework-vision>=13.0",
25
+ ]
26
+
27
+ [project.urls]
28
+ Homepage = "https://github.com/jiefeng-ai/phonectl-mcp"
29
+ Issues = "https://github.com/jiefeng-ai/phonectl-mcp/issues"
30
+
31
+ [project.scripts]
32
+ phonectl-mcp = "phonectl.server:main"
33
+ phonectl = "phonectl.cli:main"
34
+
35
+ [dependency-groups]
36
+ dev = ["pytest>=8"]
37
+
38
+ [build-system]
39
+ requires = ["hatchling>=1.27"]
40
+ build-backend = "hatchling.build"
41
+
42
+ [tool.hatch.build.targets.wheel]
43
+ packages = ["src/phonectl"]
44
+
45
+ [tool.hatch.build.targets.sdist]
46
+ exclude = ["docs/"]
@@ -0,0 +1,8 @@
1
+ """phonectl: an MCP server that drives a real iPhone over USB."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ try:
6
+ __version__ = version("phonectl-mcp")
7
+ except PackageNotFoundError: # running from a source tree that is not installed
8
+ __version__ = "0+unknown"