@floomhq/floom-mcp-sync 1.0.7 → 1.0.9

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 CHANGED
@@ -174,6 +174,7 @@ function normalizePackageFilePath(path) {
174
174
  async function planPackageSync(root, files, manifest) {
175
175
  let missing = 0;
176
176
  let unchanged = 0;
177
+ let firstMissingTarget = null;
177
178
  for (const file of files) {
178
179
  const targetKey = manifestKey(root, file.target);
179
180
  const tracked = manifest.files[targetKey];
@@ -194,8 +195,7 @@ async function planPackageSync(root, files, manifest) {
194
195
  if (state.kind === "conflict")
195
196
  return { kind: "conflict", target: file.target, reason: state.reason };
196
197
  if (state.kind === "missing") {
197
- if (tracked && files.length > 1)
198
- return { kind: "conflict", target: file.target, reason: "local package file missing since the last Floom sync" };
198
+ firstMissingTarget ??= file.target;
199
199
  missing += 1;
200
200
  continue;
201
201
  }
@@ -211,10 +211,9 @@ async function planPackageSync(root, files, manifest) {
211
211
  return { kind: "unchanged" };
212
212
  if (missing === files.length)
213
213
  return { kind: "write" };
214
- const missingFile = files.find((file) => !manifest.files[manifestKey(root, file.target)]);
215
214
  return {
216
215
  kind: "conflict",
217
- target: missingFile?.target ?? files[0]?.target ?? root,
216
+ target: firstMissingTarget ?? files[0]?.target ?? root,
218
217
  reason: "local package is only partially installed",
219
218
  };
220
219
  }
@@ -565,7 +564,7 @@ function maybeAuthWarning(log, message) {
565
564
  log(message);
566
565
  lastAuthWarningAt = now;
567
566
  }
568
- // TODO(option-B): swap polling for SSE. Add `/api/v1/sync-stream` route that
567
+ // Future option: swap polling for SSE. Add `/api/v1/sync-stream` route that
569
568
  // holds a per-user EventSource open and pushes `{type:"skills-updated"}` from a
570
569
  // Supabase post-write trigger or webhook. MCP listens, calls autoSync() on
571
570
  // event. Drops sync latency from <=60s to <=2s. Trade-off: needs reconnect
package/dist/server.js CHANGED
@@ -5,7 +5,7 @@ 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.7";
8
+ const SERVER_VERSION = "1.0.8";
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"]);
@@ -143,6 +143,12 @@ function asObject(value) {
143
143
  throw new Error("Expected object arguments.");
144
144
  return value;
145
145
  }
146
+ function assertOnlyKeys(value, allowed, label) {
147
+ const allowedSet = new Set(allowed);
148
+ const extra = Object.keys(value).find((key) => !allowedSet.has(key));
149
+ if (extra)
150
+ throw new Error(`Unexpected ${label} argument: ${extra}`);
151
+ }
146
152
  function asString(value, label, min, max) {
147
153
  if (typeof value !== "string" || value.length < min || value.length > max) {
148
154
  throw new Error(`Invalid ${label}.`);
@@ -176,9 +182,11 @@ async function callTool(params) {
176
182
  const name = asString(parsed.name, "tool name", 1, 200);
177
183
  const args = asObject(parsed.arguments ?? {});
178
184
  if (name === "floom_get_skill") {
185
+ assertOnlyKeys(args, ["slug"], "floom_get_skill");
179
186
  return ok(await getSkill(asString(args.slug, "slug", 1, 128)));
180
187
  }
181
188
  if (name === "floom_search_skills") {
189
+ assertOnlyKeys(args, ["query", "library", "type", "limit"], "floom_search_skills");
182
190
  const opts = {};
183
191
  const library = optionalString(args.library, "library", 64);
184
192
  const type = optionalEnumValue(args.type, "type", SEARCH_TYPES);
@@ -192,9 +200,11 @@ async function callTool(params) {
192
200
  return ok(await searchSkills(asString(args.query, "query", 1, 120), opts));
193
201
  }
194
202
  if (name === "floom_status") {
203
+ assertOnlyKeys(args, [], "floom_status");
195
204
  return ok(await syncStatus());
196
205
  }
197
206
  if (name === "floom_sync") {
207
+ assertOnlyKeys(args, [], "floom_sync");
198
208
  return ok(await runAutoSync());
199
209
  }
200
210
  throw new Error(`Unknown tool: ${name}`);
@@ -263,21 +273,27 @@ async function main() {
263
273
  process.once("SIGINT", () => shutdown("SIGINT"));
264
274
  process.once("SIGTERM", () => shutdown("SIGTERM"));
265
275
  const rl = createInterface({ input: stdin, crlfDelay: Infinity });
266
- for await (const line of rl) {
267
- if (!line.trim())
268
- continue;
269
- try {
270
- const message = JSON.parse(line);
271
- if (!message || typeof message !== "object" || Array.isArray(message)) {
272
- stdout.write(errorResponse(null, -32600, "Invalid request."));
276
+ try {
277
+ for await (const line of rl) {
278
+ if (!line.trim())
273
279
  continue;
280
+ try {
281
+ const message = JSON.parse(line);
282
+ if (!message || typeof message !== "object" || Array.isArray(message)) {
283
+ stdout.write(errorResponse(null, -32600, "Invalid request."));
284
+ continue;
285
+ }
286
+ await handleRequest(message);
287
+ }
288
+ catch {
289
+ stdout.write(errorResponse(null, -32700, "Parse error."));
274
290
  }
275
- await handleRequest(message);
276
- }
277
- catch {
278
- stdout.write(errorResponse(null, -32700, "Parse error."));
279
291
  }
280
292
  }
293
+ finally {
294
+ clearInterval(pollHandle);
295
+ process.stderr.write("[floom] stdin closed, stopping sync poller\n");
296
+ }
281
297
  }
282
298
  main().catch((err) => {
283
299
  process.stderr.write(`[floom] fatal: ${err instanceof Error ? err.message : String(err)}\n`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@floomhq/floom-mcp-sync",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "Lightweight Floom MCP server for installing, publishing, and startup-syncing skills.",
5
5
  "license": "MIT",
6
6
  "type": "module",