creavit-studio-mcp 1.3.6 → 1.3.7

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.3.6",
3
+ "version": "1.3.7",
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",
@@ -31,30 +31,58 @@ function endpointCandidates() {
31
31
  return files;
32
32
  }
33
33
 
34
- function readEndpoint() {
34
+ function readEndpoints() {
35
+ const found = [];
35
36
  for (const file of endpointCandidates()) {
36
37
  try {
37
38
  if (!fs.existsSync(file)) continue;
38
39
  const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
39
- if (parsed?.port && parsed?.token) return { ...parsed, file };
40
+ if (!parsed?.port || !parsed?.token) continue;
41
+ const stat = fs.statSync(file);
42
+ found.push({
43
+ ...parsed,
44
+ file,
45
+ freshness: Math.max(
46
+ Number.isFinite(Date.parse(parsed.startedAt))
47
+ ? Date.parse(parsed.startedAt)
48
+ : 0,
49
+ Number(stat.mtimeMs) || 0,
50
+ ),
51
+ });
40
52
  } catch (_) {}
41
53
  }
42
- return null;
54
+ const unique = new Map();
55
+ for (const endpoint of found) {
56
+ const key = `${endpoint.host || "127.0.0.1"}:${endpoint.port}:${endpoint.token}`;
57
+ const existing = unique.get(key);
58
+ if (!existing || endpoint.freshness > existing.freshness) {
59
+ unique.set(key, endpoint);
60
+ }
61
+ }
62
+ return [...unique.values()].sort((a, b) => b.freshness - a.freshness);
43
63
  }
44
64
 
45
65
  let cached = null;
46
66
 
47
- function currentEndpoint() {
48
- const fresh = readEndpoint();
49
- if (!fresh) {
67
+ function availableEndpoints() {
68
+ const endpoints = readEndpoints();
69
+ if (endpoints.length === 0) {
50
70
  cached = null;
51
71
  throw new BridgeUnavailableError(APP_NOT_RUNNING_HINT);
52
72
  }
53
- // Port ya da token değiştiyse uygulama yeniden başlamış demektir.
54
- if (!cached || cached.port !== fresh.port || cached.token !== fresh.token) {
55
- cached = fresh;
73
+ // The explicit in-app endpoint yields one item. For external clients, prefer
74
+ // the newest file but keep other candidates as fallbacks in case it is stale.
75
+ if (cached) {
76
+ const cachedIndex = endpoints.findIndex(
77
+ (endpoint) =>
78
+ endpoint.port === cached.port && endpoint.token === cached.token,
79
+ );
80
+ if (cachedIndex > 0) {
81
+ const [knownLive] = endpoints.splice(cachedIndex, 1);
82
+ endpoints.unshift(knownLive);
83
+ }
56
84
  }
57
- return cached;
85
+ return endpoints;
58
86
  }
59
87
 
60
88
  /**
@@ -63,52 +91,60 @@ function currentEndpoint() {
63
91
  * @param {object} [options] { method, body, timeoutMs }
64
92
  */
65
93
  async function request(route, { method = "GET", body, timeoutMs = 120_000 } = {}) {
66
- const endpoint = currentEndpoint();
67
- const url = `http://${endpoint.host || "127.0.0.1"}:${endpoint.port}${route}`;
68
-
69
- const controller = new AbortController();
70
- const timer = setTimeout(() => controller.abort(), timeoutMs);
94
+ const endpoints = availableEndpoints();
95
+ let lastConnectionError = null;
96
+
97
+ for (const endpoint of endpoints) {
98
+ const url = `http://${endpoint.host || "127.0.0.1"}:${endpoint.port}${route}`;
99
+ const controller = new AbortController();
100
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
101
+ let response;
102
+ try {
103
+ response = await fetch(url, {
104
+ method,
105
+ signal: controller.signal,
106
+ headers: {
107
+ Authorization: `Bearer ${endpoint.token}`,
108
+ ...(body ? { "Content-Type": "application/json" } : {}),
109
+ },
110
+ body: body ? JSON.stringify(body) : undefined,
111
+ });
112
+ } catch (error) {
113
+ if (error?.name === "AbortError") {
114
+ throw new Error(`Bridge did not respond within ${timeoutMs}ms: ${route}`);
115
+ }
116
+ lastConnectionError = error;
117
+ continue;
118
+ } finally {
119
+ clearTimeout(timer);
120
+ }
71
121
 
72
- let response;
73
- try {
74
- response = await fetch(url, {
75
- method,
76
- signal: controller.signal,
77
- headers: {
78
- Authorization: `Bearer ${endpoint.token}`,
79
- ...(body ? { "Content-Type": "application/json" } : {}),
80
- },
81
- body: body ? JSON.stringify(body) : undefined,
82
- });
83
- } catch (error) {
84
- cached = null;
85
- if (error?.name === "AbortError") {
86
- throw new Error(`Bridge did not respond within ${timeoutMs}ms: ${route}`);
122
+ const text = await response.text();
123
+ let payload;
124
+ try {
125
+ payload = text ? JSON.parse(text) : {};
126
+ } catch (_) {
127
+ throw new Error(
128
+ `Invalid response from bridge (${response.status}): ${text.slice(0, 200)}`,
129
+ );
87
130
  }
88
- throw new BridgeUnavailableError(
89
- `${APP_NOT_RUNNING_HINT}\n(connection error: ${error?.message || error})`,
90
- );
91
- } finally {
92
- clearTimeout(timer);
93
- }
94
131
 
95
- const text = await response.text();
96
- let payload;
97
- try {
98
- payload = text ? JSON.parse(text) : {};
99
- } catch (_) {
100
- throw new Error(`Invalid response from bridge (${response.status}): ${text.slice(0, 200)}`);
101
- }
132
+ if (!response.ok || payload?.ok === false) {
133
+ const err = payload?.error || {};
134
+ const error = new Error(err.message || `Bridge error (${response.status})`);
135
+ error.code = err.code || String(response.status);
136
+ error.details = err.details || null;
137
+ throw error;
138
+ }
102
139
 
103
- if (!response.ok || payload?.ok === false) {
104
- const err = payload?.error || {};
105
- const error = new Error(err.message || `Bridge error (${response.status})`);
106
- error.code = err.code || String(response.status);
107
- error.details = err.details || null;
108
- throw error;
140
+ cached = endpoint;
141
+ return payload.result;
109
142
  }
110
143
 
111
- return payload.result;
144
+ cached = null;
145
+ throw new BridgeUnavailableError(
146
+ `${APP_NOT_RUNNING_HINT}\n(connection error: ${lastConnectionError?.message || lastConnectionError || "no live endpoint"})`,
147
+ );
112
148
  }
113
149
 
114
150
  // Otomatik başlatma süreç başına EN FAZLA BİR KEZ denenir. Her başarısız