clickshot-mcp 0.1.2 → 0.1.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 (3) hide show
  1. package/README.md +6 -2
  2. package/package.json +1 -1
  3. package/server.js +51 -5
package/README.md CHANGED
@@ -49,8 +49,12 @@ extension ingest port.
49
49
 
50
50
  ## Tools
51
51
 
52
- - `get_recent_activity(limit)` — last N clicks as annotated screenshots + a log
53
- (URL, page title, clicked element, text)
52
+ - `get_recent_activity(limit)` — last N frames as screenshots + a log (URL, page
53
+ title, clicked element/text; periodic frames are labeled)
54
+ - `start_watching(task)` — Claude asks to watch the user perform a task. Turns on
55
+ recording + periodic frames in the extension (with a visible "Claude is watching"
56
+ indicator). The MCP client prompts the user to approve this.
57
+ - `stop_watching()` — end the watch session
54
58
  - `clear_activity()` — wipe the buffer
55
59
 
56
60
  ## Ingest API (used by the extension)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clickshot-mcp",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Local MCP server that feeds ClickShot screenshots into your running Claude (Code/Desktop) on your own subscription.",
5
5
  "type": "module",
6
6
  "bin": {
package/server.js CHANGED
@@ -18,7 +18,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
18
18
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
19
19
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
20
20
 
21
- const VERSION = "0.1.2";
21
+ const VERSION = "0.1.3";
22
22
 
23
23
  const WATCH_INTERVAL_MS = 4000; // periodic-frame cadence the extension uses while watching
24
24
 
@@ -54,18 +54,49 @@ const MAX_BUFFER = 200; // keep the most recent N captures in memory
54
54
  const captures = [];
55
55
  let nextId = 1;
56
56
 
57
- // Claude-initiated "watch" session state. The extension polls GET /watch and
58
- // starts recording (+ periodic frames) when `on` is true. Turned on by the
59
- // start_watching tool (which the MCP client gates with a user approval prompt)
60
- // and off by stop_watching or by the user via the extension.
57
+ // Claude-initiated "watch" session state. The extension holds an SSE stream
58
+ // (GET /events) and starts recording (+ periodic frames) when `on` is true.
59
+ // Turned on by the start_watching tool (which the MCP client gates with a user
60
+ // approval prompt) and off by stop_watching or by the user via the extension.
61
61
  const watch = { on: false, task: null, since: null };
62
62
 
63
+ // SSE clients (the extension's background worker). The stream keeps the MV3
64
+ // service worker alive and lets the server push state changes + frame ticks.
65
+ const sseClients = new Set();
66
+
67
+ function sseSend(res, event, data) {
68
+ try {
69
+ res.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
70
+ } catch (e) {
71
+ /* client gone; cleaned up on 'close' */
72
+ }
73
+ }
74
+
75
+ function broadcast(event, data) {
76
+ for (const res of sseClients) sseSend(res, event, data);
77
+ }
78
+
79
+ function watchState() {
80
+ return { watching: watch.on, task: watch.task, since: watch.since, intervalMs: WATCH_INTERVAL_MS };
81
+ }
82
+
63
83
  function setWatch(on, task) {
64
84
  watch.on = !!on;
65
85
  watch.task = on ? task || null : null;
66
86
  watch.since = on ? Date.now() : null;
87
+ broadcast("state", watchState()); // push the change to the extension immediately
67
88
  }
68
89
 
90
+ // One process-wide ticker drives the push channel:
91
+ // • every second: while watching, emit a frame tick on the 4s cadence
92
+ // • every ~12s: emit a state heartbeat so the worker stays alive even when idle
93
+ let tick = 0;
94
+ setInterval(() => {
95
+ tick++;
96
+ if (watch.on && tick % Math.round(WATCH_INTERVAL_MS / 1000) === 0) broadcast("frame", { t: tick });
97
+ if (tick % 12 === 0) broadcast("state", watchState());
98
+ }, 1000);
99
+
69
100
  // ---------------------------------------------------------------------------
70
101
  // MCP server definition (tools)
71
102
  // ---------------------------------------------------------------------------
@@ -229,6 +260,21 @@ function buildIngestApp() {
229
260
  res.json({ ok: true, watching: watch.on });
230
261
  });
231
262
 
263
+ // SSE push channel. The extension holds this open; it keeps the MV3 service
264
+ // worker alive and receives `state` (watch on/off) and `frame` (capture now)
265
+ // events. Replaces the old setInterval polling, which died when the worker slept.
266
+ app.get("/events", (req, res) => {
267
+ res.set({
268
+ "Content-Type": "text/event-stream",
269
+ "Cache-Control": "no-cache, no-transform",
270
+ Connection: "keep-alive",
271
+ });
272
+ res.flushHeaders?.();
273
+ sseClients.add(res);
274
+ sseSend(res, "state", watchState()); // sync current state on connect
275
+ req.on("close", () => sseClients.delete(res));
276
+ });
277
+
232
278
  return app;
233
279
  }
234
280