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

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 (56) hide show
  1. package/dist/backends/android-adb.d.ts +147 -6
  2. package/dist/backends/android-adb.js +776 -40
  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/locale.d.ts +16 -0
  42. package/dist/util/locale.js +31 -0
  43. package/dist/util/locale.js.map +1 -0
  44. package/dist/util/logger.d.ts +27 -0
  45. package/dist/util/logger.js +27 -0
  46. package/dist/util/logger.js.map +1 -0
  47. package/dist/util/output-path.d.ts +60 -0
  48. package/dist/util/output-path.js +123 -0
  49. package/dist/util/output-path.js.map +1 -0
  50. package/dist/util/package-name.d.ts +26 -0
  51. package/dist/util/package-name.js +41 -0
  52. package/dist/util/package-name.js.map +1 -0
  53. package/package.json +5 -3
  54. package/dist/server/action-mapping.d.ts +0 -21
  55. package/dist/server/action-mapping.js +0 -153
  56. package/dist/server/action-mapping.js.map +0 -1
@@ -1,157 +1,201 @@
1
1
  /**
2
- * Fastify factory `buildApp({ backend })` returns a configured `FastifyInstance`
3
- * with all routes wired up. Decoupled from `cli.ts` so unit tests can spin up
4
- * an in-process server with a mock backend.
2
+ * Express HTTP host for `device-mcp-server`'s Streamable HTTP transport.
5
3
  *
6
- * Routes (mirrors the Python `mcp_tools/server.py` reference):
7
- * GET /healthz — liveness + backend OS + tool list
8
- * POST /mcp/tools/list MCP tool discovery
9
- * POST /mcp/tools/call — invoke a single MCP tool
10
- * POST /act — translate `Action` → tool call
11
- * GET /screen.png — fast-path screenshot (no JSON envelope)
12
- * GET /events — SSE event stream (placeholder, no consumers yet)
4
+ * 0.4.0 reshape: the previous Fastify routes (`/healthz`, `/mcp/tools/list`,
5
+ * `/mcp/tools/call`, `/act`, `/screen.png`, `/events`) are GONE. The only
6
+ * surface this app exposes is `POST /mcp` the Streamable HTTP endpoint
7
+ * defined by the official MCP wire spec, hosted by
8
+ * `@modelcontextprotocol/sdk`'s `StreamableHTTPServerTransport`.
9
+ *
10
+ * Stateless mode is intentional:
11
+ * - `sessionIdGenerator: undefined` → no session header, no in-memory
12
+ * subscriber map, every POST is fully self-contained.
13
+ * - GET / DELETE on `/mcp` return 405 — those verbs only make sense in
14
+ * stateful (subscribe-for-notifications) mode which we don't use.
15
+ * - The transport is recreated per request so concurrent calls don't
16
+ * trip over a shared `_initialized` flag (this matches the SDK
17
+ * stateless example in the README).
18
+ *
19
+ * Auth (0.4.1): the loopback server still binds to `127.0.0.1` by
20
+ * default and is safe in that posture. When the operator exposes it on
21
+ * a routable interface (`--host 0.0.0.0`, container forwarding, …) two
22
+ * defence-in-depth gates kick in:
23
+ *
24
+ * - `BEEOS_DEVICE_AUTH=<token>` → `Authorization: Bearer <token>` is
25
+ * required on every `POST /mcp`. Constant-time compared so the
26
+ * check doesn't leak length differences.
27
+ * - `BEEOS_DEVICE_ALLOWED_ORIGINS=https://a,https://b` → if the
28
+ * incoming request carries an `Origin` header it must match one of
29
+ * the listed origins. Browser callers without an origin (curl /
30
+ * server-to-server) are still allowed unless `BEEOS_DEVICE_REQUIRE_ORIGIN=1`.
31
+ *
32
+ * Both are optional — leaving them unset preserves the legacy "loopback
33
+ * is trusted" stance.
13
34
  */
14
- import Fastify from "fastify";
15
- import { AcpError, DeviceError, VlmError, } from "@beeos-ai/device-common";
16
- import { mapActionToTool } from "./action-mapping.js";
17
- import { getToolFor, listToolDescriptorsFor } from "./tool-registry.js";
18
- export async function buildApp(opts) {
19
- const app = Fastify({
20
- logger: opts.logger ?? true,
21
- bodyLimit: opts.bodyLimit ?? 16 * 1024 * 1024,
35
+ import express from "express";
36
+ import { randomUUID, timingSafeEqual } from "node:crypto";
37
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
38
+ import { buildMcpServer } from "./mcp-server.js";
39
+ import { stderrLogger } from "../util/logger.js";
40
+ export function buildApp(opts) {
41
+ const log = opts.logger ?? stderrLogger;
42
+ const app = express();
43
+ app.use(express.json({ limit: opts.bodyLimit ?? 16 * 1024 * 1024 }));
44
+ // Per-request request id for log correlation (matches Fastify's old
45
+ // `reqId` semantic). Falls through if upstream proxies set their own.
46
+ app.use((req, _res, next) => {
47
+ req.reqId =
48
+ req.headers["x-request-id"] ?? randomUUID();
49
+ next();
22
50
  });
23
- app.setErrorHandler((err, _req, reply) => {
24
- if (err instanceof DeviceError) {
25
- const status = err.subtype === "unsupported" ? 501 : err.subtype === "invalid_args" ? 400 : 500;
26
- reply
27
- .status(status)
28
- .send({ error: err.message, subtype: err.subtype, retriable: err.retriable });
29
- return;
30
- }
31
- if (err instanceof AcpError) {
32
- reply.status(400).send({ error: err.message, code: err.code });
33
- return;
51
+ // ── Auth + CORS gate ───────────────────────────────────────────────────
52
+ // Resolve config from explicit opts > env. We intentionally read env
53
+ // here (not in module top-level) so tests can manipulate the env
54
+ // between buildApp() calls.
55
+ const expectedToken = opts.authToken ?? process.env.BEEOS_DEVICE_AUTH ?? "";
56
+ const allowedOriginsRaw = opts.allowedOrigins ?? process.env.BEEOS_DEVICE_ALLOWED_ORIGINS ?? "";
57
+ const allowedOrigins = parseOriginList(allowedOriginsRaw);
58
+ const requireOrigin = opts.requireOrigin ?? process.env.BEEOS_DEVICE_REQUIRE_ORIGIN === "1";
59
+ app.use("/mcp", (req, res, next) => {
60
+ const reqId = req.reqId;
61
+ // 1) Origin allow-list. We only enforce when the operator has set
62
+ // an explicit allow-list — otherwise loopback callers without
63
+ // an Origin header (Cursor, Claude Desktop) keep working.
64
+ if (allowedOrigins.length > 0) {
65
+ const origin = req.headers.origin?.trim();
66
+ if (!origin) {
67
+ if (requireOrigin) {
68
+ log.warn?.({ reqId }, "device_mcp_origin_missing");
69
+ return jsonRpcError(res, 403, -32001, "Origin header required");
70
+ }
71
+ }
72
+ else if (!allowedOrigins.includes(origin)) {
73
+ log.warn?.({ reqId, origin }, "device_mcp_origin_rejected");
74
+ return jsonRpcError(res, 403, -32001, `Origin '${origin}' is not in the allow-list`);
75
+ }
34
76
  }
35
- if (err instanceof VlmError) {
36
- reply.status(400).send({ error: err.message, retriable: err.retriable });
37
- return;
77
+ // 2) Bearer auth. Constant-time compare so a wrong-length / wrong
78
+ // bytes attempt doesn't leak information through timing.
79
+ if (expectedToken) {
80
+ const presented = parseBearer(req.headers.authorization);
81
+ if (!presented || !constantTimeEquals(presented, expectedToken)) {
82
+ log.warn?.({ reqId }, "device_mcp_auth_rejected");
83
+ return jsonRpcError(res, 401, -32001, "Invalid bearer token");
84
+ }
38
85
  }
39
- reply.status(500).send({ error: err?.message ?? "internal error" });
40
- });
41
- /* ---------- /healthz ---------- */
42
- app.get("/healthz", async () => {
43
- return {
44
- ok: true,
45
- // Wire field name kept as `backend` even though the source is
46
- // now `os` rather than `family`. Values are unchanged
47
- // (`"android"` / `"desktop-macos"` / etc) — see the BackendOs
48
- // taxonomy in @beeos-ai/device-common/backend.ts.
49
- backend: opts.backend.os,
50
- version: process.env.npm_package_version ?? "0.2.0",
51
- // Tool-set-filtered list — agents that pick a tool out of this
52
- // list are guaranteed it'll dispatch on the active backend.
53
- tools: listToolDescriptorsFor(opts.backend).map((t) => t.name),
54
- };
55
- });
56
- /* ---------- /mcp/tools/list ---------- */
57
- const toolListHandler = async () => ({
58
- tools: listToolDescriptorsFor(opts.backend),
86
+ next();
59
87
  });
60
- app.get("/mcp/tools/list", toolListHandler);
61
- app.post("/mcp/tools/list", toolListHandler);
62
- /* ---------- /mcp/tools/call ---------- */
63
- app.post("/mcp/tools/call", async (req, reply) => {
64
- const { name, arguments: args = {} } = req.body ?? {};
65
- if (!name || typeof name !== "string") {
66
- reply.status(400).send({ error: "missing tool name" });
67
- return;
68
- }
69
- const tool = getToolFor(name, opts.backend);
70
- if (!tool) {
71
- // Either the tool is unknown OR the backend lacks the required
72
- // capability — collapse both to 404 so MCP clients reading
73
- // `/mcp/tools/list` cannot bypass the filter via direct call.
74
- reply.status(404).send({ error: `unknown tool '${name}'` });
75
- return;
76
- }
77
- const startedAt = Date.now();
88
+ // ── POST /mcp ──────────────────────────────────────────────────────────
89
+ // SDK stateless mode: spin up a fresh transport per request, hand it
90
+ // to the McpServer, and let `transport.handleRequest` turn the Express
91
+ // (req, res, body) trio into JSON-RPC.
92
+ app.post("/mcp", async (req, res) => {
93
+ const reqId = req.reqId;
78
94
  try {
79
- const result = await tool.handler(args, { backend: opts.backend });
80
- const response = {
81
- ok: true,
82
- tool: name,
83
- durationMs: Date.now() - startedAt,
84
- result,
85
- };
86
- reply.send(response);
95
+ const server = buildMcpServer({
96
+ registry: opts.registry,
97
+ name: opts.name,
98
+ version: opts.version,
99
+ });
100
+ const transport = new StreamableHTTPServerTransport({
101
+ sessionIdGenerator: undefined, // stateless
102
+ enableJsonResponse: true,
103
+ });
104
+ // Make sure the per-request server/transport are released as soon
105
+ // as the response closes — otherwise we leak one McpServer per
106
+ // call.
107
+ res.on("close", () => {
108
+ transport.close().catch(() => undefined);
109
+ server.close().catch(() => undefined);
110
+ });
111
+ await server.connect(transport);
112
+ await transport.handleRequest(req, res, req.body);
87
113
  }
88
114
  catch (e) {
89
- if (e instanceof DeviceError)
90
- throw e;
91
- if (e instanceof Error && e.name === "TypeError") {
92
- reply.status(400).send({ error: e.message });
93
- return;
115
+ const message = e instanceof Error ? e.message : String(e);
116
+ log.error?.({ err: message, reqId }, "mcp_request_failed");
117
+ if (!res.headersSent) {
118
+ res.status(500).json({
119
+ jsonrpc: "2.0",
120
+ error: { code: -32603, message: `Internal error: ${message}` },
121
+ id: null,
122
+ });
94
123
  }
95
- throw e;
96
124
  }
97
125
  });
98
- /* ---------- /act ---------- */
99
- app.post("/act", async (req, reply) => {
100
- const action = req.body?.action;
101
- if (!action || typeof action.type !== "string") {
102
- reply.status(400).send({ error: "missing or malformed action" });
103
- return;
104
- }
105
- const startedAt = Date.now();
106
- const mapped = mapActionToTool(action);
107
- if ("terminal" in mapped) {
108
- const response = {
109
- ok: true,
110
- action: mapped.type,
111
- terminal: true,
112
- durationMs: Date.now() - startedAt,
113
- };
114
- reply.send(response);
115
- return;
116
- }
117
- const tool = getToolFor(mapped.toolName, opts.backend);
118
- if (!tool) {
119
- reply.status(404).send({ error: `tool '${mapped.toolName}' not registered` });
126
+ // GET / DELETE /mcp — explicit 405 so clients get a structured response
127
+ // instead of Express's default 404. The SDK's stateful flows would map
128
+ // these onto SSE / session teardown; stateless mode has no use for them.
129
+ const methodNotAllowed = (_req, res) => {
130
+ res.status(405).json({
131
+ jsonrpc: "2.0",
132
+ error: {
133
+ code: -32000,
134
+ message: "Method Not Allowed: device-mcp-server runs in stateless mode (POST /mcp only).",
135
+ },
136
+ id: null,
137
+ });
138
+ };
139
+ app.get("/mcp", methodNotAllowed);
140
+ app.delete("/mcp", methodNotAllowed);
141
+ // Final error fallback so synchronous middleware throws don't crash
142
+ // the process. Stays JSON-RPC-shaped for client uniformity.
143
+ app.use((err, _req, res, _next) => {
144
+ log.error?.({ err: err.message }, "express_error");
145
+ if (res.headersSent)
120
146
  return;
121
- }
122
- const result = await tool.handler(mapped.args, { backend: opts.backend });
123
- const response = {
124
- ok: true,
125
- action: action.type,
126
- tool: mapped.toolName,
127
- durationMs: Date.now() - startedAt,
128
- result,
129
- };
130
- reply.send(response);
131
- });
132
- /* ---------- /screen.png ---------- */
133
- app.get("/screen.png", async (_req, reply) => {
134
- // 0.2.3: backend reports the encoded format directly so we never
135
- // need to magic-byte-sniff the buffer here.
136
- const shot = await opts.backend.screenshot();
137
- reply
138
- .header("content-type", shot.format === "png" ? "image/png" : "image/jpeg")
139
- .send(shot.data);
140
- });
141
- /* ---------- /events (SSE placeholder) ---------- */
142
- app.get("/events", async (_req, reply) => {
143
- reply.raw.writeHead(200, {
144
- "Content-Type": "text/event-stream",
145
- "Cache-Control": "no-cache",
146
- Connection: "keep-alive",
147
+ res.status(500).json({
148
+ jsonrpc: "2.0",
149
+ error: { code: -32603, message: err.message },
150
+ id: null,
147
151
  });
148
- reply.raw.write(": connected\n\n");
149
- const interval = setInterval(() => {
150
- reply.raw.write(`: heartbeat ${Date.now()}\n\n`);
151
- }, 15_000);
152
- reply.raw.on("close", () => clearInterval(interval));
153
- return reply;
154
152
  });
155
153
  return app;
156
154
  }
155
+ /* ----------------------------------------------------------------------- */
156
+ /* Auth + CORS helpers */
157
+ /* ----------------------------------------------------------------------- */
158
+ /** Split a comma-separated allow-list and trim whitespace. */
159
+ function parseOriginList(raw) {
160
+ return raw
161
+ .split(",")
162
+ .map((s) => s.trim())
163
+ .filter((s) => s.length > 0);
164
+ }
165
+ /**
166
+ * Parse `Authorization: Bearer <token>` (case-insensitive on the
167
+ * scheme) and return the token, or `undefined` if absent / malformed.
168
+ */
169
+ function parseBearer(header) {
170
+ if (!header)
171
+ return undefined;
172
+ const m = /^bearer\s+(\S+)\s*$/i.exec(header);
173
+ return m ? m[1] : undefined;
174
+ }
175
+ /**
176
+ * Constant-time string comparison. Returns false fast on length
177
+ * mismatch (timingSafeEqual itself requires equal-length buffers, so
178
+ * we pad the shorter one to the longer one's length and still emit
179
+ * `false` afterwards).
180
+ */
181
+ function constantTimeEquals(a, b) {
182
+ const aBuf = Buffer.from(a, "utf8");
183
+ const bBuf = Buffer.from(b, "utf8");
184
+ if (aBuf.length !== bBuf.length) {
185
+ // Still pay the cost of one timingSafeEqual on equal-sized garbage
186
+ // so timing leaks one bit at most.
187
+ const filler = Buffer.alloc(aBuf.length, 0);
188
+ timingSafeEqual(aBuf, filler);
189
+ return false;
190
+ }
191
+ return timingSafeEqual(aBuf, bBuf);
192
+ }
193
+ /** Emit a JSON-RPC-shaped error response on the auth/CORS gate. */
194
+ function jsonRpcError(res, status, code, message) {
195
+ res.status(status).json({
196
+ jsonrpc: "2.0",
197
+ error: { code, message },
198
+ id: null,
199
+ });
200
+ }
157
201
  //# sourceMappingURL=app.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/server/app.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/server/app.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * `buildMcpServer({ registry })` — construct a configured
3
+ * `@modelcontextprotocol/sdk` server with every device-mcp-server tool
4
+ * registered against the multi-device `BackendRegistry`.
5
+ *
6
+ * Transport-agnostic. Callers pick:
7
+ * - Express + `StreamableHTTPServerTransport` (see `app.ts`) for the
8
+ * HTTP loopback path used by `device-agent` siblings.
9
+ * - `StdioServerTransport` (see `stdio.ts`) for the IDE / Cursor path.
10
+ *
11
+ * Both paths share the same `McpServer` instance type — sessionId
12
+ * semantics differ (HTTP stateless = undefined, stdio = stable for the
13
+ * lifetime of the connection) but the tool layer handles that uniformly
14
+ * via the `extra.sessionId` argument.
15
+ */
16
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
17
+ import type { BackendRegistry } from "./registry.js";
18
+ export interface BuildMcpServerOptions {
19
+ registry: BackendRegistry;
20
+ /** Override the advertised server `name` (defaults to package name). */
21
+ name?: string;
22
+ /** Override the advertised server `version` (defaults to package version). */
23
+ version?: string;
24
+ }
25
+ export declare function buildMcpServer(opts: BuildMcpServerOptions): McpServer;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * `buildMcpServer({ registry })` — construct a configured
3
+ * `@modelcontextprotocol/sdk` server with every device-mcp-server tool
4
+ * registered against the multi-device `BackendRegistry`.
5
+ *
6
+ * Transport-agnostic. Callers pick:
7
+ * - Express + `StreamableHTTPServerTransport` (see `app.ts`) for the
8
+ * HTTP loopback path used by `device-agent` siblings.
9
+ * - `StdioServerTransport` (see `stdio.ts`) for the IDE / Cursor path.
10
+ *
11
+ * Both paths share the same `McpServer` instance type — sessionId
12
+ * semantics differ (HTTP stateless = undefined, stdio = stable for the
13
+ * lifetime of the connection) but the tool layer handles that uniformly
14
+ * via the `extra.sessionId` argument.
15
+ */
16
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
17
+ import { registerAllTools } from "./tool-registry.js";
18
+ export function buildMcpServer(opts) {
19
+ const server = new McpServer({
20
+ name: opts.name ?? "device-mcp-server",
21
+ version: opts.version ?? process.env.npm_package_version ?? "0.4.1",
22
+ }, {
23
+ capabilities: {
24
+ // Tools are the only surface device-mcp-server exposes today.
25
+ // Resources / prompts / logging are intentionally left off so
26
+ // MCP clients don't pop empty UI sections for them.
27
+ tools: { listChanged: true },
28
+ },
29
+ });
30
+ registerAllTools(server, opts.registry);
31
+ return server;
32
+ }
33
+ //# sourceMappingURL=mcp-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-server.js","sourceRoot":"","sources":["../../src/server/mcp-server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAGpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAUtD,MAAM,UAAU,cAAc,CAAC,IAA2B;IACxD,MAAM,MAAM,GAAG,IAAI,SAAS,CAC1B;QACE,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,mBAAmB;QACtC,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,OAAO;KACpE,EACD;QACE,YAAY,EAAE;YACZ,8DAA8D;YAC9D,8DAA8D;YAC9D,oDAAoD;YACpD,KAAK,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE;SAC7B;KACF,CACF,CAAC;IAEF,gBAAgB,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IACxC,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,111 @@
1
+ /**
2
+ * BackendRegistry — manages multiple `SandboxBackend` instances inside a
3
+ * **single** device-mcp-server process.
4
+ *
5
+ * Replaces the legacy "one process per device" topology (mirrored from the
6
+ * `mobile-mcp` design): a single `device-mcp-server` discovers every adb device
7
+ * in the host's `adb devices` output and lazily wraps each one in an
8
+ * `AndroidAdbBackend`. Tool handlers resolve the right backend through three
9
+ * fallbacks (in priority order):
10
+ *
11
+ * 1. explicit `device` argument on the tool call (`device: "emulator-5554"`)
12
+ * 2. session-sticky default set via the `use_device` MCP tool (keyed by
13
+ * `extra.sessionId`, only meaningful for stdio / single-session clients)
14
+ * 3. single-device shortcut: if exactly one device is connected, use it
15
+ *
16
+ * If none of the above resolves to a backend the registry throws
17
+ * `DeviceError("device required", subtype:"invalid_args")` so the MCP tool
18
+ * call returns a structured error instead of silently picking a random device.
19
+ *
20
+ * The registry also owns a periodic `adb devices` poll (default 10s) so newly
21
+ * plugged devices show up in `list_available_devices` without restarting the
22
+ * process. Connect-state validation per backend is lazy — backends are
23
+ * constructed with `skipConnectValidation: true` and only emit errors at
24
+ * tool-call time.
25
+ *
26
+ * Desktop / stub backends are registered up front (one each) and re-used
27
+ * across every session — the device id for a desktop backend is its
28
+ * `os` string (`"desktop-macos"`, `"desktop-linux"`, ...).
29
+ */
30
+ import { type AdbRunner } from "../backends/android-adb.js";
31
+ import type { AndroidAdbBackendOptions } from "../backends/android-adb.js";
32
+ import type { SandboxBackend } from "../backends/base.js";
33
+ export interface AvailableDevice {
34
+ /** Stable device identifier — pass to tool calls as `device: "..."`. */
35
+ id: string;
36
+ /** Backend OS family — drives capability hints in MCP clients. */
37
+ os: SandboxBackend["os"];
38
+ /** True if this is the only connected device (the single-device default). */
39
+ isDefault: boolean;
40
+ /** Human-readable hint (e.g. `"adb"` / `"desktop"`). Optional sugar. */
41
+ source: "adb" | "desktop";
42
+ }
43
+ export interface BackendRegistryOptions {
44
+ /**
45
+ * Pre-registered "static" backends (desktop / stubs / iOS). Their `os`
46
+ * field becomes the device id used in tool calls. Use this when the
47
+ * server should ALWAYS expose a non-adb backend regardless of the host's
48
+ * adb state (e.g. when running on a Mac dev machine with `--backend
49
+ * desktop`).
50
+ */
51
+ initialBackends?: SandboxBackend[];
52
+ /**
53
+ * Whether to run the adb-devices discovery poll. Set to `false` when the
54
+ * caller only wants the static `initialBackends` (e.g. `--backend desktop`
55
+ * on a host without adb installed).
56
+ *
57
+ * Default: `true`.
58
+ */
59
+ enableAdbDiscovery?: boolean;
60
+ /** Override the spawn-backed adb runner — tests inject spies. */
61
+ adbRunner?: AdbRunner;
62
+ /** Discovery poll interval in milliseconds. Default `10_000`. */
63
+ pollIntervalMs?: number;
64
+ /**
65
+ * Construction options forwarded to every newly-discovered
66
+ * `AndroidAdbBackend`. `serial` is overridden per device, but timeouts /
67
+ * runner injection / sleep helpers flow through.
68
+ */
69
+ androidDefaults?: Omit<AndroidAdbBackendOptions, "serial" | "skipConnectValidation">;
70
+ /** Initial seed serials — useful for tests that want a known device set without polling. */
71
+ initialAdbSerials?: string[];
72
+ }
73
+ export declare class BackendRegistry {
74
+ private readonly backends;
75
+ private readonly enableAdbDiscovery;
76
+ private readonly adbRunner?;
77
+ private readonly pollIntervalMs;
78
+ private readonly androidDefaults;
79
+ private pollTimer?;
80
+ /**
81
+ * Per-session active-device map (only meaningful for stdio / single-session
82
+ * transports). For stateless HTTP `extra.sessionId` is undefined and this
83
+ * map is never written.
84
+ */
85
+ private readonly sessionDevice;
86
+ constructor(opts?: BackendRegistryOptions);
87
+ /**
88
+ * Kick off the adb-discovery poll and run one immediate scan. Returns
89
+ * after the first scan completes (so callers can `await` and immediately
90
+ * see a populated `list()`).
91
+ *
92
+ * Idempotent — safe to call multiple times.
93
+ */
94
+ start(): Promise<void>;
95
+ /** Stop the discovery poll and disconnect every backend. */
96
+ stop(): Promise<void>;
97
+ /** Snapshot of every currently-known device. */
98
+ list(): AvailableDevice[];
99
+ /**
100
+ * Resolve a backend for a single tool call. `args.device` wins; if absent,
101
+ * fall back to the session-sticky `use_device` value; if also absent and
102
+ * exactly one backend is registered, use it. Otherwise throw with a clear
103
+ * "device required" error so the MCP client knows it must call
104
+ * `list_available_devices` / `use_device` first.
105
+ */
106
+ resolve(args: Record<string, unknown> | undefined, sessionId?: string): SandboxBackend;
107
+ /** Set the session-sticky default device. Used by the `use_device` tool. */
108
+ useDevice(sessionId: string | undefined, deviceId: string): void;
109
+ private pollOnce;
110
+ private makeAdbBackend;
111
+ }