@okostream/mcp 0.12.13 → 0.12.15

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.
Files changed (2) hide show
  1. package/dist/server.js +60 -47
  2. package/package.json +1 -1
package/dist/server.js CHANGED
@@ -116291,7 +116291,7 @@ function sanitizeScopes(scopes) {
116291
116291
  }
116292
116292
 
116293
116293
  // build/version.js
116294
- var OKO_MCP_VERSION = "0.12.13";
116294
+ var OKO_MCP_VERSION = "0.12.15";
116295
116295
 
116296
116296
  // build/auth.js
116297
116297
  var state = null;
@@ -117212,15 +117212,39 @@ async function uploadMasterImage(eventId, kind, cardId, source) {
117212
117212
  });
117213
117213
  return result.data;
117214
117214
  }
117215
- async function updateCardImageInTransaction(eventId, cardId, patch) {
117215
+ function imageReplacedSinceRead(fresh, expected) {
117216
+ return fresh.imageMasterUrl !== expected.imageMasterUrl || fresh.imageUrl !== expected.imageUrl;
117217
+ }
117218
+ async function clearEventImageInTransaction(eventId, kind, cardId, expected) {
117216
117219
  const eventRef = doc(firestore, "events", eventId);
117217
117220
  await runTransaction2(firestore, async (transaction) => {
117218
117221
  const snap = await transaction.get(eventRef);
117219
117222
  if (!snap.exists())
117220
117223
  throw new Error("Event not found.");
117221
117224
  const data = snap.data();
117225
+ const fresh = readImageRef(data, kind, cardId);
117226
+ if (imageReplacedSinceRead(fresh, expected)) {
117227
+ throw new Error("The image changed between reading it and deleting it \u2014 someone replaced it in the meantime. Nothing was deleted and no file was removed. Read the event again and repeat the call if the new image should go too.");
117228
+ }
117229
+ if (kind === "card") {
117230
+ transaction.update(eventRef, {
117231
+ "blocks.cards.items": cardItemsWithImage(data, cardId, {
117232
+ imageUrl: null,
117233
+ imageMasterUrl: null,
117234
+ imageVariants: null,
117235
+ imageWidth: null,
117236
+ imageHeight: null
117237
+ })
117238
+ });
117239
+ return;
117240
+ }
117241
+ const prefix = kind === "promoBanner" ? "blocks.promoBanner" : "blocks.video.placeholder";
117222
117242
  transaction.update(eventRef, {
117223
- "blocks.cards.items": cardItemsWithImage(data, cardId, patch)
117243
+ [`${prefix}.imageMasterUrl`]: deleteField(),
117244
+ [`${prefix}.imageUrl`]: deleteField(),
117245
+ [`${prefix}.imageVariants`]: deleteField(),
117246
+ [`${prefix}.imageWidth`]: deleteField(),
117247
+ [`${prefix}.imageHeight`]: deleteField()
117224
117248
  });
117225
117249
  });
117226
117250
  }
@@ -117972,17 +117996,22 @@ function toMillis(value) {
117972
117996
  return null;
117973
117997
  }
117974
117998
  function parseTimestampCursor(cursor) {
117975
- const millis = Number(cursor);
117976
- if (Number.isFinite(millis) && millis > 0)
117977
- return Timestamp.fromMillis(millis);
117978
- const parsed = Date.parse(cursor);
117979
- if (Number.isFinite(parsed))
117980
- return Timestamp.fromMillis(parsed);
117981
- throw new Error("Invalid cursor. Use the nextCursor value returned by the previous page.");
117982
- }
117983
- function timestampCursorFrom(value) {
117999
+ const separator = cursor.indexOf("|");
118000
+ const head = separator === -1 ? cursor : cursor.slice(0, separator);
118001
+ const docId = separator === -1 ? "" : cursor.slice(separator + 1);
118002
+ const millis = Number(head);
118003
+ const at = Number.isFinite(millis) && millis > 0 ? millis : Date.parse(head);
118004
+ if (!Number.isFinite(at)) {
118005
+ throw new Error("Invalid cursor. Use the nextCursor value returned by the previous page.");
118006
+ }
118007
+ const boundary = Timestamp.fromMillis(at);
118008
+ return docId ? [boundary, docId] : [boundary];
118009
+ }
118010
+ function timestampCursorFrom(value, docId) {
117984
118011
  const millis = toMillis(value);
117985
- return millis == null ? null : String(millis);
118012
+ if (millis == null)
118013
+ return null;
118014
+ return docId ? `${millis}|${docId}` : String(millis);
117986
118015
  }
117987
118016
  var ISO_WITH_OFFSET = /(?:Z|[+-]\d{2}:?\d{2})$/;
117988
118017
  function parseIsoBound(value, label) {
@@ -118744,7 +118773,7 @@ function registerEventTools(server2) {
118744
118773
  limit(args.limit + 1)
118745
118774
  ];
118746
118775
  if (args.cursor)
118747
- constraints.splice(constraints.length - 1, 0, startAfter(parseTimestampCursor(args.cursor)));
118776
+ constraints.splice(constraints.length - 1, 0, startAfter(...parseTimestampCursor(args.cursor)));
118748
118777
  const snap = await getDocs(query2(collection(firestore, "eventAccess", uid, "grants"), ...constraints)).catch((error3) => {
118749
118778
  if (!isFirestorePermissionDenied(error3))
118750
118779
  throw error3;
@@ -118752,7 +118781,7 @@ function registerEventTools(server2) {
118752
118781
  });
118753
118782
  if (snap !== null) {
118754
118783
  const { page, hasMore } = pickPage(snap.docs, args.limit);
118755
- const nextCursor = hasMore ? timestampCursorFrom(page.at(-1)?.data()[orderField]) : null;
118784
+ const nextCursor = hasMore ? timestampCursorFrom(page.at(-1)?.data()[orderField], page.at(-1)?.id) : null;
118756
118785
  const pageItems = dateFiltered && args.workspaceId ? page.filter((item) => item.data().workspaceId === args.workspaceId) : page;
118757
118786
  let events;
118758
118787
  if (args.detail === "full") {
@@ -119339,31 +119368,10 @@ function registerImageTools(server2) {
119339
119368
  const event = await readEvent(eventId, uid, "edit");
119340
119369
  const previous = readImageRef(event, kind, cardId);
119341
119370
  requireConfirm(confirm, "oko_delete_image", { eventId, kind, cardId: cardId ?? null, imageId: previous.imageId });
119342
- if (kind === "promoBanner") {
119343
- await updateDoc(doc(firestore, "events", eventId), {
119344
- "blocks.promoBanner.imageMasterUrl": deleteField(),
119345
- "blocks.promoBanner.imageUrl": deleteField(),
119346
- "blocks.promoBanner.imageVariants": deleteField(),
119347
- "blocks.promoBanner.imageWidth": deleteField(),
119348
- "blocks.promoBanner.imageHeight": deleteField()
119349
- });
119350
- } else if (kind === "videoPlaceholder") {
119351
- await updateDoc(doc(firestore, "events", eventId), {
119352
- "blocks.video.placeholder.imageMasterUrl": deleteField(),
119353
- "blocks.video.placeholder.imageUrl": deleteField(),
119354
- "blocks.video.placeholder.imageVariants": deleteField(),
119355
- "blocks.video.placeholder.imageWidth": deleteField(),
119356
- "blocks.video.placeholder.imageHeight": deleteField()
119357
- });
119358
- } else {
119359
- await updateCardImageInTransaction(eventId, cardId, {
119360
- imageUrl: null,
119361
- imageMasterUrl: null,
119362
- imageVariants: null,
119363
- imageWidth: null,
119364
- imageHeight: null
119365
- });
119366
- }
119371
+ await clearEventImageInTransaction(eventId, kind, cardId, {
119372
+ imageMasterUrl: previous.imageMasterUrl,
119373
+ imageUrl: previous.imageUrl
119374
+ });
119367
119375
  const cleanup = await deleteStorageUrls(eventId, previous.imageMasterUrl, previous.imageUrl);
119368
119376
  return textResult({
119369
119377
  eventId,
@@ -120126,14 +120134,14 @@ function registerNotificationTools(server2) {
120126
120134
  limit(fetchLimit)
120127
120135
  ];
120128
120136
  if (cursor)
120129
- constraints.splice(1, 0, startAfter(parseTimestampCursor(cursor)));
120137
+ constraints.splice(1, 0, startAfter(...parseTimestampCursor(cursor)));
120130
120138
  const snap = await getDocs(query2(collection(firestore, "userNotifications", uid, "items"), ...constraints));
120131
120139
  const matchingDocs = unreadOnly ? snap.docs.filter((item) => item.data().read !== true) : snap.docs;
120132
120140
  const { page, hasMore: hasMoreInMatches } = pickPage(matchingDocs, max);
120133
120141
  const hasMore = hasMoreInMatches || snap.docs.length >= fetchLimit;
120134
120142
  const cursorSource = hasMoreInMatches ? page.at(-1) : snap.docs.at(-1);
120135
120143
  const notifications = page.map((item) => ({ id: item.id, ...serialize(item.data()) }));
120136
- const nextCursor = hasMore ? timestampCursorFrom(cursorSource?.data().createdAt) : null;
120144
+ const nextCursor = hasMore ? timestampCursorFrom(cursorSource?.data().createdAt, cursorSource?.id) : null;
120137
120145
  return textResult({
120138
120146
  notifications,
120139
120147
  unreadCountOnPage: notifications.filter((item) => item.read !== true).length,
@@ -120414,7 +120422,12 @@ function registerSessionTools(server2) {
120414
120422
  const rtdbCleanupWarnings = [];
120415
120423
  if (deleteRtdbData) {
120416
120424
  try {
120417
- await remove(ref(rtdb, `events/${eventId}/sessions/${sessionId}`));
120425
+ const livePointer = await get(ref(rtdb, `events/${eventId}/ctrl/activeSessionId`));
120426
+ if (livePointer.val() === sessionId) {
120427
+ rtdbCleanupWarnings.push("RTDB data kept: ctrl/activeSessionId still points at this session \u2014 deleting it would remove the only source CF49 can rebuild the report from");
120428
+ } else {
120429
+ await remove(ref(rtdb, `events/${eventId}/sessions/${sessionId}`));
120430
+ }
120418
120431
  } catch (err) {
120419
120432
  rtdbCleanupWarnings.push(err instanceof Error ? err.message : String(err));
120420
120433
  }
@@ -120454,11 +120467,11 @@ function registerSessionTools(server2) {
120454
120467
  limit(max + 1)
120455
120468
  ];
120456
120469
  if (cursor)
120457
- constraints.splice(1, 0, startAfter(parseTimestampCursor(cursor)));
120470
+ constraints.splice(1, 0, startAfter(...parseTimestampCursor(cursor)));
120458
120471
  const snap = await getDocs(query2(collection(firestore, "events", eventId, "analyticsSessions"), ...constraints));
120459
120472
  const { page, hasMore } = pickPage(snap.docs, max);
120460
120473
  const sessions = page.map((item) => compactAnalyticsSession({ id: item.id, ...item.data() }, { includeTimeline, timelineLimit, includeSnapshots }));
120461
- const nextCursor = hasMore ? timestampCursorFrom(page.at(-1)?.data().startedAt) : null;
120474
+ const nextCursor = hasMore ? timestampCursorFrom(page.at(-1)?.data().startedAt, page.at(-1)?.id) : null;
120462
120475
  return textResult({ sessions, ...cursorMeta(sessions.length, max, hasMore, nextCursor) }, { truncationHint: "Use limit/cursor or keep includeTimeline/includeSnapshots disabled." });
120463
120476
  });
120464
120477
  server2.registerTool("oko_get_session_report", {
@@ -120674,14 +120687,14 @@ function registerTelegramTools(server2) {
120674
120687
  limit(max + 1)
120675
120688
  ];
120676
120689
  if (cursor)
120677
- constraints.splice(2, 0, startAfter(parseTimestampCursor(cursor)));
120690
+ constraints.splice(2, 0, startAfter(...parseTimestampCursor(cursor)));
120678
120691
  const snap = await getDocs(query2(collection(firestore, "telegramPosts"), ...constraints));
120679
120692
  const matching = status ? snap.docs.filter((item) => item.data().status === status) : snap.docs;
120680
120693
  const { page, hasMore: hasMoreInMatches } = pickPage(matching, max);
120681
120694
  const hasMore = hasMoreInMatches || snap.docs.length >= max + 1;
120682
120695
  const cursorSource = hasMoreInMatches ? page.at(-1) : snap.docs.at(-1);
120683
120696
  const posts = page.map((item) => ({ id: item.id, ...serialize(item.data()) }));
120684
- const nextCursor = hasMore ? timestampCursorFrom(cursorSource?.data().createdAt) : null;
120697
+ const nextCursor = hasMore ? timestampCursorFrom(cursorSource?.data().createdAt, cursorSource?.id) : null;
120685
120698
  return textResult({ posts, ...cursorMeta(posts.length, max, hasMore, nextCursor) });
120686
120699
  });
120687
120700
  server2.registerTool("oko_cancel_telegram_post", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@okostream/mcp",
3
- "version": "0.12.13",
3
+ "version": "0.12.15",
4
4
  "description": "Control OKO Stream webinars from Claude, Codex, and other MCP clients.",
5
5
  "license": "UNLICENSED",
6
6
  "author": {