@okostream/mcp 0.12.6 → 0.12.8

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 +12 -2
  2. package/dist/server.js +111 -41
  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,6 +97,15 @@ 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
 
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
+
99
109
  Image URLs are fetched by the OKO server first, so a VPN, proxy or captive
100
110
  portal on the organizer's machine is not in the way. If the server cannot
101
111
  reach the URL (a private host, a login wall, a timeout), the MCP client
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.6";
116227
+ var OKO_MCP_VERSION = "0.12.8";
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.");
@@ -116327,29 +116348,40 @@ async function exchangeApiKey() {
116327
116348
  latestVersion: typeof data.latestVersion === "string" && parseVersion(data.latestVersion) ? data.latestVersion.trim() : null
116328
116349
  };
116329
116350
  }
116330
- async function ensureAuth(requiredScopes = []) {
116351
+ function singleFlight(run) {
116352
+ let inFlight = null;
116353
+ return () => {
116354
+ inFlight ??= run().finally(() => {
116355
+ inFlight = null;
116356
+ });
116357
+ return inFlight;
116358
+ };
116359
+ }
116360
+ var refreshSession = singleFlight(async () => {
116331
116361
  const now = Date.now();
116332
- if (!state || now > state.expiresAt - 5 * 60 * 1e3 || !auth.currentUser) {
116333
- const exchanged = await exchangeApiKey();
116334
- try {
116335
- await signInWithCustomToken(auth, exchanged.customToken);
116336
- } catch {
116337
- throw new OkoAuthError("unavailable", "OKO MCP received an organizer session but could not initialize it. Reconnect and try again.");
116338
- }
116339
- state = {
116340
- uid: exchanged.organizerUid,
116341
- scopes: exchanged.scopes,
116342
- expiresAt: now + exchanged.expiresIn * 1e3
116343
- };
116344
- if (exchanged.latestVersion)
116345
- latestPublishedVersion = exchanged.latestVersion;
116362
+ const exchanged = await exchangeApiKey();
116363
+ try {
116364
+ await signInWithCustomToken(auth, exchanged.customToken);
116365
+ } catch {
116366
+ throw new OkoAuthError("unavailable", "OKO MCP received an organizer session but could not initialize it. Reconnect and try again.");
116346
116367
  }
116368
+ state = {
116369
+ uid: exchanged.organizerUid,
116370
+ scopes: exchanged.scopes,
116371
+ expiresAt: now + exchanged.expiresIn * 1e3
116372
+ };
116373
+ if (exchanged.latestVersion)
116374
+ recordLatestPublishedVersion(exchanged.latestVersion);
116375
+ return state;
116376
+ });
116377
+ async function ensureAuth(requiredScopes = []) {
116378
+ const current = state && Date.now() <= state.expiresAt - 5 * 60 * 1e3 && auth.currentUser ? state : await refreshSession();
116347
116379
  for (const scope of requiredScopes) {
116348
- if (!state.scopes.includes(scope)) {
116380
+ if (!current.scopes.includes(scope)) {
116349
116381
  throw new OkoScopeError(scope);
116350
116382
  }
116351
116383
  }
116352
- return state;
116384
+ return current;
116353
116385
  }
116354
116386
  function currentScopes() {
116355
116387
  return state?.scopes ?? [];
@@ -116366,16 +116398,21 @@ async function getConnectionStatus() {
116366
116398
  });
116367
116399
  } catch (error3) {
116368
116400
  if (error3 instanceof OkoAuthError) {
116369
- return buildConnectionStatus(error3.status, { minimumVersion: error3.minimumVersion });
116401
+ return buildConnectionStatus(error3.status, authErrorOptions(error3));
116370
116402
  }
116371
116403
  return buildConnectionStatus("unavailable");
116372
116404
  }
116373
116405
  }
116406
+ function authErrorOptions(error3) {
116407
+ return {
116408
+ minimumVersion: error3.minimumVersion,
116409
+ latestVersion: error3.latestVersion,
116410
+ serverMessage: error3.serverMessage
116411
+ };
116412
+ }
116374
116413
  function authErrorResult(error3) {
116375
116414
  return {
116376
- ...textResult(buildConnectionStatus(error3.status, {
116377
- minimumVersion: error3.minimumVersion
116378
- })),
116415
+ ...textResult(buildConnectionStatus(error3.status, authErrorOptions(error3))),
116379
116416
  isError: true
116380
116417
  };
116381
116418
  }
@@ -116639,6 +116676,27 @@ async function responseBufferWithLimit(response, maxBytes) {
116639
116676
  }
116640
116677
  return Buffer.concat(chunks, total);
116641
116678
  }
116679
+ function shareLinkHint(url) {
116680
+ let host = "";
116681
+ try {
116682
+ host = new URL(url).hostname.toLowerCase();
116683
+ } catch {
116684
+ return "";
116685
+ }
116686
+ if (host === "drive.google.com" || host === "docs.google.com") {
116687
+ return " This is a Google Drive page, not the file: share it as 'anyone with the link' and use https://drive.google.com/uc?export=download&id=<fileId>, or download the file and pass source.path.";
116688
+ }
116689
+ if (host.endsWith("disk.yandex.ru") || host.endsWith("disk.yandex.com") || host === "yadi.sk" || host.endsWith("disk.360.yandex.ru")) {
116690
+ return " This is a Yandex Disk page, not the file: use the direct download link from the page (\u0421\u043A\u0430\u0447\u0430\u0442\u044C \u2192 copy link), or download the file and pass source.path.";
116691
+ }
116692
+ if (host === "dropbox.com" || host.endsWith(".dropbox.com")) {
116693
+ return " This is a Dropbox page: replace ?dl=0 with ?dl=1 in the link, or download the file and pass source.path.";
116694
+ }
116695
+ if (host.endsWith("onedrive.live.com") || host === "1drv.ms" || host.endsWith("sharepoint.com")) {
116696
+ return " This is a OneDrive page: use its direct download link, or download the file and pass source.path.";
116697
+ }
116698
+ return " Use a direct link to the image file (right-click the image \u2192 copy image address), or download the file and pass source.path.";
116699
+ }
116642
116700
  async function readRemoteImage(rawUrl, redirects = 0) {
116643
116701
  if (redirects > REMOTE_IMAGE_MAX_REDIRECTS) {
116644
116702
  throw new Error("Image URL has too many redirects.");
@@ -116671,7 +116729,7 @@ async function readRemoteImage(rawUrl, redirects = 0) {
116671
116729
  }
116672
116730
  const contentType = (response.headers.get("content-type") ?? "").split(";")[0]?.trim().toLowerCase();
116673
116731
  if (!contentType || !contentType.startsWith("image/")) {
116674
- throw new Error("Image URL must return Content-Type image/*.");
116732
+ throw new Error(`Image URL must return Content-Type image/* (got ${contentType || "none"}).${shareLinkHint(rawUrl)}`);
116675
116733
  }
116676
116734
  if (!ALLOWED_REMOTE_IMAGE_TYPES.has(contentType)) {
116677
116735
  throw new Error(`Image URL Content-Type ${contentType} is not supported. Use JPEG, PNG, WEBP, GIF, or AVIF.`);
@@ -116734,6 +116792,12 @@ function serverFetchRefusal(error3) {
116734
116792
  if (code === "functions/invalid-argument" && message.includes("Image payload is empty or too large")) {
116735
116793
  return { reason: "server_without_url_support", message: "The OKO server release in use does not fetch URLs yet." };
116736
116794
  }
116795
+ if (code === "functions/deadline-exceeded") {
116796
+ return { reason: "server_timeout", message: "The OKO server did not finish fetching the URL within its time limit." };
116797
+ }
116798
+ if (code === "functions/internal") {
116799
+ return { reason: "server_error", message: message && message !== "internal" ? message : "The OKO server failed while fetching the URL." };
116800
+ }
116737
116801
  return null;
116738
116802
  }
116739
116803
  var SERVER_FETCH_NOTE = "Image fetched by the OKO server directly from the URL; this machine's network was not involved.";
@@ -118332,7 +118396,10 @@ function registerEventTools(server2) {
118332
118396
  summary: "Read one OKO event visible to the organizer.",
118333
118397
  args: ["eventId: OKO event document id."],
118334
118398
  returns: "full event document fields plus id and public event URL.",
118335
- examples: ["Use after oko_create_webinar to verify setup and return the event URL to the user."]
118399
+ examples: ["Use after oko_create_webinar to verify setup and return the event URL to the user."],
118400
+ notes: [
118401
+ "blocks.chat.mode is a legacy field written by older cabinet versions and read by nothing; chat visibility is blocks.chat.visibility (always | hidden). Ignore mode."
118402
+ ]
118336
118403
  }),
118337
118404
  inputSchema: { eventId: eventIdField },
118338
118405
  outputSchema: eventOutputSchema,
@@ -119052,26 +119119,28 @@ function platformRejectionResult(toolName, args, readOnly) {
119052
119119
  isError: true
119053
119120
  };
119054
119121
  }
119055
- var UPDATE_NOTICE_LIMIT = 3;
119122
+ var UPDATE_NOTICE_FULL_LIMIT = 3;
119056
119123
  var updateNoticesShown = 0;
119124
+ function updateNoticeText(status, shownSoFar) {
119125
+ if (shownSoFar < UPDATE_NOTICE_FULL_LIMIT) {
119126
+ return `UPDATE AVAILABLE: ${status.message} How to update: ${(status.howTo ?? []).join(" ")}`;
119127
+ }
119128
+ 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.`;
119129
+ }
119057
119130
  function withUpdateNotice(result) {
119058
- if (updateNoticesShown >= UPDATE_NOTICE_LIMIT)
119059
- return result;
119060
119131
  const status = updateStatus();
119061
119132
  if (!status.updateAvailable || !result || typeof result !== "object")
119062
119133
  return result;
119063
119134
  const typed = result;
119064
119135
  if (!Array.isArray(typed.content))
119065
119136
  return result;
119137
+ const text = updateNoticeText(status, updateNoticesShown);
119066
119138
  updateNoticesShown += 1;
119067
119139
  return {
119068
119140
  ...typed,
119069
119141
  content: [
119070
119142
  ...typed.content,
119071
- {
119072
- type: "text",
119073
- text: `UPDATE AVAILABLE: ${status.message} How to update: ${(status.howTo ?? []).join(" ")}`
119074
- }
119143
+ { type: "text", text }
119075
119144
  ]
119076
119145
  };
119077
119146
  }
@@ -120446,7 +120515,7 @@ function registerWorkspaceTools(server2) {
120446
120515
  description: toolDescription({
120447
120516
  summary: "Accept a workspace invite code for the authenticated organizer.",
120448
120517
  args: ["code: invite code from /invite/{code}.", "confirm: must be true."],
120449
- returns: "success plus workspace id/name returned by OKO CF19.",
120518
+ returns: "success, workspace id/name, and keptExistingPermissions: true when the organizer was already a member \u2014 an invite never changes existing permissions; the owner changes them with oko_update_workspace_member.",
120450
120519
  examples: ["Use when the organizer wants to join a workspace from an invite code."]
120451
120520
  }),
120452
120521
  inputSchema: {
@@ -120455,6 +120524,7 @@ function registerWorkspaceTools(server2) {
120455
120524
  },
120456
120525
  outputSchema: external_exports.object({
120457
120526
  success: external_exports.boolean(),
120527
+ keptExistingPermissions: external_exports.boolean().optional(),
120458
120528
  workspace: external_exports.object({ id: external_exports.string(), name: external_exports.string() }).passthrough()
120459
120529
  }).passthrough(),
120460
120530
  annotations: writeTool
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@okostream/mcp",
3
- "version": "0.12.6",
3
+ "version": "0.12.8",
4
4
  "description": "Control OKO Stream webinars from Claude, Codex, and other MCP clients.",
5
5
  "license": "UNLICENSED",
6
6
  "author": {