@rebind.gg/mcp-server 0.1.3 → 0.1.4

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/README.md CHANGED
@@ -60,9 +60,16 @@ Off unless set.
60
60
  cursor and sending relative HID deltas (which map 1:1 to pixels), correcting
61
61
  against the true cursor until it lands. This sidesteps the relay's own absolute
62
62
  move, whose delta is computed from a cached cursor position that goes stale and
63
- flings the pointer to a screen edge. A mouse scale probe still runs at startup;
64
- if pointer ballistics are detected (enhance pointer precision, DPI mismatch) the
65
- probe reports it, since acceleration would break the 1:1 assumption.
63
+ flings the pointer to a screen edge. A mouse scale probe runs on the first
64
+ click/move_mouse; if pointer ballistics are detected (enhance pointer precision,
65
+ DPI mismatch) the probe reports it, since acceleration would break the 1:1
66
+ assumption.
67
+
68
+ Tool calls are serialized through a single queue: there is one physical cursor
69
+ and keyboard, so parallel tool calls from the model (e.g. two `move_mouse` in
70
+ one turn) run one after another instead of interleaving HID input. All results
71
+ are reported in the coordinate space of the last screenshot, the same space the
72
+ model emits.
66
73
 
67
74
  ## Develop
68
75
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rebind.gg/mcp-server",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "MCP server exposing Rebind as computer-use tools: screenshot, verified click, type, key, scroll, and window control over the Remote Access protocol.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/coords.ts CHANGED
@@ -28,6 +28,13 @@ export function mapToScreen(shot: ShotMapping, ix: number, iy: number): Point {
28
28
  };
29
29
  }
30
30
 
31
+ export function mapToImage(shot: ShotMapping, sx: number, sy: number): Point {
32
+ return {
33
+ x: Math.round((sx - shot.originX) * shot.scale),
34
+ y: Math.round((sy - shot.originY) * shot.scale),
35
+ };
36
+ }
37
+
31
38
  export function inImageBounds(
32
39
  shot: ShotMapping,
33
40
  ix: number,
package/src/index.ts CHANGED
@@ -12,8 +12,9 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
12
12
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
13
13
  import { RebindRemote } from "@rebind.gg/client-ts";
14
14
  import { z } from "zod";
15
+ import { mapToImage } from "./coords.ts";
15
16
  import { renderScreenshot } from "./image.ts";
16
- import { Session } from "./session.ts";
17
+ import { CLICK_EPS_PX, Session } from "./session.ts";
17
18
 
18
19
  const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
19
20
 
@@ -52,7 +53,7 @@ async function main() {
52
53
 
53
54
  const session = new Session(client);
54
55
 
55
- const server = new McpServer({ name: "rebind", version: "0.1.2" });
56
+ const server = new McpServer({ name: "rebind", version: "0.1.4" });
56
57
 
57
58
  // REBIND_AGENT_LOG=<path>: opt-in telemetry — one JSONL line per tool call
58
59
  // ({ts, tool, ok, latency_ms, note}). No screenshot bytes, no user content.
@@ -69,17 +70,28 @@ async function main() {
69
70
  // telemetry must never break a tool call
70
71
  }
71
72
  };
73
+ // one physical cursor and keyboard: tool calls must never overlap. Models
74
+ // routinely emit parallel calls in a single turn (two move_mouse at once)
75
+ // and the MCP SDK runs handlers concurrently; interleaved HID deltas and
76
+ // cursor readbacks make every overlapping move land nowhere. Chain all
77
+ // handlers through a single queue.
78
+ let chain: Promise<unknown> = Promise.resolve();
72
79
  const register = (name: string, config: object, handler: (args: any) => Promise<any>) => {
73
- server.registerTool(name, config as any, async (args: any) => {
74
- const t0 = performance.now();
75
- try {
76
- const result = await handler(args);
77
- logCall(name, true, t0);
78
- return result;
79
- } catch (e) {
80
- logCall(name, false, t0, String(e));
81
- throw e;
82
- }
80
+ server.registerTool(name, config as any, (args: any) => {
81
+ const run = async () => {
82
+ const t0 = performance.now();
83
+ try {
84
+ const result = await handler(args);
85
+ logCall(name, true, t0);
86
+ return result;
87
+ } catch (e) {
88
+ logCall(name, false, t0, String(e));
89
+ throw e;
90
+ }
91
+ };
92
+ const p = chain.then(run, run);
93
+ chain = p.catch(() => {});
94
+ return p;
83
95
  });
84
96
  };
85
97
 
@@ -287,10 +299,13 @@ async function main() {
287
299
  async ({ x, y, button, double }) => {
288
300
  const target = session.mapImagePoint(x, y);
289
301
  const r = await session.verifiedClick(target.x, target.y, button, double);
302
+ // report in the same image space the model emits — mixing in raw screen
303
+ // coordinates sends smaller models into ad-hoc rescaling math
304
+ const at = mapToImage(session.lastShot!, r.landed.x, r.landed.y);
290
305
  return text(
291
306
  r.ok
292
- ? `clicked ${button}${double ? " (double)" : ""} at screen (${r.landed.x}, ${r.landed.y})${r.corrected ? " after one correction" : ""}`
293
- : `WARNING: cursor landed at (${r.landed.x}, ${r.landed.y}) instead of (${target.x}, ${target.y}) — the click may have missed. Take a screenshot to verify.`,
307
+ ? `clicked ${button}${double ? " (double)" : ""} at image (${at.x}, ${at.y})${r.corrected ? " after one correction" : ""}`
308
+ : `WARNING: cursor landed at image (${at.x}, ${at.y}) instead of (${x}, ${y}) — the click may have missed. Take a screenshot to verify.`,
294
309
  );
295
310
  },
296
311
  );
@@ -308,7 +323,14 @@ async function main() {
308
323
  async ({ x, y }) => {
309
324
  const target = session.mapImagePoint(x, y);
310
325
  const landed = await session.moveTo(target.x, target.y);
311
- return text(`cursor at screen (${landed.x}, ${landed.y})`);
326
+ const at = mapToImage(session.lastShot!, landed.x, landed.y);
327
+ const missed =
328
+ Math.hypot(landed.x - target.x, landed.y - target.y) > CLICK_EPS_PX;
329
+ return text(
330
+ missed
331
+ ? `WARNING: cursor stopped at image (${at.x}, ${at.y}) instead of (${x}, ${y}) — take a fresh screenshot and retry`
332
+ : `cursor at image (${at.x}, ${at.y})`,
333
+ );
312
334
  },
313
335
  );
314
336