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 +1 -1
- package/src/bridgeClient.mjs +86 -50
package/package.json
CHANGED
package/src/bridgeClient.mjs
CHANGED
|
@@ -31,30 +31,58 @@ function endpointCandidates() {
|
|
|
31
31
|
return files;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
function
|
|
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
|
|
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
|
-
|
|
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
|
|
48
|
-
const
|
|
49
|
-
if (
|
|
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
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
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
|
|
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
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
104
|
-
|
|
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
|
-
|
|
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
|