appium-mcp 1.32.0 → 1.33.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 +6 -0
- package/dist/tests/tools/session/file-transfer.test.d.ts +2 -0
- package/dist/tests/tools/session/file-transfer.test.d.ts.map +1 -0
- package/dist/tests/tools/session/file-transfer.test.js +87 -0
- package/dist/tests/tools/session/file-transfer.test.js.map +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +3 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/session/file-transfer.d.ts +13 -0
- package/dist/tools/session/file-transfer.d.ts.map +1 -0
- package/dist/tools/session/file-transfer.js +159 -0
- package/dist/tools/session/file-transfer.js.map +1 -0
- package/package.json +1 -1
- package/server.json +2 -2
- package/src/tests/tools/session/file-transfer.test.ts +158 -0
- package/src/tools/README.md +1 -0
- package/src/tools/index.ts +3 -0
- package/src/tools/session/file-transfer.ts +180 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
## [1.33.0](https://github.com/appium/appium-mcp/compare/v1.32.0...v1.33.0) (2026-03-22)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* **session:** add appium_mobile_push_file and appium_mobile_pull_file ([#222](https://github.com/appium/appium-mcp/issues/222)) ([8eb1f8d](https://github.com/appium/appium-mcp/commit/8eb1f8dab342b844a54aa90f0744f7586a1d9dc7))
|
|
6
|
+
|
|
1
7
|
## [1.32.0](https://github.com/appium/appium-mcp/compare/v1.31.0...v1.32.0) (2026-03-21)
|
|
2
8
|
|
|
3
9
|
### Features
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-transfer.test.d.ts","sourceRoot":"","sources":["../../../../src/tests/tools/session/file-transfer.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { describe, test, expect, jest, beforeEach } from '@jest/globals';
|
|
2
|
+
jest.unstable_mockModule('../../../session-store', () => ({
|
|
3
|
+
getDriver: jest.fn(),
|
|
4
|
+
getPlatformName: jest.fn(),
|
|
5
|
+
PLATFORM: { ios: 'iOS', android: 'Android' },
|
|
6
|
+
}));
|
|
7
|
+
jest.unstable_mockModule('../../../command', () => ({
|
|
8
|
+
execute: jest.fn(),
|
|
9
|
+
}));
|
|
10
|
+
jest.unstable_mockModule('../../../logger', () => ({
|
|
11
|
+
default: { debug: () => { }, info: () => { }, warn: () => { }, error: () => { } },
|
|
12
|
+
}));
|
|
13
|
+
const { getDriver, getPlatformName, PLATFORM } = await import('../../../session-store.js');
|
|
14
|
+
const { execute } = await import('../../../command.js');
|
|
15
|
+
const mockGetDriver = getDriver;
|
|
16
|
+
const mockGetPlatformName = getPlatformName;
|
|
17
|
+
const mockExecute = execute;
|
|
18
|
+
describe('appium_mobile_push_file / appium_mobile_pull_file', () => {
|
|
19
|
+
const mockServer = { addTool: jest.fn() };
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
mockServer.addTool.mockClear();
|
|
22
|
+
mockExecute.mockReset();
|
|
23
|
+
});
|
|
24
|
+
test('push: throws when no driver is active', async () => {
|
|
25
|
+
const { pushFile } = await import('../../../tools/session/file-transfer.js');
|
|
26
|
+
mockGetDriver.mockReturnValue(null);
|
|
27
|
+
pushFile(mockServer);
|
|
28
|
+
const tool = mockServer.addTool.mock.calls.at(-1)?.[0];
|
|
29
|
+
await expect(tool.execute({ remotePath: '/sdcard/x.txt', payloadBase64: 'YQ==' }, undefined)).rejects.toThrow('No driver found');
|
|
30
|
+
});
|
|
31
|
+
test('push: Android uses path and data', async () => {
|
|
32
|
+
const { pushFile } = await import('../../../tools/session/file-transfer.js');
|
|
33
|
+
mockGetDriver.mockReturnValue({});
|
|
34
|
+
mockGetPlatformName.mockReturnValue(PLATFORM.android);
|
|
35
|
+
mockExecute.mockResolvedValue(undefined);
|
|
36
|
+
pushFile(mockServer);
|
|
37
|
+
const tool = mockServer.addTool.mock.calls.at(-1)?.[0];
|
|
38
|
+
await tool.execute({ remotePath: '/data/local/tmp/a.txt', payloadBase64: 'SGVsbG8=' }, undefined);
|
|
39
|
+
expect(mockExecute).toHaveBeenCalledWith(expect.anything(), 'mobile: pushFile', expect.objectContaining({
|
|
40
|
+
path: '/data/local/tmp/a.txt',
|
|
41
|
+
data: 'SGVsbG8=',
|
|
42
|
+
}));
|
|
43
|
+
});
|
|
44
|
+
test('push: iOS uses remotePath and payload', async () => {
|
|
45
|
+
const { pushFile } = await import('../../../tools/session/file-transfer.js');
|
|
46
|
+
mockGetDriver.mockReturnValue({});
|
|
47
|
+
mockGetPlatformName.mockReturnValue(PLATFORM.ios);
|
|
48
|
+
mockExecute.mockResolvedValue(undefined);
|
|
49
|
+
pushFile(mockServer);
|
|
50
|
+
const tool = mockServer.addTool.mock.calls.at(-1)?.[0];
|
|
51
|
+
await tool.execute({
|
|
52
|
+
remotePath: '@com.example.app:documents/x.txt',
|
|
53
|
+
payloadBase64: 'QQ==',
|
|
54
|
+
}, undefined);
|
|
55
|
+
expect(mockExecute).toHaveBeenCalledWith(expect.anything(), 'mobile: pushFile', expect.objectContaining({
|
|
56
|
+
remotePath: '@com.example.app:documents/x.txt',
|
|
57
|
+
payload: 'QQ==',
|
|
58
|
+
}));
|
|
59
|
+
});
|
|
60
|
+
test('pull: Android uses path', async () => {
|
|
61
|
+
const { pullFile } = await import('../../../tools/session/file-transfer.js');
|
|
62
|
+
mockGetDriver.mockReturnValue({});
|
|
63
|
+
mockGetPlatformName.mockReturnValue(PLATFORM.android);
|
|
64
|
+
mockExecute.mockResolvedValue('YmJiYg==');
|
|
65
|
+
pullFile(mockServer);
|
|
66
|
+
const tool = mockServer.addTool.mock.calls.at(-1)?.[0];
|
|
67
|
+
const result = await tool.execute({ remotePath: '/sdcard/Download/out.bin' }, undefined);
|
|
68
|
+
expect(mockExecute).toHaveBeenCalledWith(expect.anything(), 'mobile: pullFile', expect.objectContaining({ path: '/sdcard/Download/out.bin' }));
|
|
69
|
+
const parsed = JSON.parse(result.content[0].text);
|
|
70
|
+
expect(parsed.contentBase64).toBe('YmJiYg==');
|
|
71
|
+
expect(parsed.platform).toBe('Android');
|
|
72
|
+
});
|
|
73
|
+
test('pull: iOS uses remotePath', async () => {
|
|
74
|
+
const { pullFile } = await import('../../../tools/session/file-transfer.js');
|
|
75
|
+
mockGetDriver.mockReturnValue({});
|
|
76
|
+
mockGetPlatformName.mockReturnValue(PLATFORM.ios);
|
|
77
|
+
mockExecute.mockResolvedValue('eHh4');
|
|
78
|
+
pullFile(mockServer);
|
|
79
|
+
const tool = mockServer.addTool.mock.calls.at(-1)?.[0];
|
|
80
|
+
const result = await tool.execute({ remotePath: '@com.app:documents/f.txt' }, undefined);
|
|
81
|
+
expect(mockExecute).toHaveBeenCalledWith(expect.anything(), 'mobile: pullFile', expect.objectContaining({ remotePath: '@com.app:documents/f.txt' }));
|
|
82
|
+
const parsed = JSON.parse(result.content[0].text);
|
|
83
|
+
expect(parsed.contentBase64).toBe('eHh4');
|
|
84
|
+
expect(parsed.platform).toBe('iOS');
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
//# sourceMappingURL=file-transfer.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-transfer.test.js","sourceRoot":"","sources":["../../../../src/tests/tools/session/file-transfer.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAqDlC,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CA2I3D"}
|
package/dist/tools/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import { lockDevice, unlockDevice } from './session/lock.js';
|
|
|
12
12
|
import { setGeolocation, getGeolocation, resetGeolocation, } from './session/geolocation.js';
|
|
13
13
|
import deviceInfo from './session/device-info.js';
|
|
14
14
|
import batteryInfo from './session/battery-info.js';
|
|
15
|
+
import { pushFile, pullFile } from './session/file-transfer.js';
|
|
15
16
|
import bootSimulator from './ios/boot-simulator.js';
|
|
16
17
|
import setupWDA from './ios/setup-wda.js';
|
|
17
18
|
import installWDA from './ios/install-wda.js';
|
|
@@ -121,6 +122,8 @@ export default function registerTools(server) {
|
|
|
121
122
|
resetGeolocation(server);
|
|
122
123
|
deviceInfo(server);
|
|
123
124
|
batteryInfo(server);
|
|
125
|
+
pushFile(server);
|
|
126
|
+
pullFile(server);
|
|
124
127
|
// iOS Setup
|
|
125
128
|
bootSimulator(server);
|
|
126
129
|
setupWDA(server);
|
package/dist/tools/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { FastMCP } from 'fastmcp';
|
|
2
|
+
/**
|
|
3
|
+
* Push a file from the host (MCP client) to the device via `mobile: pushFile`.
|
|
4
|
+
*
|
|
5
|
+
* - Android: `{ path, data }` per UiAutomator2 / legacy JSONWP push_file.
|
|
6
|
+
* - iOS: `{ remotePath, payload }` per XCUITest execute-methods reference.
|
|
7
|
+
*/
|
|
8
|
+
export declare function pushFile(server: FastMCP): void;
|
|
9
|
+
/**
|
|
10
|
+
* Pull a file from the device via `mobile: pullFile`. Returns Base64-encoded content in the response text.
|
|
11
|
+
*/
|
|
12
|
+
export declare function pullFile(server: FastMCP): void;
|
|
13
|
+
//# sourceMappingURL=file-transfer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-transfer.d.ts","sourceRoot":"","sources":["../../../src/tools/session/file-transfer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,OAAO,EAAE,MAAM,SAAS,CAAC;AAiCtD;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAkE9C;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAqE9C"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { getDriver, getPlatformName, PLATFORM } from '../../session-store.js';
|
|
3
|
+
import { execute } from '../../command.js';
|
|
4
|
+
/**
|
|
5
|
+
* Normalize the return value of mobile: pullFile (driver may return a string
|
|
6
|
+
* or a wrapped value depending on client/driver).
|
|
7
|
+
*/
|
|
8
|
+
function normalizePullResult(result) {
|
|
9
|
+
if (typeof result === 'string') {
|
|
10
|
+
return result;
|
|
11
|
+
}
|
|
12
|
+
if (result &&
|
|
13
|
+
typeof result === 'object' &&
|
|
14
|
+
'value' in result &&
|
|
15
|
+
typeof result.value === 'string') {
|
|
16
|
+
return result.value;
|
|
17
|
+
}
|
|
18
|
+
return String(result ?? '');
|
|
19
|
+
}
|
|
20
|
+
const remotePathDescription = 'Path to the file on the device. ' +
|
|
21
|
+
'Android (UiAutomator2): use an absolute path (e.g. /data/local/tmp/foo.txt or /sdcard/Download/foo.txt). ' +
|
|
22
|
+
'iOS (XCUITest): use the formats described in the Appium XCUITest file transfer guide ' +
|
|
23
|
+
'(e.g. @com.example.app:documents/file.txt or simulator-relative paths).';
|
|
24
|
+
const payloadDescription = 'File contents encoded as Base64 (raw base64 only; do not include a data: URL prefix).';
|
|
25
|
+
/**
|
|
26
|
+
* Push a file from the host (MCP client) to the device via `mobile: pushFile`.
|
|
27
|
+
*
|
|
28
|
+
* - Android: `{ path, data }` per UiAutomator2 / legacy JSONWP push_file.
|
|
29
|
+
* - iOS: `{ remotePath, payload }` per XCUITest execute-methods reference.
|
|
30
|
+
*/
|
|
31
|
+
export function pushFile(server) {
|
|
32
|
+
const schema = z.object({
|
|
33
|
+
remotePath: z.string().min(1).describe(remotePathDescription),
|
|
34
|
+
payloadBase64: z.string().min(1).describe(payloadDescription),
|
|
35
|
+
});
|
|
36
|
+
server.addTool({
|
|
37
|
+
name: 'appium_mobile_push_file',
|
|
38
|
+
description: 'Push a file to the device using the Appium `mobile: pushFile` extension. ' +
|
|
39
|
+
'Android uses `path` + `data` (base64). iOS uses `remotePath` + `payload` (base64). ' +
|
|
40
|
+
'Path semantics on iOS follow the XCUITest file transfer guide (app containers, documents, simulator paths). ' +
|
|
41
|
+
'Large payloads produce large requests; avoid pushing huge files through MCP.',
|
|
42
|
+
parameters: schema,
|
|
43
|
+
annotations: {
|
|
44
|
+
readOnlyHint: false,
|
|
45
|
+
openWorldHint: false,
|
|
46
|
+
},
|
|
47
|
+
execute: async (args, _context) => {
|
|
48
|
+
const driver = getDriver();
|
|
49
|
+
if (!driver) {
|
|
50
|
+
throw new Error('No driver found');
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const platform = getPlatformName(driver);
|
|
54
|
+
if (platform === PLATFORM.android) {
|
|
55
|
+
await execute(driver, 'mobile: pushFile', {
|
|
56
|
+
path: args.remotePath,
|
|
57
|
+
data: args.payloadBase64,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
else if (platform === PLATFORM.ios) {
|
|
61
|
+
await execute(driver, 'mobile: pushFile', {
|
|
62
|
+
remotePath: args.remotePath,
|
|
63
|
+
payload: args.payloadBase64,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
throw new Error(`Unsupported platform: ${platform}. Only Android and iOS are supported.`);
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
content: [
|
|
71
|
+
{
|
|
72
|
+
type: 'text',
|
|
73
|
+
text: `Successfully pushed file to device path: ${args.remotePath}`,
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
80
|
+
return {
|
|
81
|
+
content: [
|
|
82
|
+
{
|
|
83
|
+
type: 'text',
|
|
84
|
+
text: `Failed to push file. err: ${message}`,
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Pull a file from the device via `mobile: pullFile`. Returns Base64-encoded content in the response text.
|
|
94
|
+
*/
|
|
95
|
+
export function pullFile(server) {
|
|
96
|
+
const schema = z.object({
|
|
97
|
+
remotePath: z.string().min(1).describe(remotePathDescription),
|
|
98
|
+
});
|
|
99
|
+
server.addTool({
|
|
100
|
+
name: 'appium_mobile_pull_file',
|
|
101
|
+
description: 'Pull a file from the device using the Appium `mobile: pullFile` extension. ' +
|
|
102
|
+
'Returns Base64-encoded file content in the response text. ' +
|
|
103
|
+
'Android uses parameter `path`; iOS uses `remotePath` with the same path formats as push. ' +
|
|
104
|
+
'Very large files may produce very large responses; prefer downloading or streaming outside MCP for big binaries.',
|
|
105
|
+
parameters: schema,
|
|
106
|
+
annotations: {
|
|
107
|
+
readOnlyHint: true,
|
|
108
|
+
openWorldHint: false,
|
|
109
|
+
},
|
|
110
|
+
execute: async (args, _context) => {
|
|
111
|
+
const driver = getDriver();
|
|
112
|
+
if (!driver) {
|
|
113
|
+
throw new Error('No driver found');
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
const platform = getPlatformName(driver);
|
|
117
|
+
let raw;
|
|
118
|
+
if (platform === PLATFORM.android) {
|
|
119
|
+
raw = await execute(driver, 'mobile: pullFile', {
|
|
120
|
+
path: args.remotePath,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
else if (platform === PLATFORM.ios) {
|
|
124
|
+
raw = await execute(driver, 'mobile: pullFile', {
|
|
125
|
+
remotePath: args.remotePath,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
throw new Error(`Unsupported platform: ${platform}. Only Android and iOS are supported.`);
|
|
130
|
+
}
|
|
131
|
+
const base64 = normalizePullResult(raw);
|
|
132
|
+
return {
|
|
133
|
+
content: [
|
|
134
|
+
{
|
|
135
|
+
type: 'text',
|
|
136
|
+
text: JSON.stringify({
|
|
137
|
+
remotePath: args.remotePath,
|
|
138
|
+
platform,
|
|
139
|
+
contentBase64: base64,
|
|
140
|
+
}),
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
147
|
+
return {
|
|
148
|
+
content: [
|
|
149
|
+
{
|
|
150
|
+
type: 'text',
|
|
151
|
+
text: `Failed to pull file. err: ${message}`,
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=file-transfer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-transfer.js","sourceRoot":"","sources":["../../../src/tools/session/file-transfer.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
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.
|
|
6
|
+
"version": "1.33.0",
|
|
7
7
|
"packages": [
|
|
8
8
|
{
|
|
9
9
|
"registryType": "npm",
|
|
10
10
|
"identifier": "appium-mcp",
|
|
11
|
-
"version": "1.
|
|
11
|
+
"version": "1.33.0",
|
|
12
12
|
"transport": {
|
|
13
13
|
"type": "stdio"
|
|
14
14
|
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { describe, test, expect, jest, beforeEach } from '@jest/globals';
|
|
2
|
+
|
|
3
|
+
jest.unstable_mockModule('../../../session-store', () => ({
|
|
4
|
+
getDriver: jest.fn(),
|
|
5
|
+
getPlatformName: jest.fn(),
|
|
6
|
+
PLATFORM: { ios: 'iOS', android: 'Android' },
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
jest.unstable_mockModule('../../../command', () => ({
|
|
10
|
+
execute: jest.fn(),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
jest.unstable_mockModule('../../../logger', () => ({
|
|
14
|
+
default: { debug: () => {}, info: () => {}, warn: () => {}, error: () => {} },
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
const { getDriver, getPlatformName, PLATFORM } =
|
|
18
|
+
await import('../../../session-store.js');
|
|
19
|
+
const { execute } = await import('../../../command.js');
|
|
20
|
+
|
|
21
|
+
const mockGetDriver = getDriver as jest.MockedFunction<typeof getDriver>;
|
|
22
|
+
const mockGetPlatformName = getPlatformName as jest.MockedFunction<
|
|
23
|
+
typeof getPlatformName
|
|
24
|
+
>;
|
|
25
|
+
const mockExecute = execute as jest.MockedFunction<typeof execute>;
|
|
26
|
+
|
|
27
|
+
describe('appium_mobile_push_file / appium_mobile_pull_file', () => {
|
|
28
|
+
const mockServer = { addTool: jest.fn() } as any;
|
|
29
|
+
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
(mockServer.addTool as jest.MockedFunction<any>).mockClear();
|
|
32
|
+
mockExecute.mockReset();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('push: throws when no driver is active', async () => {
|
|
36
|
+
const { pushFile } =
|
|
37
|
+
await import('../../../tools/session/file-transfer.js');
|
|
38
|
+
mockGetDriver.mockReturnValue(null as any);
|
|
39
|
+
pushFile(mockServer);
|
|
40
|
+
|
|
41
|
+
const tool = (mockServer.addTool as jest.MockedFunction<any>).mock.calls.at(
|
|
42
|
+
-1
|
|
43
|
+
)?.[0];
|
|
44
|
+
await expect(
|
|
45
|
+
tool.execute(
|
|
46
|
+
{ remotePath: '/sdcard/x.txt', payloadBase64: 'YQ==' },
|
|
47
|
+
undefined
|
|
48
|
+
)
|
|
49
|
+
).rejects.toThrow('No driver found');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('push: Android uses path and data', async () => {
|
|
53
|
+
const { pushFile } =
|
|
54
|
+
await import('../../../tools/session/file-transfer.js');
|
|
55
|
+
mockGetDriver.mockReturnValue({} as any);
|
|
56
|
+
mockGetPlatformName.mockReturnValue(PLATFORM.android);
|
|
57
|
+
mockExecute.mockResolvedValue(undefined);
|
|
58
|
+
|
|
59
|
+
pushFile(mockServer);
|
|
60
|
+
const tool = (mockServer.addTool as jest.MockedFunction<any>).mock.calls.at(
|
|
61
|
+
-1
|
|
62
|
+
)?.[0];
|
|
63
|
+
await tool.execute(
|
|
64
|
+
{ remotePath: '/data/local/tmp/a.txt', payloadBase64: 'SGVsbG8=' },
|
|
65
|
+
undefined
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
expect(mockExecute).toHaveBeenCalledWith(
|
|
69
|
+
expect.anything(),
|
|
70
|
+
'mobile: pushFile',
|
|
71
|
+
expect.objectContaining({
|
|
72
|
+
path: '/data/local/tmp/a.txt',
|
|
73
|
+
data: 'SGVsbG8=',
|
|
74
|
+
})
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('push: iOS uses remotePath and payload', async () => {
|
|
79
|
+
const { pushFile } =
|
|
80
|
+
await import('../../../tools/session/file-transfer.js');
|
|
81
|
+
mockGetDriver.mockReturnValue({} as any);
|
|
82
|
+
mockGetPlatformName.mockReturnValue(PLATFORM.ios);
|
|
83
|
+
mockExecute.mockResolvedValue(undefined);
|
|
84
|
+
|
|
85
|
+
pushFile(mockServer);
|
|
86
|
+
const tool = (mockServer.addTool as jest.MockedFunction<any>).mock.calls.at(
|
|
87
|
+
-1
|
|
88
|
+
)?.[0];
|
|
89
|
+
await tool.execute(
|
|
90
|
+
{
|
|
91
|
+
remotePath: '@com.example.app:documents/x.txt',
|
|
92
|
+
payloadBase64: 'QQ==',
|
|
93
|
+
},
|
|
94
|
+
undefined
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
expect(mockExecute).toHaveBeenCalledWith(
|
|
98
|
+
expect.anything(),
|
|
99
|
+
'mobile: pushFile',
|
|
100
|
+
expect.objectContaining({
|
|
101
|
+
remotePath: '@com.example.app:documents/x.txt',
|
|
102
|
+
payload: 'QQ==',
|
|
103
|
+
})
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('pull: Android uses path', async () => {
|
|
108
|
+
const { pullFile } =
|
|
109
|
+
await import('../../../tools/session/file-transfer.js');
|
|
110
|
+
mockGetDriver.mockReturnValue({} as any);
|
|
111
|
+
mockGetPlatformName.mockReturnValue(PLATFORM.android);
|
|
112
|
+
mockExecute.mockResolvedValue('YmJiYg==');
|
|
113
|
+
|
|
114
|
+
pullFile(mockServer);
|
|
115
|
+
const tool = (mockServer.addTool as jest.MockedFunction<any>).mock.calls.at(
|
|
116
|
+
-1
|
|
117
|
+
)?.[0];
|
|
118
|
+
const result = await tool.execute(
|
|
119
|
+
{ remotePath: '/sdcard/Download/out.bin' },
|
|
120
|
+
undefined
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
expect(mockExecute).toHaveBeenCalledWith(
|
|
124
|
+
expect.anything(),
|
|
125
|
+
'mobile: pullFile',
|
|
126
|
+
expect.objectContaining({ path: '/sdcard/Download/out.bin' })
|
|
127
|
+
);
|
|
128
|
+
const parsed = JSON.parse(result.content[0].text);
|
|
129
|
+
expect(parsed.contentBase64).toBe('YmJiYg==');
|
|
130
|
+
expect(parsed.platform).toBe('Android');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('pull: iOS uses remotePath', async () => {
|
|
134
|
+
const { pullFile } =
|
|
135
|
+
await import('../../../tools/session/file-transfer.js');
|
|
136
|
+
mockGetDriver.mockReturnValue({} as any);
|
|
137
|
+
mockGetPlatformName.mockReturnValue(PLATFORM.ios);
|
|
138
|
+
mockExecute.mockResolvedValue('eHh4');
|
|
139
|
+
|
|
140
|
+
pullFile(mockServer);
|
|
141
|
+
const tool = (mockServer.addTool as jest.MockedFunction<any>).mock.calls.at(
|
|
142
|
+
-1
|
|
143
|
+
)?.[0];
|
|
144
|
+
const result = await tool.execute(
|
|
145
|
+
{ remotePath: '@com.app:documents/f.txt' },
|
|
146
|
+
undefined
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
expect(mockExecute).toHaveBeenCalledWith(
|
|
150
|
+
expect.anything(),
|
|
151
|
+
'mobile: pullFile',
|
|
152
|
+
expect.objectContaining({ remotePath: '@com.app:documents/f.txt' })
|
|
153
|
+
);
|
|
154
|
+
const parsed = JSON.parse(result.content[0].text);
|
|
155
|
+
expect(parsed.contentBase64).toBe('eHh4');
|
|
156
|
+
expect(parsed.platform).toBe('iOS');
|
|
157
|
+
});
|
|
158
|
+
});
|
package/src/tools/README.md
CHANGED
|
@@ -13,6 +13,7 @@ This directory contains all MCP tools available in MCP Appium.
|
|
|
13
13
|
- `lock.ts` - Unlock device (`appium_mobile_unlock`)
|
|
14
14
|
- `select-platform.ts` - Choose Android or iOS
|
|
15
15
|
- `select-device.ts` - Choose specific device
|
|
16
|
+
- `file-transfer.ts` - Push/pull files on device (`appium_mobile_push_file`, `appium_mobile_pull_file`)
|
|
16
17
|
|
|
17
18
|
### iOS Setup (`ios/`)
|
|
18
19
|
|
package/src/tools/index.ts
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
} from './session/geolocation.js';
|
|
32
32
|
import deviceInfo from './session/device-info.js';
|
|
33
33
|
import batteryInfo from './session/battery-info.js';
|
|
34
|
+
import { pushFile, pullFile } from './session/file-transfer.js';
|
|
34
35
|
import bootSimulator from './ios/boot-simulator.js';
|
|
35
36
|
import setupWDA from './ios/setup-wda.js';
|
|
36
37
|
import installWDA from './ios/install-wda.js';
|
|
@@ -146,6 +147,8 @@ export default function registerTools(server: FastMCP): void {
|
|
|
146
147
|
resetGeolocation(server);
|
|
147
148
|
deviceInfo(server);
|
|
148
149
|
batteryInfo(server);
|
|
150
|
+
pushFile(server);
|
|
151
|
+
pullFile(server);
|
|
149
152
|
|
|
150
153
|
// iOS Setup
|
|
151
154
|
bootSimulator(server);
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import type { ContentResult, FastMCP } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getDriver, getPlatformName, PLATFORM } from '../../session-store.js';
|
|
4
|
+
import { execute } from '../../command.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Normalize the return value of mobile: pullFile (driver may return a string
|
|
8
|
+
* or a wrapped value depending on client/driver).
|
|
9
|
+
*/
|
|
10
|
+
function normalizePullResult(result: unknown): string {
|
|
11
|
+
if (typeof result === 'string') {
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
14
|
+
if (
|
|
15
|
+
result &&
|
|
16
|
+
typeof result === 'object' &&
|
|
17
|
+
'value' in result &&
|
|
18
|
+
typeof (result as { value: unknown }).value === 'string'
|
|
19
|
+
) {
|
|
20
|
+
return (result as { value: string }).value;
|
|
21
|
+
}
|
|
22
|
+
return String(result ?? '');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const remotePathDescription =
|
|
26
|
+
'Path to the file on the device. ' +
|
|
27
|
+
'Android (UiAutomator2): use an absolute path (e.g. /data/local/tmp/foo.txt or /sdcard/Download/foo.txt). ' +
|
|
28
|
+
'iOS (XCUITest): use the formats described in the Appium XCUITest file transfer guide ' +
|
|
29
|
+
'(e.g. @com.example.app:documents/file.txt or simulator-relative paths).';
|
|
30
|
+
|
|
31
|
+
const payloadDescription =
|
|
32
|
+
'File contents encoded as Base64 (raw base64 only; do not include a data: URL prefix).';
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Push a file from the host (MCP client) to the device via `mobile: pushFile`.
|
|
36
|
+
*
|
|
37
|
+
* - Android: `{ path, data }` per UiAutomator2 / legacy JSONWP push_file.
|
|
38
|
+
* - iOS: `{ remotePath, payload }` per XCUITest execute-methods reference.
|
|
39
|
+
*/
|
|
40
|
+
export function pushFile(server: FastMCP): void {
|
|
41
|
+
const schema = z.object({
|
|
42
|
+
remotePath: z.string().min(1).describe(remotePathDescription),
|
|
43
|
+
payloadBase64: z.string().min(1).describe(payloadDescription),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
server.addTool({
|
|
47
|
+
name: 'appium_mobile_push_file',
|
|
48
|
+
description:
|
|
49
|
+
'Push a file to the device using the Appium `mobile: pushFile` extension. ' +
|
|
50
|
+
'Android uses `path` + `data` (base64). iOS uses `remotePath` + `payload` (base64). ' +
|
|
51
|
+
'Path semantics on iOS follow the XCUITest file transfer guide (app containers, documents, simulator paths). ' +
|
|
52
|
+
'Large payloads produce large requests; avoid pushing huge files through MCP.',
|
|
53
|
+
parameters: schema,
|
|
54
|
+
annotations: {
|
|
55
|
+
readOnlyHint: false,
|
|
56
|
+
openWorldHint: false,
|
|
57
|
+
},
|
|
58
|
+
execute: async (
|
|
59
|
+
args: z.infer<typeof schema>,
|
|
60
|
+
_context: Record<string, unknown> | undefined
|
|
61
|
+
): Promise<ContentResult> => {
|
|
62
|
+
const driver = getDriver();
|
|
63
|
+
if (!driver) {
|
|
64
|
+
throw new Error('No driver found');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const platform = getPlatformName(driver);
|
|
69
|
+
if (platform === PLATFORM.android) {
|
|
70
|
+
await execute(driver, 'mobile: pushFile', {
|
|
71
|
+
path: args.remotePath,
|
|
72
|
+
data: args.payloadBase64,
|
|
73
|
+
});
|
|
74
|
+
} else if (platform === PLATFORM.ios) {
|
|
75
|
+
await execute(driver, 'mobile: pushFile', {
|
|
76
|
+
remotePath: args.remotePath,
|
|
77
|
+
payload: args.payloadBase64,
|
|
78
|
+
});
|
|
79
|
+
} else {
|
|
80
|
+
throw new Error(
|
|
81
|
+
`Unsupported platform: ${platform}. Only Android and iOS are supported.`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
content: [
|
|
87
|
+
{
|
|
88
|
+
type: 'text',
|
|
89
|
+
text: `Successfully pushed file to device path: ${args.remotePath}`,
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
};
|
|
93
|
+
} catch (err: unknown) {
|
|
94
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
95
|
+
return {
|
|
96
|
+
content: [
|
|
97
|
+
{
|
|
98
|
+
type: 'text',
|
|
99
|
+
text: `Failed to push file. err: ${message}`,
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Pull a file from the device via `mobile: pullFile`. Returns Base64-encoded content in the response text.
|
|
110
|
+
*/
|
|
111
|
+
export function pullFile(server: FastMCP): void {
|
|
112
|
+
const schema = z.object({
|
|
113
|
+
remotePath: z.string().min(1).describe(remotePathDescription),
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
server.addTool({
|
|
117
|
+
name: 'appium_mobile_pull_file',
|
|
118
|
+
description:
|
|
119
|
+
'Pull a file from the device using the Appium `mobile: pullFile` extension. ' +
|
|
120
|
+
'Returns Base64-encoded file content in the response text. ' +
|
|
121
|
+
'Android uses parameter `path`; iOS uses `remotePath` with the same path formats as push. ' +
|
|
122
|
+
'Very large files may produce very large responses; prefer downloading or streaming outside MCP for big binaries.',
|
|
123
|
+
parameters: schema,
|
|
124
|
+
annotations: {
|
|
125
|
+
readOnlyHint: true,
|
|
126
|
+
openWorldHint: false,
|
|
127
|
+
},
|
|
128
|
+
execute: async (
|
|
129
|
+
args: z.infer<typeof schema>,
|
|
130
|
+
_context: Record<string, unknown> | undefined
|
|
131
|
+
): Promise<ContentResult> => {
|
|
132
|
+
const driver = getDriver();
|
|
133
|
+
if (!driver) {
|
|
134
|
+
throw new Error('No driver found');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
const platform = getPlatformName(driver);
|
|
139
|
+
let raw: unknown;
|
|
140
|
+
if (platform === PLATFORM.android) {
|
|
141
|
+
raw = await execute(driver, 'mobile: pullFile', {
|
|
142
|
+
path: args.remotePath,
|
|
143
|
+
});
|
|
144
|
+
} else if (platform === PLATFORM.ios) {
|
|
145
|
+
raw = await execute(driver, 'mobile: pullFile', {
|
|
146
|
+
remotePath: args.remotePath,
|
|
147
|
+
});
|
|
148
|
+
} else {
|
|
149
|
+
throw new Error(
|
|
150
|
+
`Unsupported platform: ${platform}. Only Android and iOS are supported.`
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const base64 = normalizePullResult(raw);
|
|
155
|
+
return {
|
|
156
|
+
content: [
|
|
157
|
+
{
|
|
158
|
+
type: 'text',
|
|
159
|
+
text: JSON.stringify({
|
|
160
|
+
remotePath: args.remotePath,
|
|
161
|
+
platform,
|
|
162
|
+
contentBase64: base64,
|
|
163
|
+
}),
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
};
|
|
167
|
+
} catch (err: unknown) {
|
|
168
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
169
|
+
return {
|
|
170
|
+
content: [
|
|
171
|
+
{
|
|
172
|
+
type: 'text',
|
|
173
|
+
text: `Failed to pull file. err: ${message}`,
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
}
|