@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.
Files changed (3) hide show
  1. package/README.md +106 -0
  2. package/dist/index.js +21 -12
  3. 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).', {}, async () => {
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.0.0",
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": [