appium-mcp 1.67.1 → 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,13 @@
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
+
1
11
  ## [1.67.1](https://github.com/appium/appium-mcp/compare/v1.67.0...v1.67.1) (2026-04-27)
2
12
 
3
13
  ### Bug Fixes
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,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.1",
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.1",
6
+ "version": "1.68.0",
7
7
  "packages": [
8
8
  {
9
9
  "registryType": "npm",
10
10
  "identifier": "appium-mcp",
11
- "version": "1.67.1",
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
@@ -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
  },