creavit-studio-mcp 1.3.2 → 1.3.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.
package/README.md CHANGED
@@ -213,13 +213,17 @@ composited in.
213
213
 
214
214
  ### Record a URL walkthrough
215
215
 
216
- `creavit_record_url_walkthrough` is the high-level workflow. By default it opens
216
+ `creavit_record_url_walkthrough` is the high-level, atomic workflow. Use one call
217
+ instead of separate browser/start/interact/stop calls so client round trips and
218
+ retries cannot create gaps or duplicate recordings. By default it opens
217
219
  the URL in a frameless site-only capture window (`16:9`, `4:3`, `1:1`, `9:16`,
218
220
  or another requested ratio) and waits for the DOM, fonts, and images before
219
221
  recording. It then executes ordered semantic actions with the real macOS mouse,
220
222
  so the normal Creavit custom-cursor track receives authentic move/click events.
221
223
  It trims the timeline to `durationMs`, optionally covers the whole video with a
222
- perspective preset, mutes it, and saves it.
224
+ perspective preset, mutes it, and saves it. With `export: true`, the tool starts
225
+ the render in the background and returns promptly; `openExport: true` opens the
226
+ video automatically when rendering finishes, without requiring export UI.
223
227
 
224
228
  It does **not** add zoom ranges: the app records the real clicks and generates
225
229
  its own auto-zoom segments in the editor, which land on the actual click times.
@@ -247,7 +251,11 @@ Example input:
247
251
  "controlSystemMouse": true,
248
252
  "perspective": true,
249
253
  "perspectiveTemplate": "minimal-tilt",
250
- "mute": true
254
+ "mute": true,
255
+ "export": true,
256
+ "exportResolution": "1080p",
257
+ "exportFps": 60,
258
+ "openExport": true
251
259
  }
252
260
  ```
253
261
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "creavit-studio-mcp",
3
- "version": "1.3.2",
3
+ "version": "1.3.3",
4
4
  "description": "MCP server that lets AI coding agents (Claude Code, Codex, Cursor) drive the Creavit Studio screen recording app",
5
5
  "keywords": [
6
6
  "mcp",
package/src/protocol.mjs CHANGED
@@ -6,7 +6,7 @@ export const ENDPOINT_FILENAME = "agent-bridge.json";
6
6
 
7
7
  export const MCP_PROTOCOL_VERSION = "2024-11-05";
8
8
  export const SERVER_NAME = "creavit-studio";
9
- export const SERVER_VERSION = "1.3.1";
9
+ export const SERVER_VERSION = "1.3.2";
10
10
 
11
11
  export const JSONRPC_ERRORS = {
12
12
  PARSE_ERROR: -32700,
package/src/rpcServer.mjs CHANGED
@@ -40,6 +40,8 @@ export function startRpcServer({ listTools, callTool }) {
40
40
  protocolVersion: MCP_PROTOCOL_VERSION,
41
41
  capabilities: { tools: { listChanged: false } },
42
42
  serverInfo: { name: SERVER_NAME, version: SERVER_VERSION },
43
+ instructions:
44
+ "Plan the complete Creavit tool sequence before the first call, then execute it without unnecessary pauses. For any website workflow that includes open + record + multiple interactions + stop, use creavit_record_url_walkthrough once instead of separate browser/recording calls; it can also start export in the background and open the result when ready. Mutating recording/export calls are idempotent, so do not stop/restart merely because a client-side timeout made the result uncertain—check creavit_recording_status or creavit_events.",
43
45
  };
44
46
  },
45
47
 
@@ -135,6 +135,9 @@ function trimSegmentsToDuration(segments, targetDuration) {
135
135
  }
136
136
 
137
137
  export function browserTools() {
138
+ let activeUrlWalkthroughPromise = null;
139
+ let recentUrlWalkthrough = null;
140
+
138
141
  return [
139
142
  bridgeTool({
140
143
  name: "creavit_browser_open",
@@ -165,6 +168,17 @@ export function browserTools() {
165
168
  ),
166
169
  }),
167
170
 
171
+ bridgeTool({
172
+ name: "creavit_browser_activate",
173
+ command: "browser.activate",
174
+ description:
175
+ "Brings the capture window or external browser to the foreground without changing its size or entering fullscreen. Call immediately after recording starts if you are driving the recording manually.",
176
+ inputSchema: schema({
177
+ captureWindowId: S.number("Clean capture window ID"),
178
+ browser: S.string("External browser name when there is no captureWindowId"),
179
+ }),
180
+ }),
181
+
168
182
  bridgeTool({
169
183
  name: "creavit_browser_focus",
170
184
  command: "browser.focusPoint",
@@ -220,7 +234,7 @@ export function browserTools() {
220
234
  {
221
235
  name: "creavit_record_url_walkthrough",
222
236
  description:
223
- "Records a site in a clean configurable-ratio window after real page readiness. It can execute ordered semantic steps using the real system mouse, trims to an exact duration, applies optional perspective, and optionally mutes it. Zooms are NOT added: Creavit Studio records the real clicks and generates its own auto-zoom segments in the editor. Only pass autoZoom=true when the user explicitly asks for extra scripted zooms.",
237
+ "USE THIS SINGLE ATOMIC TOOL for requests that open a website, record several clicks/types, stop, and optionally export/open the video. Do not split that workflow into manual browser + recording calls: this tool prevents duplicate recordings and agent round-trip gaps. It opens a non-fullscreen configurable-ratio window, waits for real page readiness, keeps camera off, defaults microphone/system audio off, executes ordered semantic steps, and can export/open the final video. Zooms are NOT added: Creavit Studio records the real clicks and generates its own auto-zoom segments in the editor. Only pass autoZoom=true when the user explicitly asks for extra scripted zooms.",
224
238
  inputSchema: schema(
225
239
  {
226
240
  url: S.string("http(s) page to record"),
@@ -246,10 +260,27 @@ export function browserTools() {
246
260
  perspective: S.bool("Apply perspective to the complete result"),
247
261
  perspectiveTemplate: S.string("Perspective template id (default minimal-tilt)"),
248
262
  save: S.bool("Save the generated project after editing (default true)"),
263
+ export: S.bool("Start export in the background after recording/editing (default false)"),
264
+ exportFilePath: S.string("Optional absolute target .mp4 path; defaults beside the .crvt project"),
265
+ exportResolution: S.string("Export resolution (default 1080p)"),
266
+ exportFps: S.number("Export frame rate (default 60)"),
267
+ exportQuality: S.string("Export quality (default high)", {
268
+ enum: ["compact", "high", "max"],
269
+ }),
270
+ openExport: S.bool("Open the exported video when finished (default true when export=true)"),
249
271
  },
250
272
  ["url"],
251
273
  ),
252
- run: async (args) => {
274
+ run: (args) => {
275
+ const requestKey = JSON.stringify(args || {});
276
+ if (
277
+ recentUrlWalkthrough?.key === requestKey &&
278
+ Date.now() < recentUrlWalkthrough.expiresAt
279
+ ) {
280
+ return Promise.resolve(recentUrlWalkthrough.result);
281
+ }
282
+ if (activeUrlWalkthroughPromise) return activeUrlWalkthroughPromise;
283
+ const operation = (async () => {
253
284
  const browser = canonicalBrowser(args.browser || "Zen Browser");
254
285
  const cleanWindow = args.cleanWindow !== false;
255
286
  const loadWaitMs = Math.max(0, Math.min(60_000, Number(args.loadWaitMs) || 1500));
@@ -329,7 +360,7 @@ export function browserTools() {
329
360
  }
330
361
  const browserWindow = await waitForBrowserWindow(captureWindowId ? "Creavit Studio" : browser, titleHint, 20_000);
331
362
 
332
- await callCommand("recording.start", {
363
+ const startResult = await callCommand("recording.start", {
333
364
  options: {
334
365
  startScreen: true,
335
366
  cameraEnabled: false,
@@ -338,7 +369,19 @@ export function browserTools() {
338
369
  recordingSource: { type: "window", windowId: browserWindow.id, windowInfo: browserWindow },
339
370
  },
340
371
  }, 180_000);
372
+ if (startResult?.started === false) {
373
+ throw new Error(
374
+ "A recording is already active. Stop it before starting a URL walkthrough.",
375
+ );
376
+ }
341
377
  recordingStarted = true;
378
+ // recording.start briefly focuses the recorder controls. Restore the
379
+ // requested browser immediately so the actual walkthrough is visibly
380
+ // in front from the first recorded frame onward.
381
+ await callCommand("browser.activate", {
382
+ captureWindowId,
383
+ browser: captureWindowId ? undefined : browser,
384
+ });
342
385
  const recordingStartedAt = Date.now();
343
386
  const contentDeadline = durationMs ? recordingStartedAt + durationMs - tailMs : Number.POSITIVE_INFINITY;
344
387
  await sleep(Math.min(leadInMs, Math.max(0, contentDeadline - Date.now())));
@@ -355,6 +398,7 @@ export function browserTools() {
355
398
  selector: section.selector,
356
399
  text: section.text,
357
400
  value: section.value,
401
+ useSystemCursor: controlSystemMouse,
358
402
  }, 30_000);
359
403
  if (Number.isFinite(interaction?.x)) focus = { x: interaction.x, y: interaction.y };
360
404
  } else {
@@ -387,6 +431,12 @@ export function browserTools() {
387
431
  }
388
432
  await callCommand("recording.stop", {}, 600_000);
389
433
  recordingStarted = false;
434
+ // The source is finalized now. Closing it before editor processing/export
435
+ // avoids leaving a stale capture window over the foreground app.
436
+ if (captureWindowId) {
437
+ await callCommand("browser.close", { captureWindowId });
438
+ captureWindowId = null;
439
+ }
390
440
 
391
441
  const editorState = await waitForEditor(previousEditor?.projectFilePath, 600_000);
392
442
  const applied = {
@@ -396,6 +446,8 @@ export function browserTools() {
396
446
  perspective: null,
397
447
  saved: false,
398
448
  trimmedTo: null,
449
+ export: null,
450
+ openedExport: null,
399
451
  };
400
452
  let finalDuration = Number(editorState.duration);
401
453
  if (durationMs) {
@@ -432,7 +484,24 @@ export function browserTools() {
432
484
  await callCommand("editor.saveProject", {}, 180_000);
433
485
  applied.saved = true;
434
486
  }
435
- return textResult({ ok: true, url: args.url, browser: cleanWindow ? "Creavit Capture" : browser, cleanWindow, page: openResult, window: browserWindow, duration: finalDuration, rawDuration: editorState.duration, projectFilePath: editorState.projectFilePath, applied });
487
+ if (args.export === true) {
488
+ const exportResult = await callCommand(
489
+ "editor.export.start",
490
+ {
491
+ ...(args.exportFilePath ? { filePath: args.exportFilePath } : {}),
492
+ format: "mp4",
493
+ resolution: args.exportResolution || "1080p",
494
+ fps: Math.max(1, Math.min(120, Math.round(Number(args.exportFps) || 60))),
495
+ quality: args.exportQuality || "high",
496
+ openWhenFinished: args.openExport !== false,
497
+ },
498
+ 30_000,
499
+ );
500
+ applied.export = exportResult;
501
+ applied.openedExport =
502
+ args.openExport !== false ? "scheduled when export finishes" : null;
503
+ }
504
+ return textResult({ ok: true, url: args.url, browser: cleanWindow ? "Creavit Capture" : browser, cleanWindow, page: openResult, window: browserWindow, duration: finalDuration, rawDuration: editorState.duration, projectFilePath: editorState.projectFilePath, exportStartedInBackground: applied.export?.started === true, exportJob: applied.export?.job || null, applied });
436
505
  } finally {
437
506
  if (recordingStarted) {
438
507
  try { await callCommand("recording.stop", {}, 600_000); } catch (_) {}
@@ -441,6 +510,20 @@ export function browserTools() {
441
510
  try { await callCommand("browser.close", { captureWindowId }); } catch (_) {}
442
511
  }
443
512
  }
513
+ })();
514
+ activeUrlWalkthroughPromise = operation;
515
+ return operation.then((result) => {
516
+ recentUrlWalkthrough = {
517
+ key: requestKey,
518
+ result,
519
+ expiresAt: Date.now() + 60_000,
520
+ };
521
+ return result;
522
+ }).finally(() => {
523
+ if (activeUrlWalkthroughPromise === operation) {
524
+ activeUrlWalkthroughPromise = null;
525
+ }
526
+ });
444
527
  },
445
528
  },
446
529
  ];
@@ -314,7 +314,7 @@ export function editorTools() {
314
314
  name: "creavit_editor_export",
315
315
  command: "editor.export",
316
316
  description:
317
- "Exports the video. This takes a LONG time (minutes). Follow progress with creavit_events (export.started / export.finished / export.failed). Omitted settings fall back to whatever is selected in the editor.",
317
+ "Exports the video idempotently; concurrent retries share one render. This takes a LONG time (minutes). Follow progress with creavit_events (export.started / export.finished / export.failed), then use creavit_file_open when the user asks to open/play it. If filePath is omitted, the export is written beside the open .crvt project.",
318
318
  inputSchema: schema({
319
319
  filePath: S.string("Target file path (.mp4)"),
320
320
  format: S.string("mp4 or gif", { enum: ["mp4", "gif"] }),
@@ -325,6 +325,22 @@ export function editorTools() {
325
325
  timeoutMs: 3_600_000,
326
326
  }),
327
327
 
328
+ bridgeTool({
329
+ name: "creavit_editor_export_start",
330
+ command: "editor.export.start",
331
+ description:
332
+ "Starts one export in the background and returns immediately. Use this when tool latency matters. Progress/completion is reported by creavit_events; set openWhenFinished=true to open/play the result automatically without showing export UI.",
333
+ inputSchema: schema({
334
+ filePath: S.string("Target file path (.mp4); omit to export beside the project"),
335
+ format: S.string("mp4 or gif", { enum: ["mp4", "gif"] }),
336
+ resolution: S.string("e.g. 720p, 1080p, 4k"),
337
+ fps: S.number("Frame rate, e.g. 30 or 60"),
338
+ quality: S.string("compact | high | max"),
339
+ openWhenFinished: S.bool("Open the exported file automatically when rendering completes"),
340
+ }),
341
+ timeoutMs: 30_000,
342
+ }),
343
+
328
344
  {
329
345
  name: "creavit_editor_history",
330
346
  description: "Undoes the last editor change, or redoes it.",
@@ -71,6 +71,14 @@ export function projectTools() {
71
71
  inputSchema: schema({ filePath: S.string("Absolute path to the file") }, ["filePath"]),
72
72
  }),
73
73
 
74
+ bridgeTool({
75
+ name: "creavit_file_open",
76
+ command: "project.openFile",
77
+ description:
78
+ "Opens an existing exported video or other file with its default macOS application. Use this after creavit_editor_export when the user asks to open/play the result.",
79
+ inputSchema: schema({ filePath: S.string("Absolute path to the file") }, ["filePath"]),
80
+ }),
81
+
74
82
  bridgeTool({
75
83
  name: "creavit_project_save",
76
84
  command: "editor.saveProject",
@@ -74,7 +74,7 @@ export function systemTools() {
74
74
  name: "creavit_recording_start",
75
75
  command: "recording.start",
76
76
  description:
77
- "Starts a screen recording. Get source IDs from creavit_devices_list first. Monitor with creavit_recording_status, and finish with creavit_recording_stop.",
77
+ "Starts a screen recording idempotently: retries while starting share the same native operation, and calling it while active does not restart. Get source IDs from creavit_devices_list first. For a multi-step website recording, use ONE creavit_record_url_walkthrough call instead of this manual tool.",
78
78
  inputSchema: schema({
79
79
  options: S.object(
80
80
  "Recording options: {sourceType:'display'|'window'|'area', sourceId, cameraEnabled, micEnabled, systemAudioEnabled, delayMs, area:{x,y,width,height}}. cameraEnabled:false also turns the camera off and hides its window, so it does not appear in a screen recording; cameraEnabled:true opens and starts it.",
@@ -88,7 +88,7 @@ export function systemTools() {
88
88
  name: "creavit_recording_stop",
89
89
  command: "recording.stop",
90
90
  description:
91
- "Stops the recording. Processing can take a few minutes; the editor opens automatically when it finishes. Track progress with creavit_events (watch for recording.stopped and project.loaded).",
91
+ "Stops the recording idempotently. Processing can take a few minutes; concurrent retries share the same operation and the editor opens automatically when it finishes. Track progress with creavit_events (watch for recording.stopped and project.loaded).",
92
92
  inputSchema: schema(),
93
93
  timeoutMs: 600_000,
94
94
  }),