appium-mcp 1.86.2 → 1.86.4
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/CHANGELOG.md +12 -0
- package/dist/ai-finder/vision-finder.d.ts.map +1 -1
- package/dist/ai-finder/vision-finder.js +3 -4
- package/dist/ai-finder/vision-finder.js.map +1 -1
- package/dist/documentation.js +4 -4
- package/dist/documentation.js.map +1 -1
- package/dist/tools/interactions/screen-recording.js +3 -3
- package/dist/tools/interactions/screen-recording.js.map +1 -1
- package/dist/tools/interactions/screenshot.d.ts +4 -3
- package/dist/tools/interactions/screenshot.d.ts.map +1 -1
- package/dist/tools/interactions/screenshot.js +3 -4
- package/dist/tools/interactions/screenshot.js.map +1 -1
- package/dist/tools/ios/prepare-ios-real-device.d.ts.map +1 -1
- package/dist/tools/ios/prepare-ios-real-device.js +21 -28
- package/dist/tools/ios/prepare-ios-real-device.js.map +1 -1
- package/dist/tools/ios/prepare-ios-simulator.d.ts.map +1 -1
- package/dist/tools/ios/prepare-ios-simulator.js +8 -16
- package/dist/tools/ios/prepare-ios-simulator.js.map +1 -1
- package/dist/tools/session/create-session.d.ts.map +1 -1
- package/dist/tools/session/create-session.js +5 -4
- package/dist/tools/session/create-session.js.map +1 -1
- package/dist/ui/mcp-ui-utils.d.ts.map +1 -1
- package/dist/ui/mcp-ui-utils.js +53 -28
- package/dist/ui/mcp-ui-utils.js.map +1 -1
- package/package.json +1 -1
- package/server.json +2 -2
- package/src/ai-finder/vision-finder.ts +3 -4
- package/src/documentation.ts +4 -4
- package/src/tools/interactions/screen-recording.ts +3 -3
- package/src/tools/interactions/screenshot.ts +8 -6
- package/src/tools/ios/prepare-ios-real-device.ts +21 -27
- package/src/tools/ios/prepare-ios-simulator.ts +8 -15
- package/src/tools/session/create-session.ts +7 -4
- package/src/ui/mcp-ui-utils.ts +59 -30
|
@@ -8,10 +8,8 @@ import { z } from 'zod';
|
|
|
8
8
|
import { exec } from 'node:child_process';
|
|
9
9
|
import { promisify } from 'node:util';
|
|
10
10
|
import path from 'node:path';
|
|
11
|
-
import { access, mkdir, unlink, readdir, stat, rm } from 'node:fs/promises';
|
|
12
|
-
import { constants } from 'node:fs';
|
|
13
11
|
import os from 'node:os';
|
|
14
|
-
import { net, plist, zip } from '@appium/support';
|
|
12
|
+
import { fs, net, plist, zip } from '@appium/support';
|
|
15
13
|
import { Simctl } from 'node-simctl';
|
|
16
14
|
import { IOSManager } from '../../devicemanager/ios-manager.js';
|
|
17
15
|
import log from '../../logger.js';
|
|
@@ -44,19 +42,14 @@ interface WDAState {
|
|
|
44
42
|
}
|
|
45
43
|
|
|
46
44
|
async function fileExists(filePath: string): Promise<boolean> {
|
|
47
|
-
|
|
48
|
-
await access(filePath, constants.F_OK);
|
|
49
|
-
return true;
|
|
50
|
-
} catch {
|
|
51
|
-
return false;
|
|
52
|
-
}
|
|
45
|
+
return await fs.hasAccess(filePath);
|
|
53
46
|
}
|
|
54
47
|
|
|
55
48
|
// ── WDA download helpers ──
|
|
56
49
|
|
|
57
50
|
async function cleanupFile(filePath: string): Promise<void> {
|
|
58
51
|
try {
|
|
59
|
-
await unlink(filePath);
|
|
52
|
+
await fs.unlink(filePath);
|
|
60
53
|
} catch (err: any) {
|
|
61
54
|
if (err.code !== 'ENOENT') {
|
|
62
55
|
throw err;
|
|
@@ -105,11 +98,11 @@ async function getLatestWDAVersionFromCache(): Promise<string | null> {
|
|
|
105
98
|
return null;
|
|
106
99
|
}
|
|
107
100
|
|
|
108
|
-
const entries = await readdir(wdaCacheDir);
|
|
101
|
+
const entries = await fs.readdir(wdaCacheDir);
|
|
109
102
|
const versions = await Promise.all(
|
|
110
103
|
entries.map(async (dir) => {
|
|
111
104
|
const dirPath = path.join(wdaCacheDir, dir);
|
|
112
|
-
const stats = await stat(dirPath);
|
|
105
|
+
const stats = await fs.stat(dirPath);
|
|
113
106
|
return stats.isDirectory() ? dir : null;
|
|
114
107
|
})
|
|
115
108
|
);
|
|
@@ -263,11 +256,11 @@ async function resolveWdaAppPath(
|
|
|
263
256
|
|
|
264
257
|
// Clean any prior (possibly partial) extraction before downloading
|
|
265
258
|
if (await fileExists(extractDir)) {
|
|
266
|
-
await
|
|
259
|
+
await fs.rimraf(extractDir);
|
|
267
260
|
}
|
|
268
261
|
|
|
269
|
-
await
|
|
270
|
-
await
|
|
262
|
+
await fs.mkdirp(versionCacheDir);
|
|
263
|
+
await fs.mkdirp(extractDir);
|
|
271
264
|
|
|
272
265
|
const downloadUrl = `https://github.com/appium/WebDriverAgent/releases/download/v${wdaVersion}/${artifactPrefix}-Build-Sim-${archStr}.zip`;
|
|
273
266
|
log.info(`Downloading prebuilt WDA v${wdaVersion}...`);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { constants } from 'node:fs';
|
|
1
|
+
import { fs } from '@appium/support';
|
|
3
2
|
import { URL } from 'node:url';
|
|
4
3
|
import { getPortFromUrl } from '../../utils/url.js';
|
|
5
4
|
import { AndroidUiautomator2Driver } from 'appium-uiautomator2-driver';
|
|
@@ -452,8 +451,12 @@ async function loadCapabilitiesConfig(): Promise<CapabilitiesConfig> {
|
|
|
452
451
|
}
|
|
453
452
|
|
|
454
453
|
try {
|
|
455
|
-
await
|
|
456
|
-
|
|
454
|
+
if (!(await fs.hasAccess(configPath))) {
|
|
455
|
+
throw new Error(
|
|
456
|
+
`Capabilities config does not exist or is not accessible: ${configPath}`
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
const configContent = await fs.readFile(configPath, 'utf8');
|
|
457
460
|
return JSON.parse(configContent);
|
|
458
461
|
} catch (error: unknown) {
|
|
459
462
|
log.warn(`Failed to parse capabilities config: ${toolErrorMessage(error)}`);
|
package/src/ui/mcp-ui-utils.ts
CHANGED
|
@@ -59,23 +59,29 @@ export function createDevicePickerUI(
|
|
|
59
59
|
: 'Android Devices';
|
|
60
60
|
|
|
61
61
|
const deviceCards = devices
|
|
62
|
-
.map(
|
|
63
|
-
(device
|
|
64
|
-
|
|
62
|
+
.map((device, index) => {
|
|
63
|
+
const udid = String(device.udid);
|
|
64
|
+
const name = device.name || udid;
|
|
65
|
+
const state = device.state ? String(device.state) : undefined;
|
|
66
|
+
const type = device.type ? String(device.type) : undefined;
|
|
67
|
+
const stateClass = state ? sanitizeClassName(state) : '';
|
|
68
|
+
|
|
69
|
+
return `
|
|
70
|
+
<div class="device-card" data-udid="${escapeHtml(udid)}" data-index="${index}">
|
|
65
71
|
<div class="device-header">
|
|
66
|
-
<h3>${
|
|
67
|
-
${
|
|
72
|
+
<h3>${escapeHtml(name)}</h3>
|
|
73
|
+
${state ? `<span class="device-state ${stateClass}">${escapeHtml(state)}</span>` : ''}
|
|
68
74
|
</div>
|
|
69
75
|
<div class="device-details">
|
|
70
|
-
<p><strong>UDID:</strong> <code>${
|
|
71
|
-
${
|
|
76
|
+
<p><strong>UDID:</strong> <code>${escapeHtml(udid)}</code></p>
|
|
77
|
+
${type ? `<p><strong>Type:</strong> ${escapeHtml(type)}</p>` : ''}
|
|
72
78
|
</div>
|
|
73
|
-
<button class="select-device-btn" onclick="selectDevice(
|
|
79
|
+
<button class="select-device-btn" data-udid="${escapeHtml(udid)}" onclick="selectDevice(this.dataset.udid)">
|
|
74
80
|
Select Device
|
|
75
81
|
</button>
|
|
76
82
|
</div>
|
|
77
|
-
|
|
78
|
-
)
|
|
83
|
+
`;
|
|
84
|
+
})
|
|
79
85
|
.join('');
|
|
80
86
|
|
|
81
87
|
return `
|
|
@@ -215,8 +221,8 @@ export function createDevicePickerUI(
|
|
|
215
221
|
payload: {
|
|
216
222
|
intent: 'select-device',
|
|
217
223
|
params: {
|
|
218
|
-
platform:
|
|
219
|
-
${deviceType ? `iosDeviceType:
|
|
224
|
+
platform: ${escapeScriptValue(platform)},
|
|
225
|
+
${deviceType ? `iosDeviceType: ${escapeScriptValue(deviceType)},` : ''}
|
|
220
226
|
deviceUdid: udid
|
|
221
227
|
}
|
|
222
228
|
}
|
|
@@ -247,6 +253,8 @@ export function createScreenshotViewerUI(
|
|
|
247
253
|
screenshotBase64: string,
|
|
248
254
|
filepath: string
|
|
249
255
|
): string {
|
|
256
|
+
const downloadFilename = filepath.split('/').pop() || 'screenshot.png';
|
|
257
|
+
|
|
250
258
|
return `
|
|
251
259
|
<!DOCTYPE html>
|
|
252
260
|
<html lang="en">
|
|
@@ -370,7 +378,7 @@ export function createScreenshotViewerUI(
|
|
|
370
378
|
<div class="toolbar">
|
|
371
379
|
<div class="toolbar-left">
|
|
372
380
|
<span style="font-size: 14px; font-weight: 500;">📸 Screenshot</span>
|
|
373
|
-
<span class="filepath">${filepath}</span>
|
|
381
|
+
<span class="filepath">${escapeHtml(filepath)}</span>
|
|
374
382
|
</div>
|
|
375
383
|
<div class="toolbar-right">
|
|
376
384
|
<button class="btn btn-secondary" onclick="downloadScreenshot()">Download</button>
|
|
@@ -378,7 +386,7 @@ export function createScreenshotViewerUI(
|
|
|
378
386
|
</div>
|
|
379
387
|
</div>
|
|
380
388
|
<div class="image-container" id="imageContainer">
|
|
381
|
-
<img src="data:image/png;base64,${screenshotBase64}"
|
|
389
|
+
<img src="data:image/png;base64,${escapeHtml(screenshotBase64)}"
|
|
382
390
|
alt="Screenshot"
|
|
383
391
|
class="screenshot-img"
|
|
384
392
|
id="screenshotImg"
|
|
@@ -420,7 +428,7 @@ export function createScreenshotViewerUI(
|
|
|
420
428
|
function downloadScreenshot() {
|
|
421
429
|
const link = document.createElement('a');
|
|
422
430
|
link.href = img.src;
|
|
423
|
-
link.download =
|
|
431
|
+
link.download = ${escapeScriptValue(downloadFilename)};
|
|
424
432
|
link.click();
|
|
425
433
|
}
|
|
426
434
|
|
|
@@ -470,6 +478,9 @@ export function createSessionDashboardUI(sessionInfo: {
|
|
|
470
478
|
sessionIdStr.length > 8
|
|
471
479
|
? `${sessionIdStr.substring(0, 8)}...`
|
|
472
480
|
: sessionIdStr;
|
|
481
|
+
const deviceName = sessionInfo.deviceName;
|
|
482
|
+
const platformVersion = sessionInfo.platformVersion;
|
|
483
|
+
const udid = sessionInfo.udid;
|
|
473
484
|
|
|
474
485
|
return `
|
|
475
486
|
<!DOCTYPE html>
|
|
@@ -590,42 +601,42 @@ export function createSessionDashboardUI(sessionInfo: {
|
|
|
590
601
|
<div class="info-grid">
|
|
591
602
|
<div class="info-card">
|
|
592
603
|
<label>Session ID</label>
|
|
593
|
-
<value>${sessionIdDisplay}</value>
|
|
604
|
+
<value>${escapeHtml(sessionIdDisplay)}</value>
|
|
594
605
|
</div>
|
|
595
606
|
<div class="info-card">
|
|
596
607
|
<label>Platform</label>
|
|
597
|
-
<value>${sessionInfo.platform}</value>
|
|
608
|
+
<value>${escapeHtml(sessionInfo.platform)}</value>
|
|
598
609
|
</div>
|
|
599
610
|
<div class="info-card">
|
|
600
611
|
<label>Automation</label>
|
|
601
|
-
<value>${sessionInfo.automationName}</value>
|
|
612
|
+
<value>${escapeHtml(sessionInfo.automationName)}</value>
|
|
602
613
|
</div>
|
|
603
614
|
${
|
|
604
|
-
|
|
615
|
+
deviceName
|
|
605
616
|
? `
|
|
606
617
|
<div class="info-card">
|
|
607
618
|
<label>Device</label>
|
|
608
|
-
<value>${
|
|
619
|
+
<value>${escapeHtml(deviceName)}</value>
|
|
609
620
|
</div>
|
|
610
621
|
`
|
|
611
622
|
: ''
|
|
612
623
|
}
|
|
613
624
|
${
|
|
614
|
-
|
|
625
|
+
platformVersion
|
|
615
626
|
? `
|
|
616
627
|
<div class="info-card">
|
|
617
628
|
<label>Platform Version</label>
|
|
618
|
-
<value>${
|
|
629
|
+
<value>${escapeHtml(platformVersion)}</value>
|
|
619
630
|
</div>
|
|
620
631
|
`
|
|
621
632
|
: ''
|
|
622
633
|
}
|
|
623
634
|
${
|
|
624
|
-
|
|
635
|
+
udid
|
|
625
636
|
? `
|
|
626
637
|
<div class="info-card">
|
|
627
638
|
<label>UDID</label>
|
|
628
|
-
<value><code style="font-size: 12px;">${
|
|
639
|
+
<value><code style="font-size: 12px;">${escapeHtml(udid)}</code></value>
|
|
629
640
|
</div>
|
|
630
641
|
`
|
|
631
642
|
: ''
|
|
@@ -1260,17 +1271,17 @@ export function createAppListUI(
|
|
|
1260
1271
|
const appCards = apps
|
|
1261
1272
|
.map(
|
|
1262
1273
|
(app) => `
|
|
1263
|
-
<div class="app-card" data-package="${app.packageName}">
|
|
1274
|
+
<div class="app-card" data-package="${escapeHtml(app.packageName)}">
|
|
1264
1275
|
<div class="app-header">
|
|
1265
|
-
<h3>${app.appName || app.packageName}</h3>
|
|
1276
|
+
<h3>${escapeHtml(app.appName || app.packageName)}</h3>
|
|
1266
1277
|
</div>
|
|
1267
1278
|
<div class="app-details">
|
|
1268
|
-
<p><strong>Package:</strong> <code>${app.packageName}</code></p>
|
|
1279
|
+
<p><strong>Package:</strong> <code>${escapeHtml(app.packageName)}</code></p>
|
|
1269
1280
|
</div>
|
|
1270
1281
|
<div class="app-actions">
|
|
1271
|
-
<button class="btn btn-primary"
|
|
1272
|
-
<button class="btn btn-secondary"
|
|
1273
|
-
<button class="btn btn-danger"
|
|
1282
|
+
<button class="btn btn-primary" data-package="${escapeHtml(app.packageName)}" onclick="activateApp(this.dataset.package)">Activate</button>
|
|
1283
|
+
<button class="btn btn-secondary" data-package="${escapeHtml(app.packageName)}" onclick="terminateApp(this.dataset.package)">Terminate</button>
|
|
1284
|
+
<button class="btn btn-danger" data-package="${escapeHtml(app.packageName)}" onclick="uninstallApp(this.dataset.package)">Uninstall</button>
|
|
1274
1285
|
</div>
|
|
1275
1286
|
</div>
|
|
1276
1287
|
`
|
|
@@ -1652,3 +1663,21 @@ function escapeHtml(value: unknown): string {
|
|
|
1652
1663
|
.replace(/"/g, '"')
|
|
1653
1664
|
.replace(/'/g, ''');
|
|
1654
1665
|
}
|
|
1666
|
+
|
|
1667
|
+
function escapeScriptValue(value: unknown): string {
|
|
1668
|
+
return JSON.stringify(String(value))
|
|
1669
|
+
.replace(/</g, '\\u003C')
|
|
1670
|
+
.replace(/>/g, '\\u003E')
|
|
1671
|
+
.replace(/&/g, '\\u0026')
|
|
1672
|
+
.replace(/\u2028/g, '\\u2028')
|
|
1673
|
+
.replace(/\u2029/g, '\\u2029');
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
function sanitizeClassName(value: unknown): string {
|
|
1677
|
+
return escapeHtml(
|
|
1678
|
+
String(value)
|
|
1679
|
+
.toLowerCase()
|
|
1680
|
+
.replace(/[^a-z0-9_-]+/g, '-')
|
|
1681
|
+
.replace(/^-+|-+$/g, '')
|
|
1682
|
+
);
|
|
1683
|
+
}
|