@okostream/mcp 0.12.5 → 0.12.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.
Files changed (3) hide show
  1. package/README.md +20 -6
  2. package/dist/server.js +160 -59
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -82,8 +82,9 @@ published version. The server reports the latest version during the key
82
82
  exchange: `oko_whoami` and `oko_connection_status` return `update`, and the
83
83
  first results of a session carry an `UPDATE AVAILABLE` block while the client
84
84
  is behind, with the exact update step. Global installs update with
85
- `npm install -g @okostream/mcp@latest`; Claude Desktop users reinstall the
86
- `.mcpb` bundle from the install center.
85
+ `npm install -g @okostream/mcp@latest`. Claude Desktop is not offered for
86
+ now (the extension bundle is discontinued; the Desktop path returns when the
87
+ package ships through a directory).
87
88
 
88
89
  ## When a tool refuses
89
90
 
@@ -96,10 +97,23 @@ Refusals are explicit and never require guessing:
96
97
  OKO team.
97
98
  - `PLATFORM_REJECTED_READ` — no access to that resource, or it does not exist.
98
99
 
99
- Image URLs are resolved locally. On a machine where a VPN or proxy intercepts
100
- DNS, public hosts resolve to fake addresses (`198.18.x.x`); such uploads still
101
- work, and the result says so. Uploading a file from disk (`OKO_IMAGE_ROOTS`)
102
- always works.
100
+ **One install path.** Every supported client — Claude Code, Codex, and any
101
+ MCP client that takes a command with arguments and env — runs the package
102
+ through `npx -y @okostream/mcp@latest` (Node.js required): the latest version
103
+ is installed on each restart, so the server never falls behind. The `.mcpb`
104
+ extension bundle for Claude Desktop is no longer distributed — it never
105
+ updated itself — and the Claude Desktop path is archived until the package
106
+ ships through a directory. The key exchange refuses versions behind the
107
+ latest published one and tells the assistant how to update.
108
+
109
+ Image URLs are fetched by the OKO server first, so a VPN, proxy or captive
110
+ portal on the organizer's machine is not in the way. If the server cannot
111
+ reach the URL (a private host, a login wall, a timeout), the MCP client
112
+ fetches it from the local machine; on a machine where a VPN or proxy
113
+ intercepts DNS, public hosts resolve to fake addresses (`198.18.x.x`), such
114
+ uploads still work, and the result says so. When both roads fail, the error
115
+ names both reasons and asks for a file from disk (`OKO_IMAGE_ROOTS`), which
116
+ always works. The result's `sourceVia` says who fetched the image.
103
117
 
104
118
  ## Capabilities
105
119
 
package/dist/server.js CHANGED
@@ -70503,7 +70503,7 @@ var OKO_CONNECTION_STATUSES = [
70503
70503
  "rate_limited",
70504
70504
  "unavailable"
70505
70505
  ];
70506
- function nextSteps(status, minimumVersion) {
70506
+ function nextSteps(status, minimumVersion, latestVersion) {
70507
70507
  switch (status) {
70508
70508
  case "connected":
70509
70509
  return [{
@@ -70543,7 +70543,7 @@ function nextSteps(status, minimumVersion) {
70543
70543
  return [
70544
70544
  {
70545
70545
  id: "update_package",
70546
- label: `Update to @okostream/mcp@latest${minimumVersion ? ` (minimum ${minimumVersion})` : ""}.`,
70546
+ label: `Update to @okostream/mcp@latest${latestVersion ? ` (${latestVersion} is published` : ""}${minimumVersion ? `${latestVersion ? "; " : " ("}minimum ${minimumVersion}` : ""}${latestVersion || minimumVersion ? ")" : ""}.`,
70547
70547
  command: "npx -y @okostream/mcp@latest"
70548
70548
  },
70549
70549
  {
@@ -70566,7 +70566,7 @@ function nextSteps(status, minimumVersion) {
70566
70566
  function buildConnectionStatus(status, options2 = {}) {
70567
70567
  const connected = status === "connected";
70568
70568
  const errorCode = connected ? null : status === "update_required" ? "CLIENT_UPGRADE_REQUIRED" : status === "rate_limited" ? "RATE_LIMITED" : status === "unavailable" ? "SERVICE_UNAVAILABLE" : "AUTH_REQUIRED";
70569
- const message = connected ? "OKO MCP is connected to an active organizer account." : status === "not_configured" ? "OKO MCP is installed, but OKO_API_KEY is not configured." : status === "invalid_key" ? "OKO MCP is installed, but the configured API key is invalid or revoked." : status === "update_required" ? "This OKO MCP client version is no longer supported." : status === "rate_limited" ? "OKO temporarily rate-limited the connection attempt." : "OKO MCP could not verify the organizer connection.";
70569
+ const message = connected ? "OKO MCP is connected to an active organizer account." : status === "not_configured" ? "OKO MCP is installed, but OKO_API_KEY is not configured." : status === "invalid_key" ? "OKO MCP is installed, but the configured API key is invalid or revoked." : status === "update_required" ? options2.serverMessage ?? "This OKO MCP client version is no longer supported." : status === "rate_limited" ? "OKO temporarily rate-limited the connection attempt." : "OKO MCP could not verify the organizer connection.";
70570
70570
  return {
70571
70571
  connected,
70572
70572
  status,
@@ -70576,8 +70576,9 @@ function buildConnectionStatus(status, options2 = {}) {
70576
70576
  organizerAccountRequired: true,
70577
70577
  credentialConfigured: status !== "not_configured",
70578
70578
  links: OKO_ONBOARDING_URLS,
70579
- nextSteps: nextSteps(status, options2.minimumVersion),
70579
+ nextSteps: nextSteps(status, options2.minimumVersion, options2.latestVersion),
70580
70580
  ...options2.minimumVersion ? { minimumVersion: options2.minimumVersion } : {},
70581
+ ...options2.latestVersion ? { latestVersion: options2.latestVersion } : {},
70581
70582
  ...options2.uid ? { uid: options2.uid } : {},
70582
70583
  ...options2.scopes ? { scopes: options2.scopes } : {},
70583
70584
  ...options2.update ? { update: options2.update } : {}
@@ -116223,11 +116224,14 @@ function sanitizeScopes(scopes) {
116223
116224
  }
116224
116225
 
116225
116226
  // build/version.js
116226
- var OKO_MCP_VERSION = "0.12.5";
116227
+ var OKO_MCP_VERSION = "0.12.7";
116227
116228
 
116228
116229
  // build/auth.js
116229
116230
  var state = null;
116230
116231
  var latestPublishedVersion = null;
116232
+ function recordLatestPublishedVersion(version9) {
116233
+ latestPublishedVersion = version9 && parseVersion(version9) ? version9.trim() : null;
116234
+ }
116231
116235
  var OkoScopeError = class extends Error {
116232
116236
  scope;
116233
116237
  constructor(scope) {
@@ -116258,7 +116262,7 @@ function isNewerVersion(candidate, current) {
116258
116262
  var MCP_UPDATE_HOW_TO = [
116259
116263
  "Restart the MCP client. With the default `npx -y @okostream/mcp@latest` command the restart installs the newest version automatically.",
116260
116264
  "Global install: run `npm install -g @okostream/mcp@latest`, then restart the client.",
116261
- "Claude Desktop with the .mcpb bundle: download https://okostream.app/downloads/oko-mcp-latest.mcpb and install it over the current one."
116265
+ 'Claude Desktop with the extension bundle: the bundle is discontinued and never updated itself; switch to Claude Code, Codex, or the "Other MCP" configuration from https://okostream.app/app/mcp.'
116262
116266
  ];
116263
116267
  function updateStatus() {
116264
116268
  const latest = latestPublishedVersion;
@@ -116279,13 +116283,28 @@ function currentUid() {
116279
116283
  var OkoAuthError = class extends Error {
116280
116284
  status;
116281
116285
  minimumVersion;
116282
- constructor(status, message, minimumVersion) {
116286
+ latestVersion;
116287
+ serverMessage;
116288
+ constructor(status, message, details = {}) {
116283
116289
  super(message);
116284
116290
  this.status = status;
116285
- this.minimumVersion = minimumVersion;
116286
116291
  this.name = "OkoAuthError";
116292
+ this.minimumVersion = details.minimumVersion;
116293
+ this.latestVersion = details.latestVersion;
116294
+ this.serverMessage = details.serverMessage;
116287
116295
  }
116288
116296
  };
116297
+ function versionField(value) {
116298
+ return typeof value === "string" && parseVersion(value) ? value.trim() : void 0;
116299
+ }
116300
+ function upgradeRequiredError(body) {
116301
+ const parsed = body && typeof body === "object" ? body : {};
116302
+ const minimumVersion = versionField(parsed.minimumVersion);
116303
+ const latestVersion = versionField(parsed.latestVersion);
116304
+ const serverMessage = typeof parsed.message === "string" && parsed.message.trim() ? parsed.message.trim() : void 0;
116305
+ const fallback = `OKO MCP ${OKO_MCP_VERSION} is no longer supported. Update to @okostream/mcp@latest (minimum ${minimumVersion ?? "the current release"}${latestVersion ? `, latest ${latestVersion}` : ""}) and restart the MCP client.`;
116306
+ return new OkoAuthError("update_required", serverMessage ?? fallback, { minimumVersion, latestVersion, serverMessage });
116307
+ }
116289
116308
  async function exchangeApiKey() {
116290
116309
  if (!env.apiKey) {
116291
116310
  throw new OkoAuthError("not_configured", "OKO MCP is installed but not connected. Configure OKO_API_KEY, then restart or reconnect the MCP server.");
@@ -116304,8 +116323,10 @@ async function exchangeApiKey() {
116304
116323
  const status = response.status;
116305
116324
  const errorBody = await response.json().catch(() => ({}));
116306
116325
  if (status === 426) {
116307
- const minimumVersion = typeof errorBody.minimumVersion === "string" ? errorBody.minimumVersion : "the current release";
116308
- throw new OkoAuthError("update_required", `OKO MCP ${OKO_MCP_VERSION} is no longer supported. Update to @okostream/mcp@latest (minimum ${minimumVersion}) and restart the MCP client.`, minimumVersion);
116326
+ const error3 = upgradeRequiredError(errorBody);
116327
+ if (error3.latestVersion)
116328
+ recordLatestPublishedVersion(error3.latestVersion);
116329
+ throw error3;
116309
116330
  }
116310
116331
  if (status === 429) {
116311
116332
  throw new OkoAuthError("rate_limited", "OKO API key exchange is temporarily rate-limited. Wait one minute, then restart the MCP client.");
@@ -116342,7 +116363,7 @@ async function ensureAuth(requiredScopes = []) {
116342
116363
  expiresAt: now + exchanged.expiresIn * 1e3
116343
116364
  };
116344
116365
  if (exchanged.latestVersion)
116345
- latestPublishedVersion = exchanged.latestVersion;
116366
+ recordLatestPublishedVersion(exchanged.latestVersion);
116346
116367
  }
116347
116368
  for (const scope of requiredScopes) {
116348
116369
  if (!state.scopes.includes(scope)) {
@@ -116366,16 +116387,21 @@ async function getConnectionStatus() {
116366
116387
  });
116367
116388
  } catch (error3) {
116368
116389
  if (error3 instanceof OkoAuthError) {
116369
- return buildConnectionStatus(error3.status, { minimumVersion: error3.minimumVersion });
116390
+ return buildConnectionStatus(error3.status, authErrorOptions(error3));
116370
116391
  }
116371
116392
  return buildConnectionStatus("unavailable");
116372
116393
  }
116373
116394
  }
116395
+ function authErrorOptions(error3) {
116396
+ return {
116397
+ minimumVersion: error3.minimumVersion,
116398
+ latestVersion: error3.latestVersion,
116399
+ serverMessage: error3.serverMessage
116400
+ };
116401
+ }
116374
116402
  function authErrorResult(error3) {
116375
116403
  return {
116376
- ...textResult(buildConnectionStatus(error3.status, {
116377
- minimumVersion: error3.minimumVersion
116378
- })),
116404
+ ...textResult(buildConnectionStatus(error3.status, authErrorOptions(error3))),
116379
116405
  isError: true
116380
116406
  };
116381
116407
  }
@@ -116465,6 +116491,15 @@ function isPublicUnicastAddress(address) {
116465
116491
  return false;
116466
116492
  return import_ipaddr.default.parse(normalized).range() === "unicast";
116467
116493
  }
116494
+ function pinnedLookup(address, family) {
116495
+ return (_hostname, options2, callback) => {
116496
+ if (options2 && typeof options2 === "object" && options2.all) {
116497
+ callback(null, [{ address, family }]);
116498
+ return;
116499
+ }
116500
+ callback(null, address, family);
116501
+ };
116502
+ }
116468
116503
 
116469
116504
  // build/tools/eventAccess.js
116470
116505
  var EVENT_ACCESS_FIELD_BY_MODE = {
@@ -116640,9 +116675,7 @@ async function readRemoteImage(rawUrl, redirects = 0) {
116640
116675
  remoteFetchNotes.push(`Image fetched through a VPN/proxy with DNS interception: ${url.hostname} resolved to ${pinned.address}, so OKO could not verify the destination address itself; the connection went through the proxy. This is expected on such machines and fine for public image links.`);
116641
116676
  }
116642
116677
  const dispatcher = new import_undici.Agent({
116643
- connect: {
116644
- lookup: (_hostname, _options, callback) => callback(null, pinned.address, pinned.family)
116645
- }
116678
+ connect: { lookup: pinnedLookup(pinned.address, pinned.family) }
116646
116679
  });
116647
116680
  const controller = new AbortController();
116648
116681
  const timeout = setTimeout(() => controller.abort(), REMOTE_IMAGE_TIMEOUT_MS);
@@ -116673,7 +116706,7 @@ async function readRemoteImage(rawUrl, redirects = 0) {
116673
116706
  if (Number.isFinite(contentLength) && contentLength > MAX_SOURCE_IMAGE_BYTES) {
116674
116707
  throw new Error("Image URL is larger than 15MB.");
116675
116708
  }
116676
- return responseBufferWithLimit(response, MAX_SOURCE_IMAGE_BYTES);
116709
+ return await responseBufferWithLimit(response, MAX_SOURCE_IMAGE_BYTES);
116677
116710
  } catch (err) {
116678
116711
  if (err instanceof Error && err.name === "AbortError") {
116679
116712
  throw new Error("Image URL fetch timed out.");
@@ -116710,12 +116743,65 @@ async function readLocalImage(inputPath) {
116710
116743
  throw new Error("Image file is larger than 15MB.");
116711
116744
  return readFile(filePath);
116712
116745
  }
116713
- async function readImageSource(source) {
116714
- if (source.path)
116715
- return readLocalImage(source.path);
116716
- if (source.url)
116717
- return readRemoteImage(source.url);
116718
- throw new Error("Provide image source path or url.");
116746
+ function serverFetchRefusal(error3) {
116747
+ if (!error3 || typeof error3 !== "object")
116748
+ return null;
116749
+ const details = error3.details;
116750
+ if (details && typeof details === "object" && details.code === "REMOTE_FETCH_FAILED") {
116751
+ const record2 = details;
116752
+ const fallbackMessage = String(error3.message ?? "the server could not fetch the URL");
116753
+ return {
116754
+ reason: typeof record2.reason === "string" ? record2.reason : "unknown",
116755
+ message: typeof record2.message === "string" ? record2.message : fallbackMessage
116756
+ };
116757
+ }
116758
+ const code = error3.code;
116759
+ const message = String(error3.message ?? "");
116760
+ if (code === "functions/invalid-argument" && message.includes("Image payload is empty or too large")) {
116761
+ return { reason: "server_without_url_support", message: "The OKO server release in use does not fetch URLs yet." };
116762
+ }
116763
+ return null;
116764
+ }
116765
+ var SERVER_FETCH_NOTE = "Image fetched by the OKO server directly from the URL; this machine's network was not involved.";
116766
+ function localFallbackNote(serverFailure) {
116767
+ return `The OKO server could not fetch the URL (${serverFailure.reason}: ${serverFailure.message}), so the image was fetched from this machine instead.`;
116768
+ }
116769
+ function remoteImageUnavailableMessage(url, serverFailure, localError) {
116770
+ const localMessage = localError instanceof Error ? localError.message : String(localError);
116771
+ return `Image URL could not be fetched by the OKO server or from this machine. Server (${serverFailure.reason}): ${serverFailure.message} This machine: ${localMessage} Ask the organizer to download the image and pass its path via source.path (allowed directories come from OKO_IMAGE_ROOTS), or to share a public HTTPS link that returns the image file directly. URL: ${url}`;
116772
+ }
116773
+ async function uploadImageFromSource(eventId, kind, cardId, source) {
116774
+ if (source.path) {
116775
+ const buffer2 = await readLocalImage(source.path);
116776
+ const uploaded2 = await uploadMasterImage(eventId, kind, cardId, { imageBase64: buffer2.toString("base64") });
116777
+ return { uploaded: uploaded2, sourceBytes: buffer2.byteLength, sourceVia: "client", notes: [] };
116778
+ }
116779
+ if (!source.url)
116780
+ throw new Error("Provide image source path or url.");
116781
+ let serverFailure;
116782
+ try {
116783
+ const uploaded2 = await uploadMasterImage(eventId, kind, cardId, { sourceUrl: source.url });
116784
+ return { uploaded: uploaded2, sourceBytes: uploaded2.sourceBytes ?? 0, sourceVia: "server", notes: [SERVER_FETCH_NOTE] };
116785
+ } catch (error3) {
116786
+ const refusal = serverFetchRefusal(error3);
116787
+ if (!refusal)
116788
+ throw error3;
116789
+ serverFailure = refusal;
116790
+ }
116791
+ let buffer;
116792
+ try {
116793
+ buffer = await readRemoteImage(source.url);
116794
+ } catch (localError) {
116795
+ takeRemoteFetchNotes();
116796
+ throw new Error(remoteImageUnavailableMessage(source.url, serverFailure, localError));
116797
+ }
116798
+ const uploaded = await uploadMasterImage(eventId, kind, cardId, { imageBase64: buffer.toString("base64") });
116799
+ return {
116800
+ uploaded,
116801
+ sourceBytes: buffer.byteLength,
116802
+ sourceVia: "client",
116803
+ notes: [localFallbackNote(serverFailure), ...takeRemoteFetchNotes()]
116804
+ };
116719
116805
  }
116720
116806
  function readImageRef(eventData, kind, cardId) {
116721
116807
  const blocks = eventData.blocks && typeof eventData.blocks === "object" ? eventData.blocks : {};
@@ -116853,8 +116939,8 @@ var cardItemInputSchema = external_exports.object({
116853
116939
  formId: cardIdSchema.nullable().optional()
116854
116940
  }).strict();
116855
116941
  var imageSourceSchema = external_exports.object({
116856
- path: external_exports.string().min(1).optional().describe("Local image path on the MCP client machine."),
116857
- url: external_exports.string().url().optional().describe("HTTPS image URL to fetch from the MCP client machine.")
116942
+ path: external_exports.string().min(1).optional().describe("Local image path on the MCP client machine (inside OKO_IMAGE_ROOTS)."),
116943
+ url: external_exports.string().url().optional().describe("Public HTTPS image URL. The OKO server fetches it first (works behind VPNs and proxies); if the server cannot reach it, the MCP client fetches it from this machine; if both fail, ask the organizer for a local file via path.")
116858
116944
  }).strict().refine((source) => Boolean(source.path) !== Boolean(source.url), {
116859
116945
  message: "Provide exactly one image source: path or url."
116860
116946
  });
@@ -116920,13 +117006,13 @@ async function deleteStorageUrls(eventId, masterUrl, fallbackUrl) {
116920
117006
  const warnings = await deleteStoragePaths(eventId, paths);
116921
117007
  return { attempted: [...new Set(paths)].length, warnings };
116922
117008
  }
116923
- async function uploadMasterImage(eventId, kind, cardId, image) {
117009
+ async function uploadMasterImage(eventId, kind, cardId, source) {
116924
117010
  const uploadImage = httpsCallable(functions, "uploadMcpImage");
116925
117011
  const result = await uploadImage({
116926
117012
  eventId,
116927
117013
  kind,
116928
117014
  ...cardId ? { cardId } : {},
116929
- imageBase64: image.toString("base64")
117015
+ ...source.sourceUrl ? { sourceUrl: source.sourceUrl } : { imageBase64: source.imageBase64 }
116930
117016
  });
116931
117017
  return result.data;
116932
117018
  }
@@ -117007,8 +117093,6 @@ async function uploadEventImageForUser(uid, eventId, kind, cardId, source) {
117007
117093
  throw new Error("cardId is only valid when kind is card.");
117008
117094
  const event = await readEvent(eventId, uid, "edit");
117009
117095
  const previous = readImageRef(event, kind, cardId);
117010
- const sourceBuffer = await readImageSource(source);
117011
- const remoteNotes = takeRemoteFetchNotes();
117012
117096
  const eventBlocks = event.blocks && typeof event.blocks === "object" ? event.blocks : {};
117013
117097
  const placeholderCfg = eventBlocks.video?.placeholder && typeof eventBlocks.video.placeholder === "object" ? eventBlocks.video.placeholder : {};
117014
117098
  const blockEnabled = kind === "promoBanner" ? eventBlocks.promoBanner?.enabled === true : kind === "videoPlaceholder" ? placeholderCfg.enabled === true : readCardsConfig(event).enabled;
@@ -117022,7 +117106,7 @@ async function uploadEventImageForUser(uid, eventId, kind, cardId, source) {
117022
117106
  const previousMasterPath = storagePathFromDownloadUrl(previous.imageMasterUrl);
117023
117107
  const deleteWarnings = [];
117024
117108
  let deletesAttempted = 0;
117025
- const uploaded = await uploadMasterImage(eventId, kind, cardId, sourceBuffer);
117109
+ const { uploaded, sourceBytes, sourceVia, notes: sourceNotes } = await uploadImageFromSource(eventId, kind, cardId, source);
117026
117110
  if (!uploaded)
117027
117111
  throw new Error("Image upload did not return a result.");
117028
117112
  if (kind !== "card" || previousMasterPath && previousMasterPath !== uploaded.storagePath || !previous.imageMasterUrl && previous.imageUrl) {
@@ -117037,14 +117121,15 @@ async function uploadEventImageForUser(uid, eventId, kind, cardId, source) {
117037
117121
  storagePath: uploaded.storagePath,
117038
117122
  masterUrl: uploaded.masterUrl,
117039
117123
  variantsPending: true,
117040
- sourceBytes: sourceBuffer.byteLength,
117124
+ sourceBytes,
117125
+ sourceVia,
117041
117126
  masterBytes: uploaded.masterBytes,
117042
117127
  imageWidth: uploaded.imageWidth,
117043
117128
  imageHeight: uploaded.imageHeight,
117044
117129
  note: "Image master uploaded. CF generateImageVariants should publish thumb/card/hero variants and update imageUrl/imageVariants in about 10-30 seconds.",
117045
117130
  storageDeletesAttempted: deletesAttempted,
117046
117131
  storageDeleteWarnings: deleteWarnings,
117047
- hints: [...remoteNotes, ...uploadHints]
117132
+ hints: [...sourceNotes, ...uploadHints]
117048
117133
  };
117049
117134
  }
117050
117135
 
@@ -117186,6 +117271,7 @@ var imageOutputSchema = external_exports.object({
117186
117271
  masterUrl: external_exports.string().url(),
117187
117272
  variantsPending: external_exports.boolean(),
117188
117273
  sourceBytes: external_exports.number().int().nonnegative(),
117274
+ sourceVia: external_exports.enum(["server", "client"]).optional(),
117189
117275
  masterBytes: external_exports.number().int().nonnegative(),
117190
117276
  imageWidth: external_exports.number().int().positive(),
117191
117277
  imageHeight: external_exports.number().int().positive(),
@@ -118033,6 +118119,25 @@ async function assertCanCreateInWorkspace(uid, workspaceId) {
118033
118119
  }
118034
118120
  }
118035
118121
  }
118122
+ function buildCreateWebinarDocument(input) {
118123
+ return {
118124
+ ownerId: input.uid,
118125
+ workspaceId: input.workspaceId,
118126
+ createdAt: input.createdAt,
118127
+ title: input.title.trim(),
118128
+ pinned: false,
118129
+ video: input.video,
118130
+ meta: {
118131
+ eventDate: input.eventDate,
118132
+ speakerName: input.speakerName,
118133
+ description: input.description
118134
+ },
118135
+ blocks: {
118136
+ chat: { enabled: input.chatEnabled, visibility: "always" },
118137
+ promoBanner: { enabled: false, text: "", link: "", visibility: "always" }
118138
+ }
118139
+ };
118140
+ }
118036
118141
  async function addEventWithRtdbMirror(eventData) {
118037
118142
  const eventRef = await addDoc(collection(firestore, "events"), eventData);
118038
118143
  try {
@@ -118317,23 +118422,17 @@ function registerEventTools(server2) {
118317
118422
  const workspaceId = args.workspaceId ?? uid;
118318
118423
  await assertCanCreateInWorkspace(uid, workspaceId);
118319
118424
  const video = normalizeVideo(args.videoUrl);
118320
- const docRef = await addEventWithRtdbMirror({
118321
- ownerId: uid,
118425
+ const docRef = await addEventWithRtdbMirror(buildCreateWebinarDocument({
118426
+ uid,
118322
118427
  workspaceId,
118323
- createdAt: serverTimestamp2(),
118324
- title: args.title.trim(),
118325
- pinned: false,
118428
+ title: args.title,
118326
118429
  video,
118327
- meta: {
118328
- eventDate: args.eventDate ? Timestamp.fromDate(new Date(args.eventDate)) : null,
118329
- speakerName: args.speakerName?.trim() || null,
118330
- description: args.description?.trim() || null
118331
- },
118332
- blocks: {
118333
- chat: { enabled: args.chatEnabled, visibility: "always" },
118334
- promoBanner: { enabled: false, text: "", link: "", visibility: "always" }
118335
- }
118336
- });
118430
+ eventDate: args.eventDate ? Timestamp.fromDate(new Date(args.eventDate)) : null,
118431
+ speakerName: args.speakerName?.trim() || null,
118432
+ description: args.description?.trim() || null,
118433
+ chatEnabled: args.chatEnabled,
118434
+ createdAt: serverTimestamp2()
118435
+ }));
118337
118436
  return textResult({
118338
118437
  id: docRef.id,
118339
118438
  ...eventLinks(docRef.id),
@@ -118643,10 +118742,10 @@ function registerImageTools(server2) {
118643
118742
  "eventId: event to edit.",
118644
118743
  "kind: promoBanner, videoPlaceholder, or card.",
118645
118744
  "cardId: required when kind=card; must match an existing blocks.cards.items id.",
118646
- "source: exactly one of local path or HTTPS image URL.",
118745
+ "source: exactly one of local path or public HTTPS image URL.",
118647
118746
  "confirm: must be true because the public event image may be replaced."
118648
118747
  ],
118649
- returns: "eventId, kind, imageId, Storage path, masterUrl, byte counts, and a note that CF-generated variants are pending.",
118748
+ returns: "eventId, kind, imageId, Storage path, masterUrl, byte counts, sourceVia (server or client fetched the URL), hints, and a note that CF-generated variants are pending.",
118650
118749
  examples: [
118651
118750
  'Use kind="promoBanner" with source.url to set a webinar banner from an image link.',
118652
118751
  'Use kind="card" with cardId after the card item already exists.'
@@ -118654,7 +118753,7 @@ function registerImageTools(server2) {
118654
118753
  notes: [
118655
118754
  "Uploading an image does not enable its block: toggle the block independently via oko_configure_blocks, in either order.",
118656
118755
  "The MCP server reads local paths on the organizer machine.",
118657
- "Remote URLs must be public HTTPS images, max 15MB; private/local hosts are blocked.",
118756
+ "A URL is fetched by the OKO server first (no VPN or proxy in the way), then from this machine if the server could not reach it; if both fail, the error names both reasons \u2014 ask the organizer to download the image and pass source.path. Public HTTPS only, max 15MB; private/local hosts are blocked.",
118658
118757
  "For card images, imageId is the cardId so OKO CF4 can update the correct item.",
118659
118758
  IMAGE_ADVICE.promoBanner,
118660
118759
  IMAGE_ADVICE.videoPlaceholder,
@@ -118979,26 +119078,28 @@ function platformRejectionResult(toolName, args, readOnly) {
118979
119078
  isError: true
118980
119079
  };
118981
119080
  }
118982
- var UPDATE_NOTICE_LIMIT = 3;
119081
+ var UPDATE_NOTICE_FULL_LIMIT = 3;
118983
119082
  var updateNoticesShown = 0;
119083
+ function updateNoticeText(status, shownSoFar) {
119084
+ if (shownSoFar < UPDATE_NOTICE_FULL_LIMIT) {
119085
+ return `UPDATE AVAILABLE: ${status.message} How to update: ${(status.howTo ?? []).join(" ")}`;
119086
+ }
119087
+ return `UPDATE AVAILABLE: OKO MCP ${status.current} \u2192 ${status.latest}. Restart the MCP client to install it (npx does it automatically); see oko_connection_status for the steps.`;
119088
+ }
118984
119089
  function withUpdateNotice(result) {
118985
- if (updateNoticesShown >= UPDATE_NOTICE_LIMIT)
118986
- return result;
118987
119090
  const status = updateStatus();
118988
119091
  if (!status.updateAvailable || !result || typeof result !== "object")
118989
119092
  return result;
118990
119093
  const typed = result;
118991
119094
  if (!Array.isArray(typed.content))
118992
119095
  return result;
119096
+ const text = updateNoticeText(status, updateNoticesShown);
118993
119097
  updateNoticesShown += 1;
118994
119098
  return {
118995
119099
  ...typed,
118996
119100
  content: [
118997
119101
  ...typed.content,
118998
- {
118999
- type: "text",
119000
- text: `UPDATE AVAILABLE: ${status.message} How to update: ${(status.howTo ?? []).join(" ")}`
119001
- }
119102
+ { type: "text", text }
119002
119103
  ]
119003
119104
  };
119004
119105
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@okostream/mcp",
3
- "version": "0.12.5",
3
+ "version": "0.12.7",
4
4
  "description": "Control OKO Stream webinars from Claude, Codex, and other MCP clients.",
5
5
  "license": "UNLICENSED",
6
6
  "author": {