@okostream/mcp 0.12.9 → 0.12.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/server.js +125 -37
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -70499,6 +70499,13 @@ var OKO_CONNECTION_STATUSES = [
|
|
|
70499
70499
|
"connected",
|
|
70500
70500
|
"not_configured",
|
|
70501
70501
|
"invalid_key",
|
|
70502
|
+
// 🔴 Next to `invalid_key`, not instead of it (A7, `LR3a-2`). The key is
|
|
70503
|
+
// valid and the account behind it is switched off or blocked — the one state
|
|
70504
|
+
// in which "create a fresh key" is not merely unhelpful but impossible, and
|
|
70505
|
+
// the state in which the platform used to give exactly that advice.
|
|
70506
|
+
// A genuinely revoked key still needs the create-a-key step, so the two
|
|
70507
|
+
// states are separate codes with separate steps.
|
|
70508
|
+
"account_inactive",
|
|
70502
70509
|
"update_required",
|
|
70503
70510
|
"rate_limited",
|
|
70504
70511
|
"unavailable"
|
|
@@ -70539,6 +70546,18 @@ function nextSteps(status, minimumVersion, latestVersion) {
|
|
|
70539
70546
|
label: "Restart or reconnect the MCP server after updating the key."
|
|
70540
70547
|
}
|
|
70541
70548
|
];
|
|
70549
|
+
case "account_inactive":
|
|
70550
|
+
return [
|
|
70551
|
+
{
|
|
70552
|
+
id: "contact_support",
|
|
70553
|
+
label: "Ask OKO support to restore organizer access for this account: the key is valid, the account is switched off or blocked. Creating another key will not help \u2014 key creation needs an active account too.",
|
|
70554
|
+
url: OKO_ONBOARDING_URLS.website
|
|
70555
|
+
},
|
|
70556
|
+
{
|
|
70557
|
+
id: "restart_client",
|
|
70558
|
+
label: "Restart or reconnect the MCP server once access is restored."
|
|
70559
|
+
}
|
|
70560
|
+
];
|
|
70542
70561
|
case "update_required":
|
|
70543
70562
|
return [
|
|
70544
70563
|
{
|
|
@@ -70566,7 +70585,7 @@ function nextSteps(status, minimumVersion, latestVersion) {
|
|
|
70566
70585
|
function buildConnectionStatus(status, options2 = {}) {
|
|
70567
70586
|
const connected = status === "connected";
|
|
70568
70587
|
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" ? 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.";
|
|
70588
|
+
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 === "account_inactive" ? options2.serverMessage ?? "The OKO API key is valid, but the organizer account behind it is switched off or blocked." : 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
70589
|
return {
|
|
70571
70590
|
connected,
|
|
70572
70591
|
status,
|
|
@@ -116272,7 +116291,7 @@ function sanitizeScopes(scopes) {
|
|
|
116272
116291
|
}
|
|
116273
116292
|
|
|
116274
116293
|
// build/version.js
|
|
116275
|
-
var OKO_MCP_VERSION = "0.12.
|
|
116294
|
+
var OKO_MCP_VERSION = "0.12.11";
|
|
116276
116295
|
|
|
116277
116296
|
// build/auth.js
|
|
116278
116297
|
var state = null;
|
|
@@ -116345,6 +116364,17 @@ var OkoAuthError = class extends Error {
|
|
|
116345
116364
|
function versionField(value) {
|
|
116346
116365
|
return typeof value === "string" && parseVersion(value) ? value.trim() : void 0;
|
|
116347
116366
|
}
|
|
116367
|
+
function inactiveOrganizerError(body) {
|
|
116368
|
+
const parsed = body && typeof body === "object" ? body : {};
|
|
116369
|
+
if (parsed.error !== "organizer_inactive")
|
|
116370
|
+
return null;
|
|
116371
|
+
const serverMessage = typeof parsed.message === "string" && parsed.message.trim() ? parsed.message.trim() : void 0;
|
|
116372
|
+
return new OkoAuthError("account_inactive", serverMessage ?? "The OKO API key is valid, but the organizer account behind it is switched off or blocked. Creating another key will not help \u2014 contact OKO support to restore access.", { serverMessage });
|
|
116373
|
+
}
|
|
116374
|
+
function serverRefusalNote(body) {
|
|
116375
|
+
const parsed = body && typeof body === "object" ? body : {};
|
|
116376
|
+
return typeof parsed.message === "string" && parsed.message.trim() ? parsed.message.trim() : void 0;
|
|
116377
|
+
}
|
|
116348
116378
|
function upgradeRequiredError(body) {
|
|
116349
116379
|
const parsed = body && typeof body === "object" ? body : {};
|
|
116350
116380
|
const minimumVersion = versionField(parsed.minimumVersion);
|
|
@@ -116379,7 +116409,16 @@ async function exchangeApiKey() {
|
|
|
116379
116409
|
if (status === 429) {
|
|
116380
116410
|
throw new OkoAuthError("rate_limited", "OKO API key exchange is temporarily rate-limited. Wait one minute, then restart the MCP client.");
|
|
116381
116411
|
}
|
|
116382
|
-
|
|
116412
|
+
if (status === 401) {
|
|
116413
|
+
const inactive = inactiveOrganizerError(errorBody);
|
|
116414
|
+
if (inactive)
|
|
116415
|
+
throw inactive;
|
|
116416
|
+
}
|
|
116417
|
+
throw status === 401 ? new OkoAuthError("invalid_key", "OKO API key is invalid or revoked. Create a fresh key in the OKO MCP install center and replace OKO_API_KEY.") : new OkoAuthError("unavailable", [
|
|
116418
|
+
`OKO organizer authentication failed with HTTP ${status}.`,
|
|
116419
|
+
serverRefusalNote(errorBody),
|
|
116420
|
+
"Check your connection and try again."
|
|
116421
|
+
].filter(Boolean).join(" "));
|
|
116383
116422
|
}
|
|
116384
116423
|
const data = await response.json();
|
|
116385
116424
|
if (typeof data.customToken !== "string") {
|
|
@@ -116611,17 +116650,19 @@ async function readEventAccess(eventId, uid, mode) {
|
|
|
116611
116650
|
return null;
|
|
116612
116651
|
})
|
|
116613
116652
|
]);
|
|
116614
|
-
|
|
116615
|
-
|
|
116616
|
-
|
|
116617
|
-
const allowed = grant === null ? legacyEventAllowsMode(data, uid, mode) : data.ownerId === uid || grant.exists() && grantAllowsMode(grant.data(), mode);
|
|
116618
|
-
return { data, allowed };
|
|
116653
|
+
const data = snap.data() ?? {};
|
|
116654
|
+
const holds = (wanted) => grant === null ? legacyEventAllowsMode(data, uid, wanted) : data.ownerId === uid || grant.exists() && grantAllowsMode(grant.data(), wanted);
|
|
116655
|
+
return { data, exists: snap.exists(), visible: holds("read"), allowed: holds(mode) };
|
|
116619
116656
|
}
|
|
116620
116657
|
async function eventGrantsMode(eventId, uid, mode) {
|
|
116621
116658
|
return (await readEventAccess(eventId, uid, mode)).allowed;
|
|
116622
116659
|
}
|
|
116660
|
+
var EVENT_NOT_FOUND_MESSAGE = "Event not found.";
|
|
116623
116661
|
async function readEvent(eventId, uid, mode = "read") {
|
|
116624
|
-
const { data, allowed } = await readEventAccess(eventId, uid, mode);
|
|
116662
|
+
const { data, exists, visible, allowed } = await readEventAccess(eventId, uid, mode);
|
|
116663
|
+
if (!visible || !exists) {
|
|
116664
|
+
throw new Error(EVENT_NOT_FOUND_MESSAGE);
|
|
116665
|
+
}
|
|
116625
116666
|
if (!allowed) {
|
|
116626
116667
|
throw new Error(`Event does not grant ${mode} access to this organizer.`);
|
|
116627
116668
|
}
|
|
@@ -117154,16 +117195,43 @@ async function updateCardImageInTransaction(eventId, cardId, patch) {
|
|
|
117154
117195
|
});
|
|
117155
117196
|
});
|
|
117156
117197
|
}
|
|
117157
|
-
|
|
117198
|
+
function descendPath(root, segments) {
|
|
117199
|
+
let current = root;
|
|
117200
|
+
for (const segment of segments) {
|
|
117201
|
+
if (current === null || typeof current !== "object")
|
|
117202
|
+
return void 0;
|
|
117203
|
+
current = current[segment];
|
|
117204
|
+
}
|
|
117205
|
+
return current;
|
|
117206
|
+
}
|
|
117207
|
+
function valueAfterPatch(fresh, patch, path) {
|
|
117208
|
+
if (Object.prototype.hasOwnProperty.call(patch, path))
|
|
117209
|
+
return patch[path];
|
|
117210
|
+
const segments = path.split(".");
|
|
117211
|
+
for (let cut = segments.length - 1; cut > 0; cut--) {
|
|
117212
|
+
const ancestor = segments.slice(0, cut).join(".");
|
|
117213
|
+
if (!Object.prototype.hasOwnProperty.call(patch, ancestor))
|
|
117214
|
+
continue;
|
|
117215
|
+
return descendPath(patch[ancestor], segments.slice(cut));
|
|
117216
|
+
}
|
|
117217
|
+
return descendPath(fresh, segments);
|
|
117218
|
+
}
|
|
117219
|
+
function scheduleMissesDate(sessionMode, eventDate) {
|
|
117220
|
+
return sessionMode === "schedule" && !eventDate;
|
|
117221
|
+
}
|
|
117222
|
+
async function writeEventPatchWithCards(eventId, basePatch, buildItems, validate = null) {
|
|
117158
117223
|
const eventRef = doc(firestore, "events", eventId);
|
|
117159
117224
|
await runTransaction2(firestore, async (transaction) => {
|
|
117160
117225
|
const snap = await transaction.get(eventRef);
|
|
117161
117226
|
if (!snap.exists())
|
|
117162
117227
|
throw new Error("Event not found.");
|
|
117228
|
+
const fresh = snap.data();
|
|
117163
117229
|
const patch = { ...basePatch };
|
|
117164
117230
|
if (buildItems) {
|
|
117165
|
-
patch["blocks.cards.items"] = buildItems(
|
|
117231
|
+
patch["blocks.cards.items"] = buildItems(fresh);
|
|
117166
117232
|
}
|
|
117233
|
+
if (validate)
|
|
117234
|
+
validate(fresh, patch);
|
|
117167
117235
|
if (Object.keys(patch).length === 0)
|
|
117168
117236
|
return;
|
|
117169
117237
|
transaction.update(eventRef, patch);
|
|
@@ -118384,6 +118452,9 @@ function validateEventSetup(eventId, event) {
|
|
|
118384
118452
|
} else if (eventDateMs < Date.now() - 60 * 60 * 1e3) {
|
|
118385
118453
|
add(warnings, "date_in_past", "meta.eventDate", "Configured webinar date appears to be in the past.");
|
|
118386
118454
|
}
|
|
118455
|
+
if (scheduleMissesDate(event.analyticsSessionMode, meta2.eventDate)) {
|
|
118456
|
+
add(errors, "schedule_missing_date", "analyticsSessionMode", "Session mode is 'schedule' but meta.eventDate is empty, so OKO can never start or end the session automatically and the reminder is never sent. Set eventDate, or switch sessionMode to 'manual' \u2014 oko_update_event accepts both fields in one call.");
|
|
118457
|
+
}
|
|
118387
118458
|
const duplicateFormIds = /* @__PURE__ */ new Set();
|
|
118388
118459
|
const seenFormIds = /* @__PURE__ */ new Set();
|
|
118389
118460
|
forms.forEach((form, index) => {
|
|
@@ -118499,6 +118570,24 @@ function telegramBlocksPatch(args) {
|
|
|
118499
118570
|
"telegram.channelIds": args.telegramChannelIds
|
|
118500
118571
|
};
|
|
118501
118572
|
}
|
|
118573
|
+
function assertScheduleHasDate(fresh, patch) {
|
|
118574
|
+
const missing = scheduleMissesDate(valueAfterPatch(fresh, patch, "analyticsSessionMode"), valueAfterPatch(fresh, patch, "meta.eventDate"));
|
|
118575
|
+
if (!missing)
|
|
118576
|
+
return;
|
|
118577
|
+
throw new Error("sessionMode 'schedule' requires meta.eventDate: without a date OKO can never start the session automatically. Set eventDate in the same call, or switch sessionMode to 'manual' in the same call to clear the date.");
|
|
118578
|
+
}
|
|
118579
|
+
function assertFormBindingsExist(fresh, patch) {
|
|
118580
|
+
const formsAfterPatch = valueAfterPatch(fresh, patch, "forms");
|
|
118581
|
+
const knownFormIds = new Set((Array.isArray(formsAfterPatch) ? formsAfterPatch : []).map((form) => typeof form?.id === "string" ? form.id : null).filter((id) => Boolean(id)));
|
|
118582
|
+
for (const field2 of ["blocks.promoBanner.formId", "blocks.primaryCta.formId"]) {
|
|
118583
|
+
if (!Object.prototype.hasOwnProperty.call(patch, field2))
|
|
118584
|
+
continue;
|
|
118585
|
+
const value = patch[field2];
|
|
118586
|
+
if (typeof value === "string" && value && !knownFormIds.has(value)) {
|
|
118587
|
+
throw new Error(`${field2}: form "${value}" not found in event.forms. Create the form in the same call (or beforehand) \u2014 a button bound to a missing form does nothing for viewers.`);
|
|
118588
|
+
}
|
|
118589
|
+
}
|
|
118590
|
+
}
|
|
118502
118591
|
function configureBlocksScopes(args) {
|
|
118503
118592
|
return Object.keys(telegramBlocksPatch(args)).length > 0 ? ["setup:write", "telegram:write"] : ["setup:write"];
|
|
118504
118593
|
}
|
|
@@ -118678,7 +118767,8 @@ function registerEventTools(server2) {
|
|
|
118678
118767
|
returns: "event id and the list of Firestore fields updated.",
|
|
118679
118768
|
examples: ["Use for title/date/speaker/video edits; use oko_configure_blocks for blocks and forms."],
|
|
118680
118769
|
notes: [
|
|
118681
|
-
"sessionMode 'schedule' makes OKO start and end the analytics session automatically at eventDate for durationMinutes; 'manual' keeps the Start/End buttons and a Telegram reminder 10 minutes before eventDate. This is still an analytics session, not an event status."
|
|
118770
|
+
"sessionMode 'schedule' makes OKO start and end the analytics session automatically at eventDate for durationMinutes; 'manual' keeps the Start/End buttons and a Telegram reminder 10 minutes before eventDate. This is still an analytics session, not an event status.",
|
|
118771
|
+
"sessionMode 'schedule' and eventDate travel together: setting the mode without a date, or clearing the date while the mode stays, is refused. Change both in one call."
|
|
118682
118772
|
]
|
|
118683
118773
|
}),
|
|
118684
118774
|
inputSchema: {
|
|
@@ -118704,13 +118794,7 @@ function registerEventTools(server2) {
|
|
|
118704
118794
|
if (requiredScopes.size === 0)
|
|
118705
118795
|
throw new Error("Provide at least one field to update.");
|
|
118706
118796
|
const { uid } = await authed([...requiredScopes]);
|
|
118707
|
-
|
|
118708
|
-
if (args.sessionMode === "schedule") {
|
|
118709
|
-
const dateAfterPatch = args.eventDate !== void 0 ? args.eventDate : current.meta?.eventDate ?? null;
|
|
118710
|
-
if (!dateAfterPatch) {
|
|
118711
|
-
throw new Error("sessionMode 'schedule' requires meta.eventDate: without a date OKO can never start the session automatically. Set eventDate in the same call, or keep sessionMode 'manual'.");
|
|
118712
|
-
}
|
|
118713
|
-
}
|
|
118797
|
+
await readEvent(args.eventId, uid, "edit");
|
|
118714
118798
|
const patch = {};
|
|
118715
118799
|
if (args.title !== void 0)
|
|
118716
118800
|
patch.title = args.title.trim();
|
|
@@ -118735,7 +118819,7 @@ function registerEventTools(server2) {
|
|
|
118735
118819
|
patch.analyticsSessionMode = args.sessionMode;
|
|
118736
118820
|
if (args.durationMinutes !== void 0)
|
|
118737
118821
|
patch["meta.durationMinutes"] = args.durationMinutes;
|
|
118738
|
-
await
|
|
118822
|
+
await writeEventPatchWithCards(args.eventId, patch, null, assertScheduleHasDate);
|
|
118739
118823
|
return textResult({ id: args.eventId, updated: Object.keys(patch) });
|
|
118740
118824
|
});
|
|
118741
118825
|
server2.registerTool("oko_configure_blocks", {
|
|
@@ -118833,17 +118917,7 @@ function registerEventTools(server2) {
|
|
|
118833
118917
|
}))
|
|
118834
118918
|
}));
|
|
118835
118919
|
Object.assign(patch, telegramPatch);
|
|
118836
|
-
|
|
118837
|
-
const knownFormIds = new Set((Array.isArray(formsAfterPatch) ? formsAfterPatch : []).map((form) => typeof form?.id === "string" ? form.id : null).filter((id) => Boolean(id)));
|
|
118838
|
-
for (const [field2, value] of [
|
|
118839
|
-
["blocks.promoBanner.formId", patch["blocks.promoBanner.formId"]],
|
|
118840
|
-
["blocks.primaryCta.formId", patch["blocks.primaryCta.formId"]]
|
|
118841
|
-
]) {
|
|
118842
|
-
if (typeof value === "string" && value && !knownFormIds.has(value)) {
|
|
118843
|
-
throw new Error(`${field2}: form "${value}" not found in event.forms. Create the form in the same call (or beforehand) \u2014 a button bound to a missing form does nothing for viewers.`);
|
|
118844
|
-
}
|
|
118845
|
-
}
|
|
118846
|
-
await updateDoc(doc(firestore, "events", args.eventId), patch);
|
|
118920
|
+
await writeEventPatchWithCards(args.eventId, patch, null, assertFormBindingsExist);
|
|
118847
118921
|
const hints = [];
|
|
118848
118922
|
if (args.chat?.visibility !== void 0) {
|
|
118849
118923
|
hints.push("blocks.chat.visibility was stored but the event page does not read it: the chat panel exists exactly when blocks.chat.enabled is true, and sending is opened or closed live with oko_set_chat_mode. Drop the field.");
|
|
@@ -119676,14 +119750,18 @@ function registerLifecycleTools(server2) {
|
|
|
119676
119750
|
}
|
|
119677
119751
|
|
|
119678
119752
|
// build/tools/live.js
|
|
119753
|
+
var CHAT_LIVE_SETTINGS = ["mode", "slowModeMs", "automodEnabled", "allowViewerLinks"];
|
|
119679
119754
|
function registerLiveTools(server2) {
|
|
119680
119755
|
server2.registerTool("oko_get_live_state", {
|
|
119681
119756
|
title: "Get live state",
|
|
119682
119757
|
description: toolDescription({
|
|
119683
119758
|
summary: "Read main RTDB live controls, aggregate analytics, and chat settings for an event.",
|
|
119684
119759
|
args: ["eventId: event to inspect."],
|
|
119685
|
-
returns: "eventId, agg metrics, ctrl live overrides, and chat settings.",
|
|
119686
|
-
examples: ["Use during a webinar before changing live block visibility or starting/ending sessions."]
|
|
119760
|
+
returns: "eventId, agg metrics, ctrl live overrides, and chat settings (mode, slowModeMs, automodEnabled, allowViewerLinks).",
|
|
119761
|
+
examples: ["Use during a webinar before changing live block visibility or starting/ending sessions."],
|
|
119762
|
+
notes: [
|
|
119763
|
+
"Live state does not carry the event team: to see who has access to an event and with which permissions, use oko_list_workspace_access on the event's workspace."
|
|
119764
|
+
]
|
|
119687
119765
|
}),
|
|
119688
119766
|
inputSchema: { eventId: eventIdField },
|
|
119689
119767
|
outputSchema: liveStateOutputSchema,
|
|
@@ -119691,12 +119769,18 @@ function registerLiveTools(server2) {
|
|
|
119691
119769
|
}, async ({ eventId }) => {
|
|
119692
119770
|
const { uid } = await authed(["analytics:read"]);
|
|
119693
119771
|
await readEvent(eventId, uid, "analytics");
|
|
119694
|
-
const [aggSnap, ctrlSnap,
|
|
119772
|
+
const [aggSnap, ctrlSnap, ...chatSnaps] = await Promise.all([
|
|
119695
119773
|
get(ref(rtdb, `events/${eventId}/agg`)),
|
|
119696
119774
|
get(ref(rtdb, `events/${eventId}/ctrl`)),
|
|
119697
|
-
get(ref(chatRtdb, `settings/${eventId}`))
|
|
119775
|
+
...CHAT_LIVE_SETTINGS.map((field2) => get(ref(chatRtdb, `settings/${eventId}/${field2}`)))
|
|
119698
119776
|
]);
|
|
119699
|
-
|
|
119777
|
+
const chat = {};
|
|
119778
|
+
CHAT_LIVE_SETTINGS.forEach((field2, index) => {
|
|
119779
|
+
const value = chatSnaps[index]?.val();
|
|
119780
|
+
if (value !== null && value !== void 0)
|
|
119781
|
+
chat[field2] = value;
|
|
119782
|
+
});
|
|
119783
|
+
return textResult({ eventId, agg: aggSnap.val() ?? {}, ctrl: ctrlSnap.val() ?? {}, chat });
|
|
119700
119784
|
});
|
|
119701
119785
|
server2.registerTool("oko_set_block_visibility", {
|
|
119702
119786
|
title: "Set live block visibility",
|
|
@@ -120831,7 +120915,11 @@ function registerWorkspaceTools(server2) {
|
|
|
120831
120915
|
"workspaceId: workspace owned by the authenticated organizer.",
|
|
120832
120916
|
"code: optional invite code, 16-64 chars; omitted means generated (recommended).",
|
|
120833
120917
|
"email: optional intended recipient email.",
|
|
120834
|
-
|
|
120918
|
+
// The schema is `workspacePermissionsSchema`: nine booleans, all
|
|
120919
|
+
// required, no extra keys. "optional whitelist" read as "send the
|
|
120920
|
+
// ones you want", and every such call was rejected by validation
|
|
120921
|
+
// before it reached the platform.
|
|
120922
|
+
"permissions: required object of all nine workspace permission booleans (createEvents, editSetup, deleteEvents, controlLive, moderateChat, startSession, viewAnalytics, downloadReports, sendNotifications) \u2014 every key must be present, extra keys are rejected; set false for the ones the invitee should not get.",
|
|
120835
120923
|
"confirm: must be true."
|
|
120836
120924
|
],
|
|
120837
120925
|
returns: "workspaceId, invite code, inviteUrl, and created=true.",
|