@magentaesolutions/device-screenshot-mcp 1.0.0 → 1.2.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 +106 -0
- package/dist/index.js +21 -12
- package/package.json +2 -1
package/README.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Device Screenshot MCP
|
|
2
|
+
|
|
3
|
+
MCP server for taking screenshots from a physical iOS device connected via USB. Works with Claude Code across all projects.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- **pymobiledevice3** — Python tool for communicating with iOS devices
|
|
8
|
+
- **pymobiledevice3 tunneld** — must be running as root for iOS 17+
|
|
9
|
+
|
|
10
|
+
### Install pymobiledevice3
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip3 install pymobiledevice3
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### Start tunneld
|
|
17
|
+
|
|
18
|
+
The tunnel daemon must run as root. You can either start it manually:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
sudo pymobiledevice3 remote tunneld
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Or install it as a LaunchDaemon for auto-start on boot. Create `/Library/LaunchDaemons/com.pymobiledevice3.tunneld.plist`:
|
|
25
|
+
|
|
26
|
+
```xml
|
|
27
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
28
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
29
|
+
<plist version="1.0">
|
|
30
|
+
<dict>
|
|
31
|
+
<key>Label</key>
|
|
32
|
+
<string>com.pymobiledevice3.tunneld</string>
|
|
33
|
+
<key>ProgramArguments</key>
|
|
34
|
+
<array>
|
|
35
|
+
<string>/path/to/pymobiledevice3</string>
|
|
36
|
+
<string>remote</string>
|
|
37
|
+
<string>tunneld</string>
|
|
38
|
+
</array>
|
|
39
|
+
<key>EnvironmentVariables</key>
|
|
40
|
+
<dict>
|
|
41
|
+
<key>PYTHONPATH</key>
|
|
42
|
+
<string>/path/to/python/site-packages</string>
|
|
43
|
+
</dict>
|
|
44
|
+
<key>RunAtLoad</key>
|
|
45
|
+
<true/>
|
|
46
|
+
<key>KeepAlive</key>
|
|
47
|
+
<true/>
|
|
48
|
+
<key>StandardOutPath</key>
|
|
49
|
+
<string>/tmp/pymobiledevice3-tunneld.log</string>
|
|
50
|
+
<key>StandardErrorPath</key>
|
|
51
|
+
<string>/tmp/pymobiledevice3-tunneld.log</string>
|
|
52
|
+
</dict>
|
|
53
|
+
</plist>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Then load it:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
sudo cp com.pymobiledevice3.tunneld.plist /Library/LaunchDaemons/
|
|
60
|
+
sudo launchctl load /Library/LaunchDaemons/com.pymobiledevice3.tunneld.plist
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### iOS device requirements
|
|
64
|
+
|
|
65
|
+
- Developer Mode enabled (Settings > Privacy & Security > Developer Mode)
|
|
66
|
+
- Connected via USB and trusted
|
|
67
|
+
|
|
68
|
+
## Setup
|
|
69
|
+
|
|
70
|
+
Add to `~/.claude/.mcp.json` for global access, or to a project's `.mcp.json`:
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"mcpServers": {
|
|
75
|
+
"device": {
|
|
76
|
+
"command": "npx",
|
|
77
|
+
"args": ["-y", "@magentaesolutions/device-screenshot-mcp@latest"],
|
|
78
|
+
"env": {
|
|
79
|
+
"PYMOBILEDEVICE3_PATH": "/path/to/pymobiledevice3",
|
|
80
|
+
"SCREENSHOT_DIR": "/path/to/project/screenshots"
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Environment Variables
|
|
88
|
+
|
|
89
|
+
| Variable | Required | Default | Description |
|
|
90
|
+
|---|---|---|---|
|
|
91
|
+
| `PYMOBILEDEVICE3_PATH` | No | `pymobiledevice3` | Path to the pymobiledevice3 binary |
|
|
92
|
+
| `SCREENSHOT_DIR` | No | cwd | Default directory for saved screenshots. `save_to` paths are resolved relative to this. |
|
|
93
|
+
|
|
94
|
+
## Tools
|
|
95
|
+
|
|
96
|
+
| Tool | Description |
|
|
97
|
+
|---|---|
|
|
98
|
+
| `take_screenshot` | Capture a PNG screenshot from the connected iOS device |
|
|
99
|
+
|
|
100
|
+
### take_screenshot
|
|
101
|
+
|
|
102
|
+
| Parameter | Type | Required | Description |
|
|
103
|
+
|---|---|---|---|
|
|
104
|
+
| `save_to` | string | No | Filename or path for saving a copy (e.g. `screenshot.png`). Resolved relative to `SCREENSHOT_DIR` if set, otherwise relative to cwd. Creates directories if needed. |
|
|
105
|
+
|
|
106
|
+
Returns the screenshot as an inline image. If `save_to` is provided, also saves a copy to that path.
|
package/dist/index.js
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
3
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
4
|
import { execFile } from 'node:child_process';
|
|
5
|
-
import { readFile, unlink } from 'node:fs/promises';
|
|
5
|
+
import { copyFile, mkdir, readFile, unlink } from 'node:fs/promises';
|
|
6
6
|
import { tmpdir } from 'node:os';
|
|
7
|
-
import { join } from 'node:path';
|
|
7
|
+
import { dirname, join, resolve } from 'node:path';
|
|
8
|
+
import { z } from 'zod';
|
|
8
9
|
const PMD3 = process.env.PYMOBILEDEVICE3_PATH || 'pymobiledevice3';
|
|
10
|
+
const SCREENSHOT_DIR = process.env.SCREENSHOT_DIR;
|
|
9
11
|
const server = new McpServer({
|
|
10
12
|
name: 'device-screenshot',
|
|
11
13
|
version: '1.0.0',
|
|
@@ -20,21 +22,28 @@ function exec(cmd, args, timeout = 15000) {
|
|
|
20
22
|
});
|
|
21
23
|
});
|
|
22
24
|
}
|
|
23
|
-
server.tool('take_screenshot', 'Take a screenshot from a connected physical iOS device via USB. Returns the image as a PNG. Requires pymobiledevice3 tunneld to be running (sudo pymobiledevice3 remote tunneld).', {
|
|
25
|
+
server.tool('take_screenshot', 'Take a screenshot from a connected physical iOS device via USB. Returns the image as a PNG. Optionally saves a copy to disk. Requires pymobiledevice3 tunneld to be running (sudo pymobiledevice3 remote tunneld).', {
|
|
26
|
+
save_to: z.string().optional().describe('Optional filename or path to save the screenshot (e.g. "screenshot.png"). Resolved relative to SCREENSHOT_DIR if set, otherwise relative to cwd. Creates directories if needed.'),
|
|
27
|
+
}, async ({ save_to }) => {
|
|
24
28
|
const outPath = join(tmpdir(), `device-screenshot-${Date.now()}.png`);
|
|
25
29
|
try {
|
|
26
30
|
await exec(PMD3, ['developer', 'dvt', 'screenshot', outPath, '--tunnel', '']);
|
|
27
31
|
const data = await readFile(outPath);
|
|
32
|
+
const content = [
|
|
33
|
+
{
|
|
34
|
+
type: 'image',
|
|
35
|
+
data: data.toString('base64'),
|
|
36
|
+
mimeType: 'image/png',
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
if (save_to) {
|
|
40
|
+
const savePath = SCREENSHOT_DIR ? resolve(SCREENSHOT_DIR, save_to) : resolve(save_to);
|
|
41
|
+
await mkdir(dirname(savePath), { recursive: true });
|
|
42
|
+
await copyFile(outPath, savePath);
|
|
43
|
+
content.push({ type: 'text', text: `Saved to ${savePath}` });
|
|
44
|
+
}
|
|
28
45
|
await unlink(outPath).catch(() => { });
|
|
29
|
-
return {
|
|
30
|
-
content: [
|
|
31
|
-
{
|
|
32
|
-
type: 'image',
|
|
33
|
-
data: data.toString('base64'),
|
|
34
|
-
mimeType: 'image/png',
|
|
35
|
-
},
|
|
36
|
-
],
|
|
37
|
-
};
|
|
46
|
+
return { content };
|
|
38
47
|
}
|
|
39
48
|
catch (e) {
|
|
40
49
|
const msg = e instanceof Error ? e.message : String(e);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@magentaesolutions/device-screenshot-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "MCP server for taking screenshots from physical iOS devices via USB",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
],
|
|
12
12
|
"scripts": {
|
|
13
13
|
"build": "tsc",
|
|
14
|
+
"publish:npm": "npm publish --access public",
|
|
14
15
|
"prepublishOnly": "npm run build"
|
|
15
16
|
},
|
|
16
17
|
"keywords": [
|