@metamask/device-mcp 0.2.0 → 0.3.1
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 +28 -1
- package/README.md +55 -2
- package/dist/backends/adb-backend.cjs +12 -5
- package/dist/backends/adb-backend.cjs.map +1 -1
- package/dist/backends/adb-backend.d.cts +8 -2
- package/dist/backends/adb-backend.d.cts.map +1 -1
- package/dist/backends/adb-backend.d.mts +8 -2
- package/dist/backends/adb-backend.d.mts.map +1 -1
- package/dist/backends/adb-backend.mjs +12 -5
- package/dist/backends/adb-backend.mjs.map +1 -1
- package/dist/backends/appium-backend.cjs +16 -7
- package/dist/backends/appium-backend.cjs.map +1 -1
- package/dist/backends/appium-backend.d.cts +8 -2
- package/dist/backends/appium-backend.d.cts.map +1 -1
- package/dist/backends/appium-backend.d.mts +8 -2
- package/dist/backends/appium-backend.d.mts.map +1 -1
- package/dist/backends/appium-backend.mjs +16 -7
- package/dist/backends/appium-backend.mjs.map +1 -1
- package/dist/backends/idb-backend.cjs +12 -5
- package/dist/backends/idb-backend.cjs.map +1 -1
- package/dist/backends/idb-backend.d.cts +8 -2
- package/dist/backends/idb-backend.d.cts.map +1 -1
- package/dist/backends/idb-backend.d.mts +8 -2
- package/dist/backends/idb-backend.d.mts.map +1 -1
- package/dist/backends/idb-backend.mjs +12 -5
- package/dist/backends/idb-backend.mjs.map +1 -1
- package/dist/backends/index.cjs +4 -3
- package/dist/backends/index.cjs.map +1 -1
- package/dist/backends/index.d.cts.map +1 -1
- package/dist/backends/index.d.mts.map +1 -1
- package/dist/backends/index.mjs +4 -3
- package/dist/backends/index.mjs.map +1 -1
- package/dist/backends/types.cjs.map +1 -1
- package/dist/backends/types.d.cts +19 -1
- package/dist/backends/types.d.cts.map +1 -1
- package/dist/backends/types.d.mts +19 -1
- package/dist/backends/types.d.mts.map +1 -1
- package/dist/backends/types.mjs.map +1 -1
- package/dist/hermes/hermes-cdp.cjs +674 -0
- package/dist/hermes/hermes-cdp.cjs.map +1 -0
- package/dist/hermes/hermes-cdp.d.cts +248 -0
- package/dist/hermes/hermes-cdp.d.cts.map +1 -0
- package/dist/hermes/hermes-cdp.d.mts +248 -0
- package/dist/hermes/hermes-cdp.d.mts.map +1 -0
- package/dist/hermes/hermes-cdp.mjs +665 -0
- package/dist/hermes/hermes-cdp.mjs.map +1 -0
- package/dist/hermes/session.cjs +196 -0
- package/dist/hermes/session.cjs.map +1 -0
- package/dist/hermes/session.d.cts +163 -0
- package/dist/hermes/session.d.cts.map +1 -0
- package/dist/hermes/session.d.mts +163 -0
- package/dist/hermes/session.d.mts.map +1 -0
- package/dist/hermes/session.mjs +189 -0
- package/dist/hermes/session.mjs.map +1 -0
- package/dist/index.cjs +21 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +7 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +3 -0
- package/dist/index.mjs.map +1 -1
- package/dist/server.cjs +3 -0
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts.map +1 -1
- package/dist/server.d.mts.map +1 -1
- package/dist/server.mjs +4 -1
- package/dist/server.mjs.map +1 -1
- package/dist/tools/hermes-cdp.cjs +102 -0
- package/dist/tools/hermes-cdp.cjs.map +1 -0
- package/dist/tools/hermes-cdp.d.cts +18 -0
- package/dist/tools/hermes-cdp.d.cts.map +1 -0
- package/dist/tools/hermes-cdp.d.mts +18 -0
- package/dist/tools/hermes-cdp.d.mts.map +1 -0
- package/dist/tools/hermes-cdp.mjs +99 -0
- package/dist/tools/hermes-cdp.mjs.map +1 -0
- package/dist/tools/hermes-targets.cjs +127 -0
- package/dist/tools/hermes-targets.cjs.map +1 -0
- package/dist/tools/hermes-targets.d.cts +20 -0
- package/dist/tools/hermes-targets.d.cts.map +1 -0
- package/dist/tools/hermes-targets.d.mts +20 -0
- package/dist/tools/hermes-targets.d.mts.map +1 -0
- package/dist/tools/hermes-targets.mjs +124 -0
- package/dist/tools/hermes-targets.mjs.map +1 -0
- package/dist/tools/index.cjs +5 -1
- package/dist/tools/index.cjs.map +1 -1
- package/dist/tools/index.d.cts +2 -0
- package/dist/tools/index.d.cts.map +1 -1
- package/dist/tools/index.d.mts +2 -0
- package/dist/tools/index.d.mts.map +1 -1
- package/dist/tools/index.mjs +2 -0
- package/dist/tools/index.mjs.map +1 -1
- package/dist/tools/screenshot.cjs +8 -3
- package/dist/tools/screenshot.cjs.map +1 -1
- package/dist/tools/screenshot.d.cts.map +1 -1
- package/dist/tools/screenshot.d.mts.map +1 -1
- package/dist/tools/screenshot.mjs +8 -3
- package/dist/tools/screenshot.mjs.map +1 -1
- package/dist/utils/output-path.cjs +128 -0
- package/dist/utils/output-path.cjs.map +1 -0
- package/dist/utils/output-path.d.cts +41 -0
- package/dist/utils/output-path.d.cts.map +1 -0
- package/dist/utils/output-path.d.mts +41 -0
- package/dist/utils/output-path.d.mts.map +1 -0
- package/dist/utils/output-path.mjs +123 -0
- package/dist/utils/output-path.mjs.map +1 -0
- package/package.json +1 -1
package/dist/tools/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.registerSelectDeviceTool = exports.registerListDevicesTool = exports.registerGetElementTextTool = exports.registerGenerateLocatorsTool = exports.registerScreenRecordingTool = exports.registerClipboardTool = exports.registerContextTool = exports.registerWindowSizeTool = exports.registerAlertTextTool = exports.registerScrollToElementTool = exports.registerLogsTool = exports.registerDismissAlertTool = exports.registerDismissKeyboardTool = exports.registerPressButtonTool = exports.registerCloseAppTool = exports.registerOpenAppTool = exports.registerDeviceInfoTool = exports.registerAppStateTool = exports.registerWaitForTool = exports.registerLongPressTool = exports.registerSwipeTool = exports.registerTypeTool = exports.registerTapCoordinatesTool = exports.registerTapElementTool = exports.registerScreenshotTool = exports.registerSnapshotTool = void 0;
|
|
3
|
+
exports.registerHermesTargetsTool = exports.registerHermesCdpTool = exports.registerSelectDeviceTool = exports.registerListDevicesTool = exports.registerGetElementTextTool = exports.registerGenerateLocatorsTool = exports.registerScreenRecordingTool = exports.registerClipboardTool = exports.registerContextTool = exports.registerWindowSizeTool = exports.registerAlertTextTool = exports.registerScrollToElementTool = exports.registerLogsTool = exports.registerDismissAlertTool = exports.registerDismissKeyboardTool = exports.registerPressButtonTool = exports.registerCloseAppTool = exports.registerOpenAppTool = exports.registerDeviceInfoTool = exports.registerAppStateTool = exports.registerWaitForTool = exports.registerLongPressTool = exports.registerSwipeTool = exports.registerTypeTool = exports.registerTapCoordinatesTool = exports.registerTapElementTool = exports.registerScreenshotTool = exports.registerSnapshotTool = void 0;
|
|
4
4
|
var snapshot_js_1 = require("./snapshot.cjs");
|
|
5
5
|
Object.defineProperty(exports, "registerSnapshotTool", { enumerable: true, get: function () { return snapshot_js_1.registerSnapshotTool; } });
|
|
6
6
|
var screenshot_js_1 = require("./screenshot.cjs");
|
|
@@ -53,4 +53,8 @@ var list_devices_js_1 = require("./list-devices.cjs");
|
|
|
53
53
|
Object.defineProperty(exports, "registerListDevicesTool", { enumerable: true, get: function () { return list_devices_js_1.registerListDevicesTool; } });
|
|
54
54
|
var select_device_js_1 = require("./select-device.cjs");
|
|
55
55
|
Object.defineProperty(exports, "registerSelectDeviceTool", { enumerable: true, get: function () { return select_device_js_1.registerSelectDeviceTool; } });
|
|
56
|
+
var hermes_cdp_js_1 = require("./hermes-cdp.cjs");
|
|
57
|
+
Object.defineProperty(exports, "registerHermesCdpTool", { enumerable: true, get: function () { return hermes_cdp_js_1.registerHermesCdpTool; } });
|
|
58
|
+
var hermes_targets_js_1 = require("./hermes-targets.cjs");
|
|
59
|
+
Object.defineProperty(exports, "registerHermesTargetsTool", { enumerable: true, get: function () { return hermes_targets_js_1.registerHermesTargetsTool; } });
|
|
56
60
|
//# sourceMappingURL=index.cjs.map
|
package/dist/tools/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":";;;AAAA,8CAAqD;AAA5C,mHAAA,oBAAoB,OAAA;AAC7B,kDAAyD;AAAhD,uHAAA,sBAAsB,OAAA;AAC/B,oDAA0D;AAAjD,wHAAA,sBAAsB,OAAA;AAC/B,4DAAkE;AAAzD,gIAAA,0BAA0B,OAAA;AACnC,gDAAkD;AAAzC,gHAAA,gBAAgB,OAAA;AACzB,wCAA+C;AAAtC,6GAAA,iBAAiB,OAAA;AAC1B,kDAAwD;AAA/C,sHAAA,qBAAqB,OAAA;AAC9B,8CAAoD;AAA3C,kHAAA,mBAAmB,OAAA;AAC5B,gDAAsD;AAA7C,oHAAA,oBAAoB,OAAA;AAC7B,oDAA0D;AAAjD,wHAAA,sBAAsB,OAAA;AAC/B,8CAAoD;AAA3C,kHAAA,mBAAmB,OAAA;AAC5B,gDAAsD;AAA7C,oHAAA,oBAAoB,OAAA;AAC7B,sDAA4D;AAAnD,0HAAA,uBAAuB,OAAA;AAChC,8DAAoE;AAA3D,kIAAA,2BAA2B,OAAA;AACpC,wDAA8D;AAArD,4HAAA,wBAAwB,OAAA;AACjC,sCAA6C;AAApC,2GAAA,gBAAgB,OAAA;AACzB,gEAAqE;AAA5D,mIAAA,2BAA2B,OAAA;AACpC,kDAAwD;AAA/C,sHAAA,qBAAqB,OAAA;AAC9B,oDAA0D;AAAjD,wHAAA,sBAAsB,OAAA;AAC/B,4CAAmD;AAA1C,iHAAA,mBAAmB,OAAA;AAC5B,gDAAuD;AAA9C,qHAAA,qBAAqB,OAAA;AAC9B,8DAAoE;AAA3D,kIAAA,2BAA2B,OAAA;AACpC,gEAAsE;AAA7D,oIAAA,4BAA4B,OAAA;AACrC,8DAAmE;AAA1D,iIAAA,0BAA0B,OAAA;AACnC,sDAA4D;AAAnD,0HAAA,uBAAuB,OAAA;AAChC,wDAA8D;AAArD,4HAAA,wBAAwB,OAAA","sourcesContent":["export { registerSnapshotTool } from './snapshot.js';\nexport { registerScreenshotTool } from './screenshot.js';\nexport { registerTapElementTool } from './tap-element.js';\nexport { registerTapCoordinatesTool } from './tap-coordinates.js';\nexport { registerTypeTool } from './type-text.js';\nexport { registerSwipeTool } from './swipe.js';\nexport { registerLongPressTool } from './long-press.js';\nexport { registerWaitForTool } from './wait-for.js';\nexport { registerAppStateTool } from './app-state.js';\nexport { registerDeviceInfoTool } from './device-info.js';\nexport { registerOpenAppTool } from './open-app.js';\nexport { registerCloseAppTool } from './close-app.js';\nexport { registerPressButtonTool } from './press-button.js';\nexport { registerDismissKeyboardTool } from './dismiss-keyboard.js';\nexport { registerDismissAlertTool } from './dismiss-alert.js';\nexport { registerLogsTool } from './logs.js';\nexport { registerScrollToElementTool } from './scroll-to-element.js';\nexport { registerAlertTextTool } from './alert-text.js';\nexport { registerWindowSizeTool } from './window-size.js';\nexport { registerContextTool } from './context.js';\nexport { registerClipboardTool } from './clipboard.js';\nexport { registerScreenRecordingTool } from './screen-recording.js';\nexport { registerGenerateLocatorsTool } from './generate-locators.js';\nexport { registerGetElementTextTool } from './get-element-text.js';\nexport { registerListDevicesTool } from './list-devices.js';\nexport { registerSelectDeviceTool } from './select-device.js';\n"]}
|
|
1
|
+
{"version":3,"file":"index.cjs","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":";;;AAAA,8CAAqD;AAA5C,mHAAA,oBAAoB,OAAA;AAC7B,kDAAyD;AAAhD,uHAAA,sBAAsB,OAAA;AAC/B,oDAA0D;AAAjD,wHAAA,sBAAsB,OAAA;AAC/B,4DAAkE;AAAzD,gIAAA,0BAA0B,OAAA;AACnC,gDAAkD;AAAzC,gHAAA,gBAAgB,OAAA;AACzB,wCAA+C;AAAtC,6GAAA,iBAAiB,OAAA;AAC1B,kDAAwD;AAA/C,sHAAA,qBAAqB,OAAA;AAC9B,8CAAoD;AAA3C,kHAAA,mBAAmB,OAAA;AAC5B,gDAAsD;AAA7C,oHAAA,oBAAoB,OAAA;AAC7B,oDAA0D;AAAjD,wHAAA,sBAAsB,OAAA;AAC/B,8CAAoD;AAA3C,kHAAA,mBAAmB,OAAA;AAC5B,gDAAsD;AAA7C,oHAAA,oBAAoB,OAAA;AAC7B,sDAA4D;AAAnD,0HAAA,uBAAuB,OAAA;AAChC,8DAAoE;AAA3D,kIAAA,2BAA2B,OAAA;AACpC,wDAA8D;AAArD,4HAAA,wBAAwB,OAAA;AACjC,sCAA6C;AAApC,2GAAA,gBAAgB,OAAA;AACzB,gEAAqE;AAA5D,mIAAA,2BAA2B,OAAA;AACpC,kDAAwD;AAA/C,sHAAA,qBAAqB,OAAA;AAC9B,oDAA0D;AAAjD,wHAAA,sBAAsB,OAAA;AAC/B,4CAAmD;AAA1C,iHAAA,mBAAmB,OAAA;AAC5B,gDAAuD;AAA9C,qHAAA,qBAAqB,OAAA;AAC9B,8DAAoE;AAA3D,kIAAA,2BAA2B,OAAA;AACpC,gEAAsE;AAA7D,oIAAA,4BAA4B,OAAA;AACrC,8DAAmE;AAA1D,iIAAA,0BAA0B,OAAA;AACnC,sDAA4D;AAAnD,0HAAA,uBAAuB,OAAA;AAChC,wDAA8D;AAArD,4HAAA,wBAAwB,OAAA;AACjC,kDAAwD;AAA/C,sHAAA,qBAAqB,OAAA;AAC9B,0DAAgE;AAAvD,8HAAA,yBAAyB,OAAA","sourcesContent":["export { registerSnapshotTool } from './snapshot.js';\nexport { registerScreenshotTool } from './screenshot.js';\nexport { registerTapElementTool } from './tap-element.js';\nexport { registerTapCoordinatesTool } from './tap-coordinates.js';\nexport { registerTypeTool } from './type-text.js';\nexport { registerSwipeTool } from './swipe.js';\nexport { registerLongPressTool } from './long-press.js';\nexport { registerWaitForTool } from './wait-for.js';\nexport { registerAppStateTool } from './app-state.js';\nexport { registerDeviceInfoTool } from './device-info.js';\nexport { registerOpenAppTool } from './open-app.js';\nexport { registerCloseAppTool } from './close-app.js';\nexport { registerPressButtonTool } from './press-button.js';\nexport { registerDismissKeyboardTool } from './dismiss-keyboard.js';\nexport { registerDismissAlertTool } from './dismiss-alert.js';\nexport { registerLogsTool } from './logs.js';\nexport { registerScrollToElementTool } from './scroll-to-element.js';\nexport { registerAlertTextTool } from './alert-text.js';\nexport { registerWindowSizeTool } from './window-size.js';\nexport { registerContextTool } from './context.js';\nexport { registerClipboardTool } from './clipboard.js';\nexport { registerScreenRecordingTool } from './screen-recording.js';\nexport { registerGenerateLocatorsTool } from './generate-locators.js';\nexport { registerGetElementTextTool } from './get-element-text.js';\nexport { registerListDevicesTool } from './list-devices.js';\nexport { registerSelectDeviceTool } from './select-device.js';\nexport { registerHermesCdpTool } from './hermes-cdp.js';\nexport { registerHermesTargetsTool } from './hermes-targets.js';\n"]}
|
package/dist/tools/index.d.cts
CHANGED
|
@@ -24,4 +24,6 @@ export { registerGenerateLocatorsTool } from "./generate-locators.cjs";
|
|
|
24
24
|
export { registerGetElementTextTool } from "./get-element-text.cjs";
|
|
25
25
|
export { registerListDevicesTool } from "./list-devices.cjs";
|
|
26
26
|
export { registerSelectDeviceTool } from "./select-device.cjs";
|
|
27
|
+
export { registerHermesCdpTool } from "./hermes-cdp.cjs";
|
|
28
|
+
export { registerHermesTargetsTool } from "./hermes-targets.cjs";
|
|
27
29
|
//# sourceMappingURL=index.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,uBAAsB;AACrD,OAAO,EAAE,sBAAsB,EAAE,yBAAwB;AACzD,OAAO,EAAE,sBAAsB,EAAE,0BAAyB;AAC1D,OAAO,EAAE,0BAA0B,EAAE,8BAA6B;AAClE,OAAO,EAAE,gBAAgB,EAAE,wBAAuB;AAClD,OAAO,EAAE,iBAAiB,EAAE,oBAAmB;AAC/C,OAAO,EAAE,qBAAqB,EAAE,yBAAwB;AACxD,OAAO,EAAE,mBAAmB,EAAE,uBAAsB;AACpD,OAAO,EAAE,oBAAoB,EAAE,wBAAuB;AACtD,OAAO,EAAE,sBAAsB,EAAE,0BAAyB;AAC1D,OAAO,EAAE,mBAAmB,EAAE,uBAAsB;AACpD,OAAO,EAAE,oBAAoB,EAAE,wBAAuB;AACtD,OAAO,EAAE,uBAAuB,EAAE,2BAA0B;AAC5D,OAAO,EAAE,2BAA2B,EAAE,+BAA8B;AACpE,OAAO,EAAE,wBAAwB,EAAE,4BAA2B;AAC9D,OAAO,EAAE,gBAAgB,EAAE,mBAAkB;AAC7C,OAAO,EAAE,2BAA2B,EAAE,gCAA+B;AACrE,OAAO,EAAE,qBAAqB,EAAE,yBAAwB;AACxD,OAAO,EAAE,sBAAsB,EAAE,0BAAyB;AAC1D,OAAO,EAAE,mBAAmB,EAAE,sBAAqB;AACnD,OAAO,EAAE,qBAAqB,EAAE,wBAAuB;AACvD,OAAO,EAAE,2BAA2B,EAAE,+BAA8B;AACpE,OAAO,EAAE,4BAA4B,EAAE,gCAA+B;AACtE,OAAO,EAAE,0BAA0B,EAAE,+BAA8B;AACnE,OAAO,EAAE,uBAAuB,EAAE,2BAA0B;AAC5D,OAAO,EAAE,wBAAwB,EAAE,4BAA2B"}
|
|
1
|
+
{"version":3,"file":"index.d.cts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,uBAAsB;AACrD,OAAO,EAAE,sBAAsB,EAAE,yBAAwB;AACzD,OAAO,EAAE,sBAAsB,EAAE,0BAAyB;AAC1D,OAAO,EAAE,0BAA0B,EAAE,8BAA6B;AAClE,OAAO,EAAE,gBAAgB,EAAE,wBAAuB;AAClD,OAAO,EAAE,iBAAiB,EAAE,oBAAmB;AAC/C,OAAO,EAAE,qBAAqB,EAAE,yBAAwB;AACxD,OAAO,EAAE,mBAAmB,EAAE,uBAAsB;AACpD,OAAO,EAAE,oBAAoB,EAAE,wBAAuB;AACtD,OAAO,EAAE,sBAAsB,EAAE,0BAAyB;AAC1D,OAAO,EAAE,mBAAmB,EAAE,uBAAsB;AACpD,OAAO,EAAE,oBAAoB,EAAE,wBAAuB;AACtD,OAAO,EAAE,uBAAuB,EAAE,2BAA0B;AAC5D,OAAO,EAAE,2BAA2B,EAAE,+BAA8B;AACpE,OAAO,EAAE,wBAAwB,EAAE,4BAA2B;AAC9D,OAAO,EAAE,gBAAgB,EAAE,mBAAkB;AAC7C,OAAO,EAAE,2BAA2B,EAAE,gCAA+B;AACrE,OAAO,EAAE,qBAAqB,EAAE,yBAAwB;AACxD,OAAO,EAAE,sBAAsB,EAAE,0BAAyB;AAC1D,OAAO,EAAE,mBAAmB,EAAE,sBAAqB;AACnD,OAAO,EAAE,qBAAqB,EAAE,wBAAuB;AACvD,OAAO,EAAE,2BAA2B,EAAE,+BAA8B;AACpE,OAAO,EAAE,4BAA4B,EAAE,gCAA+B;AACtE,OAAO,EAAE,0BAA0B,EAAE,+BAA8B;AACnE,OAAO,EAAE,uBAAuB,EAAE,2BAA0B;AAC5D,OAAO,EAAE,wBAAwB,EAAE,4BAA2B;AAC9D,OAAO,EAAE,qBAAqB,EAAE,yBAAwB;AACxD,OAAO,EAAE,yBAAyB,EAAE,6BAA4B"}
|
package/dist/tools/index.d.mts
CHANGED
|
@@ -24,4 +24,6 @@ export { registerGenerateLocatorsTool } from "./generate-locators.mjs";
|
|
|
24
24
|
export { registerGetElementTextTool } from "./get-element-text.mjs";
|
|
25
25
|
export { registerListDevicesTool } from "./list-devices.mjs";
|
|
26
26
|
export { registerSelectDeviceTool } from "./select-device.mjs";
|
|
27
|
+
export { registerHermesCdpTool } from "./hermes-cdp.mjs";
|
|
28
|
+
export { registerHermesTargetsTool } from "./hermes-targets.mjs";
|
|
27
29
|
//# sourceMappingURL=index.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,uBAAsB;AACrD,OAAO,EAAE,sBAAsB,EAAE,yBAAwB;AACzD,OAAO,EAAE,sBAAsB,EAAE,0BAAyB;AAC1D,OAAO,EAAE,0BAA0B,EAAE,8BAA6B;AAClE,OAAO,EAAE,gBAAgB,EAAE,wBAAuB;AAClD,OAAO,EAAE,iBAAiB,EAAE,oBAAmB;AAC/C,OAAO,EAAE,qBAAqB,EAAE,yBAAwB;AACxD,OAAO,EAAE,mBAAmB,EAAE,uBAAsB;AACpD,OAAO,EAAE,oBAAoB,EAAE,wBAAuB;AACtD,OAAO,EAAE,sBAAsB,EAAE,0BAAyB;AAC1D,OAAO,EAAE,mBAAmB,EAAE,uBAAsB;AACpD,OAAO,EAAE,oBAAoB,EAAE,wBAAuB;AACtD,OAAO,EAAE,uBAAuB,EAAE,2BAA0B;AAC5D,OAAO,EAAE,2BAA2B,EAAE,+BAA8B;AACpE,OAAO,EAAE,wBAAwB,EAAE,4BAA2B;AAC9D,OAAO,EAAE,gBAAgB,EAAE,mBAAkB;AAC7C,OAAO,EAAE,2BAA2B,EAAE,gCAA+B;AACrE,OAAO,EAAE,qBAAqB,EAAE,yBAAwB;AACxD,OAAO,EAAE,sBAAsB,EAAE,0BAAyB;AAC1D,OAAO,EAAE,mBAAmB,EAAE,sBAAqB;AACnD,OAAO,EAAE,qBAAqB,EAAE,wBAAuB;AACvD,OAAO,EAAE,2BAA2B,EAAE,+BAA8B;AACpE,OAAO,EAAE,4BAA4B,EAAE,gCAA+B;AACtE,OAAO,EAAE,0BAA0B,EAAE,+BAA8B;AACnE,OAAO,EAAE,uBAAuB,EAAE,2BAA0B;AAC5D,OAAO,EAAE,wBAAwB,EAAE,4BAA2B"}
|
|
1
|
+
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,uBAAsB;AACrD,OAAO,EAAE,sBAAsB,EAAE,yBAAwB;AACzD,OAAO,EAAE,sBAAsB,EAAE,0BAAyB;AAC1D,OAAO,EAAE,0BAA0B,EAAE,8BAA6B;AAClE,OAAO,EAAE,gBAAgB,EAAE,wBAAuB;AAClD,OAAO,EAAE,iBAAiB,EAAE,oBAAmB;AAC/C,OAAO,EAAE,qBAAqB,EAAE,yBAAwB;AACxD,OAAO,EAAE,mBAAmB,EAAE,uBAAsB;AACpD,OAAO,EAAE,oBAAoB,EAAE,wBAAuB;AACtD,OAAO,EAAE,sBAAsB,EAAE,0BAAyB;AAC1D,OAAO,EAAE,mBAAmB,EAAE,uBAAsB;AACpD,OAAO,EAAE,oBAAoB,EAAE,wBAAuB;AACtD,OAAO,EAAE,uBAAuB,EAAE,2BAA0B;AAC5D,OAAO,EAAE,2BAA2B,EAAE,+BAA8B;AACpE,OAAO,EAAE,wBAAwB,EAAE,4BAA2B;AAC9D,OAAO,EAAE,gBAAgB,EAAE,mBAAkB;AAC7C,OAAO,EAAE,2BAA2B,EAAE,gCAA+B;AACrE,OAAO,EAAE,qBAAqB,EAAE,yBAAwB;AACxD,OAAO,EAAE,sBAAsB,EAAE,0BAAyB;AAC1D,OAAO,EAAE,mBAAmB,EAAE,sBAAqB;AACnD,OAAO,EAAE,qBAAqB,EAAE,wBAAuB;AACvD,OAAO,EAAE,2BAA2B,EAAE,+BAA8B;AACpE,OAAO,EAAE,4BAA4B,EAAE,gCAA+B;AACtE,OAAO,EAAE,0BAA0B,EAAE,+BAA8B;AACnE,OAAO,EAAE,uBAAuB,EAAE,2BAA0B;AAC5D,OAAO,EAAE,wBAAwB,EAAE,4BAA2B;AAC9D,OAAO,EAAE,qBAAqB,EAAE,yBAAwB;AACxD,OAAO,EAAE,yBAAyB,EAAE,6BAA4B"}
|
package/dist/tools/index.mjs
CHANGED
|
@@ -24,4 +24,6 @@ export { registerGenerateLocatorsTool } from "./generate-locators.mjs";
|
|
|
24
24
|
export { registerGetElementTextTool } from "./get-element-text.mjs";
|
|
25
25
|
export { registerListDevicesTool } from "./list-devices.mjs";
|
|
26
26
|
export { registerSelectDeviceTool } from "./select-device.mjs";
|
|
27
|
+
export { registerHermesCdpTool } from "./hermes-cdp.mjs";
|
|
28
|
+
export { registerHermesTargetsTool } from "./hermes-targets.mjs";
|
|
27
29
|
//# sourceMappingURL=index.mjs.map
|
package/dist/tools/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,uBAAsB;AACrD,OAAO,EAAE,sBAAsB,EAAE,yBAAwB;AACzD,OAAO,EAAE,sBAAsB,EAAE,0BAAyB;AAC1D,OAAO,EAAE,0BAA0B,EAAE,8BAA6B;AAClE,OAAO,EAAE,gBAAgB,EAAE,wBAAuB;AAClD,OAAO,EAAE,iBAAiB,EAAE,oBAAmB;AAC/C,OAAO,EAAE,qBAAqB,EAAE,yBAAwB;AACxD,OAAO,EAAE,mBAAmB,EAAE,uBAAsB;AACpD,OAAO,EAAE,oBAAoB,EAAE,wBAAuB;AACtD,OAAO,EAAE,sBAAsB,EAAE,0BAAyB;AAC1D,OAAO,EAAE,mBAAmB,EAAE,uBAAsB;AACpD,OAAO,EAAE,oBAAoB,EAAE,wBAAuB;AACtD,OAAO,EAAE,uBAAuB,EAAE,2BAA0B;AAC5D,OAAO,EAAE,2BAA2B,EAAE,+BAA8B;AACpE,OAAO,EAAE,wBAAwB,EAAE,4BAA2B;AAC9D,OAAO,EAAE,gBAAgB,EAAE,mBAAkB;AAC7C,OAAO,EAAE,2BAA2B,EAAE,gCAA+B;AACrE,OAAO,EAAE,qBAAqB,EAAE,yBAAwB;AACxD,OAAO,EAAE,sBAAsB,EAAE,0BAAyB;AAC1D,OAAO,EAAE,mBAAmB,EAAE,sBAAqB;AACnD,OAAO,EAAE,qBAAqB,EAAE,wBAAuB;AACvD,OAAO,EAAE,2BAA2B,EAAE,+BAA8B;AACpE,OAAO,EAAE,4BAA4B,EAAE,gCAA+B;AACtE,OAAO,EAAE,0BAA0B,EAAE,+BAA8B;AACnE,OAAO,EAAE,uBAAuB,EAAE,2BAA0B;AAC5D,OAAO,EAAE,wBAAwB,EAAE,4BAA2B","sourcesContent":["export { registerSnapshotTool } from './snapshot.js';\nexport { registerScreenshotTool } from './screenshot.js';\nexport { registerTapElementTool } from './tap-element.js';\nexport { registerTapCoordinatesTool } from './tap-coordinates.js';\nexport { registerTypeTool } from './type-text.js';\nexport { registerSwipeTool } from './swipe.js';\nexport { registerLongPressTool } from './long-press.js';\nexport { registerWaitForTool } from './wait-for.js';\nexport { registerAppStateTool } from './app-state.js';\nexport { registerDeviceInfoTool } from './device-info.js';\nexport { registerOpenAppTool } from './open-app.js';\nexport { registerCloseAppTool } from './close-app.js';\nexport { registerPressButtonTool } from './press-button.js';\nexport { registerDismissKeyboardTool } from './dismiss-keyboard.js';\nexport { registerDismissAlertTool } from './dismiss-alert.js';\nexport { registerLogsTool } from './logs.js';\nexport { registerScrollToElementTool } from './scroll-to-element.js';\nexport { registerAlertTextTool } from './alert-text.js';\nexport { registerWindowSizeTool } from './window-size.js';\nexport { registerContextTool } from './context.js';\nexport { registerClipboardTool } from './clipboard.js';\nexport { registerScreenRecordingTool } from './screen-recording.js';\nexport { registerGenerateLocatorsTool } from './generate-locators.js';\nexport { registerGetElementTextTool } from './get-element-text.js';\nexport { registerListDevicesTool } from './list-devices.js';\nexport { registerSelectDeviceTool } from './select-device.js';\n"]}
|
|
1
|
+
{"version":3,"file":"index.mjs","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,uBAAsB;AACrD,OAAO,EAAE,sBAAsB,EAAE,yBAAwB;AACzD,OAAO,EAAE,sBAAsB,EAAE,0BAAyB;AAC1D,OAAO,EAAE,0BAA0B,EAAE,8BAA6B;AAClE,OAAO,EAAE,gBAAgB,EAAE,wBAAuB;AAClD,OAAO,EAAE,iBAAiB,EAAE,oBAAmB;AAC/C,OAAO,EAAE,qBAAqB,EAAE,yBAAwB;AACxD,OAAO,EAAE,mBAAmB,EAAE,uBAAsB;AACpD,OAAO,EAAE,oBAAoB,EAAE,wBAAuB;AACtD,OAAO,EAAE,sBAAsB,EAAE,0BAAyB;AAC1D,OAAO,EAAE,mBAAmB,EAAE,uBAAsB;AACpD,OAAO,EAAE,oBAAoB,EAAE,wBAAuB;AACtD,OAAO,EAAE,uBAAuB,EAAE,2BAA0B;AAC5D,OAAO,EAAE,2BAA2B,EAAE,+BAA8B;AACpE,OAAO,EAAE,wBAAwB,EAAE,4BAA2B;AAC9D,OAAO,EAAE,gBAAgB,EAAE,mBAAkB;AAC7C,OAAO,EAAE,2BAA2B,EAAE,gCAA+B;AACrE,OAAO,EAAE,qBAAqB,EAAE,yBAAwB;AACxD,OAAO,EAAE,sBAAsB,EAAE,0BAAyB;AAC1D,OAAO,EAAE,mBAAmB,EAAE,sBAAqB;AACnD,OAAO,EAAE,qBAAqB,EAAE,wBAAuB;AACvD,OAAO,EAAE,2BAA2B,EAAE,+BAA8B;AACpE,OAAO,EAAE,4BAA4B,EAAE,gCAA+B;AACtE,OAAO,EAAE,0BAA0B,EAAE,+BAA8B;AACnE,OAAO,EAAE,uBAAuB,EAAE,2BAA0B;AAC5D,OAAO,EAAE,wBAAwB,EAAE,4BAA2B;AAC9D,OAAO,EAAE,qBAAqB,EAAE,yBAAwB;AACxD,OAAO,EAAE,yBAAyB,EAAE,6BAA4B","sourcesContent":["export { registerSnapshotTool } from './snapshot.js';\nexport { registerScreenshotTool } from './screenshot.js';\nexport { registerTapElementTool } from './tap-element.js';\nexport { registerTapCoordinatesTool } from './tap-coordinates.js';\nexport { registerTypeTool } from './type-text.js';\nexport { registerSwipeTool } from './swipe.js';\nexport { registerLongPressTool } from './long-press.js';\nexport { registerWaitForTool } from './wait-for.js';\nexport { registerAppStateTool } from './app-state.js';\nexport { registerDeviceInfoTool } from './device-info.js';\nexport { registerOpenAppTool } from './open-app.js';\nexport { registerCloseAppTool } from './close-app.js';\nexport { registerPressButtonTool } from './press-button.js';\nexport { registerDismissKeyboardTool } from './dismiss-keyboard.js';\nexport { registerDismissAlertTool } from './dismiss-alert.js';\nexport { registerLogsTool } from './logs.js';\nexport { registerScrollToElementTool } from './scroll-to-element.js';\nexport { registerAlertTextTool } from './alert-text.js';\nexport { registerWindowSizeTool } from './window-size.js';\nexport { registerContextTool } from './context.js';\nexport { registerClipboardTool } from './clipboard.js';\nexport { registerScreenRecordingTool } from './screen-recording.js';\nexport { registerGenerateLocatorsTool } from './generate-locators.js';\nexport { registerGetElementTextTool } from './get-element-text.js';\nexport { registerListDevicesTool } from './list-devices.js';\nexport { registerSelectDeviceTool } from './select-device.js';\nexport { registerHermesCdpTool } from './hermes-cdp.js';\nexport { registerHermesTargetsTool } from './hermes-targets.js';\n"]}
|
|
@@ -17,14 +17,19 @@ function registerScreenshotTool(server, backend) {
|
|
|
17
17
|
annotations: { readOnlyHint: true },
|
|
18
18
|
}, async ({ outputPath }) => {
|
|
19
19
|
try {
|
|
20
|
-
const result = await backend.screenshot(outputPath);
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
const result = await backend.screenshot(outputPath, { encode: true });
|
|
21
|
+
const imageContent = result.data
|
|
22
|
+
? [
|
|
23
23
|
{
|
|
24
24
|
type: 'image',
|
|
25
25
|
data: result.data,
|
|
26
26
|
mimeType: `image/${result.format}`,
|
|
27
27
|
},
|
|
28
|
+
]
|
|
29
|
+
: [];
|
|
30
|
+
return {
|
|
31
|
+
content: [
|
|
32
|
+
...imageContent,
|
|
28
33
|
{
|
|
29
34
|
type: 'text',
|
|
30
35
|
text: result.path
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"screenshot.cjs","sourceRoot":"","sources":["../../src/tools/screenshot.ts"],"names":[],"mappings":";;AAMA,
|
|
1
|
+
{"version":3,"file":"screenshot.cjs","sourceRoot":"","sources":["../../src/tools/screenshot.ts"],"names":[],"mappings":";;AAMA,wDA+CC;AApDD,6BAAwB;AAExB,4CAA0C;AAG1C,SAAgB,sBAAsB,CACpC,MAAiB,EACjB,OAAsB;IAEtB,MAAM,CAAC,YAAY,CACjB,mBAAmB,EACnB;QACE,KAAK,EAAE,YAAY;QACnB,WAAW,EACT,qDAAqD;YACrD,mEAAmE;QACrE,WAAW,EAAE;YACX,UAAU,EAAE,OAAC;iBACV,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,QAAQ,CAAC,6CAA6C,CAAC;SAC3D;QACD,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE;KACpC,EACD,KAAK,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE;QACvB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,UAAU,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;YACtE,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI;gBAC9B,CAAC,CAAC;oBACE;wBACE,IAAI,EAAE,OAAgB;wBACtB,IAAI,EAAE,MAAM,CAAC,IAAI;wBACjB,QAAQ,EAAE,SAAS,MAAM,CAAC,MAAM,EAAE;qBACnC;iBACF;gBACH,CAAC,CAAC,EAAE,CAAC;YACP,OAAO;gBACL,OAAO,EAAE;oBACP,GAAG,YAAY;oBACf;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,MAAM,CAAC,IAAI;4BACf,CAAC,CAAC,uBAAuB,MAAM,CAAC,IAAI,EAAE;4BACtC,CAAC,CAAC,qBAAqB;qBAC1B;iBACF;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,IAAA,uBAAW,EAAC,KAAK,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC","sourcesContent":["import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\n\nimport { errorResult } from './shared.js';\nimport type { DeviceBackend } from '../backends/types.js';\n\nexport function registerScreenshotTool(\n server: McpServer,\n backend: DeviceBackend,\n): void {\n server.registerTool(\n 'device_screenshot',\n {\n title: 'Screenshot',\n description:\n 'Capture a screenshot of the current device screen. ' +\n 'Returns base64-encoded PNG data. Optionally saves to a file path.',\n inputSchema: {\n outputPath: z\n .string()\n .optional()\n .describe('File path to save the screenshot (optional)'),\n },\n annotations: { readOnlyHint: true },\n },\n async ({ outputPath }) => {\n try {\n const result = await backend.screenshot(outputPath, { encode: true });\n const imageContent = result.data\n ? [\n {\n type: 'image' as const,\n data: result.data,\n mimeType: `image/${result.format}`,\n },\n ]\n : [];\n return {\n content: [\n ...imageContent,\n {\n type: 'text' as const,\n text: result.path\n ? `Screenshot saved to ${result.path}`\n : 'Screenshot captured',\n },\n ],\n };\n } catch (error) {\n return errorResult(error);\n }\n },\n );\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"screenshot.d.cts","sourceRoot":"","sources":["../../src/tools/screenshot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,gDAAgD;AAIzE,OAAO,KAAK,EAAE,aAAa,EAAE,8BAA6B;AAE1D,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,aAAa,GACrB,IAAI,
|
|
1
|
+
{"version":3,"file":"screenshot.d.cts","sourceRoot":"","sources":["../../src/tools/screenshot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,gDAAgD;AAIzE,OAAO,KAAK,EAAE,aAAa,EAAE,8BAA6B;AAE1D,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,aAAa,GACrB,IAAI,CA4CN"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"screenshot.d.mts","sourceRoot":"","sources":["../../src/tools/screenshot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,gDAAgD;AAIzE,OAAO,KAAK,EAAE,aAAa,EAAE,8BAA6B;AAE1D,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,aAAa,GACrB,IAAI,
|
|
1
|
+
{"version":3,"file":"screenshot.d.mts","sourceRoot":"","sources":["../../src/tools/screenshot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,gDAAgD;AAIzE,OAAO,KAAK,EAAE,aAAa,EAAE,8BAA6B;AAE1D,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,aAAa,GACrB,IAAI,CA4CN"}
|
|
@@ -14,14 +14,19 @@ export function registerScreenshotTool(server, backend) {
|
|
|
14
14
|
annotations: { readOnlyHint: true },
|
|
15
15
|
}, async ({ outputPath }) => {
|
|
16
16
|
try {
|
|
17
|
-
const result = await backend.screenshot(outputPath);
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
const result = await backend.screenshot(outputPath, { encode: true });
|
|
18
|
+
const imageContent = result.data
|
|
19
|
+
? [
|
|
20
20
|
{
|
|
21
21
|
type: 'image',
|
|
22
22
|
data: result.data,
|
|
23
23
|
mimeType: `image/${result.format}`,
|
|
24
24
|
},
|
|
25
|
+
]
|
|
26
|
+
: [];
|
|
27
|
+
return {
|
|
28
|
+
content: [
|
|
29
|
+
...imageContent,
|
|
25
30
|
{
|
|
26
31
|
type: 'text',
|
|
27
32
|
text: result.path
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"screenshot.mjs","sourceRoot":"","sources":["../../src/tools/screenshot.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,YAAY;AAExB,OAAO,EAAE,WAAW,EAAE,qBAAoB;AAG1C,MAAM,UAAU,sBAAsB,CACpC,MAAiB,EACjB,OAAsB;IAEtB,MAAM,CAAC,YAAY,CACjB,mBAAmB,EACnB;QACE,KAAK,EAAE,YAAY;QACnB,WAAW,EACT,qDAAqD;YACrD,mEAAmE;QACrE,WAAW,EAAE;YACX,UAAU,EAAE,CAAC;iBACV,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,QAAQ,CAAC,6CAA6C,CAAC;SAC3D;QACD,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE;KACpC,EACD,KAAK,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE;QACvB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"screenshot.mjs","sourceRoot":"","sources":["../../src/tools/screenshot.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,YAAY;AAExB,OAAO,EAAE,WAAW,EAAE,qBAAoB;AAG1C,MAAM,UAAU,sBAAsB,CACpC,MAAiB,EACjB,OAAsB;IAEtB,MAAM,CAAC,YAAY,CACjB,mBAAmB,EACnB;QACE,KAAK,EAAE,YAAY;QACnB,WAAW,EACT,qDAAqD;YACrD,mEAAmE;QACrE,WAAW,EAAE;YACX,UAAU,EAAE,CAAC;iBACV,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,QAAQ,CAAC,6CAA6C,CAAC;SAC3D;QACD,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE;KACpC,EACD,KAAK,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE;QACvB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,UAAU,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;YACtE,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI;gBAC9B,CAAC,CAAC;oBACE;wBACE,IAAI,EAAE,OAAgB;wBACtB,IAAI,EAAE,MAAM,CAAC,IAAI;wBACjB,QAAQ,EAAE,SAAS,MAAM,CAAC,MAAM,EAAE;qBACnC;iBACF;gBACH,CAAC,CAAC,EAAE,CAAC;YACP,OAAO;gBACL,OAAO,EAAE;oBACP,GAAG,YAAY;oBACf;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,MAAM,CAAC,IAAI;4BACf,CAAC,CAAC,uBAAuB,MAAM,CAAC,IAAI,EAAE;4BACtC,CAAC,CAAC,qBAAqB;qBAC1B;iBACF;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC","sourcesContent":["import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\n\nimport { errorResult } from './shared.js';\nimport type { DeviceBackend } from '../backends/types.js';\n\nexport function registerScreenshotTool(\n server: McpServer,\n backend: DeviceBackend,\n): void {\n server.registerTool(\n 'device_screenshot',\n {\n title: 'Screenshot',\n description:\n 'Capture a screenshot of the current device screen. ' +\n 'Returns base64-encoded PNG data. Optionally saves to a file path.',\n inputSchema: {\n outputPath: z\n .string()\n .optional()\n .describe('File path to save the screenshot (optional)'),\n },\n annotations: { readOnlyHint: true },\n },\n async ({ outputPath }) => {\n try {\n const result = await backend.screenshot(outputPath, { encode: true });\n const imageContent = result.data\n ? [\n {\n type: 'image' as const,\n data: result.data,\n mimeType: `image/${result.format}`,\n },\n ]\n : [];\n return {\n content: [\n ...imageContent,\n {\n type: 'text' as const,\n text: result.path\n ? `Screenshot saved to ${result.path}`\n : 'Screenshot captured',\n },\n ],\n };\n } catch (error) {\n return errorResult(error);\n }\n },\n );\n}\n"]}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveArtifactPath = resolveArtifactPath;
|
|
4
|
+
exports.writeArtifactFile = writeArtifactFile;
|
|
5
|
+
exports.hardenArtifactFile = hardenArtifactFile;
|
|
6
|
+
const node_crypto_1 = require("node:crypto");
|
|
7
|
+
const node_fs_1 = require("node:fs");
|
|
8
|
+
const promises_1 = require("node:fs/promises");
|
|
9
|
+
const node_os_1 = require("node:os");
|
|
10
|
+
const node_path_1 = require("node:path");
|
|
11
|
+
/** Kinds of on-disk device artifact and their file extension. */
|
|
12
|
+
const ARTIFACT_EXTENSION = {
|
|
13
|
+
screenshot: 'png',
|
|
14
|
+
recording: 'mp4',
|
|
15
|
+
};
|
|
16
|
+
/** Owner read/write only — device artifacts may contain sensitive UI (seed phrases, keys, balances). */
|
|
17
|
+
const ARTIFACT_MODE = 0o600;
|
|
18
|
+
/**
|
|
19
|
+
* Open flags for writing an artifact: create/truncate, write-only, and
|
|
20
|
+
* `O_NOFOLLOW` so a pre-planted symlink at the destination is never followed
|
|
21
|
+
* (defeats `/tmp` symlink / TOCTOU attacks). `O_NOFOLLOW` is a no-op on
|
|
22
|
+
* platforms that do not define it.
|
|
23
|
+
*/
|
|
24
|
+
/* eslint-disable no-bitwise -- POSIX open(2) flags are combined with bitwise OR */
|
|
25
|
+
const ARTIFACT_WRITE_FLAGS = node_fs_1.constants.O_WRONLY |
|
|
26
|
+
node_fs_1.constants.O_CREAT |
|
|
27
|
+
node_fs_1.constants.O_TRUNC |
|
|
28
|
+
(node_fs_1.constants.O_NOFOLLOW ?? 0);
|
|
29
|
+
/* eslint-enable no-bitwise */
|
|
30
|
+
let sessionTempDir = null;
|
|
31
|
+
/**
|
|
32
|
+
* Lazily create (once per process) a private temporary directory with
|
|
33
|
+
* owner-only permissions (`0700`) to hold default artifacts. Because the
|
|
34
|
+
* directory itself is unreadable to other users, files placed inside it are
|
|
35
|
+
* protected even when written by an external tool (idb/adb) that we cannot
|
|
36
|
+
* pass secure open flags to.
|
|
37
|
+
*
|
|
38
|
+
* @returns The absolute path to the per-process private temp directory.
|
|
39
|
+
*/
|
|
40
|
+
function getSessionTempDir() {
|
|
41
|
+
if (!sessionTempDir) {
|
|
42
|
+
sessionTempDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'device-mcp-'));
|
|
43
|
+
}
|
|
44
|
+
return sessionTempDir;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Resolve the optional output-directory allowlist from the environment.
|
|
48
|
+
*
|
|
49
|
+
* @returns The absolute allowed base directory, or `null` when unrestricted.
|
|
50
|
+
*/
|
|
51
|
+
function allowedBaseDir() {
|
|
52
|
+
const configured = process.env.DEVICE_MCP_OUTPUT_DIR?.trim();
|
|
53
|
+
return configured ? (0, node_path_1.resolve)(configured) : null;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Reject a caller-supplied path that escapes `DEVICE_MCP_OUTPUT_DIR` when that
|
|
57
|
+
* sandbox is configured. No-op when the env var is unset.
|
|
58
|
+
*
|
|
59
|
+
* @param target - The already-resolved absolute destination path.
|
|
60
|
+
*/
|
|
61
|
+
function assertWithinAllowedBase(target) {
|
|
62
|
+
const base = allowedBaseDir();
|
|
63
|
+
if (!base) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const rel = (0, node_path_1.relative)(base, target);
|
|
67
|
+
if (rel.length === 0 || rel.startsWith('..') || (0, node_path_1.isAbsolute)(rel)) {
|
|
68
|
+
throw new Error(`Refusing to write outside DEVICE_MCP_OUTPUT_DIR (${base}): ${target}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Compute a safe absolute path for a device artifact.
|
|
73
|
+
*
|
|
74
|
+
* - When no `outputPath` is given, returns an unpredictable file name inside a
|
|
75
|
+
* private per-process temp directory. This replaces the previous predictable
|
|
76
|
+
* `/tmp/device-mcp-*-${Date.now()}` names, which were vulnerable to symlink
|
|
77
|
+
* and information-disclosure attacks in the shared temp directory.
|
|
78
|
+
* - When an `outputPath` is given, it is rejected if it contains a NUL byte,
|
|
79
|
+
* resolved to an absolute path, and — when `DEVICE_MCP_OUTPUT_DIR` is set —
|
|
80
|
+
* confined to that directory.
|
|
81
|
+
*
|
|
82
|
+
* @param outputPath - Optional caller-supplied destination path.
|
|
83
|
+
* @param kind - The artifact kind, used to pick the default file extension.
|
|
84
|
+
* @returns The resolved, validated absolute path to write to.
|
|
85
|
+
*/
|
|
86
|
+
function resolveArtifactPath(outputPath, kind) {
|
|
87
|
+
if (outputPath === undefined) {
|
|
88
|
+
const token = (0, node_crypto_1.randomBytes)(8).toString('hex');
|
|
89
|
+
return (0, node_path_1.join)(getSessionTempDir(), `${kind}-${token}.${ARTIFACT_EXTENSION[kind]}`);
|
|
90
|
+
}
|
|
91
|
+
if (outputPath.includes('\0')) {
|
|
92
|
+
throw new Error('Invalid output path: contains a NUL byte');
|
|
93
|
+
}
|
|
94
|
+
const target = (0, node_path_1.resolve)(outputPath);
|
|
95
|
+
assertWithinAllowedBase(target);
|
|
96
|
+
return target;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Write bytes to an artifact path with hardened permissions and open flags:
|
|
100
|
+
* owner-only (`0600`) and `O_NOFOLLOW`. Use for artifacts we write directly
|
|
101
|
+
* in-process (e.g. the Appium backend decoding a base64 screenshot).
|
|
102
|
+
*
|
|
103
|
+
* @param path - The destination path (should come from `resolveArtifactPath`).
|
|
104
|
+
* @param data - The bytes to write.
|
|
105
|
+
*/
|
|
106
|
+
async function writeArtifactFile(path, data) {
|
|
107
|
+
await (0, promises_1.writeFile)(path, data, {
|
|
108
|
+
mode: ARTIFACT_MODE,
|
|
109
|
+
flag: ARTIFACT_WRITE_FLAGS,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Best-effort tightening of permissions on a file written by an external tool
|
|
114
|
+
* (idb/adb create the file themselves, so we cannot pass secure open flags).
|
|
115
|
+
* Silently ignored when unsupported or when the file is absent.
|
|
116
|
+
*
|
|
117
|
+
* @param path - The path of the file to restrict to owner-only access.
|
|
118
|
+
*/
|
|
119
|
+
function hardenArtifactFile(path) {
|
|
120
|
+
try {
|
|
121
|
+
(0, node_fs_1.chmodSync)(path, ARTIFACT_MODE);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
// Best-effort: the filesystem may not support chmod (e.g. some mounts on
|
|
125
|
+
// Windows) or the external tool may have failed to create the file.
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=output-path.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output-path.cjs","sourceRoot":"","sources":["../../src/utils/output-path.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomBytes } from 'node:crypto';\nimport { chmodSync, constants as fsConstants, mkdtempSync } from 'node:fs';\nimport { writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { isAbsolute, join, relative, resolve } from 'node:path';\n\n/** Kinds of on-disk device artifact and their file extension. */\nconst ARTIFACT_EXTENSION = {\n screenshot: 'png',\n recording: 'mp4',\n} as const;\n\nexport type ArtifactKind = keyof typeof ARTIFACT_EXTENSION;\n\n/** Owner read/write only — device artifacts may contain sensitive UI (seed phrases, keys, balances). */\nconst ARTIFACT_MODE = 0o600;\n\n/**\n * Open flags for writing an artifact: create/truncate, write-only, and\n * `O_NOFOLLOW` so a pre-planted symlink at the destination is never followed\n * (defeats `/tmp` symlink / TOCTOU attacks). `O_NOFOLLOW` is a no-op on\n * platforms that do not define it.\n */\n/* eslint-disable no-bitwise -- POSIX open(2) flags are combined with bitwise OR */\nconst ARTIFACT_WRITE_FLAGS =\n fsConstants.O_WRONLY |\n fsConstants.O_CREAT |\n fsConstants.O_TRUNC |\n (fsConstants.O_NOFOLLOW ?? 0);\n/* eslint-enable no-bitwise */\n\nlet sessionTempDir: string | null = null;\n\n/**\n * Lazily create (once per process) a private temporary directory with\n * owner-only permissions (`0700`) to hold default artifacts. Because the\n * directory itself is unreadable to other users, files placed inside it are\n * protected even when written by an external tool (idb/adb) that we cannot\n * pass secure open flags to.\n *\n * @returns The absolute path to the per-process private temp directory.\n */\nfunction getSessionTempDir(): string {\n if (!sessionTempDir) {\n sessionTempDir = mkdtempSync(join(tmpdir(), 'device-mcp-'));\n }\n return sessionTempDir;\n}\n\n/**\n * Resolve the optional output-directory allowlist from the environment.\n *\n * @returns The absolute allowed base directory, or `null` when unrestricted.\n */\nfunction allowedBaseDir(): string | null {\n const configured = process.env.DEVICE_MCP_OUTPUT_DIR?.trim();\n return configured ? resolve(configured) : null;\n}\n\n/**\n * Reject a caller-supplied path that escapes `DEVICE_MCP_OUTPUT_DIR` when that\n * sandbox is configured. No-op when the env var is unset.\n *\n * @param target - The already-resolved absolute destination path.\n */\nfunction assertWithinAllowedBase(target: string): void {\n const base = allowedBaseDir();\n if (!base) {\n return;\n }\n const rel = relative(base, target);\n if (rel.length === 0 || rel.startsWith('..') || isAbsolute(rel)) {\n throw new Error(\n `Refusing to write outside DEVICE_MCP_OUTPUT_DIR (${base}): ${target}`,\n );\n }\n}\n\n/**\n * Compute a safe absolute path for a device artifact.\n *\n * - When no `outputPath` is given, returns an unpredictable file name inside a\n * private per-process temp directory. This replaces the previous predictable\n * `/tmp/device-mcp-*-${Date.now()}` names, which were vulnerable to symlink\n * and information-disclosure attacks in the shared temp directory.\n * - When an `outputPath` is given, it is rejected if it contains a NUL byte,\n * resolved to an absolute path, and — when `DEVICE_MCP_OUTPUT_DIR` is set —\n * confined to that directory.\n *\n * @param outputPath - Optional caller-supplied destination path.\n * @param kind - The artifact kind, used to pick the default file extension.\n * @returns The resolved, validated absolute path to write to.\n */\nexport function resolveArtifactPath(\n outputPath: string | undefined,\n kind: ArtifactKind,\n): string {\n if (outputPath === undefined) {\n const token = randomBytes(8).toString('hex');\n return join(\n getSessionTempDir(),\n `${kind}-${token}.${ARTIFACT_EXTENSION[kind]}`,\n );\n }\n\n if (outputPath.includes('\\0')) {\n throw new Error('Invalid output path: contains a NUL byte');\n }\n\n const target = resolve(outputPath);\n assertWithinAllowedBase(target);\n return target;\n}\n\n/**\n * Write bytes to an artifact path with hardened permissions and open flags:\n * owner-only (`0600`) and `O_NOFOLLOW`. Use for artifacts we write directly\n * in-process (e.g. the Appium backend decoding a base64 screenshot).\n *\n * @param path - The destination path (should come from `resolveArtifactPath`).\n * @param data - The bytes to write.\n */\nexport async function writeArtifactFile(\n path: string,\n data: Buffer,\n): Promise<void> {\n await writeFile(path, data, {\n mode: ARTIFACT_MODE,\n flag: ARTIFACT_WRITE_FLAGS,\n });\n}\n\n/**\n * Best-effort tightening of permissions on a file written by an external tool\n * (idb/adb create the file themselves, so we cannot pass secure open flags).\n * Silently ignored when unsupported or when the file is absent.\n *\n * @param path - The path of the file to restrict to owner-only access.\n */\nexport function hardenArtifactFile(path: string): void {\n try {\n chmodSync(path, ARTIFACT_MODE);\n } catch {\n // Best-effort: the filesystem may not support chmod (e.g. some mounts on\n // Windows) or the external tool may have failed to create the file.\n }\n}\n"]}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** Kinds of on-disk device artifact and their file extension. */
|
|
2
|
+
declare const ARTIFACT_EXTENSION: {
|
|
3
|
+
readonly screenshot: "png";
|
|
4
|
+
readonly recording: "mp4";
|
|
5
|
+
};
|
|
6
|
+
export type ArtifactKind = keyof typeof ARTIFACT_EXTENSION;
|
|
7
|
+
/**
|
|
8
|
+
* Compute a safe absolute path for a device artifact.
|
|
9
|
+
*
|
|
10
|
+
* - When no `outputPath` is given, returns an unpredictable file name inside a
|
|
11
|
+
* private per-process temp directory. This replaces the previous predictable
|
|
12
|
+
* `/tmp/device-mcp-*-${Date.now()}` names, which were vulnerable to symlink
|
|
13
|
+
* and information-disclosure attacks in the shared temp directory.
|
|
14
|
+
* - When an `outputPath` is given, it is rejected if it contains a NUL byte,
|
|
15
|
+
* resolved to an absolute path, and — when `DEVICE_MCP_OUTPUT_DIR` is set —
|
|
16
|
+
* confined to that directory.
|
|
17
|
+
*
|
|
18
|
+
* @param outputPath - Optional caller-supplied destination path.
|
|
19
|
+
* @param kind - The artifact kind, used to pick the default file extension.
|
|
20
|
+
* @returns The resolved, validated absolute path to write to.
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveArtifactPath(outputPath: string | undefined, kind: ArtifactKind): string;
|
|
23
|
+
/**
|
|
24
|
+
* Write bytes to an artifact path with hardened permissions and open flags:
|
|
25
|
+
* owner-only (`0600`) and `O_NOFOLLOW`. Use for artifacts we write directly
|
|
26
|
+
* in-process (e.g. the Appium backend decoding a base64 screenshot).
|
|
27
|
+
*
|
|
28
|
+
* @param path - The destination path (should come from `resolveArtifactPath`).
|
|
29
|
+
* @param data - The bytes to write.
|
|
30
|
+
*/
|
|
31
|
+
export declare function writeArtifactFile(path: string, data: Buffer): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Best-effort tightening of permissions on a file written by an external tool
|
|
34
|
+
* (idb/adb create the file themselves, so we cannot pass secure open flags).
|
|
35
|
+
* Silently ignored when unsupported or when the file is absent.
|
|
36
|
+
*
|
|
37
|
+
* @param path - The path of the file to restrict to owner-only access.
|
|
38
|
+
*/
|
|
39
|
+
export declare function hardenArtifactFile(path: string): void;
|
|
40
|
+
export {};
|
|
41
|
+
//# sourceMappingURL=output-path.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output-path.d.cts","sourceRoot":"","sources":["../../src/utils/output-path.ts"],"names":[],"mappings":"AAMA,iEAAiE;AACjE,QAAA,MAAM,kBAAkB;;;CAGd,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG,MAAM,OAAO,kBAAkB,CAAC;AAkE3D;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,IAAI,EAAE,YAAY,GACjB,MAAM,CAgBR;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAKf;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAOrD"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** Kinds of on-disk device artifact and their file extension. */
|
|
2
|
+
declare const ARTIFACT_EXTENSION: {
|
|
3
|
+
readonly screenshot: "png";
|
|
4
|
+
readonly recording: "mp4";
|
|
5
|
+
};
|
|
6
|
+
export type ArtifactKind = keyof typeof ARTIFACT_EXTENSION;
|
|
7
|
+
/**
|
|
8
|
+
* Compute a safe absolute path for a device artifact.
|
|
9
|
+
*
|
|
10
|
+
* - When no `outputPath` is given, returns an unpredictable file name inside a
|
|
11
|
+
* private per-process temp directory. This replaces the previous predictable
|
|
12
|
+
* `/tmp/device-mcp-*-${Date.now()}` names, which were vulnerable to symlink
|
|
13
|
+
* and information-disclosure attacks in the shared temp directory.
|
|
14
|
+
* - When an `outputPath` is given, it is rejected if it contains a NUL byte,
|
|
15
|
+
* resolved to an absolute path, and — when `DEVICE_MCP_OUTPUT_DIR` is set —
|
|
16
|
+
* confined to that directory.
|
|
17
|
+
*
|
|
18
|
+
* @param outputPath - Optional caller-supplied destination path.
|
|
19
|
+
* @param kind - The artifact kind, used to pick the default file extension.
|
|
20
|
+
* @returns The resolved, validated absolute path to write to.
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveArtifactPath(outputPath: string | undefined, kind: ArtifactKind): string;
|
|
23
|
+
/**
|
|
24
|
+
* Write bytes to an artifact path with hardened permissions and open flags:
|
|
25
|
+
* owner-only (`0600`) and `O_NOFOLLOW`. Use for artifacts we write directly
|
|
26
|
+
* in-process (e.g. the Appium backend decoding a base64 screenshot).
|
|
27
|
+
*
|
|
28
|
+
* @param path - The destination path (should come from `resolveArtifactPath`).
|
|
29
|
+
* @param data - The bytes to write.
|
|
30
|
+
*/
|
|
31
|
+
export declare function writeArtifactFile(path: string, data: Buffer): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Best-effort tightening of permissions on a file written by an external tool
|
|
34
|
+
* (idb/adb create the file themselves, so we cannot pass secure open flags).
|
|
35
|
+
* Silently ignored when unsupported or when the file is absent.
|
|
36
|
+
*
|
|
37
|
+
* @param path - The path of the file to restrict to owner-only access.
|
|
38
|
+
*/
|
|
39
|
+
export declare function hardenArtifactFile(path: string): void;
|
|
40
|
+
export {};
|
|
41
|
+
//# sourceMappingURL=output-path.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output-path.d.mts","sourceRoot":"","sources":["../../src/utils/output-path.ts"],"names":[],"mappings":"AAMA,iEAAiE;AACjE,QAAA,MAAM,kBAAkB;;;CAGd,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG,MAAM,OAAO,kBAAkB,CAAC;AAkE3D;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,IAAI,EAAE,YAAY,GACjB,MAAM,CAgBR;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAKf;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAOrD"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { chmodSync, constants as fsConstants, mkdtempSync } from "node:fs";
|
|
3
|
+
import { writeFile } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
6
|
+
/** Kinds of on-disk device artifact and their file extension. */
|
|
7
|
+
const ARTIFACT_EXTENSION = {
|
|
8
|
+
screenshot: 'png',
|
|
9
|
+
recording: 'mp4',
|
|
10
|
+
};
|
|
11
|
+
/** Owner read/write only — device artifacts may contain sensitive UI (seed phrases, keys, balances). */
|
|
12
|
+
const ARTIFACT_MODE = 0o600;
|
|
13
|
+
/**
|
|
14
|
+
* Open flags for writing an artifact: create/truncate, write-only, and
|
|
15
|
+
* `O_NOFOLLOW` so a pre-planted symlink at the destination is never followed
|
|
16
|
+
* (defeats `/tmp` symlink / TOCTOU attacks). `O_NOFOLLOW` is a no-op on
|
|
17
|
+
* platforms that do not define it.
|
|
18
|
+
*/
|
|
19
|
+
/* eslint-disable no-bitwise -- POSIX open(2) flags are combined with bitwise OR */
|
|
20
|
+
const ARTIFACT_WRITE_FLAGS = fsConstants.O_WRONLY |
|
|
21
|
+
fsConstants.O_CREAT |
|
|
22
|
+
fsConstants.O_TRUNC |
|
|
23
|
+
(fsConstants.O_NOFOLLOW ?? 0);
|
|
24
|
+
/* eslint-enable no-bitwise */
|
|
25
|
+
let sessionTempDir = null;
|
|
26
|
+
/**
|
|
27
|
+
* Lazily create (once per process) a private temporary directory with
|
|
28
|
+
* owner-only permissions (`0700`) to hold default artifacts. Because the
|
|
29
|
+
* directory itself is unreadable to other users, files placed inside it are
|
|
30
|
+
* protected even when written by an external tool (idb/adb) that we cannot
|
|
31
|
+
* pass secure open flags to.
|
|
32
|
+
*
|
|
33
|
+
* @returns The absolute path to the per-process private temp directory.
|
|
34
|
+
*/
|
|
35
|
+
function getSessionTempDir() {
|
|
36
|
+
if (!sessionTempDir) {
|
|
37
|
+
sessionTempDir = mkdtempSync(join(tmpdir(), 'device-mcp-'));
|
|
38
|
+
}
|
|
39
|
+
return sessionTempDir;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Resolve the optional output-directory allowlist from the environment.
|
|
43
|
+
*
|
|
44
|
+
* @returns The absolute allowed base directory, or `null` when unrestricted.
|
|
45
|
+
*/
|
|
46
|
+
function allowedBaseDir() {
|
|
47
|
+
const configured = process.env.DEVICE_MCP_OUTPUT_DIR?.trim();
|
|
48
|
+
return configured ? resolve(configured) : null;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Reject a caller-supplied path that escapes `DEVICE_MCP_OUTPUT_DIR` when that
|
|
52
|
+
* sandbox is configured. No-op when the env var is unset.
|
|
53
|
+
*
|
|
54
|
+
* @param target - The already-resolved absolute destination path.
|
|
55
|
+
*/
|
|
56
|
+
function assertWithinAllowedBase(target) {
|
|
57
|
+
const base = allowedBaseDir();
|
|
58
|
+
if (!base) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const rel = relative(base, target);
|
|
62
|
+
if (rel.length === 0 || rel.startsWith('..') || isAbsolute(rel)) {
|
|
63
|
+
throw new Error(`Refusing to write outside DEVICE_MCP_OUTPUT_DIR (${base}): ${target}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Compute a safe absolute path for a device artifact.
|
|
68
|
+
*
|
|
69
|
+
* - When no `outputPath` is given, returns an unpredictable file name inside a
|
|
70
|
+
* private per-process temp directory. This replaces the previous predictable
|
|
71
|
+
* `/tmp/device-mcp-*-${Date.now()}` names, which were vulnerable to symlink
|
|
72
|
+
* and information-disclosure attacks in the shared temp directory.
|
|
73
|
+
* - When an `outputPath` is given, it is rejected if it contains a NUL byte,
|
|
74
|
+
* resolved to an absolute path, and — when `DEVICE_MCP_OUTPUT_DIR` is set —
|
|
75
|
+
* confined to that directory.
|
|
76
|
+
*
|
|
77
|
+
* @param outputPath - Optional caller-supplied destination path.
|
|
78
|
+
* @param kind - The artifact kind, used to pick the default file extension.
|
|
79
|
+
* @returns The resolved, validated absolute path to write to.
|
|
80
|
+
*/
|
|
81
|
+
export function resolveArtifactPath(outputPath, kind) {
|
|
82
|
+
if (outputPath === undefined) {
|
|
83
|
+
const token = randomBytes(8).toString('hex');
|
|
84
|
+
return join(getSessionTempDir(), `${kind}-${token}.${ARTIFACT_EXTENSION[kind]}`);
|
|
85
|
+
}
|
|
86
|
+
if (outputPath.includes('\0')) {
|
|
87
|
+
throw new Error('Invalid output path: contains a NUL byte');
|
|
88
|
+
}
|
|
89
|
+
const target = resolve(outputPath);
|
|
90
|
+
assertWithinAllowedBase(target);
|
|
91
|
+
return target;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Write bytes to an artifact path with hardened permissions and open flags:
|
|
95
|
+
* owner-only (`0600`) and `O_NOFOLLOW`. Use for artifacts we write directly
|
|
96
|
+
* in-process (e.g. the Appium backend decoding a base64 screenshot).
|
|
97
|
+
*
|
|
98
|
+
* @param path - The destination path (should come from `resolveArtifactPath`).
|
|
99
|
+
* @param data - The bytes to write.
|
|
100
|
+
*/
|
|
101
|
+
export async function writeArtifactFile(path, data) {
|
|
102
|
+
await writeFile(path, data, {
|
|
103
|
+
mode: ARTIFACT_MODE,
|
|
104
|
+
flag: ARTIFACT_WRITE_FLAGS,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Best-effort tightening of permissions on a file written by an external tool
|
|
109
|
+
* (idb/adb create the file themselves, so we cannot pass secure open flags).
|
|
110
|
+
* Silently ignored when unsupported or when the file is absent.
|
|
111
|
+
*
|
|
112
|
+
* @param path - The path of the file to restrict to owner-only access.
|
|
113
|
+
*/
|
|
114
|
+
export function hardenArtifactFile(path) {
|
|
115
|
+
try {
|
|
116
|
+
chmodSync(path, ARTIFACT_MODE);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// Best-effort: the filesystem may not support chmod (e.g. some mounts on
|
|
120
|
+
// Windows) or the external tool may have failed to create the file.
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=output-path.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output-path.mjs","sourceRoot":"","sources":["../../src/utils/output-path.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomBytes } from 'node:crypto';\nimport { chmodSync, constants as fsConstants, mkdtempSync } from 'node:fs';\nimport { writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { isAbsolute, join, relative, resolve } from 'node:path';\n\n/** Kinds of on-disk device artifact and their file extension. */\nconst ARTIFACT_EXTENSION = {\n screenshot: 'png',\n recording: 'mp4',\n} as const;\n\nexport type ArtifactKind = keyof typeof ARTIFACT_EXTENSION;\n\n/** Owner read/write only — device artifacts may contain sensitive UI (seed phrases, keys, balances). */\nconst ARTIFACT_MODE = 0o600;\n\n/**\n * Open flags for writing an artifact: create/truncate, write-only, and\n * `O_NOFOLLOW` so a pre-planted symlink at the destination is never followed\n * (defeats `/tmp` symlink / TOCTOU attacks). `O_NOFOLLOW` is a no-op on\n * platforms that do not define it.\n */\n/* eslint-disable no-bitwise -- POSIX open(2) flags are combined with bitwise OR */\nconst ARTIFACT_WRITE_FLAGS =\n fsConstants.O_WRONLY |\n fsConstants.O_CREAT |\n fsConstants.O_TRUNC |\n (fsConstants.O_NOFOLLOW ?? 0);\n/* eslint-enable no-bitwise */\n\nlet sessionTempDir: string | null = null;\n\n/**\n * Lazily create (once per process) a private temporary directory with\n * owner-only permissions (`0700`) to hold default artifacts. Because the\n * directory itself is unreadable to other users, files placed inside it are\n * protected even when written by an external tool (idb/adb) that we cannot\n * pass secure open flags to.\n *\n * @returns The absolute path to the per-process private temp directory.\n */\nfunction getSessionTempDir(): string {\n if (!sessionTempDir) {\n sessionTempDir = mkdtempSync(join(tmpdir(), 'device-mcp-'));\n }\n return sessionTempDir;\n}\n\n/**\n * Resolve the optional output-directory allowlist from the environment.\n *\n * @returns The absolute allowed base directory, or `null` when unrestricted.\n */\nfunction allowedBaseDir(): string | null {\n const configured = process.env.DEVICE_MCP_OUTPUT_DIR?.trim();\n return configured ? resolve(configured) : null;\n}\n\n/**\n * Reject a caller-supplied path that escapes `DEVICE_MCP_OUTPUT_DIR` when that\n * sandbox is configured. No-op when the env var is unset.\n *\n * @param target - The already-resolved absolute destination path.\n */\nfunction assertWithinAllowedBase(target: string): void {\n const base = allowedBaseDir();\n if (!base) {\n return;\n }\n const rel = relative(base, target);\n if (rel.length === 0 || rel.startsWith('..') || isAbsolute(rel)) {\n throw new Error(\n `Refusing to write outside DEVICE_MCP_OUTPUT_DIR (${base}): ${target}`,\n );\n }\n}\n\n/**\n * Compute a safe absolute path for a device artifact.\n *\n * - When no `outputPath` is given, returns an unpredictable file name inside a\n * private per-process temp directory. This replaces the previous predictable\n * `/tmp/device-mcp-*-${Date.now()}` names, which were vulnerable to symlink\n * and information-disclosure attacks in the shared temp directory.\n * - When an `outputPath` is given, it is rejected if it contains a NUL byte,\n * resolved to an absolute path, and — when `DEVICE_MCP_OUTPUT_DIR` is set —\n * confined to that directory.\n *\n * @param outputPath - Optional caller-supplied destination path.\n * @param kind - The artifact kind, used to pick the default file extension.\n * @returns The resolved, validated absolute path to write to.\n */\nexport function resolveArtifactPath(\n outputPath: string | undefined,\n kind: ArtifactKind,\n): string {\n if (outputPath === undefined) {\n const token = randomBytes(8).toString('hex');\n return join(\n getSessionTempDir(),\n `${kind}-${token}.${ARTIFACT_EXTENSION[kind]}`,\n );\n }\n\n if (outputPath.includes('\\0')) {\n throw new Error('Invalid output path: contains a NUL byte');\n }\n\n const target = resolve(outputPath);\n assertWithinAllowedBase(target);\n return target;\n}\n\n/**\n * Write bytes to an artifact path with hardened permissions and open flags:\n * owner-only (`0600`) and `O_NOFOLLOW`. Use for artifacts we write directly\n * in-process (e.g. the Appium backend decoding a base64 screenshot).\n *\n * @param path - The destination path (should come from `resolveArtifactPath`).\n * @param data - The bytes to write.\n */\nexport async function writeArtifactFile(\n path: string,\n data: Buffer,\n): Promise<void> {\n await writeFile(path, data, {\n mode: ARTIFACT_MODE,\n flag: ARTIFACT_WRITE_FLAGS,\n });\n}\n\n/**\n * Best-effort tightening of permissions on a file written by an external tool\n * (idb/adb create the file themselves, so we cannot pass secure open flags).\n * Silently ignored when unsupported or when the file is absent.\n *\n * @param path - The path of the file to restrict to owner-only access.\n */\nexport function hardenArtifactFile(path: string): void {\n try {\n chmodSync(path, ARTIFACT_MODE);\n } catch {\n // Best-effort: the filesystem may not support chmod (e.g. some mounts on\n // Windows) or the external tool may have failed to create the file.\n }\n}\n"]}
|
package/package.json
CHANGED