creavit-studio-mcp 1.2.0 → 1.2.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "creavit-studio-mcp",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
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",
@@ -0,0 +1,104 @@
1
+ // MCP sunucusu — uygulama kapalıysa başlatma.
2
+ //
3
+ // Köprü yalnızca uygulama çalışırken vardır. Ajanın "önce uygulamayı sen aç"
4
+ // diyememesi her oturumun başında elle müdahale gerektiriyordu. Burası
5
+ // uygulamayı bulur, açar ve köprü hazır olana kadar bekler.
6
+
7
+ import { spawn } from "node:child_process";
8
+ import fs from "node:fs";
9
+ import os from "node:os";
10
+ import path from "node:path";
11
+
12
+ const APP_BUNDLE_NAME = "Creavit Studio.app";
13
+
14
+ const DEFAULT_APP_PATHS = [
15
+ path.join("/Applications", APP_BUNDLE_NAME),
16
+ path.join(os.homedir(), "Applications", APP_BUNDLE_NAME),
17
+ ];
18
+
19
+ /** Açılışın tamamlanması için verilen süre; soğuk başlangıç yavaş olabilir. */
20
+ export const DEFAULT_LAUNCH_TIMEOUT_MS = 45_000;
21
+ const POLL_INTERVAL_MS = 500;
22
+
23
+ export class AppLaunchError extends Error {
24
+ constructor(message) {
25
+ super(message);
26
+ this.name = "AppLaunchError";
27
+ }
28
+ }
29
+
30
+ /** Uygulama paketinin yolu: env override > /Applications > ~/Applications */
31
+ export function resolveAppPath() {
32
+ const fromEnv = process.env.CREAVIT_APP_PATH;
33
+ const candidates = fromEnv ? [fromEnv, ...DEFAULT_APP_PATHS] : DEFAULT_APP_PATHS;
34
+
35
+ for (const candidate of candidates) {
36
+ try {
37
+ if (candidate && fs.existsSync(candidate)) return candidate;
38
+ } catch (_) {}
39
+ }
40
+ return null;
41
+ }
42
+
43
+ /** Otomatik başlatma kapatılabilir olmalı (CI, kısıtlı ortam). */
44
+ export function isAutoLaunchEnabled() {
45
+ return process.env.CREAVIT_MCP_AUTOLAUNCH !== "0";
46
+ }
47
+
48
+ const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
49
+
50
+ /**
51
+ * Uygulamayı açar (beklemez). macOS'ta `open` kullanılır: zaten açıksa yeni
52
+ * kopya başlatmaz, kapalıysa başlatır.
53
+ */
54
+ export function launchApp() {
55
+ if (process.platform !== "darwin") {
56
+ throw new AppLaunchError(
57
+ "Automatic launch is only supported on macOS. Start Creavit Studio manually.",
58
+ );
59
+ }
60
+
61
+ const appPath = resolveAppPath();
62
+ if (!appPath) {
63
+ throw new AppLaunchError(
64
+ `Could not find ${APP_BUNDLE_NAME} in /Applications or ~/Applications. ` +
65
+ "Set CREAVIT_APP_PATH to the bundle path, or start the app manually.",
66
+ );
67
+ }
68
+
69
+ const child = spawn("open", ["-a", appPath], {
70
+ detached: true,
71
+ stdio: "ignore",
72
+ });
73
+ child.unref();
74
+ return appPath;
75
+ }
76
+
77
+ /**
78
+ * Uygulama çalışmıyorsa başlatır ve köprü yanıt verene kadar bekler.
79
+ *
80
+ * @param {object} options
81
+ * @param {() => Promise<boolean>} options.isRunning Köprü sağlık kontrolü
82
+ * @param {number} [options.timeoutMs]
83
+ * @returns {Promise<{ launched: boolean, appPath: string|null }>}
84
+ */
85
+ export async function ensureAppRunning({
86
+ isRunning,
87
+ timeoutMs = DEFAULT_LAUNCH_TIMEOUT_MS,
88
+ }) {
89
+ if (await isRunning()) return { launched: false, appPath: null };
90
+
91
+ const appPath = launchApp();
92
+ const deadline = Date.now() + timeoutMs;
93
+
94
+ while (Date.now() < deadline) {
95
+ await delay(POLL_INTERVAL_MS);
96
+ if (await isRunning()) return { launched: true, appPath };
97
+ }
98
+
99
+ throw new AppLaunchError(
100
+ `Launched ${appPath} but its agent bridge did not become ready within ` +
101
+ `${Math.round(timeoutMs / 1000)}s. The app may still be starting, or the ` +
102
+ "bridge may be disabled (CREAVIT_AGENT_BRIDGE=0).",
103
+ );
104
+ }
@@ -7,6 +7,7 @@ import fs from "node:fs";
7
7
  import os from "node:os";
8
8
  import path from "node:path";
9
9
  import { ENDPOINT_FILENAME, APP_NOT_RUNNING_HINT } from "./protocol.mjs";
10
+ import { ensureAppRunning, isAutoLaunchEnabled } from "./appLauncher.mjs";
10
11
 
11
12
  const DEFAULT_ENDPOINT_DIRS = [
12
13
  path.join(os.homedir(), "Library", "Application Support", "creavit-studio"),
@@ -107,12 +108,39 @@ async function request(route, { method = "GET", body, timeoutMs = 120_000 } = {}
107
108
  return payload.result;
108
109
  }
109
110
 
111
+ // Otomatik başlatma süreç başına EN FAZLA BİR KEZ denenir. Her başarısız
112
+ // çağrıda yeniden denemek, uygulama gerçekten açılamıyorsa her aracı
113
+ // dakikalarca bekletirdi.
114
+ let autoLaunchAttempt = null;
115
+
116
+ async function tryAutoLaunch() {
117
+ if (!isAutoLaunchEnabled()) return false;
118
+ if (!autoLaunchAttempt) {
119
+ autoLaunchAttempt = ensureAppRunning({ isRunning: isAppRunning })
120
+ .then(() => true)
121
+ .catch(() => false);
122
+ }
123
+ return autoLaunchAttempt;
124
+ }
125
+
110
126
  export async function callCommand(command, params, timeoutMs) {
111
- return request("/rpc", {
112
- method: "POST",
113
- body: { command, params: params || {}, timeoutMs },
114
- timeoutMs: timeoutMs || 120_000,
115
- });
127
+ const send = () =>
128
+ request("/rpc", {
129
+ method: "POST",
130
+ body: { command, params: params || {}, timeoutMs },
131
+ timeoutMs: timeoutMs || 120_000,
132
+ });
133
+
134
+ try {
135
+ return await send();
136
+ } catch (error) {
137
+ // Uygulama kapalıysa aç ve komutu bir kez tekrarla; ajanın "önce
138
+ // uygulamayı elle başlat" demek zorunda kalmaması için.
139
+ if (!(error instanceof BridgeUnavailableError)) throw error;
140
+ const launched = await tryAutoLaunch();
141
+ if (!launched) throw error;
142
+ return send();
143
+ }
116
144
  }
117
145
 
118
146
  export async function health() {
@@ -178,7 +178,8 @@ export function browserTools() {
178
178
  bridgeTool({
179
179
  name: "creavit_browser_interact",
180
180
  command: "browser.interact",
181
- description: "Finds a page element by CSS selector or visible text, scrolls to it, moves the real system mouse, and performs a real hover/click/type action.",
181
+ description:
182
+ "Finds a page element by CSS selector or visible text, scrolls to it, and performs a hover/click/type action. By default it moves the REAL system mouse, which looks natural in a recording. Set useSystemCursor:false when the user asks you not to touch their cursor — the click is still delivered to the page, the pointer just never moves.",
182
183
  inputSchema: schema(
183
184
  {
184
185
  captureWindowId: S.number("Capture window ID"),
@@ -187,6 +188,9 @@ export function browserTools() {
187
188
  text: S.string("Visible text fallback"),
188
189
  value: S.string("Text to enter for type"),
189
190
  moveDurationMs: S.number("Real mouse travel duration (default 520)"),
191
+ useSystemCursor: S.bool(
192
+ "Move the real system cursor (default true). false = never touch the user's pointer; clicks are sent synthetically.",
193
+ ),
190
194
  },
191
195
  ["captureWindowId", "action"],
192
196
  ),
@@ -1,10 +1,38 @@
1
1
  // MCP araçları — uygulama durumu, kayıt akışı, cihazlar ve olay akışı.
2
2
 
3
3
  import { bridgeTool, schema, S, textResult } from "./defineTool.mjs";
4
- import { callCommand } from "../bridgeClient.mjs";
4
+ import { callCommand, isAppRunning } from "../bridgeClient.mjs";
5
+ import {
6
+ ensureAppRunning,
7
+ resolveAppPath,
8
+ DEFAULT_LAUNCH_TIMEOUT_MS,
9
+ } from "../appLauncher.mjs";
5
10
 
6
11
  export function systemTools() {
7
12
  return [
13
+ {
14
+ name: "creavit_app_launch",
15
+ description:
16
+ "Starts Creavit Studio if it is not already running, then waits until its agent bridge is ready. Looks for the app in /Applications and ~/Applications (override with CREAVIT_APP_PATH). Other tools also auto-launch on first use, so call this only when you want to start the app explicitly or check where it was found.",
17
+ inputSchema: schema({
18
+ timeoutMs: S.number(
19
+ `How long to wait for the bridge after launching (default ${DEFAULT_LAUNCH_TIMEOUT_MS})`,
20
+ ),
21
+ }),
22
+ run: async (args) => {
23
+ const appPath = resolveAppPath();
24
+ const { launched } = await ensureAppRunning({
25
+ isRunning: isAppRunning,
26
+ timeoutMs: args?.timeoutMs || DEFAULT_LAUNCH_TIMEOUT_MS,
27
+ });
28
+ return textResult({
29
+ launched,
30
+ alreadyRunning: !launched,
31
+ appPath,
32
+ });
33
+ },
34
+ },
35
+
8
36
  bridgeTool({
9
37
  name: "creavit_app_info",
10
38
  description:
@@ -49,7 +77,7 @@ export function systemTools() {
49
77
  "Starts a screen recording. Get source IDs from creavit_devices_list first. Monitor with creavit_recording_status, and finish with creavit_recording_stop.",
50
78
  inputSchema: schema({
51
79
  options: S.object(
52
- "Recording options: {sourceType:'display'|'window'|'area', sourceId, cameraEnabled, micEnabled, systemAudioEnabled, delayMs, area:{x,y,width,height}}",
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.",
53
81
  ),
54
82
  }),
55
83
  timeoutMs: 180_000,