appium-mcp 1.67.0 → 1.68.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/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ ## [1.68.0](https://github.com/appium/appium-mcp/compare/v1.67.1...v1.68.0) (2026-04-29)
2
+
3
+ ### Features
4
+
5
+ * **tools:** single appium_mobile_clipboard tool ([#292](https://github.com/appium/appium-mcp/issues/292)) ([ce7c7dc](https://github.com/appium/appium-mcp/commit/ce7c7dc6e20f0dd43b0ebf3f56651065af55fa8c))
6
+
7
+ ### Bug Fixes
8
+
9
+ * loading mcp server to start ([#298](https://github.com/appium/appium-mcp/issues/298)) ([82414d6](https://github.com/appium/appium-mcp/commit/82414d6dbec351887758aae9414ac9adfc4de22d))
10
+
11
+ ## [1.67.1](https://github.com/appium/appium-mcp/compare/v1.67.0...v1.67.1) (2026-04-27)
12
+
13
+ ### Bug Fixes
14
+
15
+ * support zoom in/out via custom coords ([#291](https://github.com/appium/appium-mcp/issues/291)) ([251e144](https://github.com/appium/appium-mcp/commit/251e144f9a21ca0909df93ba52bc8b748e4e20b4))
16
+
1
17
  ## [1.67.0](https://github.com/appium/appium-mcp/compare/v1.66.0...v1.67.0) (2026-04-23)
2
18
 
3
19
  ### Features
package/README.md CHANGED
@@ -343,8 +343,7 @@ The default regex pattern allows any URL that starts with `http://` or `https://
343
343
  | `appium_mobile_hide_keyboard` | Dismiss the on-screen keyboard (`mobile: hideKeyboard`) |
344
344
  | `appium_mobile_is_keyboard_shown` | Whether the on-screen keyboard is visible (`mobile: isKeyboardShown`) |
345
345
  | `appium_get_text` | Get text content from an element |
346
- | `appium_get_clipboard` | Get the current clipboard content as plain text from the device |
347
- | `appium_set_clipboard` | Set the device clipboard to the provided plain text |
346
+ | `appium_mobile_clipboard` | Read or set device clipboard plain text. `action=get` \| `set` (`content` required for set). |
348
347
  | `appium_alert` | Handle alerts with `action` = `accept`, `dismiss`, or `get_text` (optional `buttonLabel`) |
349
348
 
350
349
  ### Screen & Navigation
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ async function startServer() {
9
9
  log.info('Starting MCP Appium MCP Server...');
10
10
  try {
11
11
  if (useHttpStream) {
12
- server.start({
12
+ await server.start({
13
13
  transportType: 'httpStream',
14
14
  httpStream: {
15
15
  endpoint: '/sse',
@@ -21,7 +21,7 @@ async function startServer() {
21
21
  }
22
22
  else {
23
23
  // Start with stdio transport
24
- server.start({
24
+ await server.start({
25
25
  transportType: 'stdio',
26
26
  });
27
27
  log.info('Server started with stdio transport');
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,GAAG,MAAM,aAAa,CAAC;AAE9B,+BAA+B;AAC/B,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACnC,MAAM,aAAa,GAAG,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC;AACpD,MAAM,IAAI,GACR,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC;AAEzE,KAAK,UAAU,WAAW;IACxB,GAAG,CAAC,IAAI,CAAC,mCAAmC,CAAC,CAAC;IAE9C,IAAI,CAAC;QACH,IAAI,aAAa,EAAE,CAAC;YAClB,MAAM,CAAC,KAAK,CAAC;gBACX,aAAa,EAAE,YAAY;gBAC3B,UAAU,EAAE;oBACV,QAAQ,EAAE,MAAM;oBAChB,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;iBACzB;aACF,CAAC,CAAC;YAEH,GAAG,CAAC,IAAI,CACN,gEAAgE,IAAI,MAAM,CAC3E,CAAC;YACF,GAAG,CAAC,IAAI,CAAC,mCAAmC,CAAC,CAAC;QAChD,CAAC;aAAM,CAAC;YACN,6BAA6B;YAC7B,MAAM,CAAC,KAAK,CAAC;gBACX,aAAa,EAAE,OAAO;aACvB,CAAC,CAAC;YAEH,GAAG,CAAC,IAAI,CAAC,qCAAqC,CAAC,CAAC;YAChD,GAAG,CAAC,IAAI,CAAC,mCAAmC,CAAC,CAAC;QAChD,CAAC;IACH,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QACpB,GAAG,CAAC,KAAK,CAAC,wBAAwB,EAAE,KAAK,CAAC,CAAC;QAC3C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AAED,mBAAmB;AACnB,WAAW,EAAE,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,GAAG,MAAM,aAAa,CAAC;AAE9B,+BAA+B;AAC/B,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACnC,MAAM,aAAa,GAAG,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC;AACpD,MAAM,IAAI,GACR,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC;AAEzE,KAAK,UAAU,WAAW;IACxB,GAAG,CAAC,IAAI,CAAC,mCAAmC,CAAC,CAAC;IAE9C,IAAI,CAAC;QACH,IAAI,aAAa,EAAE,CAAC;YAClB,MAAM,MAAM,CAAC,KAAK,CAAC;gBACjB,aAAa,EAAE,YAAY;gBAC3B,UAAU,EAAE;oBACV,QAAQ,EAAE,MAAM;oBAChB,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;iBACzB;aACF,CAAC,CAAC;YAEH,GAAG,CAAC,IAAI,CACN,gEAAgE,IAAI,MAAM,CAC3E,CAAC;YACF,GAAG,CAAC,IAAI,CAAC,mCAAmC,CAAC,CAAC;QAChD,CAAC;aAAM,CAAC;YACN,6BAA6B;YAC7B,MAAM,MAAM,CAAC,KAAK,CAAC;gBACjB,aAAa,EAAE,OAAO;aACvB,CAAC,CAAC;YAEH,GAAG,CAAC,IAAI,CAAC,qCAAqC,CAAC,CAAC;YAChD,GAAG,CAAC,IAAI,CAAC,mCAAmC,CAAC,CAAC;QAChD,CAAC;IACH,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QACpB,GAAG,CAAC,KAAK,CAAC,wBAAwB,EAAE,KAAK,CAAC,CAAC;QAC3C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AAED,mBAAmB;AACnB,WAAW,EAAE,CAAC"}
@@ -1,5 +1,18 @@
1
1
  import type { ContentResult } from 'fastmcp';
2
2
  import type { DriverInstance } from '../../../session-store.js';
3
3
  import type { GestureArgs } from '../schema.js';
4
+ type RectLike = {
5
+ x: number;
6
+ y: number;
7
+ width: number;
8
+ height: number;
9
+ };
10
+ type PinchTarget = {
11
+ cx: number;
12
+ cy: number;
13
+ spread: number;
14
+ };
15
+ export declare function resolveElementPinchTarget(elementRect: RectLike, windowRect: RectLike): PinchTarget;
4
16
  export declare function handlePinchZoom(driver: DriverInstance, args: GestureArgs): Promise<ContentResult>;
17
+ export {};
5
18
  //# sourceMappingURL=pinch.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pinch.d.ts","sourceRoot":"","sources":["../../../../src/tools/gestures/handlers/pinch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAahE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAIhD,wBAAsB,eAAe,CACnC,MAAM,EAAE,cAAc,EACtB,IAAI,EAAE,WAAW,GAChB,OAAO,CAAC,aAAa,CAAC,CAmGxB"}
1
+ {"version":3,"file":"pinch.d.ts","sourceRoot":"","sources":["../../../../src/tools/gestures/handlers/pinch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAahE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAMhD,KAAK,QAAQ,GAAG;IACd,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,KAAK,WAAW,GAAG;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAmBF,wBAAgB,yBAAyB,CACvC,WAAW,EAAE,QAAQ,EACrB,UAAU,EAAE,QAAQ,GACnB,WAAW,CAyCb;AAwCD,wBAAsB,eAAe,CACnC,MAAM,EAAE,cAAc,EACtB,IAAI,EAAE,WAAW,GAChB,OAAO,CAAC,aAAa,CAAC,CA6JxB"}
@@ -2,12 +2,79 @@ import { getPlatformName, PLATFORM } from '../../../session-store.js';
2
2
  import { execute, getElementRect, getWindowRect, performActions, } from '../../../command.js';
3
3
  import { errorResult, textResult, toolErrorMessage, } from '../../tool-response.js';
4
4
  const DEFAULT_VELOCITY = 2.2;
5
+ const DEFAULT_PINCH_SPREAD_RATIO = 0.3;
6
+ const MIN_ELEMENT_PINCH_SPREAD_RATIO = 0.15;
7
+ function clamp(value, min, max) {
8
+ return Math.min(max, Math.max(min, value));
9
+ }
10
+ function maxSpreadForCenter(cx, cy, windowRect) {
11
+ const left = windowRect.x ?? 0;
12
+ const top = windowRect.y ?? 0;
13
+ const right = left + windowRect.width - 1;
14
+ const bottom = top + windowRect.height - 1;
15
+ return Math.max(1, Math.min(cx - left, right - cx, cy - top, bottom - cy));
16
+ }
17
+ export function resolveElementPinchTarget(elementRect, windowRect) {
18
+ const windowLeft = windowRect.x ?? 0;
19
+ const windowTop = windowRect.y ?? 0;
20
+ const windowRight = windowLeft + windowRect.width;
21
+ const windowBottom = windowTop + windowRect.height;
22
+ const visibleLeft = Math.max(windowLeft, elementRect.x);
23
+ const visibleTop = Math.max(windowTop, elementRect.y);
24
+ const visibleRight = Math.min(windowRight, elementRect.x + elementRect.width);
25
+ const visibleBottom = Math.min(windowBottom, elementRect.y + elementRect.height);
26
+ const rawCx = visibleRight > visibleLeft
27
+ ? visibleLeft + (visibleRight - visibleLeft) / 2
28
+ : elementRect.x + elementRect.width / 2;
29
+ const rawCy = visibleBottom > visibleTop
30
+ ? visibleTop + (visibleBottom - visibleTop) / 2
31
+ : elementRect.y + elementRect.height / 2;
32
+ const cx = Math.floor(clamp(rawCx, windowLeft, windowRight - 1));
33
+ const cy = Math.floor(clamp(rawCy, windowTop, windowBottom - 1));
34
+ const windowBase = Math.min(windowRect.width, windowRect.height);
35
+ const elementBase = Math.min(elementRect.width, elementRect.height);
36
+ const desired = Math.max(Math.floor(elementBase * DEFAULT_PINCH_SPREAD_RATIO), Math.floor(windowBase * MIN_ELEMENT_PINCH_SPREAD_RATIO));
37
+ const cappedDesired = Math.min(desired, Math.floor(windowBase * DEFAULT_PINCH_SPREAD_RATIO));
38
+ return {
39
+ cx,
40
+ cy,
41
+ spread: Math.min(cappedDesired, maxSpreadForCenter(cx, cy, windowRect)),
42
+ };
43
+ }
44
+ function resolveWindowPinchTarget(windowRect) {
45
+ const cx = Math.floor((windowRect.x ?? 0) + windowRect.width / 2);
46
+ const cy = Math.floor((windowRect.y ?? 0) + windowRect.height / 2);
47
+ return {
48
+ cx,
49
+ cy,
50
+ spread: Math.floor(Math.min(windowRect.width, windowRect.height) * DEFAULT_PINCH_SPREAD_RATIO),
51
+ };
52
+ }
53
+ function resolveCoordinatePinchTarget(x, y, windowRect) {
54
+ const left = windowRect.x ?? 0;
55
+ const top = windowRect.y ?? 0;
56
+ const right = left + windowRect.width;
57
+ const bottom = top + windowRect.height;
58
+ if (x < left || x >= right || y < top || y >= bottom) {
59
+ return `pinch_zoom coordinates (${x}, ${y}) are outside window bounds (${windowRect.width}x${windowRect.height}).`;
60
+ }
61
+ const desired = Math.floor(Math.min(windowRect.width, windowRect.height) * DEFAULT_PINCH_SPREAD_RATIO);
62
+ return {
63
+ cx: x,
64
+ cy: y,
65
+ spread: Math.min(desired, maxSpreadForCenter(x, y, windowRect)),
66
+ };
67
+ }
5
68
  export async function handlePinchZoom(driver, args) {
6
69
  if (args.scale === undefined) {
7
70
  return errorResult('pinch_zoom requires a scale value (e.g. 0.5 to zoom out, 2.0 to zoom in).');
8
71
  }
9
72
  const scale = args.scale;
10
73
  const velocity = args.velocity ?? DEFAULT_VELOCITY;
74
+ if (!args.elementUUID && (args.x !== undefined) !== (args.y !== undefined)) {
75
+ return errorResult('pinch_zoom requires both x and y when using custom coordinates.');
76
+ }
77
+ const useCustomCoords = !args.elementUUID && args.x !== undefined && args.y !== undefined;
11
78
  try {
12
79
  const platform = getPlatformName(driver);
13
80
  let cx;
@@ -15,19 +82,24 @@ export async function handlePinchZoom(driver, args) {
15
82
  let spread;
16
83
  let windowRect = null;
17
84
  if (args.elementUUID) {
85
+ windowRect = await getWindowRect(driver);
18
86
  const rect = await getElementRect(driver, args.elementUUID);
19
- cx = Math.floor(rect.x + rect.width / 2);
20
- cy = Math.floor(rect.y + rect.height / 2);
21
- spread = Math.floor(Math.min(rect.width, rect.height) * 0.3);
87
+ ({ cx, cy, spread } = resolveElementPinchTarget(rect, windowRect));
88
+ }
89
+ else if (useCustomCoords) {
90
+ windowRect = await getWindowRect(driver);
91
+ const target = resolveCoordinatePinchTarget(args.x, args.y, windowRect);
92
+ if (typeof target === 'string') {
93
+ return errorResult(target);
94
+ }
95
+ ({ cx, cy, spread } = target);
22
96
  }
23
97
  else {
24
98
  windowRect = await getWindowRect(driver);
25
- cx = Math.floor(windowRect.width / 2);
26
- cy = Math.floor(windowRect.height / 2);
27
- spread = Math.floor(Math.min(windowRect.width, windowRect.height) * 0.3);
99
+ ({ cx, cy, spread } = resolveWindowPinchTarget(windowRect));
28
100
  }
29
101
  if (scale < 1) {
30
- // Zoom out: two fingers move from spread apart to close together. W3C Actions on both platforms.
102
+ // Zoom out: W3C Actions on all platforms; fingers move together.
31
103
  const startSpread = spread;
32
104
  const endSpread = Math.max(1, Math.floor(spread * scale));
33
105
  const duration = Math.floor((1 / Math.abs(velocity)) * 1000);
@@ -56,6 +128,49 @@ export async function handlePinchZoom(driver, args) {
56
128
  },
57
129
  ]);
58
130
  }
131
+ else if (useCustomCoords && platform === PLATFORM.android) {
132
+ // Zoom in at a custom center on Android: use the native pinchOpenGesture
133
+ // with a region centered at (cx, cy). spread is pre-clamped so the region
134
+ // fits within the window.
135
+ const percent = Math.min(0.99, 1 - 1 / scale);
136
+ await execute(driver, 'mobile: pinchOpenGesture', {
137
+ left: cx - spread,
138
+ top: cy - spread,
139
+ width: 2 * spread,
140
+ height: 2 * spread,
141
+ percent,
142
+ });
143
+ }
144
+ else if (useCustomCoords) {
145
+ // Zoom in at a custom center on iOS using W3C Actions
146
+ const startSpread = Math.max(1, Math.floor(spread / scale));
147
+ const endSpread = spread;
148
+ const duration = Math.floor((1 / Math.abs(velocity)) * 1000);
149
+ await performActions(driver, [
150
+ {
151
+ type: 'pointer',
152
+ id: 'finger1',
153
+ parameters: { pointerType: 'touch' },
154
+ actions: [
155
+ { type: 'pointerMove', duration: 0, x: cx - startSpread, y: cy },
156
+ { type: 'pointerDown', button: 0 },
157
+ { type: 'pointerMove', duration, x: cx - endSpread, y: cy },
158
+ { type: 'pointerUp', button: 0 },
159
+ ],
160
+ },
161
+ {
162
+ type: 'pointer',
163
+ id: 'finger2',
164
+ parameters: { pointerType: 'touch' },
165
+ actions: [
166
+ { type: 'pointerMove', duration: 0, x: cx + startSpread, y: cy },
167
+ { type: 'pointerDown', button: 0 },
168
+ { type: 'pointerMove', duration, x: cx + endSpread, y: cy },
169
+ { type: 'pointerUp', button: 0 },
170
+ ],
171
+ },
172
+ ]);
173
+ }
59
174
  else if (platform === PLATFORM.ios) {
60
175
  const params = {
61
176
  scale,
@@ -67,8 +182,8 @@ export async function handlePinchZoom(driver, args) {
67
182
  await execute(driver, 'mobile: pinch', params);
68
183
  }
69
184
  else if (platform === PLATFORM.android) {
70
- // Convert scale factor to percent (01) for pinchOpenGesture.
71
- // scale=2 0.5, scale=4 0.75, scale=10 0.9. Capped at 0.99.
185
+ // Convert scale factor to percent (0-1) for pinchOpenGesture.
186
+ // scale=2 -> 0.5, scale=4 -> 0.75, scale=10 -> 0.9. Capped at 0.99.
72
187
  const percent = Math.min(0.99, 1 - 1 / scale);
73
188
  const params = { percent };
74
189
  if (args.elementUUID) {
@@ -87,7 +202,11 @@ export async function handlePinchZoom(driver, args) {
87
202
  return errorResult(`pinch_zoom is not supported on platform '${platform}'. Supported: iOS, Android.`);
88
203
  }
89
204
  const direction = scale < 1 ? 'out' : 'in';
90
- const target = args.elementUUID ? `element ${args.elementUUID}` : 'screen';
205
+ const target = args.elementUUID
206
+ ? `element ${args.elementUUID}`
207
+ : useCustomCoords
208
+ ? `coordinates (${cx}, ${cy})`
209
+ : 'screen';
91
210
  return textResult(`Successfully pinched ${direction} (scale=${scale}) on ${target}.`);
92
211
  }
93
212
  catch (err) {
@@ -1 +1 @@
1
- {"version":3,"file":"pinch.js","sourceRoot":"","sources":["../../../../src/tools/gestures/handlers/pinch.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pinch.js","sourceRoot":"","sources":["../../../../src/tools/gestures/handlers/pinch.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/tools/gestures/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,eAAO,MAAM,eAAe,oGAQlB,CAAC;AAEX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7D,eAAO,MAAM,YAAY,qCAAsC,CAAC;AAChE,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvD,eAAO,MAAM,uBAAuB,uCAAwC,CAAC;AAC7E,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,uBAAuB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE5E,eAAO,MAAM,kBAAkB,yJAUrB,CAAC;AAEX,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAkJxB,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/tools/gestures/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,eAAO,MAAM,eAAe,oGAQlB,CAAC;AAEX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7D,eAAO,MAAM,YAAY,qCAAsC,CAAC;AAChE,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvD,eAAO,MAAM,uBAAuB,uCAAwC,CAAC;AAC7E,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,uBAAuB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE5E,eAAO,MAAM,kBAAkB,yJAUrB,CAAC;AAEX,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAoJxB,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC"}
@@ -47,7 +47,8 @@ export const gestureSchema = z.object({
47
47
  .optional()
48
48
  .describe(`X coordinate. ` +
49
49
  `For tap/double_tap/long_press: tap location (alternative to elementUUID). ` +
50
- `For scroll/swipe: starting X for custom-coordinate mode (requires y, endX, endY).`),
50
+ `For scroll/swipe: starting X for custom-coordinate mode (requires y, endX, endY). ` +
51
+ `For pinch_zoom: center X of the pinch. Requires y. Ignored if elementUUID is set.`),
51
52
  y: z
52
53
  .number()
53
54
  .int()
@@ -55,7 +56,8 @@ export const gestureSchema = z.object({
55
56
  .optional()
56
57
  .describe(`Y coordinate. ` +
57
58
  `For tap/double_tap/long_press: tap location. ` +
58
- `For scroll/swipe: starting Y for custom-coordinate mode.`),
59
+ `For scroll/swipe: starting Y for custom-coordinate mode. ` +
60
+ `For pinch_zoom: center Y of the pinch. Requires x. Ignored if elementUUID is set.`),
59
61
  endX: z
60
62
  .number()
61
63
  .int()
@@ -1 +1 @@
1
- {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../src/tools/gestures/schema.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../src/tools/gestures/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEpD,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,KAAK;IACL,YAAY;IACZ,YAAY;IACZ,QAAQ;IACR,OAAO;IACP,YAAY;IACZ,mBAAmB;CACX,CAAC;AAIX,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAU,CAAC;AAGhE,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAU,CAAC;AAG7E,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,OAAO;IACP,IAAI;IACJ,MAAM;IACN,YAAY;IACZ,kBAAkB;IAClB,cAAc;IACd,sBAAsB;IACtB,uBAAuB;IACvB,kBAAkB;CACV,CAAC;AAEX,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC;IACpC,MAAM,EAAE,CAAC;SACN,IAAI,CAAC,eAAe,CAAC;SACrB,QAAQ,CACP,sBAAsB;QACpB,uCAAuC;QACvC,uFAAuF;QACvF,sEAAsE;QACtE,0DAA0D;QAC1D,2GAA2G;QAC3G,4FAA4F;QAC5F,6GAA6G;QAC7G,yIAAyI;QACzI,gFAAgF,CACnF;IAEH,WAAW,EAAE,iBAAiB;SAC3B,QAAQ,EAAE;SACV,QAAQ,CACP,6FAA6F;QAC3F,oDAAoD;QACpD,iIAAiI,CACpI;IAEH,CAAC,EAAE,CAAC;SACD,MAAM,EAAE;SACR,GAAG,EAAE;SACL,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,EAAE;SACV,QAAQ,CACP,gBAAgB;QACd,4EAA4E;QAC5E,oFAAoF;QACpF,mFAAmF,CACtF;IACH,CAAC,EAAE,CAAC;SACD,MAAM,EAAE;SACR,GAAG,EAAE;SACL,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,EAAE;SACV,QAAQ,CACP,gBAAgB;QACd,+CAA+C;QAC/C,2DAA2D;QAC3D,mFAAmF,CACtF;IACH,IAAI,EAAE,CAAC;SACJ,MAAM,EAAE;SACR,GAAG,EAAE;SACL,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,EAAE;SACV,QAAQ,CACP,uEAAuE,CACxE;IACH,IAAI,EAAE,CAAC;SACJ,MAAM,EAAE;SACR,GAAG,EAAE;SACL,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,EAAE;SACV,QAAQ,CACP,uEAAuE,CACxE;IAEH,SAAS,EAAE,CAAC;SACT,IAAI,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;SACrC,QAAQ,EAAE;SACV,QAAQ,CACP,gGAAgG;QAC9F,+FAA+F,CAClG;IAEH,KAAK,EAAE,CAAC;SACL,IAAI,CAAC,YAAY,CAAC;SAClB,QAAQ,EAAE;SACV,QAAQ,CACP,8KAA8K,CAC/K;IAEH,QAAQ,EAAE,CAAC;SACR,MAAM,EAAE;SACR,GAAG,EAAE;SACL,GAAG,CAAC,CAAC,CAAC;SACN,GAAG,CAAC,KAAK,CAAC;SACV,QAAQ,EAAE;SACV,QAAQ,CACP,2FAA2F;QACzF,oFAAoF,CACvF;IAEH,KAAK,EAAE,CAAC;SACL,MAAM,EAAE;SACR,GAAG,CAAC,IAAI,CAAC;SACT,GAAG,CAAC,EAAE,CAAC;SACP,QAAQ,EAAE;SACV,QAAQ,CACP,wJAAwJ,CACzJ;IACH,QAAQ,EAAE,CAAC;SACR,MAAM,EAAE;SACR,GAAG,CAAC,GAAG,CAAC;SACR,GAAG,CAAC,EAAE,CAAC;SACP,QAAQ,EAAE;SACV,QAAQ,CACP,8EAA8E,CAC/E;IAEH,QAAQ,EAAE,CAAC;SACR,IAAI,CAAC,kBAAkB,CAAC;SACxB,QAAQ,EAAE;SACV,QAAQ,CAAC,oDAAoD,CAAC;IACjE,QAAQ,EAAE,CAAC;SACR,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,QAAQ,CAAC,0DAA0D,CAAC;IAEvE,iBAAiB,EAAE,CAAC;SACjB,MAAM,EAAE;SACR,GAAG,EAAE;SACL,GAAG,CAAC,CAAC,CAAC;SACN,GAAG,CAAC,EAAE,CAAC;SACP,QAAQ,EAAE;SACV,OAAO,CAAC,EAAE,CAAC;SACX,QAAQ,CACP,oGAAoG,CACrG;IAEH,cAAc,EAAE,CAAC;SACd,MAAM,EAAE;SACR,GAAG,CAAC,IAAI,CAAC;SACT,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,EAAE;SACV,QAAQ,CACP,oGAAoG;QAClG,qGAAqG,CACxG;IAEH,oBAAoB,EAAE,CAAC;SACpB,IAAI,CAAC,uBAAuB,CAAC;SAC7B,QAAQ,EAAE;SACV,QAAQ,CACP,0HAA0H;QACxH,qCAAqC,CACxC;IAEH,SAAS,EAAE,CAAC;SACT,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,QAAQ,CAAC,4DAA4D,CAAC;CAC1E,CAAC,CAAC"}
@@ -1,13 +1,3 @@
1
1
  import type { FastMCP } from 'fastmcp';
2
- /**
3
- * Register clipboard read/write tools.
4
- *
5
- * - appium_get_clipboard: reads the current clipboard content as plain text
6
- * - appium_set_clipboard: writes plain text to the clipboard
7
- *
8
- * Both tools rely on the `mobile: getClipboard` / `mobile: setClipboard`
9
- * Appium execute commands and work on Android, iOS, and remote WebDriver
10
- * sessions.
11
- */
12
2
  export default function clipboard(server: FastMCP): void;
13
3
  //# sourceMappingURL=clipboard.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"clipboard.d.ts","sourceRoot":"","sources":["../../../src/tools/interactions/clipboard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,OAAO,EAAE,MAAM,SAAS,CAAC;AAUtD;;;;;;;;;GASG;AACH,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAyFvD"}
1
+ {"version":3,"file":"clipboard.d.ts","sourceRoot":"","sources":["../../../src/tools/interactions/clipboard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,OAAO,EAAE,MAAM,SAAS,CAAC;AA0DtD,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAiCvD"}
@@ -1,84 +1,65 @@
1
1
  import { z } from 'zod';
2
2
  import { getClipboard, setClipboard } from '../../command.js';
3
3
  import { resolveDriver, textResult, errorResult, toolErrorMessage, } from '../tool-response.js';
4
- /**
5
- * Register clipboard read/write tools.
6
- *
7
- * - appium_get_clipboard: reads the current clipboard content as plain text
8
- * - appium_set_clipboard: writes plain text to the clipboard
9
- *
10
- * Both tools rely on the `mobile: getClipboard` / `mobile: setClipboard`
11
- * Appium execute commands and work on Android, iOS, and remote WebDriver
12
- * sessions.
13
- */
4
+ const schema = z.object({
5
+ action: z
6
+ .enum(['get', 'set'])
7
+ .describe('get: read device clipboard as plain text. set: write plain text to the clipboard.'),
8
+ content: z
9
+ .string()
10
+ .optional()
11
+ .describe('Required when action is set. Plain text to put on the clipboard.'),
12
+ sessionId: z
13
+ .string()
14
+ .optional()
15
+ .describe('Session ID to target. If omitted, uses the active session.'),
16
+ });
17
+ async function handleGet(sessionId) {
18
+ const resolved = resolveDriver(sessionId);
19
+ if (!resolved.ok) {
20
+ return resolved.result;
21
+ }
22
+ const { driver } = resolved;
23
+ const text = await getClipboard(driver);
24
+ if (!text) {
25
+ return textResult('Clipboard is empty.');
26
+ }
27
+ return textResult(`Clipboard content: ${text}`);
28
+ }
29
+ async function handleSet(sessionId, content) {
30
+ const resolved = resolveDriver(sessionId);
31
+ if (!resolved.ok) {
32
+ return resolved.result;
33
+ }
34
+ const { driver } = resolved;
35
+ await setClipboard(driver, content);
36
+ return textResult(`Successfully set clipboard content to: ${content}`);
37
+ }
14
38
  export default function clipboard(server) {
15
- // ─── Get Clipboard ────────────────────────────────────────────────────────
16
- server.addTool({
17
- name: 'appium_mobile_get_clipboard',
18
- description: 'Get the current clipboard content as plain text from the device. ' +
19
- 'Works on Android (UiAutomator2) and iOS (XCUITest). ' +
20
- 'Returns an empty string if the clipboard is empty.',
21
- parameters: z.object({
22
- sessionId: z
23
- .string()
24
- .optional()
25
- .describe('Session ID to target. If omitted, uses the active session.'),
26
- }),
27
- annotations: {
28
- readOnlyHint: true,
29
- openWorldHint: false,
30
- },
31
- execute: async (args, _context) => {
32
- const resolved = resolveDriver(args.sessionId);
33
- if (!resolved.ok) {
34
- return resolved.result;
35
- }
36
- const { driver } = resolved;
37
- try {
38
- const content = await getClipboard(driver);
39
- if (!content) {
40
- return textResult('Clipboard is empty.');
41
- }
42
- return textResult(`Clipboard content: ${content}`);
43
- }
44
- catch (err) {
45
- return errorResult(`Failed to get clipboard content. err: ${toolErrorMessage(err)}`);
46
- }
47
- },
48
- });
49
- // ─── Set Clipboard ────────────────────────────────────────────────────────
50
- const setClipboardSchema = z.object({
51
- content: z
52
- .string()
53
- .describe('The plain text content to write to the device clipboard'),
54
- sessionId: z
55
- .string()
56
- .optional()
57
- .describe('Session ID to target. If omitted, uses the active session.'),
58
- });
59
39
  server.addTool({
60
- name: 'appium_mobile_set_clipboard',
61
- description: 'Set the device clipboard to the provided plain text. ' +
62
- 'Works on Android (UiAutomator2) and iOS (XCUITest). ' +
63
- 'Useful for pre-filling clipboard content before testing paste operations, ' +
64
- 'or for injecting long strings without typing them character by character.',
65
- parameters: setClipboardSchema,
40
+ name: 'appium_mobile_clipboard',
41
+ description: 'Read or set the device clipboard as plain text (Android UiAutomator2 / iOS XCUITest). ' +
42
+ 'action=get returns current text; action=set requires content.',
43
+ parameters: schema,
66
44
  annotations: {
67
45
  readOnlyHint: false,
68
46
  openWorldHint: false,
69
47
  },
70
48
  execute: async (args, _context) => {
71
- const resolved = resolveDriver(args.sessionId);
72
- if (!resolved.ok) {
73
- return resolved.result;
74
- }
75
- const { driver } = resolved;
76
49
  try {
77
- await setClipboard(driver, args.content);
78
- return textResult(`Successfully set clipboard content to: ${args.content}`);
50
+ switch (args.action) {
51
+ case 'get':
52
+ return await handleGet(args.sessionId);
53
+ case 'set': {
54
+ if (args.content === undefined) {
55
+ return errorResult('content is required for set action');
56
+ }
57
+ return await handleSet(args.sessionId, args.content);
58
+ }
59
+ }
79
60
  }
80
61
  catch (err) {
81
- return errorResult(`Failed to set clipboard content. err: ${toolErrorMessage(err)}`);
62
+ return errorResult(`Failed to ${args.action} clipboard. err: ${toolErrorMessage(err)}`);
82
63
  }
83
64
  },
84
65
  });
@@ -1 +1 @@
1
- {"version":3,"file":"clipboard.js","sourceRoot":"","sources":["../../../src/tools/interactions/clipboard.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"clipboard.js","sourceRoot":"","sources":["../../../src/tools/interactions/clipboard.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EACL,aAAa,EACb,UAAU,EACV,WAAW,EACX,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAE7B,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC;IACtB,MAAM,EAAE,CAAC;SACN,IAAI,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;SACpB,QAAQ,CACP,mFAAmF,CACpF;IACH,OAAO,EAAE,CAAC;SACP,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,QAAQ,CACP,kEAAkE,CACnE;IACH,SAAS,EAAE,CAAC;SACT,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,QAAQ,CAAC,4DAA4D,CAAC;CAC1E,CAAC,CAAC;AAIH,KAAK,UAAU,SAAS,CAAC,SAAkB;IACzC,MAAM,QAAQ,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;IAC1C,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,OAAO,QAAQ,CAAC,MAAM,CAAC;IACzB,CAAC;IACD,MAAM,EAAE,MAAM,EAAE,GAAG,QAAQ,CAAC;IAE5B,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,MAAM,CAAC,CAAC;IACxC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,UAAU,CAAC,qBAAqB,CAAC,CAAC;IAC3C,CAAC;IACD,OAAO,UAAU,CAAC,sBAAsB,IAAI,EAAE,CAAC,CAAC;AAClD,CAAC;AAED,KAAK,UAAU,SAAS,CACtB,SAA6B,EAC7B,OAAe;IAEf,MAAM,QAAQ,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;IAC1C,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,OAAO,QAAQ,CAAC,MAAM,CAAC;IACzB,CAAC;IACD,MAAM,EAAE,MAAM,EAAE,GAAG,QAAQ,CAAC;IAE5B,MAAM,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,OAAO,UAAU,CAAC,0CAA0C,OAAO,EAAE,CAAC,CAAC;AACzE,CAAC;AAED,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,MAAe;IAC/C,MAAM,CAAC,OAAO,CAAC;QACb,IAAI,EAAE,yBAAyB;QAC/B,WAAW,EACT,wFAAwF;YACxF,+DAA+D;QACjE,UAAU,EAAE,MAAM;QAClB,WAAW,EAAE;YACX,YAAY,EAAE,KAAK;YACnB,aAAa,EAAE,KAAK;SACrB;QACD,OAAO,EAAE,KAAK,EACZ,IAAmB,EACnB,QAA6C,EACrB,EAAE;YAC1B,IAAI,CAAC;gBACH,QAAQ,IAAI,CAAC,MAAM,EAAE,CAAC;oBACpB,KAAK,KAAK;wBACR,OAAO,MAAM,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;oBACzC,KAAK,KAAK,CAAC,CAAC,CAAC;wBACX,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;4BAC/B,OAAO,WAAW,CAAC,oCAAoC,CAAC,CAAC;wBAC3D,CAAC;wBACD,OAAO,MAAM,SAAS,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;oBACvD,CAAC;gBACH,CAAC;YACH,CAAC;YAAC,OAAO,GAAY,EAAE,CAAC;gBACtB,OAAO,WAAW,CAChB,aAAa,IAAI,CAAC,MAAM,oBAAoB,gBAAgB,CAAC,GAAG,CAAC,EAAE,CACpE,CAAC;YACJ,CAAC;QACH,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "appium-mcp",
3
3
  "mcpName": "io.github.appium/appium-mcp",
4
- "version": "1.67.0",
4
+ "version": "1.68.0",
5
5
  "type": "module",
6
6
  "repository": {
7
7
  "type": "git",
package/server.json CHANGED
@@ -3,12 +3,12 @@
3
3
  "name": "io.github.appium/appium-mcp",
4
4
  "title": "MCP Appium - Mobile Development and Automation Server",
5
5
  "description": "MCP server for Appium mobile automation on iOS and Android devices with test creation tools.",
6
- "version": "1.67.0",
6
+ "version": "1.68.0",
7
7
  "packages": [
8
8
  {
9
9
  "registryType": "npm",
10
10
  "identifier": "appium-mcp",
11
- "version": "1.67.0",
11
+ "version": "1.68.0",
12
12
  "transport": {
13
13
  "type": "stdio"
14
14
  }
package/src/index.ts CHANGED
@@ -14,7 +14,7 @@ async function startServer(): Promise<void> {
14
14
 
15
15
  try {
16
16
  if (useHttpStream) {
17
- server.start({
17
+ await server.start({
18
18
  transportType: 'httpStream',
19
19
  httpStream: {
20
20
  endpoint: '/sse',
@@ -28,7 +28,7 @@ async function startServer(): Promise<void> {
28
28
  log.info('Waiting for client connections...');
29
29
  } else {
30
30
  // Start with stdio transport
31
- server.start({
31
+ await server.start({
32
32
  transportType: 'stdio',
33
33
  });
34
34
 
Binary file
@@ -15,6 +15,122 @@ import {
15
15
  import type { GestureArgs } from '../schema.js';
16
16
 
17
17
  const DEFAULT_VELOCITY = 2.2;
18
+ const DEFAULT_PINCH_SPREAD_RATIO = 0.3;
19
+ const MIN_ELEMENT_PINCH_SPREAD_RATIO = 0.15;
20
+
21
+ type RectLike = {
22
+ x: number;
23
+ y: number;
24
+ width: number;
25
+ height: number;
26
+ };
27
+
28
+ type PinchTarget = {
29
+ cx: number;
30
+ cy: number;
31
+ spread: number;
32
+ };
33
+
34
+ function clamp(value: number, min: number, max: number): number {
35
+ return Math.min(max, Math.max(min, value));
36
+ }
37
+
38
+ function maxSpreadForCenter(
39
+ cx: number,
40
+ cy: number,
41
+ windowRect: RectLike
42
+ ): number {
43
+ const left = windowRect.x ?? 0;
44
+ const top = windowRect.y ?? 0;
45
+ const right = left + windowRect.width - 1;
46
+ const bottom = top + windowRect.height - 1;
47
+
48
+ return Math.max(1, Math.min(cx - left, right - cx, cy - top, bottom - cy));
49
+ }
50
+
51
+ export function resolveElementPinchTarget(
52
+ elementRect: RectLike,
53
+ windowRect: RectLike
54
+ ): PinchTarget {
55
+ const windowLeft = windowRect.x ?? 0;
56
+ const windowTop = windowRect.y ?? 0;
57
+ const windowRight = windowLeft + windowRect.width;
58
+ const windowBottom = windowTop + windowRect.height;
59
+
60
+ const visibleLeft = Math.max(windowLeft, elementRect.x);
61
+ const visibleTop = Math.max(windowTop, elementRect.y);
62
+ const visibleRight = Math.min(windowRight, elementRect.x + elementRect.width);
63
+ const visibleBottom = Math.min(
64
+ windowBottom,
65
+ elementRect.y + elementRect.height
66
+ );
67
+
68
+ const rawCx =
69
+ visibleRight > visibleLeft
70
+ ? visibleLeft + (visibleRight - visibleLeft) / 2
71
+ : elementRect.x + elementRect.width / 2;
72
+ const rawCy =
73
+ visibleBottom > visibleTop
74
+ ? visibleTop + (visibleBottom - visibleTop) / 2
75
+ : elementRect.y + elementRect.height / 2;
76
+
77
+ const cx = Math.floor(clamp(rawCx, windowLeft, windowRight - 1));
78
+ const cy = Math.floor(clamp(rawCy, windowTop, windowBottom - 1));
79
+ const windowBase = Math.min(windowRect.width, windowRect.height);
80
+ const elementBase = Math.min(elementRect.width, elementRect.height);
81
+ const desired = Math.max(
82
+ Math.floor(elementBase * DEFAULT_PINCH_SPREAD_RATIO),
83
+ Math.floor(windowBase * MIN_ELEMENT_PINCH_SPREAD_RATIO)
84
+ );
85
+ const cappedDesired = Math.min(
86
+ desired,
87
+ Math.floor(windowBase * DEFAULT_PINCH_SPREAD_RATIO)
88
+ );
89
+
90
+ return {
91
+ cx,
92
+ cy,
93
+ spread: Math.min(cappedDesired, maxSpreadForCenter(cx, cy, windowRect)),
94
+ };
95
+ }
96
+
97
+ function resolveWindowPinchTarget(windowRect: RectLike): PinchTarget {
98
+ const cx = Math.floor((windowRect.x ?? 0) + windowRect.width / 2);
99
+ const cy = Math.floor((windowRect.y ?? 0) + windowRect.height / 2);
100
+
101
+ return {
102
+ cx,
103
+ cy,
104
+ spread: Math.floor(
105
+ Math.min(windowRect.width, windowRect.height) * DEFAULT_PINCH_SPREAD_RATIO
106
+ ),
107
+ };
108
+ }
109
+
110
+ function resolveCoordinatePinchTarget(
111
+ x: number,
112
+ y: number,
113
+ windowRect: RectLike
114
+ ): PinchTarget | string {
115
+ const left = windowRect.x ?? 0;
116
+ const top = windowRect.y ?? 0;
117
+ const right = left + windowRect.width;
118
+ const bottom = top + windowRect.height;
119
+
120
+ if (x < left || x >= right || y < top || y >= bottom) {
121
+ return `pinch_zoom coordinates (${x}, ${y}) are outside window bounds (${windowRect.width}x${windowRect.height}).`;
122
+ }
123
+
124
+ const desired = Math.floor(
125
+ Math.min(windowRect.width, windowRect.height) * DEFAULT_PINCH_SPREAD_RATIO
126
+ );
127
+
128
+ return {
129
+ cx: x,
130
+ cy: y,
131
+ spread: Math.min(desired, maxSpreadForCenter(x, y, windowRect)),
132
+ };
133
+ }
18
134
 
19
135
  export async function handlePinchZoom(
20
136
  driver: DriverInstance,
@@ -28,6 +144,14 @@ export async function handlePinchZoom(
28
144
  const scale = args.scale;
29
145
  const velocity = args.velocity ?? DEFAULT_VELOCITY;
30
146
 
147
+ if (!args.elementUUID && (args.x !== undefined) !== (args.y !== undefined)) {
148
+ return errorResult(
149
+ 'pinch_zoom requires both x and y when using custom coordinates.'
150
+ );
151
+ }
152
+ const useCustomCoords =
153
+ !args.elementUUID && args.x !== undefined && args.y !== undefined;
154
+
31
155
  try {
32
156
  const platform = getPlatformName(driver);
33
157
 
@@ -37,23 +161,69 @@ export async function handlePinchZoom(
37
161
  let windowRect: Awaited<ReturnType<typeof getWindowRect>> | null = null;
38
162
 
39
163
  if (args.elementUUID) {
164
+ windowRect = await getWindowRect(driver);
40
165
  const rect = await getElementRect(driver, args.elementUUID);
41
- cx = Math.floor(rect.x + rect.width / 2);
42
- cy = Math.floor(rect.y + rect.height / 2);
43
- spread = Math.floor(Math.min(rect.width, rect.height) * 0.3);
166
+ ({ cx, cy, spread } = resolveElementPinchTarget(rect, windowRect));
167
+ } else if (useCustomCoords) {
168
+ windowRect = await getWindowRect(driver);
169
+ const target = resolveCoordinatePinchTarget(args.x!, args.y!, windowRect);
170
+ if (typeof target === 'string') {
171
+ return errorResult(target);
172
+ }
173
+ ({ cx, cy, spread } = target);
44
174
  } else {
45
175
  windowRect = await getWindowRect(driver);
46
- cx = Math.floor(windowRect.width / 2);
47
- cy = Math.floor(windowRect.height / 2);
48
- spread = Math.floor(Math.min(windowRect.width, windowRect.height) * 0.3);
176
+ ({ cx, cy, spread } = resolveWindowPinchTarget(windowRect));
49
177
  }
50
178
 
51
179
  if (scale < 1) {
52
- // Zoom out: two fingers move from spread apart to close together. W3C Actions on both platforms.
180
+ // Zoom out: W3C Actions on all platforms; fingers move together.
53
181
  const startSpread = spread;
54
182
  const endSpread = Math.max(1, Math.floor(spread * scale));
55
183
  const duration = Math.floor((1 / Math.abs(velocity)) * 1000);
56
184
 
185
+ await performActions(driver, [
186
+ {
187
+ type: 'pointer',
188
+ id: 'finger1',
189
+ parameters: { pointerType: 'touch' },
190
+ actions: [
191
+ { type: 'pointerMove', duration: 0, x: cx - startSpread, y: cy },
192
+ { type: 'pointerDown', button: 0 },
193
+ { type: 'pointerMove', duration, x: cx - endSpread, y: cy },
194
+ { type: 'pointerUp', button: 0 },
195
+ ],
196
+ },
197
+ {
198
+ type: 'pointer',
199
+ id: 'finger2',
200
+ parameters: { pointerType: 'touch' },
201
+ actions: [
202
+ { type: 'pointerMove', duration: 0, x: cx + startSpread, y: cy },
203
+ { type: 'pointerDown', button: 0 },
204
+ { type: 'pointerMove', duration, x: cx + endSpread, y: cy },
205
+ { type: 'pointerUp', button: 0 },
206
+ ],
207
+ },
208
+ ]);
209
+ } else if (useCustomCoords && platform === PLATFORM.android) {
210
+ // Zoom in at a custom center on Android: use the native pinchOpenGesture
211
+ // with a region centered at (cx, cy). spread is pre-clamped so the region
212
+ // fits within the window.
213
+ const percent = Math.min(0.99, 1 - 1 / scale);
214
+ await execute(driver, 'mobile: pinchOpenGesture', {
215
+ left: cx - spread,
216
+ top: cy - spread,
217
+ width: 2 * spread,
218
+ height: 2 * spread,
219
+ percent,
220
+ });
221
+ } else if (useCustomCoords) {
222
+ // Zoom in at a custom center on iOS using W3C Actions
223
+ const startSpread = Math.max(1, Math.floor(spread / scale));
224
+ const endSpread = spread;
225
+ const duration = Math.floor((1 / Math.abs(velocity)) * 1000);
226
+
57
227
  await performActions(driver, [
58
228
  {
59
229
  type: 'pointer',
@@ -88,8 +258,8 @@ export async function handlePinchZoom(
88
258
  }
89
259
  await execute(driver, 'mobile: pinch', params);
90
260
  } else if (platform === PLATFORM.android) {
91
- // Convert scale factor to percent (01) for pinchOpenGesture.
92
- // scale=2 0.5, scale=4 0.75, scale=10 0.9. Capped at 0.99.
261
+ // Convert scale factor to percent (0-1) for pinchOpenGesture.
262
+ // scale=2 -> 0.5, scale=4 -> 0.75, scale=10 -> 0.9. Capped at 0.99.
93
263
  const percent = Math.min(0.99, 1 - 1 / scale);
94
264
  const params: Record<string, unknown> = { percent };
95
265
  if (args.elementUUID) {
@@ -109,7 +279,11 @@ export async function handlePinchZoom(
109
279
  }
110
280
 
111
281
  const direction = scale < 1 ? 'out' : 'in';
112
- const target = args.elementUUID ? `element ${args.elementUUID}` : 'screen';
282
+ const target = args.elementUUID
283
+ ? `element ${args.elementUUID}`
284
+ : useCustomCoords
285
+ ? `coordinates (${cx}, ${cy})`
286
+ : 'screen';
113
287
  return textResult(
114
288
  `Successfully pinched ${direction} (scale=${scale}) on ${target}.`
115
289
  );
@@ -63,7 +63,8 @@ export const gestureSchema = z.object({
63
63
  .describe(
64
64
  `X coordinate. ` +
65
65
  `For tap/double_tap/long_press: tap location (alternative to elementUUID). ` +
66
- `For scroll/swipe: starting X for custom-coordinate mode (requires y, endX, endY).`
66
+ `For scroll/swipe: starting X for custom-coordinate mode (requires y, endX, endY). ` +
67
+ `For pinch_zoom: center X of the pinch. Requires y. Ignored if elementUUID is set.`
67
68
  ),
68
69
  y: z
69
70
  .number()
@@ -73,7 +74,8 @@ export const gestureSchema = z.object({
73
74
  .describe(
74
75
  `Y coordinate. ` +
75
76
  `For tap/double_tap/long_press: tap location. ` +
76
- `For scroll/swipe: starting Y for custom-coordinate mode.`
77
+ `For scroll/swipe: starting Y for custom-coordinate mode. ` +
78
+ `For pinch_zoom: center Y of the pinch. Requires x. Ignored if elementUUID is set.`
77
79
  ),
78
80
  endX: z
79
81
  .number()
@@ -8,101 +8,83 @@ import {
8
8
  toolErrorMessage,
9
9
  } from '../tool-response.js';
10
10
 
11
- /**
12
- * Register clipboard read/write tools.
13
- *
14
- * - appium_get_clipboard: reads the current clipboard content as plain text
15
- * - appium_set_clipboard: writes plain text to the clipboard
16
- *
17
- * Both tools rely on the `mobile: getClipboard` / `mobile: setClipboard`
18
- * Appium execute commands and work on Android, iOS, and remote WebDriver
19
- * sessions.
20
- */
21
- export default function clipboard(server: FastMCP): void {
22
- // ─── Get Clipboard ────────────────────────────────────────────────────────
11
+ const schema = z.object({
12
+ action: z
13
+ .enum(['get', 'set'])
14
+ .describe(
15
+ 'get: read device clipboard as plain text. set: write plain text to the clipboard.'
16
+ ),
17
+ content: z
18
+ .string()
19
+ .optional()
20
+ .describe(
21
+ 'Required when action is set. Plain text to put on the clipboard.'
22
+ ),
23
+ sessionId: z
24
+ .string()
25
+ .optional()
26
+ .describe('Session ID to target. If omitted, uses the active session.'),
27
+ });
23
28
 
24
- server.addTool({
25
- name: 'appium_mobile_get_clipboard',
26
- description:
27
- 'Get the current clipboard content as plain text from the device. ' +
28
- 'Works on Android (UiAutomator2) and iOS (XCUITest). ' +
29
- 'Returns an empty string if the clipboard is empty.',
30
- parameters: z.object({
31
- sessionId: z
32
- .string()
33
- .optional()
34
- .describe('Session ID to target. If omitted, uses the active session.'),
35
- }),
36
- annotations: {
37
- readOnlyHint: true,
38
- openWorldHint: false,
39
- },
40
- execute: async (
41
- args: { sessionId?: string },
42
- _context: Record<string, unknown> | undefined
43
- ): Promise<ContentResult> => {
44
- const resolved = resolveDriver(args.sessionId);
45
- if (!resolved.ok) {
46
- return resolved.result;
47
- }
48
- const { driver } = resolved;
29
+ type ClipboardArgs = z.infer<typeof schema>;
49
30
 
50
- try {
51
- const content = await getClipboard(driver);
52
- if (!content) {
53
- return textResult('Clipboard is empty.');
54
- }
55
- return textResult(`Clipboard content: ${content}`);
56
- } catch (err: unknown) {
57
- return errorResult(
58
- `Failed to get clipboard content. err: ${toolErrorMessage(err)}`
59
- );
60
- }
61
- },
62
- });
31
+ async function handleGet(sessionId?: string): Promise<ContentResult> {
32
+ const resolved = resolveDriver(sessionId);
33
+ if (!resolved.ok) {
34
+ return resolved.result;
35
+ }
36
+ const { driver } = resolved;
63
37
 
64
- // ─── Set Clipboard ────────────────────────────────────────────────────────
38
+ const text = await getClipboard(driver);
39
+ if (!text) {
40
+ return textResult('Clipboard is empty.');
41
+ }
42
+ return textResult(`Clipboard content: ${text}`);
43
+ }
65
44
 
66
- const setClipboardSchema = z.object({
67
- content: z
68
- .string()
69
- .describe('The plain text content to write to the device clipboard'),
70
- sessionId: z
71
- .string()
72
- .optional()
73
- .describe('Session ID to target. If omitted, uses the active session.'),
74
- });
45
+ async function handleSet(
46
+ sessionId: string | undefined,
47
+ content: string
48
+ ): Promise<ContentResult> {
49
+ const resolved = resolveDriver(sessionId);
50
+ if (!resolved.ok) {
51
+ return resolved.result;
52
+ }
53
+ const { driver } = resolved;
75
54
 
55
+ await setClipboard(driver, content);
56
+ return textResult(`Successfully set clipboard content to: ${content}`);
57
+ }
58
+
59
+ export default function clipboard(server: FastMCP): void {
76
60
  server.addTool({
77
- name: 'appium_mobile_set_clipboard',
61
+ name: 'appium_mobile_clipboard',
78
62
  description:
79
- 'Set the device clipboard to the provided plain text. ' +
80
- 'Works on Android (UiAutomator2) and iOS (XCUITest). ' +
81
- 'Useful for pre-filling clipboard content before testing paste operations, ' +
82
- 'or for injecting long strings without typing them character by character.',
83
- parameters: setClipboardSchema,
63
+ 'Read or set the device clipboard as plain text (Android UiAutomator2 / iOS XCUITest). ' +
64
+ 'action=get returns current text; action=set requires content.',
65
+ parameters: schema,
84
66
  annotations: {
85
67
  readOnlyHint: false,
86
68
  openWorldHint: false,
87
69
  },
88
70
  execute: async (
89
- args: z.infer<typeof setClipboardSchema>,
71
+ args: ClipboardArgs,
90
72
  _context: Record<string, unknown> | undefined
91
73
  ): Promise<ContentResult> => {
92
- const resolved = resolveDriver(args.sessionId);
93
- if (!resolved.ok) {
94
- return resolved.result;
95
- }
96
- const { driver } = resolved;
97
-
98
74
  try {
99
- await setClipboard(driver, args.content);
100
- return textResult(
101
- `Successfully set clipboard content to: ${args.content}`
102
- );
75
+ switch (args.action) {
76
+ case 'get':
77
+ return await handleGet(args.sessionId);
78
+ case 'set': {
79
+ if (args.content === undefined) {
80
+ return errorResult('content is required for set action');
81
+ }
82
+ return await handleSet(args.sessionId, args.content);
83
+ }
84
+ }
103
85
  } catch (err: unknown) {
104
86
  return errorResult(
105
- `Failed to set clipboard content. err: ${toolErrorMessage(err)}`
87
+ `Failed to ${args.action} clipboard. err: ${toolErrorMessage(err)}`
106
88
  );
107
89
  }
108
90
  },