@okostream/mcp 0.13.1 → 0.14.0
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 +47 -12
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -116230,6 +116230,15 @@ if (!env.firebase.chatDatabaseURL) {
|
|
|
116230
116230
|
throw new Error("OKO MCP: chat database URL is not configured. Set OKO_FIREBASE_CHAT_DB_URL (or keep the bundled default) \u2014 chat paths do not exist in the main database.");
|
|
116231
116231
|
}
|
|
116232
116232
|
var chatRtdb = getDatabase(firebaseApp, env.firebase.chatDatabaseURL);
|
|
116233
|
+
async function serverNowMs(db = chatRtdb) {
|
|
116234
|
+
const offset = await new Promise((resolve3) => {
|
|
116235
|
+
onValue(ref(db, ".info/serverTimeOffset"), (snap) => {
|
|
116236
|
+
const v = snap.val();
|
|
116237
|
+
resolve3(typeof v === "number" && Number.isFinite(v) ? v : 0);
|
|
116238
|
+
}, () => resolve3(0), { onlyOnce: true });
|
|
116239
|
+
});
|
|
116240
|
+
return Date.now() + offset;
|
|
116241
|
+
}
|
|
116233
116242
|
var functions = getFunctions(firebaseApp, "us-central1");
|
|
116234
116243
|
|
|
116235
116244
|
// build/scopes.js
|
|
@@ -116291,7 +116300,7 @@ function sanitizeScopes(scopes) {
|
|
|
116291
116300
|
}
|
|
116292
116301
|
|
|
116293
116302
|
// build/version.js
|
|
116294
|
-
var OKO_MCP_VERSION = "0.
|
|
116303
|
+
var OKO_MCP_VERSION = "0.14.0";
|
|
116295
116304
|
|
|
116296
116305
|
// build/auth.js
|
|
116297
116306
|
var state = null;
|
|
@@ -117601,13 +117610,24 @@ var notificationsMarkOutputSchema = external_exports.object({
|
|
|
117601
117610
|
markedRead: external_exports.number().int().nonnegative(),
|
|
117602
117611
|
notificationIds: external_exports.array(external_exports.string())
|
|
117603
117612
|
}).passthrough();
|
|
117613
|
+
var FORM_FIELD_TYPES = ["text", "tel", "email", "textarea", "select", "checkbox"];
|
|
117604
117614
|
var formFieldSchema = external_exports.object({
|
|
117605
117615
|
id: external_exports.string().min(1).optional(),
|
|
117606
117616
|
label: external_exports.string().max(120),
|
|
117607
|
-
type: external_exports.enum(
|
|
117617
|
+
type: external_exports.enum(FORM_FIELD_TYPES).default("text").describe("text | tel | email | textarea answer with free text; select (one answer) and checkbox (several) answer with one of `options`."),
|
|
117608
117618
|
required: external_exports.boolean().default(false),
|
|
117609
|
-
placeholder: external_exports.string().max(160).nullable().optional()
|
|
117610
|
-
|
|
117619
|
+
placeholder: external_exports.string().max(160).nullable().optional(),
|
|
117620
|
+
options: external_exports.array(external_exports.string().trim().min(1).max(80)).max(20).optional().describe("Choices of a select or checkbox field: 1\u201320 unique option texts. The lead stores the option text itself \u2014 a string for select, an array of strings for checkbox.")
|
|
117621
|
+
}).strict().superRefine((field2, ctx) => {
|
|
117622
|
+
const choice = field2.type === "select" || field2.type === "checkbox";
|
|
117623
|
+
const count = new Set(field2.options ?? []).size;
|
|
117624
|
+
if (choice && count === 0) {
|
|
117625
|
+
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["options"], message: `A ${field2.type} field needs at least one option.` });
|
|
117626
|
+
}
|
|
117627
|
+
if (!choice && field2.options !== void 0) {
|
|
117628
|
+
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["options"], message: `options belong to select or checkbox fields only; this field is ${field2.type}.` });
|
|
117629
|
+
}
|
|
117630
|
+
});
|
|
117611
117631
|
var formSchema = external_exports.object({
|
|
117612
117632
|
id: external_exports.string().min(1).optional(),
|
|
117613
117633
|
title: external_exports.string().max(160).nullable().optional(),
|
|
@@ -118346,7 +118366,7 @@ function registerChatTools(server2) {
|
|
|
118346
118366
|
const { uid } = await authed(["chat:moderate"]);
|
|
118347
118367
|
await readEvent(eventId, uid, "moderate");
|
|
118348
118368
|
requireConfirm(confirm, "oko_delete_chat_message", { eventId, messageId });
|
|
118349
|
-
await set(ref(chatRtdb, `mod/deleted/${eventId}/${messageId}`), { deletedAt: serverTimestamp(), ts:
|
|
118369
|
+
await set(ref(chatRtdb, `mod/deleted/${eventId}/${messageId}`), { deletedAt: serverTimestamp(), ts: await serverNowMs(), by: uid, reason: "manual" });
|
|
118350
118370
|
return textResult({ eventId, messageId, deleted: true });
|
|
118351
118371
|
});
|
|
118352
118372
|
server2.registerTool("oko_mute_user", {
|
|
@@ -118364,7 +118384,7 @@ function registerChatTools(server2) {
|
|
|
118364
118384
|
const actor = await authed(["chat:moderate"]);
|
|
118365
118385
|
await readEvent(eventId, actor.uid, "moderate");
|
|
118366
118386
|
requireConfirm(confirm, "oko_mute_user", { eventId, targetUid, minutes });
|
|
118367
|
-
await set(ref(chatRtdb, `mutes/${eventId}/${targetUid}`), { until:
|
|
118387
|
+
await set(ref(chatRtdb, `mutes/${eventId}/${targetUid}`), { until: await serverNowMs() + minutes * 6e4, by: actor.uid, reason: "manual" });
|
|
118368
118388
|
return textResult({ eventId, targetUid, mutedMinutes: minutes });
|
|
118369
118389
|
});
|
|
118370
118390
|
server2.registerTool("oko_ban_user", {
|
|
@@ -118386,9 +118406,10 @@ function registerChatTools(server2) {
|
|
|
118386
118406
|
const messagesSnap = await get(query(ref(chatRtdb, `messages/${eventId}`), limitToLast(500)));
|
|
118387
118407
|
const messages = messagesSnap.val() ?? {};
|
|
118388
118408
|
const deletes = {};
|
|
118409
|
+
const nowMs = await serverNowMs();
|
|
118389
118410
|
for (const [messageId, message] of Object.entries(messages)) {
|
|
118390
118411
|
if (message.uid === targetUid)
|
|
118391
|
-
deletes[`mod/deleted/${eventId}/${messageId}`] = { deletedAt: serverTimestamp(), ts:
|
|
118412
|
+
deletes[`mod/deleted/${eventId}/${messageId}`] = { deletedAt: serverTimestamp(), ts: nowMs, by: actor.uid, reason: "ban" };
|
|
118392
118413
|
}
|
|
118393
118414
|
if (Object.keys(deletes).length > 0)
|
|
118394
118415
|
await update(ref(chatRtdb), deletes);
|
|
@@ -118543,7 +118564,7 @@ function eventRuntimeHint(runtime) {
|
|
|
118543
118564
|
}
|
|
118544
118565
|
|
|
118545
118566
|
// build/tools/setupValidation.js
|
|
118546
|
-
function validateEventSetup(eventId, event) {
|
|
118567
|
+
function validateEventSetup(eventId, event, nowMs = Date.now()) {
|
|
118547
118568
|
const errors = [];
|
|
118548
118569
|
const warnings = [];
|
|
118549
118570
|
const suggestions = [];
|
|
@@ -118564,7 +118585,7 @@ function validateEventSetup(eventId, event) {
|
|
|
118564
118585
|
const eventDateMs = toMillis(meta2.eventDate);
|
|
118565
118586
|
if (eventDateMs == null) {
|
|
118566
118587
|
add(suggestions, "date_missing", "meta.eventDate", "Add the webinar date/time if it is known.");
|
|
118567
|
-
} else if (eventDateMs <
|
|
118588
|
+
} else if (eventDateMs < nowMs - 60 * 60 * 1e3) {
|
|
118568
118589
|
add(warnings, "date_in_past", "meta.eventDate", "Configured webinar date appears to be in the past.");
|
|
118569
118590
|
}
|
|
118570
118591
|
if (scheduleMissesDate(event.analyticsSessionMode, meta2.eventDate)) {
|
|
@@ -118583,6 +118604,16 @@ function validateEventSetup(eventId, event) {
|
|
|
118583
118604
|
const fields = Array.isArray(form.fields) ? form.fields : [];
|
|
118584
118605
|
if (fields.length === 0)
|
|
118585
118606
|
add(warnings, "form_fields_missing", `forms[${index}].fields`, "Lead form has no fields.");
|
|
118607
|
+
fields.forEach((rawField, fieldIndex) => {
|
|
118608
|
+
const field2 = asRecord(rawField);
|
|
118609
|
+
const type = safeString(field2.type);
|
|
118610
|
+
if (type !== "select" && type !== "checkbox")
|
|
118611
|
+
return;
|
|
118612
|
+
const options2 = Array.isArray(field2.options) ? field2.options : [];
|
|
118613
|
+
if (!options2.some((item) => typeof item === "string" && item.trim())) {
|
|
118614
|
+
add(errors, "form_field_options_missing", `forms[${index}].fields[${fieldIndex}].options`, `A ${type} field has no options, so the viewer sees an empty list. Add at least one option or change the field type.`);
|
|
118615
|
+
}
|
|
118616
|
+
});
|
|
118586
118617
|
});
|
|
118587
118618
|
for (const id of duplicateFormIds)
|
|
118588
118619
|
add(errors, "form_id_duplicate", "forms", `Duplicate form id: ${id}.`);
|
|
@@ -118853,7 +118884,7 @@ function registerEventTools(server2) {
|
|
|
118853
118884
|
}, async ({ eventId }) => {
|
|
118854
118885
|
const { uid } = await authed(["events:read"]);
|
|
118855
118886
|
const data = await readEvent(eventId, uid, "read");
|
|
118856
|
-
return textResult(validateEventSetup(eventId, data));
|
|
118887
|
+
return textResult(validateEventSetup(eventId, data, await serverNowMs()));
|
|
118857
118888
|
});
|
|
118858
118889
|
server2.registerTool("oko_create_webinar", {
|
|
118859
118890
|
title: "Create webinar",
|
|
@@ -119083,7 +119114,11 @@ function registerEventTools(server2) {
|
|
|
119083
119114
|
label: field2.label.trim(),
|
|
119084
119115
|
type: field2.type,
|
|
119085
119116
|
required: field2.required === true,
|
|
119086
|
-
placeholder: field2.placeholder?.trim() || null
|
|
119117
|
+
placeholder: field2.placeholder?.trim() || null,
|
|
119118
|
+
// Options are written for choice types only (the schema refuses them
|
|
119119
|
+
// elsewhere); duplicates collapse, order is kept — the viewer sees the
|
|
119120
|
+
// list in this order and a checkbox answer is stored in it.
|
|
119121
|
+
...field2.type === "select" || field2.type === "checkbox" ? { options: [...new Set((field2.options ?? []).map((option) => option.trim()).filter(Boolean))] } : {}
|
|
119087
119122
|
}))
|
|
119088
119123
|
}));
|
|
119089
119124
|
Object.assign(patch, telegramPatch);
|
|
@@ -119654,7 +119689,7 @@ function registerLeadTools(server2) {
|
|
|
119654
119689
|
"limit: max rows, default 50, max 200.",
|
|
119655
119690
|
"cursor: lead document id returned as nextCursor by the previous page."
|
|
119656
119691
|
],
|
|
119657
|
-
returns: "leads or csv plus count, limit, hasMore/has_more, nextCursor/next_cursor. Without formId the rows are newest-first across pages; with formId they are newest-first WITHIN a page only, because paging there follows document ids \u2014 to rank one form's submissions by time, read every page and sort by submittedAt.",
|
|
119692
|
+
returns: "leads or csv plus count, limit, hasMore/has_more, nextCursor/next_cursor. A lead's values are keyed by field id: a checkbox field answers with an array of option texts, every other field with a string (in the CSV both sit inside valuesJson). Without formId the rows are newest-first across pages; with formId they are newest-first WITHIN a page only, because paging there follows document ids \u2014 to rank one form's submissions by time, read every page and sort by submittedAt.",
|
|
119658
119693
|
examples: ["Use formId to export one form; if Firestore requests an index, create it from the error link and rerun."]
|
|
119659
119694
|
}),
|
|
119660
119695
|
inputSchema: {
|