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 +1 -1
- package/src/appLauncher.mjs +104 -0
- package/src/bridgeClient.mjs +33 -5
- package/src/tools/browserTools.mjs +5 -1
- package/src/tools/systemTools.mjs +30 -2
package/package.json
CHANGED
|
@@ -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
|
+
}
|
package/src/bridgeClient.mjs
CHANGED
|
@@ -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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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:
|
|
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,
|