@floomhq/floom-mcp-sync 1.0.10 → 1.0.11
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/dist/auto-sync.js +10 -7
- package/dist/lib/api.js +33 -15
- package/dist/server.js +15 -2
- package/package.json +1 -1
package/dist/auto-sync.js
CHANGED
|
@@ -233,7 +233,7 @@ async function manifestHasMissingTrackedFile(manifest, root) {
|
|
|
233
233
|
}
|
|
234
234
|
return false;
|
|
235
235
|
}
|
|
236
|
-
export async function autoSync(log = (message) => process.stderr.write(`${message}\n`)) {
|
|
236
|
+
export async function autoSync(log = (message) => process.stderr.write(`${message}\n`), signal) {
|
|
237
237
|
const cfg = await readConfig();
|
|
238
238
|
if (!cfg) {
|
|
239
239
|
maybeAuthWarning(log, "[floom] not signed in; skipping sync (run `npx -y @floomhq/floom login`)");
|
|
@@ -246,7 +246,7 @@ export async function autoSync(log = (message) => process.stderr.write(`${messag
|
|
|
246
246
|
const apiUrl = apiUrlFromConfig(cfg);
|
|
247
247
|
let response;
|
|
248
248
|
try {
|
|
249
|
-
response = await getJsonWithEtag(`${apiUrl}/api/v1/me/skills`, cfg.accessToken, cachedEtag);
|
|
249
|
+
response = await getJsonWithEtag(`${apiUrl}/api/v1/me/skills`, cfg.accessToken, cachedEtag, signal);
|
|
250
250
|
}
|
|
251
251
|
catch (err) {
|
|
252
252
|
if (err instanceof FloomApiError && err.status === 401) {
|
|
@@ -257,7 +257,7 @@ export async function autoSync(log = (message) => process.stderr.write(`${messag
|
|
|
257
257
|
}
|
|
258
258
|
if (response.status === 304) {
|
|
259
259
|
if (await manifestHasMissingTrackedFile(manifest, root)) {
|
|
260
|
-
response = await getJsonWithEtag(`${apiUrl}/api/v1/me/skills`, cfg.accessToken, null);
|
|
260
|
+
response = await getJsonWithEtag(`${apiUrl}/api/v1/me/skills`, cfg.accessToken, null, signal);
|
|
261
261
|
}
|
|
262
262
|
else {
|
|
263
263
|
maybeHeartbeat(log);
|
|
@@ -414,7 +414,7 @@ export async function autoSync(log = (message) => process.stderr.write(`${messag
|
|
|
414
414
|
if (updated > 0) {
|
|
415
415
|
// Activation telemetry counts syncs that write new content. Best-effort;
|
|
416
416
|
// never blocks or throws.
|
|
417
|
-
void emitSyncCompleted(apiUrl, cfg.accessToken, { total, updated, unchanged }).catch(() => { });
|
|
417
|
+
void emitSyncCompleted(apiUrl, cfg.accessToken, { total, updated, unchanged }, signal).catch(() => { });
|
|
418
418
|
}
|
|
419
419
|
}
|
|
420
420
|
else if (conflicts > 0) {
|
|
@@ -427,9 +427,9 @@ export async function autoSync(log = (message) => process.stderr.write(`${messag
|
|
|
427
427
|
return { synced: total, unchanged, updated, conflicts };
|
|
428
428
|
});
|
|
429
429
|
}
|
|
430
|
-
async function emitSyncCompleted(apiUrl, token, props) {
|
|
430
|
+
async function emitSyncCompleted(apiUrl, token, props, signal) {
|
|
431
431
|
try {
|
|
432
|
-
|
|
432
|
+
const init = {
|
|
433
433
|
method: "POST",
|
|
434
434
|
headers: {
|
|
435
435
|
"Content-Type": "application/json",
|
|
@@ -444,7 +444,10 @@ async function emitSyncCompleted(apiUrl, token, props) {
|
|
|
444
444
|
unchanged: props.unchanged,
|
|
445
445
|
},
|
|
446
446
|
}),
|
|
447
|
-
}
|
|
447
|
+
};
|
|
448
|
+
if (signal)
|
|
449
|
+
init.signal = signal;
|
|
450
|
+
await fetch(`${apiUrl}/api/v1/events`, init);
|
|
448
451
|
}
|
|
449
452
|
catch {
|
|
450
453
|
// never throw from telemetry
|
package/dist/lib/api.js
CHANGED
|
@@ -20,11 +20,14 @@ async function readError(res) {
|
|
|
20
20
|
}
|
|
21
21
|
return text;
|
|
22
22
|
}
|
|
23
|
-
export async function getJson(url, token) {
|
|
23
|
+
export async function getJson(url, token, signal) {
|
|
24
24
|
const headers = {};
|
|
25
25
|
if (token)
|
|
26
26
|
headers.authorization = `Bearer ${token}`;
|
|
27
|
-
const
|
|
27
|
+
const init = { headers };
|
|
28
|
+
if (signal)
|
|
29
|
+
init.signal = signal;
|
|
30
|
+
const res = await fetch(url, init);
|
|
28
31
|
if (!res.ok)
|
|
29
32
|
throw new FloomApiError(res.status, await readError(res));
|
|
30
33
|
return (await res.json());
|
|
@@ -33,11 +36,14 @@ export async function getJson(url, token) {
|
|
|
33
36
|
* GET helper that participates in HTTP conditional requests via ETag.
|
|
34
37
|
* Pass the previously-seen ETag (or null on first call). On 304, body is null.
|
|
35
38
|
*/
|
|
36
|
-
export async function getJsonWithEtag(url, token, etag) {
|
|
39
|
+
export async function getJsonWithEtag(url, token, etag, signal) {
|
|
37
40
|
const headers = { authorization: `Bearer ${token}` };
|
|
38
41
|
if (etag)
|
|
39
42
|
headers["if-none-match"] = etag;
|
|
40
|
-
const
|
|
43
|
+
const init = { headers };
|
|
44
|
+
if (signal)
|
|
45
|
+
init.signal = signal;
|
|
46
|
+
const res = await fetch(url, init);
|
|
41
47
|
const responseEtag = res.headers.get("etag");
|
|
42
48
|
if (res.status === 304) {
|
|
43
49
|
return { status: 304, body: null, etag: responseEtag ?? etag };
|
|
@@ -47,43 +53,55 @@ export async function getJsonWithEtag(url, token, etag) {
|
|
|
47
53
|
const body = (await res.json());
|
|
48
54
|
return { status: res.status, body, etag: responseEtag };
|
|
49
55
|
}
|
|
50
|
-
export async function getText(url) {
|
|
51
|
-
const
|
|
56
|
+
export async function getText(url, signal) {
|
|
57
|
+
const init = {};
|
|
58
|
+
if (signal)
|
|
59
|
+
init.signal = signal;
|
|
60
|
+
const res = await fetch(url, init);
|
|
52
61
|
if (!res.ok)
|
|
53
62
|
throw new FloomApiError(res.status, await readError(res));
|
|
54
63
|
return res.text();
|
|
55
64
|
}
|
|
56
|
-
export async function postJson(url, token, body) {
|
|
57
|
-
const
|
|
65
|
+
export async function postJson(url, token, body, signal) {
|
|
66
|
+
const init = {
|
|
58
67
|
method: "POST",
|
|
59
68
|
headers: {
|
|
60
69
|
authorization: `Bearer ${token}`,
|
|
61
70
|
"content-type": "application/json",
|
|
62
71
|
},
|
|
63
72
|
body: JSON.stringify(body),
|
|
64
|
-
}
|
|
73
|
+
};
|
|
74
|
+
if (signal)
|
|
75
|
+
init.signal = signal;
|
|
76
|
+
const res = await fetch(url, init);
|
|
65
77
|
if (!res.ok)
|
|
66
78
|
throw new FloomApiError(res.status, await readError(res));
|
|
67
79
|
return (await res.json());
|
|
68
80
|
}
|
|
69
|
-
export async function putJson(url, token, body) {
|
|
70
|
-
const
|
|
81
|
+
export async function putJson(url, token, body, signal) {
|
|
82
|
+
const init = {
|
|
71
83
|
method: "PUT",
|
|
72
84
|
headers: {
|
|
73
85
|
authorization: `Bearer ${token}`,
|
|
74
86
|
"content-type": "application/json",
|
|
75
87
|
},
|
|
76
88
|
body: JSON.stringify(body),
|
|
77
|
-
}
|
|
89
|
+
};
|
|
90
|
+
if (signal)
|
|
91
|
+
init.signal = signal;
|
|
92
|
+
const res = await fetch(url, init);
|
|
78
93
|
if (!res.ok)
|
|
79
94
|
throw new FloomApiError(res.status, await readError(res));
|
|
80
95
|
return (await res.json());
|
|
81
96
|
}
|
|
82
|
-
export async function deleteRequest(url, token) {
|
|
83
|
-
const
|
|
97
|
+
export async function deleteRequest(url, token, signal) {
|
|
98
|
+
const init = {
|
|
84
99
|
method: "DELETE",
|
|
85
100
|
headers: { authorization: `Bearer ${token}` },
|
|
86
|
-
}
|
|
101
|
+
};
|
|
102
|
+
if (signal)
|
|
103
|
+
init.signal = signal;
|
|
104
|
+
const res = await fetch(url, init);
|
|
87
105
|
if (!res.ok)
|
|
88
106
|
throw new FloomApiError(res.status, await readError(res));
|
|
89
107
|
return (await res.json());
|
package/dist/server.js
CHANGED
|
@@ -5,17 +5,28 @@ import { autoSync } from "./auto-sync.js";
|
|
|
5
5
|
import { getSkill } from "./tools/get.js";
|
|
6
6
|
import { searchSkills } from "./tools/search.js";
|
|
7
7
|
import { syncStatus } from "./tools/status.js";
|
|
8
|
-
const SERVER_VERSION = "1.0.
|
|
8
|
+
const SERVER_VERSION = "1.0.11";
|
|
9
9
|
const DEFAULT_INTERVAL_MS = 60_000;
|
|
10
10
|
const MIN_INTERVAL_MS = 10_000;
|
|
11
11
|
const SEARCH_TYPES = new Set(["knowledge", "instruction", "workflow", "skill"]);
|
|
12
12
|
let syncInFlight = null;
|
|
13
|
+
let syncAbortController = null;
|
|
13
14
|
function runAutoSync() {
|
|
14
|
-
|
|
15
|
+
if (syncInFlight)
|
|
16
|
+
return syncInFlight;
|
|
17
|
+
const controller = new AbortController();
|
|
18
|
+
syncAbortController = controller;
|
|
19
|
+
syncInFlight = autoSync(undefined, controller.signal).finally(() => {
|
|
20
|
+
if (syncAbortController === controller)
|
|
21
|
+
syncAbortController = null;
|
|
15
22
|
syncInFlight = null;
|
|
16
23
|
});
|
|
17
24
|
return syncInFlight;
|
|
18
25
|
}
|
|
26
|
+
function abortAutoSync() {
|
|
27
|
+
syncAbortController?.abort();
|
|
28
|
+
syncAbortController = null;
|
|
29
|
+
}
|
|
19
30
|
function usage() {
|
|
20
31
|
return `
|
|
21
32
|
floom-mcp-sync v${SERVER_VERSION}
|
|
@@ -267,6 +278,7 @@ async function main() {
|
|
|
267
278
|
const pollHandle = startPolling(intervalMs, syncState);
|
|
268
279
|
const shutdown = (signal) => {
|
|
269
280
|
clearInterval(pollHandle);
|
|
281
|
+
abortAutoSync();
|
|
270
282
|
process.stderr.write(`[floom] received ${signal}, stopping sync poller\n`);
|
|
271
283
|
process.exit(0);
|
|
272
284
|
};
|
|
@@ -292,6 +304,7 @@ async function main() {
|
|
|
292
304
|
}
|
|
293
305
|
finally {
|
|
294
306
|
clearInterval(pollHandle);
|
|
307
|
+
abortAutoSync();
|
|
295
308
|
process.stderr.write("[floom] stdin closed, stopping sync poller\n");
|
|
296
309
|
}
|
|
297
310
|
}
|