@beeos-ai/device-mcp-server 0.3.0 → 0.4.3

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.
Files changed (58) hide show
  1. package/dist/backends/android-adb.d.ts +149 -6
  2. package/dist/backends/android-adb.js +854 -42
  3. package/dist/backends/android-adb.js.map +1 -1
  4. package/dist/backends/base.d.ts +243 -7
  5. package/dist/backends/base.js +81 -2
  6. package/dist/backends/base.js.map +1 -1
  7. package/dist/backends/desktop.d.ts +3 -2
  8. package/dist/backends/desktop.js +9 -3
  9. package/dist/backends/desktop.js.map +1 -1
  10. package/dist/backends/linux.js +3 -0
  11. package/dist/backends/linux.js.map +1 -1
  12. package/dist/backends/mac.d.ts +11 -2
  13. package/dist/backends/mac.js +39 -1
  14. package/dist/backends/mac.js.map +1 -1
  15. package/dist/backends/stubs/windows.js +3 -0
  16. package/dist/backends/stubs/windows.js.map +1 -1
  17. package/dist/cli.d.ts +40 -26
  18. package/dist/cli.js +118 -84
  19. package/dist/cli.js.map +1 -1
  20. package/dist/index.d.ts +9 -6
  21. package/dist/index.js +9 -6
  22. package/dist/index.js.map +1 -1
  23. package/dist/server/app.d.ts +60 -17
  24. package/dist/server/app.js +182 -138
  25. package/dist/server/app.js.map +1 -1
  26. package/dist/server/mcp-server.d.ts +25 -0
  27. package/dist/server/mcp-server.js +33 -0
  28. package/dist/server/mcp-server.js.map +1 -0
  29. package/dist/server/registry.d.ts +111 -0
  30. package/dist/server/registry.js +191 -0
  31. package/dist/server/registry.js.map +1 -0
  32. package/dist/server/stdio.d.ts +29 -0
  33. package/dist/server/stdio.js +35 -0
  34. package/dist/server/stdio.js.map +1 -0
  35. package/dist/server/tool-registry.d.ts +60 -35
  36. package/dist/server/tool-registry.js +911 -434
  37. package/dist/server/tool-registry.js.map +1 -1
  38. package/dist/util/adb-files.d.ts +25 -1
  39. package/dist/util/adb-files.js +95 -0
  40. package/dist/util/adb-files.js.map +1 -1
  41. package/dist/util/android-apps.js +209 -9
  42. package/dist/util/android-apps.js.map +1 -1
  43. package/dist/util/locale.d.ts +16 -0
  44. package/dist/util/locale.js +31 -0
  45. package/dist/util/locale.js.map +1 -0
  46. package/dist/util/logger.d.ts +27 -0
  47. package/dist/util/logger.js +27 -0
  48. package/dist/util/logger.js.map +1 -0
  49. package/dist/util/output-path.d.ts +60 -0
  50. package/dist/util/output-path.js +123 -0
  51. package/dist/util/output-path.js.map +1 -0
  52. package/dist/util/package-name.d.ts +26 -0
  53. package/dist/util/package-name.js +41 -0
  54. package/dist/util/package-name.js.map +1 -0
  55. package/package.json +5 -3
  56. package/dist/server/action-mapping.d.ts +0 -21
  57. package/dist/server/action-mapping.js +0 -153
  58. package/dist/server/action-mapping.js.map +0 -1
@@ -0,0 +1,31 @@
1
+ /**
2
+ * BCP-47-ish locale-tag validation.
3
+ *
4
+ * mobile-mcp parity (round 3): mirrors `validateLocale` from
5
+ * `mobile-mcp/src/utils.ts` — `[a-zA-Z0-9,\- ]` only. The character class is
6
+ * deliberately permissive (BCP-47 allows tags like `zh-Hant-TW`, lists like
7
+ * `fr-FR,en-US`, and Android's `cmd locale set-app-locales` accepts space-
8
+ * delimited preference lists), but tight enough to keep shell metachars
9
+ * (`$`, `` ` ``, `;`, `|`) and path separators (`/`, `\`) out.
10
+ *
11
+ * Used at the MCP tool boundary (`launch_app({ locale })`) so a prompt-
12
+ * injected `locale: "$(rm -rf /)"` is rejected before reaching `adb shell`
13
+ * — `sh\`...\`` template literal in `android-adb.ts` already escapes the
14
+ * value, so this is defence-in-depth (and a faster, clearer error).
15
+ */
16
+ import { DeviceError } from "@beeos-ai/device-common";
17
+ const LOCALE_RE = /^[a-zA-Z0-9,\- ]+$/;
18
+ const MAX_LOCALE_LENGTH = 256;
19
+ export function assertValidLocale(s) {
20
+ if (typeof s !== "string" || s.length === 0) {
21
+ throw new DeviceError("locale must be a non-empty string", { subtype: "invalid_args", retriable: false });
22
+ }
23
+ if (s.length > MAX_LOCALE_LENGTH) {
24
+ throw new DeviceError(`locale '${s.slice(0, 32)}…' exceeds ${MAX_LOCALE_LENGTH} chars`, { subtype: "invalid_args", retriable: false });
25
+ }
26
+ if (!LOCALE_RE.test(s)) {
27
+ throw new DeviceError(`locale '${s}' contains invalid characters (allowed: a-z A-Z 0-9 , - space)`, { subtype: "invalid_args", retriable: false });
28
+ }
29
+ return s;
30
+ }
31
+ //# sourceMappingURL=locale.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locale.js","sourceRoot":"","sources":["../../src/util/locale.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAEtD,MAAM,SAAS,GAAG,oBAAoB,CAAC;AACvC,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAE9B,MAAM,UAAU,iBAAiB,CAAC,CAAS;IACzC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5C,MAAM,IAAI,WAAW,CACnB,mCAAmC,EACnC,EAAE,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,KAAK,EAAE,CAC9C,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,CAAC,MAAM,GAAG,iBAAiB,EAAE,CAAC;QACjC,MAAM,IAAI,WAAW,CACnB,WAAW,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,cAAc,iBAAiB,QAAQ,EAChE,EAAE,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,KAAK,EAAE,CAC9C,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,WAAW,CACnB,WAAW,CAAC,gEAAgE,EAC5E,EAAE,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,KAAK,EAAE,CAC9C,CAAC;IACJ,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Stderr-only structured logger used by both transports.
3
+ *
4
+ * Stdio mode requires that the `stdout` channel carries ONLY MCP JSON-RPC
5
+ * frames — anything else corrupts the protocol. The default pino logger
6
+ * also writes to stdout. We pin it to stderr explicitly here and re-use
7
+ * the same instance from the HTTP transport so both modes log
8
+ * consistently.
9
+ *
10
+ * Consumers can plug in any pino-shaped logger via `BuildAppOptions.logger`
11
+ * / `RunStdioOptions.logger`. The bundled instance is intentionally
12
+ * minimal — no transports, no formatting, no rotation. Operators who
13
+ * want more should bring their own pino instance with the destination of
14
+ * their choice (also pinned to stderr — never stdout).
15
+ *
16
+ * Why not `console.error`? `console.*` ends up in different streams on
17
+ * different runtimes (especially when wrapped by libraries like jest /
18
+ * vitest) and the pino instance avoids that surprise — it always goes
19
+ * to fd 2 directly.
20
+ */
21
+ export interface Logger {
22
+ info?: (obj: unknown, msg?: string) => void;
23
+ warn?: (obj: unknown, msg?: string) => void;
24
+ error?: (obj: unknown, msg?: string) => void;
25
+ debug?: (obj: unknown, msg?: string) => void;
26
+ }
27
+ export declare const stderrLogger: Logger;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Stderr-only structured logger used by both transports.
3
+ *
4
+ * Stdio mode requires that the `stdout` channel carries ONLY MCP JSON-RPC
5
+ * frames — anything else corrupts the protocol. The default pino logger
6
+ * also writes to stdout. We pin it to stderr explicitly here and re-use
7
+ * the same instance from the HTTP transport so both modes log
8
+ * consistently.
9
+ *
10
+ * Consumers can plug in any pino-shaped logger via `BuildAppOptions.logger`
11
+ * / `RunStdioOptions.logger`. The bundled instance is intentionally
12
+ * minimal — no transports, no formatting, no rotation. Operators who
13
+ * want more should bring their own pino instance with the destination of
14
+ * their choice (also pinned to stderr — never stdout).
15
+ *
16
+ * Why not `console.error`? `console.*` ends up in different streams on
17
+ * different runtimes (especially when wrapped by libraries like jest /
18
+ * vitest) and the pino instance avoids that surprise — it always goes
19
+ * to fd 2 directly.
20
+ */
21
+ import pino from "pino";
22
+ export const stderrLogger = pino({
23
+ level: process.env.LOG_LEVEL ?? "info",
24
+ base: { component: "device-mcp-server" },
25
+ timestamp: pino.stdTimeFunctions.isoTime,
26
+ }, pino.destination(2));
27
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/util/logger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,IAAI,MAAM,MAAM,CAAC;AASxB,MAAM,CAAC,MAAM,YAAY,GAAW,IAAI,CACtC;IACE,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,MAAM;IACtC,IAAI,EAAE,EAAE,SAAS,EAAE,mBAAmB,EAAE;IACxC,SAAS,EAAE,IAAI,CAAC,gBAAgB,CAAC,OAAO;CACzC,EACD,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CACpB,CAAC"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Output-path sandbox for tools that write to the host filesystem.
3
+ *
4
+ * `screenshot_to_file({ path })` and `screen_record_stop({ path })` accept a
5
+ * caller-supplied destination path and previously fed it directly to
6
+ * `writeFile` / `adb pull <local>`. A prompt-injected LLM could therefore
7
+ * write `/etc/hosts` or `~/.ssh/authorized_keys` (subject only to OS file
8
+ * permissions), which is not the security posture we want for a tool
9
+ * loaded into Cursor / Claude Desktop / production agents.
10
+ *
11
+ * The resolution mirrors mobile-mcp's `validateOutputPath` (see
12
+ * `mobile-mcp/src/utils.ts` L18-L87): allow the caller-supplied path only
13
+ * if it resolves under one of the trusted roots:
14
+ *
15
+ * 1. `process.cwd()` — the operator's working directory at server start
16
+ * 2. `os.tmpdir()` — the system temp directory (and its `/private/tmp`
17
+ * alias on macOS, where `os.tmpdir()` reports `/var/folders/.../T/`
18
+ * but `/tmp` is the symlinked entry point most users type)
19
+ * 3. Each comma- or colon-separated entry of `BEEOS_DEVICE_OUTPUT_DIRS`
20
+ * (operator opt-in escape hatch for mounted volumes / shared dirs)
21
+ *
22
+ * Anything outside the trusted set throws a `DeviceError("invalid_args")`
23
+ * before the destructive operation runs.
24
+ */
25
+ /**
26
+ * Returns the absolute, resolved form of `target` if it sits under one of
27
+ * the trusted roots. Otherwise throws `DeviceError("invalid_args")` —
28
+ * never returns a path that escaped the sandbox.
29
+ *
30
+ * Symlinks are NOT followed: we only canonicalise via `path.resolve` so a
31
+ * legitimate symlink under cwd that points outside is still considered
32
+ * inside (the kernel will enforce real permissions at write time).
33
+ * Following symlinks here would surprise operators who deliberately link
34
+ * a writable cwd entry to an external mount.
35
+ */
36
+ export declare function assertSafeOutputPath(target: string, opts?: {
37
+ extraRoots?: string;
38
+ }): string;
39
+ /**
40
+ * Same as {@link assertSafeOutputPath}, but ALSO restricts the file
41
+ * extension to a fixed allow-list. Used by tools that need both sandbox
42
+ * and codec consistency:
43
+ *
44
+ * - `screenshot_to_file({ path })` — `.png` / `.jpg` / `.jpeg`
45
+ * - `screen_record_start({ output })` — `.mp4`
46
+ * - `screen_record_stop({ path })` — `.mp4`
47
+ *
48
+ * mobile-mcp parity (round 3): mirrors `validateFileExtension` from
49
+ * `mobile-mcp/src/utils.ts` — without it, a prompt-injected agent could
50
+ * write a screenshot to `~/Downloads/cute.png.exe` (cwd-relative) and
51
+ * have the OS treat it as an executable on platforms that key on file
52
+ * extension. We do the extension check AFTER the path is sandboxed, so
53
+ * the sandbox failure mode (clearer) still wins on outright bad paths.
54
+ *
55
+ * `allowedExts` should be lowercase and dot-prefixed (`[".png", ".jpg"]`).
56
+ * Comparison is case-insensitive.
57
+ */
58
+ export declare function assertSafeOutputPathWithExt(target: string, allowedExts: readonly string[], opts?: {
59
+ extraRoots?: string;
60
+ }): string;
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Output-path sandbox for tools that write to the host filesystem.
3
+ *
4
+ * `screenshot_to_file({ path })` and `screen_record_stop({ path })` accept a
5
+ * caller-supplied destination path and previously fed it directly to
6
+ * `writeFile` / `adb pull <local>`. A prompt-injected LLM could therefore
7
+ * write `/etc/hosts` or `~/.ssh/authorized_keys` (subject only to OS file
8
+ * permissions), which is not the security posture we want for a tool
9
+ * loaded into Cursor / Claude Desktop / production agents.
10
+ *
11
+ * The resolution mirrors mobile-mcp's `validateOutputPath` (see
12
+ * `mobile-mcp/src/utils.ts` L18-L87): allow the caller-supplied path only
13
+ * if it resolves under one of the trusted roots:
14
+ *
15
+ * 1. `process.cwd()` — the operator's working directory at server start
16
+ * 2. `os.tmpdir()` — the system temp directory (and its `/private/tmp`
17
+ * alias on macOS, where `os.tmpdir()` reports `/var/folders/.../T/`
18
+ * but `/tmp` is the symlinked entry point most users type)
19
+ * 3. Each comma- or colon-separated entry of `BEEOS_DEVICE_OUTPUT_DIRS`
20
+ * (operator opt-in escape hatch for mounted volumes / shared dirs)
21
+ *
22
+ * Anything outside the trusted set throws a `DeviceError("invalid_args")`
23
+ * before the destructive operation runs.
24
+ */
25
+ import * as path from "node:path";
26
+ import * as os from "node:os";
27
+ import { DeviceError } from "@beeos-ai/device-common";
28
+ /** Resolve and freeze the trusted-roots list once per call. */
29
+ function trustedRoots(extraRootsRaw) {
30
+ // process.cwd() may shift across calls (rare, but tests exercise it),
31
+ // so we recompute on every invocation — micro-perf is negligible.
32
+ const cwd = path.resolve(process.cwd());
33
+ const tmp = path.resolve(os.tmpdir());
34
+ const roots = new Set([cwd, tmp]);
35
+ // macOS `os.tmpdir()` resolves to `/var/folders/.../T/` but the canonical
36
+ // user-visible path is `/private/tmp` (and the bare `/tmp` symlink). Add
37
+ // both aliases explicitly so `--output-dir /tmp/foo` callers don't get
38
+ // a misleading "outside sandbox" error on macOS.
39
+ if (process.platform === "darwin") {
40
+ roots.add("/private/tmp");
41
+ roots.add("/tmp");
42
+ }
43
+ // Operator opt-in extras. Comma OR colon separated to match the
44
+ // convention used elsewhere in BeeOS (`PATH`-like colons on POSIX,
45
+ // commas in env-style lists).
46
+ const raw = extraRootsRaw ?? process.env.BEEOS_DEVICE_OUTPUT_DIRS ?? "";
47
+ for (const entry of raw.split(/[,:]/)) {
48
+ const trimmed = entry.trim();
49
+ if (trimmed.length > 0)
50
+ roots.add(path.resolve(trimmed));
51
+ }
52
+ return [...roots];
53
+ }
54
+ /**
55
+ * Returns the absolute, resolved form of `target` if it sits under one of
56
+ * the trusted roots. Otherwise throws `DeviceError("invalid_args")` —
57
+ * never returns a path that escaped the sandbox.
58
+ *
59
+ * Symlinks are NOT followed: we only canonicalise via `path.resolve` so a
60
+ * legitimate symlink under cwd that points outside is still considered
61
+ * inside (the kernel will enforce real permissions at write time).
62
+ * Following symlinks here would surprise operators who deliberately link
63
+ * a writable cwd entry to an external mount.
64
+ */
65
+ export function assertSafeOutputPath(target, opts = {}) {
66
+ if (typeof target !== "string" || target.length === 0) {
67
+ throw new DeviceError("output path must be a non-empty string", {
68
+ subtype: "invalid_args",
69
+ retriable: false,
70
+ });
71
+ }
72
+ const resolved = path.resolve(target);
73
+ const roots = trustedRoots(opts.extraRoots);
74
+ for (const root of roots) {
75
+ if (isUnder(root, resolved))
76
+ return resolved;
77
+ }
78
+ throw new DeviceError(`output path '${target}' is outside the sandbox (allowed roots: ${roots.join(", ")}). Set BEEOS_DEVICE_OUTPUT_DIRS to widen the allow-list.`, { subtype: "invalid_args", retriable: false });
79
+ }
80
+ /**
81
+ * Same as {@link assertSafeOutputPath}, but ALSO restricts the file
82
+ * extension to a fixed allow-list. Used by tools that need both sandbox
83
+ * and codec consistency:
84
+ *
85
+ * - `screenshot_to_file({ path })` — `.png` / `.jpg` / `.jpeg`
86
+ * - `screen_record_start({ output })` — `.mp4`
87
+ * - `screen_record_stop({ path })` — `.mp4`
88
+ *
89
+ * mobile-mcp parity (round 3): mirrors `validateFileExtension` from
90
+ * `mobile-mcp/src/utils.ts` — without it, a prompt-injected agent could
91
+ * write a screenshot to `~/Downloads/cute.png.exe` (cwd-relative) and
92
+ * have the OS treat it as an executable on platforms that key on file
93
+ * extension. We do the extension check AFTER the path is sandboxed, so
94
+ * the sandbox failure mode (clearer) still wins on outright bad paths.
95
+ *
96
+ * `allowedExts` should be lowercase and dot-prefixed (`[".png", ".jpg"]`).
97
+ * Comparison is case-insensitive.
98
+ */
99
+ export function assertSafeOutputPathWithExt(target, allowedExts, opts = {}) {
100
+ const safe = assertSafeOutputPath(target, opts);
101
+ const ext = path.extname(safe).toLowerCase();
102
+ if (!allowedExts.includes(ext)) {
103
+ throw new DeviceError(`output path '${target}' has unsupported extension '${ext || "(none)"}' (allowed: ${allowedExts.join(", ")})`, { subtype: "invalid_args", retriable: false });
104
+ }
105
+ return safe;
106
+ }
107
+ /** True iff `child` is `root` or a descendant. Both inputs must be resolved. */
108
+ function isUnder(root, child) {
109
+ if (child === root)
110
+ return true;
111
+ const rel = path.relative(root, child);
112
+ // `path.relative` returns "" for identity, "..[/...]" when `child`
113
+ // escapes upward, or an absolute path on Windows when crossing drive
114
+ // letters. All three escape cases are filtered out below.
115
+ if (rel === "" || rel === ".")
116
+ return true;
117
+ if (rel.startsWith(".."))
118
+ return false;
119
+ if (path.isAbsolute(rel))
120
+ return false;
121
+ return true;
122
+ }
123
+ //# sourceMappingURL=output-path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output-path.js","sourceRoot":"","sources":["../../src/util/output-path.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Android package-name validation.
3
+ *
4
+ * mobile-mcp parity (round 3): mirrors `validatePackageName` from
5
+ * `mobile-mcp/src/utils.ts` — `[a-zA-Z0-9._]` only, with no leading dot
6
+ * relaxation since Android's own package-name grammar (RFC-1034 segments
7
+ * separated by `.`) maps cleanly onto this character class.
8
+ *
9
+ * Used at the MCP tool boundary (`terminate_app`, `uninstall_app`) to fail
10
+ * a malformed input EARLY — before it travels through the runner, gets
11
+ * shell-escaped by `sh\`...\``, and produces a confusing "package not
12
+ * found" error from `pm uninstall`. We deliberately do NOT call this in
13
+ * `launch_app` because that handler accepts user-friendly display names
14
+ * (e.g. "WeChat" -> `com.tencent.mm`) which are mapped to package ids
15
+ * inside the backend.
16
+ */
17
+ /**
18
+ * Throw `DeviceError({ subtype: "invalid_args" })` if `name` doesn't match
19
+ * the Android package-name grammar. Returns `name` unchanged on success so
20
+ * callers can write `assertValidPackageName(args.pkg)` inline.
21
+ *
22
+ * Empty / non-string / over-length inputs all reject. Defaults inherit
23
+ * `userActionable=true` from the `invalid_args` subtype so the LLM sees a
24
+ * plain-text actionable response instead of an `isError` protocol failure.
25
+ */
26
+ export declare function assertValidPackageName(name: string): string;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Android package-name validation.
3
+ *
4
+ * mobile-mcp parity (round 3): mirrors `validatePackageName` from
5
+ * `mobile-mcp/src/utils.ts` — `[a-zA-Z0-9._]` only, with no leading dot
6
+ * relaxation since Android's own package-name grammar (RFC-1034 segments
7
+ * separated by `.`) maps cleanly onto this character class.
8
+ *
9
+ * Used at the MCP tool boundary (`terminate_app`, `uninstall_app`) to fail
10
+ * a malformed input EARLY — before it travels through the runner, gets
11
+ * shell-escaped by `sh\`...\``, and produces a confusing "package not
12
+ * found" error from `pm uninstall`. We deliberately do NOT call this in
13
+ * `launch_app` because that handler accepts user-friendly display names
14
+ * (e.g. "WeChat" -> `com.tencent.mm`) which are mapped to package ids
15
+ * inside the backend.
16
+ */
17
+ import { DeviceError } from "@beeos-ai/device-common";
18
+ const PACKAGE_NAME_RE = /^[a-zA-Z0-9._]+$/;
19
+ const MAX_PACKAGE_NAME_LENGTH = 255;
20
+ /**
21
+ * Throw `DeviceError({ subtype: "invalid_args" })` if `name` doesn't match
22
+ * the Android package-name grammar. Returns `name` unchanged on success so
23
+ * callers can write `assertValidPackageName(args.pkg)` inline.
24
+ *
25
+ * Empty / non-string / over-length inputs all reject. Defaults inherit
26
+ * `userActionable=true` from the `invalid_args` subtype so the LLM sees a
27
+ * plain-text actionable response instead of an `isError` protocol failure.
28
+ */
29
+ export function assertValidPackageName(name) {
30
+ if (typeof name !== "string" || name.length === 0) {
31
+ throw new DeviceError("package name must be a non-empty string", { subtype: "invalid_args", retriable: false });
32
+ }
33
+ if (name.length > MAX_PACKAGE_NAME_LENGTH) {
34
+ throw new DeviceError(`package name '${name.slice(0, 32)}…' exceeds ${MAX_PACKAGE_NAME_LENGTH} chars`, { subtype: "invalid_args", retriable: false });
35
+ }
36
+ if (!PACKAGE_NAME_RE.test(name)) {
37
+ throw new DeviceError(`package name '${name}' contains invalid characters (allowed: a-z A-Z 0-9 . _)`, { subtype: "invalid_args", retriable: false });
38
+ }
39
+ return name;
40
+ }
41
+ //# sourceMappingURL=package-name.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package-name.js","sourceRoot":"","sources":["../../src/util/package-name.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAEtD,MAAM,eAAe,GAAG,kBAAkB,CAAC;AAC3C,MAAM,uBAAuB,GAAG,GAAG,CAAC;AAEpC;;;;;;;;GAQG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAAY;IACjD,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClD,MAAM,IAAI,WAAW,CACnB,yCAAyC,EACzC,EAAE,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,KAAK,EAAE,CAC9C,CAAC;IACJ,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,GAAG,uBAAuB,EAAE,CAAC;QAC1C,MAAM,IAAI,WAAW,CACnB,iBAAiB,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,cAAc,uBAAuB,QAAQ,EAC/E,EAAE,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,KAAK,EAAE,CAC9C,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAChC,MAAM,IAAI,WAAW,CACnB,iBAAiB,IAAI,0DAA0D,EAC/E,EAAE,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,KAAK,EAAE,CAC9C,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@beeos-ai/device-mcp-server",
3
- "version": "0.3.0",
4
- "description": "In-pod HTTP MCP tool server for BeeOS device-agent (Android / desktop / mobile sandboxes).",
3
+ "version": "0.4.3",
4
+ "description": "Singleton multi-device MCP tool server for BeeOS device-agent (Android / desktop / mobile sandboxes). Implements the Model Context Protocol over Streamable HTTP and stdio.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",
@@ -34,13 +34,15 @@
34
34
  "node": ">=20"
35
35
  },
36
36
  "dependencies": {
37
- "fastify": "^5.8.5",
37
+ "@modelcontextprotocol/sdk": "^1.29.0",
38
+ "express": "^4.22.1",
38
39
  "pino": "^10.3.1",
39
40
  "sharp": "^0.34.5",
40
41
  "zod": "^4.3.6",
41
42
  "@beeos-ai/device-common": "0.3.0"
42
43
  },
43
44
  "devDependencies": {
45
+ "@types/express": "^4.17.25",
44
46
  "@types/node": "^22.0.0",
45
47
  "tsx": "^4.19.0",
46
48
  "typescript": "^5.8.0",
@@ -1,21 +0,0 @@
1
- /**
2
- * action-mapping — translate a unified `Action` (`type+target+payload`) into
3
- * the matching MCP tool name + arguments.
4
- *
5
- * Used by `POST /act` so callers can hand the server a high-level Action and
6
- * skip wiring up a tool name themselves. Mirrors the Python reference's
7
- * `_action_to_call` table.
8
- */
9
- import { type Action, type ActionType } from "@beeos-ai/device-common";
10
- export interface ToolCall {
11
- toolName: string;
12
- args: Record<string, unknown>;
13
- }
14
- export interface UnsupportedActionResult {
15
- /** Terminal verbs (`done`, `fail`, `wait`, `call_for_user`) — server replies
16
- * with `{ ok: true, action }` and does NOT touch the backend. */
17
- terminal: true;
18
- type: ActionType;
19
- }
20
- export type ActionMappingResult = ToolCall | UnsupportedActionResult;
21
- export declare function mapActionToTool(action: Action): ActionMappingResult;
@@ -1,153 +0,0 @@
1
- /**
2
- * action-mapping — translate a unified `Action` (`type+target+payload`) into
3
- * the matching MCP tool name + arguments.
4
- *
5
- * Used by `POST /act` so callers can hand the server a high-level Action and
6
- * skip wiring up a tool name themselves. Mirrors the Python reference's
7
- * `_action_to_call` table.
8
- */
9
- import { TERMINAL_ACTIONS, } from "@beeos-ai/device-common";
10
- export function mapActionToTool(action) {
11
- if (TERMINAL_ACTIONS.has(action.type) || action.type === "wait") {
12
- return { terminal: true, type: action.type };
13
- }
14
- const target = action.target;
15
- const payload = action.payload ?? {};
16
- switch (action.type) {
17
- case "click":
18
- case "tap":
19
- case "double_click":
20
- case "right_click":
21
- case "long_press":
22
- case "move": {
23
- const xy = pointFromTarget(target);
24
- return {
25
- toolName: action.type === "move" ? "move" : action.type,
26
- args: {
27
- ...xy,
28
- ...(action.type === "click" && payload.button ? { button: payload.button } : {}),
29
- ...(action.type === "click" && payload.clicks ? { clicks: payload.clicks } : {}),
30
- ...(action.type === "long_press" && payload.durationMs
31
- ? { durationMs: payload.durationMs }
32
- : {}),
33
- },
34
- };
35
- }
36
- case "drag":
37
- case "swipe": {
38
- const seg = segmentFromTarget(target);
39
- return {
40
- toolName: action.type,
41
- args: {
42
- ...seg,
43
- ...(payload.durationMs ? { durationMs: payload.durationMs } : {}),
44
- },
45
- };
46
- }
47
- case "scroll": {
48
- const xy = target ? pointFromTarget(target) : { x: 0, y: 0 };
49
- return {
50
- toolName: "scroll",
51
- args: {
52
- ...xy,
53
- direction: payload.direction ?? "down",
54
- ...(payload.amount ? { amount: payload.amount } : {}),
55
- },
56
- };
57
- }
58
- case "type":
59
- return {
60
- toolName: "type",
61
- args: { text: payload.text ?? "" },
62
- };
63
- case "key":
64
- return {
65
- toolName: "key",
66
- args: { key: payload.key ?? payload.keycode ?? "" },
67
- };
68
- case "hotkey":
69
- return {
70
- toolName: "hotkey",
71
- args: { keys: payload.keys ?? [] },
72
- };
73
- case "back":
74
- return { toolName: "back", args: {} };
75
- case "home":
76
- return { toolName: "home", args: {} };
77
- case "navigate":
78
- return {
79
- toolName: "back",
80
- args: {},
81
- };
82
- case "launch_app": {
83
- const fromTarget = target && (target.kind === "text" || target.kind === "selector")
84
- ? target.value
85
- : undefined;
86
- const pkg = fromTarget ??
87
- payload.pkg ??
88
- payload.appName ??
89
- payload.packageOrName ??
90
- payload.package ??
91
- "";
92
- return {
93
- toolName: "launch_app",
94
- args: {
95
- pkg,
96
- ...(payload.activity ? { activity: payload.activity } : {}),
97
- },
98
- };
99
- }
100
- case "screenshot":
101
- return { toolName: "screenshot", args: {} };
102
- case "ui_dump":
103
- return {
104
- toolName: "ui_dump",
105
- args: {
106
- ...(payload.query ? { query: payload.query } : {}),
107
- ...(payload.format ? { format: payload.format } : {}),
108
- },
109
- };
110
- case "execute_command":
111
- case "run_code":
112
- return {
113
- toolName: "execute_command",
114
- args: {
115
- body: payload.body ?? payload.command ?? "",
116
- ...(payload.timeoutS ? { timeoutS: payload.timeoutS } : {}),
117
- ...(payload.cwd ? { cwd: payload.cwd } : {}),
118
- },
119
- };
120
- case "file_read": {
121
- const path = payload.path ??
122
- (target && (target.kind === "text" || target.kind === "selector")
123
- ? target.value
124
- : "");
125
- return { toolName: "file_read", args: { path } };
126
- }
127
- case "file_write": {
128
- const path = payload.path ??
129
- (target && (target.kind === "text" || target.kind === "selector")
130
- ? target.value
131
- : "");
132
- return {
133
- toolName: "file_write",
134
- args: { path, contentB64: payload.contentB64 ?? "" },
135
- };
136
- }
137
- default:
138
- throw new Error(`unsupported action type: ${String(action.type)}`);
139
- }
140
- }
141
- function pointFromTarget(target) {
142
- if (target && target.kind === "point") {
143
- return { x: target.x, y: target.y };
144
- }
145
- throw new TypeError("action.target must be {kind:'point', x, y} for this action type");
146
- }
147
- function segmentFromTarget(target) {
148
- if (target && target.kind === "box") {
149
- return { x1: target.x1, y1: target.y1, x2: target.x2, y2: target.y2 };
150
- }
151
- throw new TypeError("action.target must be {kind:'box', x1, y1, x2, y2} for this action type");
152
- }
153
- //# sourceMappingURL=action-mapping.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"action-mapping.js","sourceRoot":"","sources":["../../src/server/action-mapping.ts"],"names":[],"mappings":"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"}