@okostream/mcp 0.5.1 → 0.5.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.2 - 2026-07-10
4
+
5
+ - Fixed webinar create/duplicate output schemas so successful writes cannot be reported as protocol failures.
6
+ - Made Firestore event creation compensate automatically when the required RTDB mirror fails.
7
+ - Removed a stale RTDB mirror rewrite from metadata updates.
8
+ - Routed event deletion through an authorized server callable that cleans Firestore subcollections, both RTDB instances, Storage, participants, active sessions, and scheduled Telegram posts.
9
+ - Added a repeatable production smoke workflow against an exact published npm version.
10
+
3
11
  ## 0.5.1 - 2026-07-10
4
12
 
5
13
  - Replaced hand-maintained remote-image IP ranges with a tested public-unicast policy covering IPv4, IPv6, mapped, reserved, documentation, benchmark, multicast, and private address classes.
package/dist/server.js CHANGED
@@ -23,7 +23,7 @@ if (process.argv.includes("--help")) {
23
23
  }
24
24
  const server = new McpServer({
25
25
  name: "oko-mcp-server",
26
- version: "0.5.1",
26
+ version: "0.5.2",
27
27
  }, {
28
28
  instructions: OKO_MCP_INSTRUCTIONS,
29
29
  });
package/dist/tools.js CHANGED
@@ -77,11 +77,21 @@ const updateOutputSchema = z.object({
77
77
  eventId: z.string().optional(),
78
78
  updated: z.array(z.string()).optional(),
79
79
  }).passthrough();
80
- const createWebinarOutputSchema = {
81
- id: z.string(),
80
+ const eventLinksOutputShape = {
82
81
  url: z.string().url(),
83
- workspaceId: z.string(),
82
+ setupUrl: z.string().url(),
83
+ analyticsUrl: z.string().url(),
84
+ registrationUrl: z.string().url(),
85
+ loginUrl: z.string().url(),
86
+ nmoRegistrationUrl: z.string().url().optional(),
87
+ nmoLoginUrl: z.string().url().optional(),
84
88
  };
89
+ const createWebinarOutputSchema = z.object({
90
+ id: z.string(),
91
+ workspaceId: z.string(),
92
+ sourceEventId: z.string().optional(),
93
+ ...eventLinksOutputShape,
94
+ }).passthrough();
85
95
  const cursorMetaSchema = {
86
96
  count: z.number().int().nonnegative(),
87
97
  limit: z.number().int().positive(),
@@ -100,7 +110,7 @@ const eventsOutputSchema = z.object({
100
110
  }).passthrough();
101
111
  const eventOutputSchema = z.object({
102
112
  id: z.string(),
103
- url: z.string().url(),
113
+ ...eventLinksOutputShape,
104
114
  }).passthrough();
105
115
  const liveStateOutputSchema = z.object({
106
116
  eventId: z.string(),
@@ -1099,6 +1109,22 @@ function initialEventAccess(uid) {
1099
1109
  notificationSenders: [uid],
1100
1110
  };
1101
1111
  }
1112
+ async function addEventWithRtdbMirror(uid, workspaceId, eventData) {
1113
+ const eventRef = await addDoc(collection(firestore, "events"), eventData);
1114
+ try {
1115
+ await update(ref(rtdb, `events/${eventRef.id}`), { ownerId: uid, workspaceId });
1116
+ }
1117
+ catch (error) {
1118
+ try {
1119
+ await deleteDoc(eventRef);
1120
+ }
1121
+ catch (rollbackError) {
1122
+ throw new AggregateError([error, rollbackError], `RTDB mirror failed for event ${eventRef.id}, and Firestore rollback also failed.`);
1123
+ }
1124
+ throw error;
1125
+ }
1126
+ return eventRef;
1127
+ }
1102
1128
  async function assertCanCreateInWorkspace(uid, workspaceId) {
1103
1129
  if (workspaceId === uid)
1104
1130
  return;
@@ -2040,7 +2066,7 @@ export function registerTools(server) {
2040
2066
  const workspaceId = args.workspaceId ?? uid;
2041
2067
  await assertCanCreateInWorkspace(uid, workspaceId);
2042
2068
  const video = normalizeVideo(args.videoUrl);
2043
- const docRef = await addDoc(collection(firestore, "events"), {
2069
+ const docRef = await addEventWithRtdbMirror(uid, workspaceId, {
2044
2070
  ownerId: uid,
2045
2071
  workspaceId,
2046
2072
  ...initialEventAccess(uid),
@@ -2058,10 +2084,6 @@ export function registerTools(server) {
2058
2084
  promoBanner: { enabled: false, text: "", link: "", visibility: "always" },
2059
2085
  },
2060
2086
  });
2061
- await update(ref(rtdb, `events/${docRef.id}`), {
2062
- ownerId: uid,
2063
- workspaceId,
2064
- });
2065
2087
  return textResult({ id: docRef.id, ...eventLinks(docRef.id), workspaceId });
2066
2088
  });
2067
2089
  server.registerTool("oko_update_event", {
@@ -2096,7 +2118,7 @@ export function registerTools(server) {
2096
2118
  if (requiredScopes.size === 0)
2097
2119
  throw new Error("Provide at least one field to update.");
2098
2120
  const { uid } = await authed([...requiredScopes]);
2099
- const data = await readEvent(args.eventId, uid, "edit");
2121
+ await readEvent(args.eventId, uid, "edit");
2100
2122
  const patch = {};
2101
2123
  if (args.title !== undefined)
2102
2124
  patch.title = args.title.trim();
@@ -2114,10 +2136,6 @@ export function registerTools(server) {
2114
2136
  if (args.pinned !== undefined)
2115
2137
  patch.pinned = args.pinned;
2116
2138
  await updateDoc(doc(firestore, "events", args.eventId), patch);
2117
- await update(ref(rtdb, `events/${args.eventId}`), {
2118
- ownerId: safeString(data.ownerId, uid),
2119
- workspaceId: safeString(data.workspaceId, uid),
2120
- });
2121
2139
  return textResult({ id: args.eventId, updated: Object.keys(patch) });
2122
2140
  });
2123
2141
  server.registerTool("oko_configure_blocks", {
@@ -2499,11 +2517,11 @@ export function registerTools(server) {
2499
2517
  server.registerTool("oko_delete_event", {
2500
2518
  title: "Delete event",
2501
2519
  description: toolDescription({
2502
- summary: "Delete an OKO event document.",
2520
+ summary: "Delete an OKO event and its associated Firestore, RTDB, chat, Storage, participant, and scheduled-post data.",
2503
2521
  args: ["eventId: event to delete.", "confirm: must be true."],
2504
- returns: "deleted event id.",
2522
+ returns: "deleted event id and cross-store cleanup counts.",
2505
2523
  examples: ["Use only after the user explicitly confirms the exact event title/id."],
2506
- notes: ["Destructive. Does not control stream state; it deletes the event record."],
2524
+ notes: ["Destructive. The server validates the current per-event delete capability and performs idempotent cross-store cleanup."],
2507
2525
  }),
2508
2526
  inputSchema: { eventId: eventIdField, confirm: z.boolean().optional().describe("Required true for deletion.") },
2509
2527
  outputSchema: z.object({ deleted: z.string() }).passthrough(),
@@ -2512,8 +2530,9 @@ export function registerTools(server) {
2512
2530
  const { uid } = await authed(["events:delete"]);
2513
2531
  const data = await readEvent(eventId, uid, "delete");
2514
2532
  requireConfirm(confirm, "oko_delete_event", { eventId, title: data.title });
2515
- await deleteDoc(doc(firestore, "events", eventId));
2516
- return textResult({ deleted: eventId });
2533
+ const deleteEvent = httpsCallable(functions, "deleteEventCompletely");
2534
+ const result = await deleteEvent({ eventId });
2535
+ return textResult(result.data);
2517
2536
  });
2518
2537
  server.registerTool("oko_duplicate_event", {
2519
2538
  title: "Duplicate event",
@@ -2535,7 +2554,7 @@ export function registerTools(server) {
2535
2554
  const source = await readEvent(eventId, uid, "read");
2536
2555
  const workspaceId = targetWorkspaceId ?? safeString(source.workspaceId, uid);
2537
2556
  await assertCanCreateInWorkspace(uid, workspaceId);
2538
- const newRef = await addDoc(collection(firestore, "events"), {
2557
+ const newRef = await addEventWithRtdbMirror(uid, workspaceId, {
2539
2558
  ownerId: uid,
2540
2559
  workspaceId,
2541
2560
  ...initialEventAccess(uid),
@@ -2551,7 +2570,6 @@ export function registerTools(server) {
2551
2570
  ...(source.nmoCriteria && typeof source.nmoCriteria === "object" ? { nmoCriteria: source.nmoCriteria } : {}),
2552
2571
  ...(source.nmoExtraFields && typeof source.nmoExtraFields === "object" ? { nmoExtraFields: source.nmoExtraFields } : {}),
2553
2572
  });
2554
- await update(ref(rtdb, `events/${newRef.id}`), { ownerId: uid, workspaceId });
2555
2573
  return textResult({ id: newRef.id, sourceEventId: eventId, workspaceId, ...eventLinks(newRef.id) });
2556
2574
  });
2557
2575
  server.registerTool("oko_move_event", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@okostream/mcp",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "description": "MCP server for managing OKO Stream webinars as an organizer.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -26,6 +26,7 @@
26
26
  "test": "npm run build && node --test test/*.test.mjs",
27
27
  "eval:contract": "npm run build && node eval/contract-eval.mjs",
28
28
  "eval:model": "npm run build && node eval/model-eval.mjs",
29
+ "smoke:prod": "node eval/prod-smoke.mjs",
29
30
  "prepublishOnly": "npm run build",
30
31
  "start": "node dist/server.js"
31
32
  },