vibo-mcp 1.3.2 → 1.4.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/bundle.js CHANGED
@@ -31120,7 +31120,7 @@ function toolAnnotations(opts = {}) {
31120
31120
  }
31121
31121
 
31122
31122
  // src/version.ts
31123
- var VERSION = "1.3.2";
31123
+ var VERSION = "1.4.0";
31124
31124
 
31125
31125
  // src/client.ts
31126
31126
  import { dirname as dirname2, join as join2 } from "path";
@@ -31299,8 +31299,11 @@ function saveSession(session) {
31299
31299
  }
31300
31300
 
31301
31301
  // src/client.ts
31302
- var __dirname = dirname2(fileURLToPath(import.meta.url));
31303
- await loadDotenvSafely({ path: join2(__dirname, "..", ".env"), override: false });
31302
+ try {
31303
+ const __dirname = dirname2(fileURLToPath(import.meta.url));
31304
+ await loadDotenvSafely({ path: join2(__dirname, "..", ".env"), override: false });
31305
+ } catch {
31306
+ }
31304
31307
  var DEFAULT_API_URL = "https://api.vibodj.com/v2/graphql";
31305
31308
  var SERVICE = "Vibo";
31306
31309
  var SIGN_IN_HOST = "https://web.vibodj.com";
@@ -31327,24 +31330,37 @@ var ViboClient = class {
31327
31330
  email;
31328
31331
  password;
31329
31332
  // Not readonly: cleared by setTokens() after a browser capture seeds a session.
31330
- configError;
31333
+ configError = null;
31331
31334
  accessToken;
31332
31335
  refreshTokenValue;
31336
+ // The constructor is PURE — it does no filesystem / homedir / async-I/O /
31337
+ // random op — so it is safe to run at Worker global scope (where the
31338
+ // module-level `export const client` singleton below is constructed). The
31339
+ // saved-session fallback and the config-error determination both touch
31340
+ // homedir()/the filesystem via loadSession(), so they are deferred to the
31341
+ // first request through `ensureConfigResolved()`. No effect on the stdio path.
31342
+ configResolved = false;
31333
31343
  // Single-flight guards so concurrent tool calls never race two logins /
31334
31344
  // refreshes against each other (à la mcp-utils' TokenManager).
31335
31345
  loginInFlight = null;
31336
31346
  reauthInFlight = null;
31337
- constructor() {
31338
- this.apiUrl = readEnvVar("VIBO_API_URL") ?? DEFAULT_API_URL;
31339
- const email3 = readEnvVar("VIBO_EMAIL");
31340
- const password = readEnvVar("VIBO_PASSWORD");
31341
- const accessToken = readEnvVar("VIBO_ACCESS_TOKEN");
31342
- const refreshToken = readEnvVar("VIBO_REFRESH_TOKEN");
31343
- this.email = email3 ?? null;
31344
- this.password = password ?? null;
31345
- this.accessToken = accessToken ?? null;
31346
- this.refreshTokenValue = refreshToken ?? null;
31347
- const haveLogin = Boolean(email3 && password);
31347
+ constructor(opts = {}) {
31348
+ this.apiUrl = opts.apiUrl ?? readEnvVar("VIBO_API_URL") ?? DEFAULT_API_URL;
31349
+ this.email = opts.email ?? readEnvVar("VIBO_EMAIL") ?? null;
31350
+ this.password = opts.password ?? readEnvVar("VIBO_PASSWORD") ?? null;
31351
+ this.accessToken = opts.accessToken ?? readEnvVar("VIBO_ACCESS_TOKEN") ?? null;
31352
+ this.refreshTokenValue = opts.refreshToken ?? readEnvVar("VIBO_REFRESH_TOKEN") ?? null;
31353
+ }
31354
+ /**
31355
+ * Resolve the saved-session fallback and the deferred config error on first
31356
+ * use. Kept out of the constructor so construction is pure (Worker-safe):
31357
+ * `loadSession()` reads homedir()/the filesystem, which the Workers runtime
31358
+ * forbids at global scope. Runs its body at most once.
31359
+ */
31360
+ ensureConfigResolved() {
31361
+ if (this.configResolved) return;
31362
+ this.configResolved = true;
31363
+ const haveLogin = Boolean(this.email && this.password);
31348
31364
  if (!this.accessToken && !haveLogin) {
31349
31365
  const saved = loadSession();
31350
31366
  if (saved) {
@@ -31360,8 +31376,6 @@ var ViboClient = class {
31360
31376
  hint: "Set VIBO_EMAIL and VIBO_PASSWORD (recommended); paste a captured VIBO_ACCESS_TOKEN (+ VIBO_REFRESH_TOKEN); or run vibo_capture_session to grab the token from your signed-in web.vibodj.com browser tab (Apple/Google/Facebook accounts)."
31361
31377
  }
31362
31378
  );
31363
- } else {
31364
- this.configError = null;
31365
31379
  }
31366
31380
  }
31367
31381
  /**
@@ -31382,6 +31396,7 @@ var ViboClient = class {
31382
31396
  }
31383
31397
  /** Run a GraphQL operation, transparently authenticating + retrying once on token expiry. */
31384
31398
  async gql(query, variables = {}) {
31399
+ this.ensureConfigResolved();
31385
31400
  if (this.configError) throw this.configError;
31386
31401
  const token = await this.ensureAccessToken();
31387
31402
  let res = await this.post(query, variables, token);
@@ -31393,44 +31408,38 @@ var ViboClient = class {
31393
31408
  }
31394
31409
  /**
31395
31410
  * Run a GraphQL operation that uploads one or more files (the `Upload` scalar),
31396
- * using the graphql-multipart-request spec. `fileMap` maps a dotted variable
31397
- * path (e.g. "variables.photo" or "variables.payload.answer.images.0") to a
31398
- * local file path; `variables` must carry `null` at each of those positions.
31399
- * Same auth + single-retry-on-expiry behavior as `gql`.
31411
+ * using the graphql-multipart-request spec. `files` maps a dotted variable
31412
+ * path (e.g. "variables.photo" or "variables.payload.answer.images.0") to an
31413
+ * in-memory {@link UploadFile} (blob + filename); `variables` must carry
31414
+ * `null` at each of those positions. The bytes arrive already resolved (from
31415
+ * a local file on stdio, or inline base64 on the hosted connector — see
31416
+ * src/upload-source.ts), so this method never touches the filesystem and runs
31417
+ * unchanged in the Workers runtime (`FormData`/`Blob`/`fetch` are all
31418
+ * available there). Same auth + single-retry-on-expiry behavior as `gql`.
31400
31419
  */
31401
- async gqlUpload(query, variables, fileMap) {
31420
+ async gqlUpload(query, variables, files) {
31421
+ this.ensureConfigResolved();
31402
31422
  if (this.configError) throw this.configError;
31403
31423
  const token = await this.ensureAccessToken();
31404
- let res = await this.postMultipart(query, variables, fileMap, token);
31424
+ let res = await this.postMultipart(query, variables, files, token);
31405
31425
  if (this.isAuthError(res.status, res.body.errors)) {
31406
31426
  const fresh = await this.reauthenticate();
31407
- res = await this.postMultipart(query, variables, fileMap, fresh);
31427
+ res = await this.postMultipart(query, variables, files, fresh);
31408
31428
  }
31409
31429
  return this.unwrap(res.status, res.body);
31410
31430
  }
31411
- async postMultipart(query, variables, fileMap, token) {
31412
- const { openAsBlob } = await import("node:fs");
31413
- const { basename } = await import("node:path");
31431
+ async postMultipart(query, variables, files, token) {
31414
31432
  const form = new FormData();
31415
31433
  form.append("operations", JSON.stringify({ query, variables }));
31416
- const paths = Object.keys(fileMap);
31434
+ const paths = Object.keys(files);
31417
31435
  const map2 = {};
31418
31436
  paths.forEach((varPath, i) => {
31419
31437
  map2[String(i)] = [varPath];
31420
31438
  });
31421
31439
  form.append("map", JSON.stringify(map2));
31422
31440
  for (let i = 0; i < paths.length; i++) {
31423
- const filePath = fileMap[paths[i]];
31424
- let blob;
31425
- try {
31426
- blob = await openAsBlob(filePath);
31427
- } catch (err) {
31428
- throw new McpToolError(`Could not read file for upload: ${filePath}`, {
31429
- hint: "Provide an absolute path to a readable local file.",
31430
- cause: err
31431
- });
31432
- }
31433
- form.append(String(i), blob, basename(filePath));
31441
+ const file2 = files[paths[i]];
31442
+ form.append(String(i), file2.blob, file2.filename);
31434
31443
  }
31435
31444
  const headers = { "apollo-require-preflight": "true" };
31436
31445
  if (token) headers["x-token"] = token;
@@ -31892,7 +31901,7 @@ var UPLOAD_USER_PHOTO = `
31892
31901
  `;
31893
31902
 
31894
31903
  // src/tools/profile.ts
31895
- function registerProfileTools(server) {
31904
+ function registerProfileTools(server, client2) {
31896
31905
  server.registerTool(
31897
31906
  "vibo_get_me",
31898
31907
  {
@@ -31900,7 +31909,7 @@ function registerProfileTools(server) {
31900
31909
  annotations: toolAnnotations({ title: "Get my Vibo profile", readOnly: true })
31901
31910
  },
31902
31911
  async () => {
31903
- const data = await client.gql(GET_ME);
31912
+ const data = await client2.gql(GET_ME);
31904
31913
  return textResult(data.me);
31905
31914
  }
31906
31915
  );
@@ -31911,7 +31920,7 @@ function registerProfileTools(server) {
31911
31920
  annotations: toolAnnotations({ title: "Vibo healthcheck", readOnly: true })
31912
31921
  },
31913
31922
  async () => {
31914
- const data = await client.gql(GET_ME);
31923
+ const data = await client2.gql(GET_ME);
31915
31924
  return textResult({ ok: true, userId: data.me._id, email: data.me.email });
31916
31925
  }
31917
31926
  );
@@ -31923,6 +31932,10 @@ var skipSchema = external_exports.number().int().min(0).optional().describe("Num
31923
31932
  function pagination(limit, skip) {
31924
31933
  return { skip: skip ?? 0, limit: limit ?? 20 };
31925
31934
  }
31935
+ var inlineFileSchema = external_exports.object({
31936
+ data: external_exports.string().describe("Base64-encoded file bytes (a `data:` URL prefix is allowed)."),
31937
+ filename: external_exports.string().optional().describe("Filename to send with this file.")
31938
+ });
31926
31939
  function previewResult(action, willSend) {
31927
31940
  return textResult({
31928
31941
  preview: true,
@@ -31933,7 +31946,7 @@ function previewResult(action, willSend) {
31933
31946
  }
31934
31947
 
31935
31948
  // src/tools/events.ts
31936
- function registerEventTools(server) {
31949
+ function registerEventTools(server, client2) {
31937
31950
  server.registerTool(
31938
31951
  "vibo_list_events",
31939
31952
  {
@@ -31952,7 +31965,7 @@ function registerEventTools(server) {
31952
31965
  ...q ? { filter: { q } } : {}
31953
31966
  };
31954
31967
  const doc = past ? LIST_HISTORY_EVENTS : LIST_UPCOMING_EVENTS;
31955
- const data = await client.gql(doc, variables);
31968
+ const data = await client2.gql(doc, variables);
31956
31969
  return textResult(past ? data.historyEvents : data.upcomingEvents);
31957
31970
  }
31958
31971
  );
@@ -31966,7 +31979,7 @@ function registerEventTools(server) {
31966
31979
  }
31967
31980
  },
31968
31981
  async ({ eventId }) => {
31969
- const data = await client.gql(GET_EVENT, { eventId });
31982
+ const data = await client2.gql(GET_EVENT, { eventId });
31970
31983
  return textResult(data.event);
31971
31984
  }
31972
31985
  );
@@ -31986,12 +31999,12 @@ function registerEventTools(server) {
31986
31999
  return previewResult("joinEvent", isUrl ? { deepLink: link } : { hash: link });
31987
32000
  }
31988
32001
  if (isUrl) {
31989
- const data2 = await client.gql(JOIN_EVENT_BY_DEEP_LINK, {
32002
+ const data2 = await client2.gql(JOIN_EVENT_BY_DEEP_LINK, {
31990
32003
  deepLink: link
31991
32004
  });
31992
32005
  return textResult({ joined: true, eventId: data2.joinEventViaDeepLink._id });
31993
32006
  }
31994
- const data = await client.gql(JOIN_EVENT_BY_HASH, { hash: link });
32007
+ const data = await client2.gql(JOIN_EVENT_BY_HASH, { hash: link });
31995
32008
  return textResult({ joined: true, eventId: data.joinEventByHash._id });
31996
32009
  }
31997
32010
  );
@@ -32007,7 +32020,7 @@ function registerEventTools(server) {
32007
32020
  },
32008
32021
  async ({ eventId, confirm }) => {
32009
32022
  if (!confirm) return previewResult("leaveEvent", { eventId });
32010
- const data = await client.gql(LEAVE_EVENT, { eventId });
32023
+ const data = await client2.gql(LEAVE_EVENT, { eventId });
32011
32024
  return textResult({ left: true, eventId, result: data.leaveEvent });
32012
32025
  }
32013
32026
  );
@@ -32039,14 +32052,14 @@ function registerEventTools(server) {
32039
32052
  });
32040
32053
  }
32041
32054
  if (!confirm) return previewResult("createEventContact", { eventId, payload });
32042
- const data = await client.gql(CREATE_EVENT_CONTACT, { eventId, payload });
32055
+ const data = await client2.gql(CREATE_EVENT_CONTACT, { eventId, payload });
32043
32056
  return textResult(data.createEventContact);
32044
32057
  }
32045
32058
  );
32046
32059
  }
32047
32060
 
32048
32061
  // src/tools/sections.ts
32049
- function registerSectionTools(server) {
32062
+ function registerSectionTools(server, client2) {
32050
32063
  server.registerTool(
32051
32064
  "vibo_list_sections",
32052
32065
  {
@@ -32057,14 +32070,14 @@ function registerSectionTools(server) {
32057
32070
  }
32058
32071
  },
32059
32072
  async ({ eventId }) => {
32060
- const data = await client.gql(LIST_SECTIONS, { eventId });
32073
+ const data = await client2.gql(LIST_SECTIONS, { eventId });
32061
32074
  return textResult(data.sections);
32062
32075
  }
32063
32076
  );
32064
32077
  }
32065
32078
 
32066
32079
  // src/tools/songs.ts
32067
- function registerSongTools(server) {
32080
+ function registerSongTools(server, client2) {
32068
32081
  server.registerTool(
32069
32082
  "vibo_get_section_songs",
32070
32083
  {
@@ -32094,7 +32107,7 @@ function registerSongTools(server) {
32094
32107
  ...Object.keys(filter).length ? { filter } : {},
32095
32108
  ...sortField ? { sort: { field: sortField, direction: sortDirection ?? "desc" } } : {}
32096
32109
  };
32097
- const data = await client.gql(GET_SECTION_SONGS, variables);
32110
+ const data = await client2.gql(GET_SECTION_SONGS, variables);
32098
32111
  return textResult(data.getSectionSongs);
32099
32112
  }
32100
32113
  );
@@ -32112,7 +32125,7 @@ function registerSongTools(server) {
32112
32125
  }
32113
32126
  },
32114
32127
  async ({ eventId, sectionId, query, source, limit }) => {
32115
- const data = await client.gql(SEARCH_SONGS, {
32128
+ const data = await client2.gql(SEARCH_SONGS, {
32116
32129
  eventId,
32117
32130
  sectionId,
32118
32131
  filter: { q: query, source: source ?? "searchField" },
@@ -32143,7 +32156,7 @@ function registerSongTools(server) {
32143
32156
  if (artist !== void 0) song.artist = artist;
32144
32157
  const payload = { song };
32145
32158
  if (!confirm) return previewResult("addSongToSection", { eventId, sectionId, payload });
32146
- const data = await client.gql(ADD_SONG_TO_SECTION, {
32159
+ const data = await client2.gql(ADD_SONG_TO_SECTION, {
32147
32160
  eventId,
32148
32161
  sectionId,
32149
32162
  payload
@@ -32166,7 +32179,7 @@ function registerSongTools(server) {
32166
32179
  },
32167
32180
  async ({ eventId, sectionId, songId, liked, confirm }) => {
32168
32181
  if (!confirm) return previewResult("toggleLike", { eventId, sectionId, songId, liked });
32169
- const data = await client.gql(TOGGLE_LIKE, {
32182
+ const data = await client2.gql(TOGGLE_LIKE, {
32170
32183
  eventId,
32171
32184
  sectionId,
32172
32185
  songId,
@@ -32179,7 +32192,7 @@ function registerSongTools(server) {
32179
32192
 
32180
32193
  // src/tools/playlists.ts
32181
32194
  var sourceSchema = external_exports.enum(["spotify", "appleMusic"]).describe("Streaming source \u2014 must be connected to your Vibo account.");
32182
- function registerPlaylistTools(server) {
32195
+ function registerPlaylistTools(server, client2) {
32183
32196
  server.registerTool(
32184
32197
  "vibo_get_playlists",
32185
32198
  {
@@ -32193,7 +32206,7 @@ function registerPlaylistTools(server) {
32193
32206
  }
32194
32207
  },
32195
32208
  async ({ source, q, limit, skip }) => {
32196
- const data = await client.gql(GET_PLAYLISTS, {
32209
+ const data = await client2.gql(GET_PLAYLISTS, {
32197
32210
  source,
32198
32211
  pagination: pagination(limit, skip),
32199
32212
  ...q ? { filter: { q } } : {}
@@ -32214,7 +32227,7 @@ function registerPlaylistTools(server) {
32214
32227
  }
32215
32228
  },
32216
32229
  async ({ playlistId, source, limit, skip }) => {
32217
- const data = await client.gql(GET_PLAYLIST_SONGS, {
32230
+ const data = await client2.gql(GET_PLAYLIST_SONGS, {
32218
32231
  playlistId,
32219
32232
  source,
32220
32233
  pagination: pagination(limit, skip)
@@ -32240,7 +32253,7 @@ function registerPlaylistTools(server) {
32240
32253
  if (title !== void 0) variables.title = title;
32241
32254
  if (onlyFlagged !== void 0) variables.filter = { isFlagged: onlyFlagged };
32242
32255
  if (!confirm) return previewResult("exportEventToSpotify", variables);
32243
- const data = await client.gql(EXPORT_EVENT_TO_SPOTIFY, variables);
32256
+ const data = await client2.gql(EXPORT_EVENT_TO_SPOTIFY, variables);
32244
32257
  return textResult(data.exportEventToSpotify);
32245
32258
  }
32246
32259
  );
@@ -32262,14 +32275,14 @@ function registerPlaylistTools(server) {
32262
32275
  if (title !== void 0) variables.title = title;
32263
32276
  if (onlyFlagged !== void 0) variables.filter = { isFlagged: onlyFlagged };
32264
32277
  if (!confirm) return previewResult("exportEventToAppleMusic", variables);
32265
- const data = await client.gql(EXPORT_EVENT_TO_APPLE_MUSIC, variables);
32278
+ const data = await client2.gql(EXPORT_EVENT_TO_APPLE_MUSIC, variables);
32266
32279
  return textResult(data.exportEventToAppleMusic);
32267
32280
  }
32268
32281
  );
32269
32282
  }
32270
32283
 
32271
32284
  // src/tools/notifications.ts
32272
- function registerNotificationTools(server) {
32285
+ function registerNotificationTools(server, client2) {
32273
32286
  server.registerTool(
32274
32287
  "vibo_list_notifications",
32275
32288
  {
@@ -32281,7 +32294,7 @@ function registerNotificationTools(server) {
32281
32294
  }
32282
32295
  },
32283
32296
  async ({ limit, skip }) => {
32284
- const data = await client.gql(GET_NOTIFICATIONS, {
32297
+ const data = await client2.gql(GET_NOTIFICATIONS, {
32285
32298
  pagination: pagination(limit, skip)
32286
32299
  });
32287
32300
  return textResult(data.getNotifications);
@@ -32294,7 +32307,7 @@ function registerNotificationTools(server) {
32294
32307
  annotations: toolAnnotations({ title: "Unread notification count", readOnly: true })
32295
32308
  },
32296
32309
  async () => {
32297
- const data = await client.gql(GET_NOTIFICATIONS_COUNT);
32310
+ const data = await client2.gql(GET_NOTIFICATIONS_COUNT);
32298
32311
  return textResult(data.getNotificationsCount);
32299
32312
  }
32300
32313
  );
@@ -32319,14 +32332,52 @@ function registerNotificationTools(server) {
32319
32332
  if (notificationIds?.length) variables.notificationIds = notificationIds;
32320
32333
  if (readAll) variables.readAll = true;
32321
32334
  if (!confirm) return previewResult("markAsRead", variables);
32322
- const data = await client.gql(MARK_AS_READ, variables);
32335
+ const data = await client2.gql(MARK_AS_READ, variables);
32323
32336
  return textResult({ marked: true, result: data.markAsRead });
32324
32337
  }
32325
32338
  );
32326
32339
  }
32327
32340
 
32341
+ // src/upload-source.ts
32342
+ var DEFAULT_FILENAME = "upload";
32343
+ function blobFromBase64(data, filename) {
32344
+ const base643 = data.includes(",") && data.startsWith("data:") ? data.slice(data.indexOf(",") + 1) : data;
32345
+ let binary;
32346
+ try {
32347
+ binary = atob(base643.trim());
32348
+ } catch (err) {
32349
+ throw new McpToolError("Could not decode inline file data \u2014 expected base64.", {
32350
+ hint: "Pass the file bytes as a base64 string in `fileData` (a `data:` URL prefix is allowed).",
32351
+ cause: err
32352
+ });
32353
+ }
32354
+ const bytes = new Uint8Array(binary.length);
32355
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
32356
+ return { blob: new Blob([bytes]), filename: filename ?? DEFAULT_FILENAME };
32357
+ }
32358
+ var nodeUploadResolver = async (ref) => {
32359
+ if (ref.path) {
32360
+ const { openAsBlob } = await import("node:fs");
32361
+ const { basename } = await import("node:path");
32362
+ let blob;
32363
+ try {
32364
+ blob = await openAsBlob(ref.path);
32365
+ } catch (err) {
32366
+ throw new McpToolError(`Could not read file for upload: ${ref.path}`, {
32367
+ hint: "Provide an absolute path to a readable local file.",
32368
+ cause: err
32369
+ });
32370
+ }
32371
+ return { blob, filename: ref.filename ?? basename(ref.path) };
32372
+ }
32373
+ if (ref.data) return blobFromBase64(ref.data, ref.filename);
32374
+ throw new McpToolError("No file provided for upload.", {
32375
+ hint: "Pass a local file `path` (or inline base64 `fileData`)."
32376
+ });
32377
+ };
32378
+
32328
32379
  // src/tools/questions.ts
32329
- function registerQuestionTools(server) {
32380
+ function registerQuestionTools(server, client2, resolveUpload = nodeUploadResolver) {
32330
32381
  server.registerTool(
32331
32382
  "vibo_list_section_questions",
32332
32383
  {
@@ -32338,7 +32389,7 @@ function registerQuestionTools(server) {
32338
32389
  }
32339
32390
  },
32340
32391
  async ({ eventId, sectionId }) => {
32341
- const data = await client.gql(LIST_SECTION_QUESTIONS, {
32392
+ const data = await client2.gql(LIST_SECTION_QUESTIONS, {
32342
32393
  eventId,
32343
32394
  sectionId
32344
32395
  });
@@ -32348,7 +32399,7 @@ function registerQuestionTools(server) {
32348
32399
  server.registerTool(
32349
32400
  "vibo_answer_question",
32350
32401
  {
32351
- description: "Answer a section planning question. Provide the field matching the question's type: `text` for a text question, `selectedOptions` (array of option _ids from vibo_list_section_questions) for radio/checkbox/select, or `link` (array of URLs) for a link question. Use `otherOptionTitle` with the question's \"other\" option. For photo/file questions, pass `imagePaths` / `filePaths` (absolute local paths). Confirm-gated.",
32402
+ description: "Answer a section planning question. Provide the field matching the question's type: `text` for a text question, `selectedOptions` (array of option _ids from vibo_list_section_questions) for radio/checkbox/select, or `link` (array of URLs) for a link question. Use `otherOptionTitle` with the question's \"other\" option. For photo/file questions, pass local paths (`imagePaths`/`filePaths`) on the stdio server, or inline base64 bytes (`images`/`files`) on the hosted connector. Confirm-gated.",
32352
32403
  annotations: toolAnnotations({ title: "Answer Vibo question", readOnly: false }),
32353
32404
  inputSchema: {
32354
32405
  eventId: external_exports.string().describe("Event id."),
@@ -32358,17 +32409,27 @@ function registerQuestionTools(server) {
32358
32409
  selectedOptions: external_exports.array(external_exports.string()).optional().describe("Option _ids to select, for radio/checkbox/select questions."),
32359
32410
  link: external_exports.array(external_exports.string()).optional().describe("URL(s), for a link question."),
32360
32411
  otherOptionTitle: external_exports.string().optional().describe(`Free-text value when selecting the question's "other" option.`),
32361
- imagePaths: external_exports.array(external_exports.string()).optional().describe("Absolute local image file paths, for a photo question."),
32362
- filePaths: external_exports.array(external_exports.string()).optional().describe("Absolute local file paths, for a file-attachment question."),
32412
+ imagePaths: external_exports.array(external_exports.string()).optional().describe("Absolute local image file paths, for a photo question (local/stdio server only)."),
32413
+ filePaths: external_exports.array(external_exports.string()).optional().describe("Absolute local file paths, for a file-attachment question (local/stdio server only)."),
32414
+ images: external_exports.array(inlineFileSchema).optional().describe("Inline base64 images, for a photo question (used by the hosted connector, which has no filesystem)."),
32415
+ files: external_exports.array(inlineFileSchema).optional().describe("Inline base64 files, for a file-attachment question (used by the hosted connector)."),
32363
32416
  confirm: schemaConfirm
32364
32417
  }
32365
32418
  },
32366
- async ({ eventId, sectionId, questionId, text, selectedOptions, link, otherOptionTitle, imagePaths, filePaths, confirm }) => {
32367
- const hasImages = (imagePaths?.length ?? 0) > 0;
32368
- const hasFiles = (filePaths?.length ?? 0) > 0;
32419
+ async ({ eventId, sectionId, questionId, text, selectedOptions, link, otherOptionTitle, imagePaths, filePaths, images, files, confirm }) => {
32420
+ const imageRefs = [
32421
+ ...(imagePaths ?? []).map((path) => ({ path })),
32422
+ ...(images ?? []).map((f) => ({ data: f.data, filename: f.filename }))
32423
+ ];
32424
+ const fileRefs = [
32425
+ ...(filePaths ?? []).map((path) => ({ path })),
32426
+ ...(files ?? []).map((f) => ({ data: f.data, filename: f.filename }))
32427
+ ];
32428
+ const hasImages = imageRefs.length > 0;
32429
+ const hasFiles = fileRefs.length > 0;
32369
32430
  if (text === void 0 && selectedOptions === void 0 && link === void 0 && !hasImages && !hasFiles) {
32370
- throw new McpToolError("Provide an answer: text, selectedOptions, link, imagePaths, or filePaths.", {
32371
- hint: "Match the question's type \u2014 text \u2192 `text`, radio/checkbox/select \u2192 `selectedOptions`, link \u2192 `link`, photo/file \u2192 `imagePaths`/`filePaths`."
32431
+ throw new McpToolError("Provide an answer: text, selectedOptions, link, imagePaths/images, or filePaths/files.", {
32432
+ hint: "Match the question's type \u2014 text \u2192 `text`, radio/checkbox/select \u2192 `selectedOptions`, link \u2192 `link`, photo/file \u2192 `imagePaths`/`filePaths` (stdio) or `images`/`files` (hosted connector)."
32372
32433
  });
32373
32434
  }
32374
32435
  const answer = {};
@@ -32377,25 +32438,36 @@ function registerQuestionTools(server) {
32377
32438
  if (link !== void 0) answer.link = link;
32378
32439
  if (otherOptionTitle !== void 0) answer.otherOptionTitle = otherOptionTitle;
32379
32440
  if (hasImages || hasFiles) {
32380
- if (hasImages) answer.images = imagePaths.map(() => null);
32381
- if (hasFiles) answer.files = filePaths.map(() => null);
32441
+ if (hasImages) answer.images = imageRefs.map(() => null);
32442
+ if (hasFiles) answer.files = fileRefs.map(() => null);
32382
32443
  const payload2 = { answer };
32383
- const fileMap = {};
32384
- (imagePaths ?? []).forEach((p, i) => fileMap[`variables.payload.answer.images.${i}`] = p);
32385
- (filePaths ?? []).forEach((p, i) => fileMap[`variables.payload.answer.files.${i}`] = p);
32444
+ const previewUploads = {};
32445
+ imageRefs.forEach((ref, i) => {
32446
+ previewUploads[`variables.payload.answer.images.${i}`] = ref.path ?? "(inline bytes)";
32447
+ });
32448
+ fileRefs.forEach((ref, i) => {
32449
+ previewUploads[`variables.payload.answer.files.${i}`] = ref.path ?? "(inline bytes)";
32450
+ });
32386
32451
  if (!confirm) {
32387
- return previewResult("answerEventSectionQuestionV2", { eventId, sectionId, questionId, payload: payload2, uploads: fileMap });
32452
+ return previewResult("answerEventSectionQuestionV2", { eventId, sectionId, questionId, payload: payload2, uploads: previewUploads });
32388
32453
  }
32389
- const data2 = await client.gqlUpload(
32454
+ const resolvedFiles = {};
32455
+ for (let i = 0; i < imageRefs.length; i++) {
32456
+ resolvedFiles[`variables.payload.answer.images.${i}`] = await resolveUpload(imageRefs[i]);
32457
+ }
32458
+ for (let i = 0; i < fileRefs.length; i++) {
32459
+ resolvedFiles[`variables.payload.answer.files.${i}`] = await resolveUpload(fileRefs[i]);
32460
+ }
32461
+ const data2 = await client2.gqlUpload(
32390
32462
  ANSWER_SECTION_QUESTION,
32391
32463
  { eventId, sectionId, questionId, payload: payload2 },
32392
- fileMap
32464
+ resolvedFiles
32393
32465
  );
32394
32466
  return textResult(data2.answerEventSectionQuestionV2);
32395
32467
  }
32396
32468
  const payload = { answer };
32397
32469
  if (!confirm) return previewResult("answerEventSectionQuestionV2", { eventId, sectionId, questionId, payload });
32398
- const data = await client.gql(ANSWER_SECTION_QUESTION, {
32470
+ const data = await client2.gql(ANSWER_SECTION_QUESTION, {
32399
32471
  eventId,
32400
32472
  sectionId,
32401
32473
  questionId,
@@ -32407,7 +32479,7 @@ function registerQuestionTools(server) {
32407
32479
  }
32408
32480
 
32409
32481
  // src/tools/song-management.ts
32410
- function registerSongManagementTools(server) {
32482
+ function registerSongManagementTools(server, client2) {
32411
32483
  server.registerTool(
32412
32484
  "vibo_remove_song_from_section",
32413
32485
  {
@@ -32423,7 +32495,7 @@ function registerSongManagementTools(server) {
32423
32495
  async ({ eventId, sectionId, songIds, confirm }) => {
32424
32496
  const vars = { eventId, sectionId, songIds };
32425
32497
  if (!confirm) return previewResult("removeSectionSongsV2", vars);
32426
- const data = await client.gql(REMOVE_SECTION_SONGS, vars);
32498
+ const data = await client2.gql(REMOVE_SECTION_SONGS, vars);
32427
32499
  return textResult(data.removeSectionSongsV2);
32428
32500
  }
32429
32501
  );
@@ -32454,7 +32526,7 @@ function registerSongManagementTools(server) {
32454
32526
  }
32455
32527
  const vars = { eventId, sectionId, songIds, payload };
32456
32528
  if (!confirm) return previewResult("updateSectionSongs", vars);
32457
- const data = await client.gql(UPDATE_SECTION_SONGS, vars);
32529
+ const data = await client2.gql(UPDATE_SECTION_SONGS, vars);
32458
32530
  return textResult(data.updateSectionSongs);
32459
32531
  }
32460
32532
  );
@@ -32474,7 +32546,7 @@ function registerSongManagementTools(server) {
32474
32546
  async ({ eventId, sourceSectionId, targetSectionId, songIds, confirm }) => {
32475
32547
  const vars = { eventId, sourceSectionId, targetSectionId, songIds };
32476
32548
  if (!confirm) return previewResult("moveSectionSongsV2", vars);
32477
- const data = await client.gql(MOVE_SECTION_SONGS, vars);
32549
+ const data = await client2.gql(MOVE_SECTION_SONGS, vars);
32478
32550
  return textResult(data.moveSectionSongsV2);
32479
32551
  }
32480
32552
  );
@@ -32494,14 +32566,14 @@ function registerSongManagementTools(server) {
32494
32566
  async ({ eventId, sectionId, sourceSongIds, targetSongId, confirm }) => {
32495
32567
  const vars = { eventId, sectionId, sourceSongIds, targetSongId: targetSongId ?? null };
32496
32568
  if (!confirm) return previewResult("reorderSongsBatch", vars);
32497
- const data = await client.gql(REORDER_SONGS, vars);
32569
+ const data = await client2.gql(REORDER_SONGS, vars);
32498
32570
  return textResult(data.reorderSongsBatch);
32499
32571
  }
32500
32572
  );
32501
32573
  }
32502
32574
 
32503
32575
  // src/tools/comments.ts
32504
- function registerCommentTools(server) {
32576
+ function registerCommentTools(server, client2) {
32505
32577
  server.registerTool(
32506
32578
  "vibo_comment_on_song",
32507
32579
  {
@@ -32518,7 +32590,7 @@ function registerCommentTools(server) {
32518
32590
  async ({ eventId, sectionId, songId, message, confirm }) => {
32519
32591
  const vars = { eventId, sectionId, songId, payload: { message } };
32520
32592
  if (!confirm) return previewResult("createSongComment", vars);
32521
- const data = await client.gql(CREATE_SONG_COMMENT, vars);
32593
+ const data = await client2.gql(CREATE_SONG_COMMENT, vars);
32522
32594
  return textResult(data.createSongComment);
32523
32595
  }
32524
32596
  );
@@ -32538,7 +32610,7 @@ function registerCommentTools(server) {
32538
32610
  async ({ eventId, sectionId, songId, commentId, confirm }) => {
32539
32611
  const vars = { eventId, sectionId, songId, commentId };
32540
32612
  if (!confirm) return previewResult("deleteSongComment", vars);
32541
- const data = await client.gql(DELETE_SONG_COMMENT, vars);
32613
+ const data = await client2.gql(DELETE_SONG_COMMENT, vars);
32542
32614
  return textResult(data.deleteSongComment);
32543
32615
  }
32544
32616
  );
@@ -32557,7 +32629,7 @@ function registerCommentTools(server) {
32557
32629
  async ({ eventId, sectionId, message, confirm }) => {
32558
32630
  const vars = { eventId, sectionId, payload: { message } };
32559
32631
  if (!confirm) return previewResult("createSectionComment", vars);
32560
- const data = await client.gql(CREATE_SECTION_COMMENT, vars);
32632
+ const data = await client2.gql(CREATE_SECTION_COMMENT, vars);
32561
32633
  return textResult(data.createSectionComment);
32562
32634
  }
32563
32635
  );
@@ -32576,14 +32648,14 @@ function registerCommentTools(server) {
32576
32648
  async ({ eventId, sectionId, commentId, confirm }) => {
32577
32649
  const vars = { eventId, sectionId, commentId };
32578
32650
  if (!confirm) return previewResult("deleteSectionComment", vars);
32579
- const data = await client.gql(DELETE_SECTION_COMMENT, vars);
32651
+ const data = await client2.gql(DELETE_SECTION_COMMENT, vars);
32580
32652
  return textResult(data.deleteSectionComment);
32581
32653
  }
32582
32654
  );
32583
32655
  }
32584
32656
 
32585
32657
  // src/tools/ideas.ts
32586
- function registerIdeasTools(server) {
32658
+ function registerIdeasTools(server, client2) {
32587
32659
  server.registerTool(
32588
32660
  "vibo_list_section_song_ideas",
32589
32661
  {
@@ -32597,7 +32669,7 @@ function registerIdeasTools(server) {
32597
32669
  }
32598
32670
  },
32599
32671
  async ({ eventId, sectionId, limit, skip }) => {
32600
- const data = await client.gql(LIST_SECTION_SONG_IDEAS, {
32672
+ const data = await client2.gql(LIST_SECTION_SONG_IDEAS, {
32601
32673
  eventId,
32602
32674
  sectionId,
32603
32675
  pagination: pagination(limit, skip)
@@ -32619,7 +32691,7 @@ function registerIdeasTools(server) {
32619
32691
  }
32620
32692
  },
32621
32693
  async ({ eventId, sectionId, songIdeasId, limit, skip }) => {
32622
- const data = await client.gql(LIST_SONG_IDEAS_SONGS, {
32694
+ const data = await client2.gql(LIST_SONG_IDEAS_SONGS, {
32623
32695
  eventId,
32624
32696
  sectionId,
32625
32697
  songIdeasId,
@@ -32631,7 +32703,7 @@ function registerIdeasTools(server) {
32631
32703
  }
32632
32704
 
32633
32705
  // src/tools/imports.ts
32634
- function registerImportTools(server) {
32706
+ function registerImportTools(server, client2) {
32635
32707
  server.registerTool(
32636
32708
  "vibo_import_playlist_to_section",
32637
32709
  {
@@ -32657,7 +32729,7 @@ function registerImportTools(server) {
32657
32729
  tracksToIgnore: tracksToIgnore ?? []
32658
32730
  };
32659
32731
  if (!confirm) return previewResult("importPlaylistToSectionWeb", vars);
32660
- const data = await client.gql(
32732
+ const data = await client2.gql(
32661
32733
  IMPORT_PLAYLIST_TO_SECTION,
32662
32734
  vars
32663
32735
  );
@@ -32667,7 +32739,7 @@ function registerImportTools(server) {
32667
32739
  }
32668
32740
 
32669
32741
  // src/tools/collaboration.ts
32670
- function registerCollaborationTools(server) {
32742
+ function registerCollaborationTools(server, client2) {
32671
32743
  server.registerTool(
32672
32744
  "vibo_list_event_users",
32673
32745
  {
@@ -32683,7 +32755,7 @@ function registerCollaborationTools(server) {
32683
32755
  async ({ eventId, usersType, limit, skip }) => {
32684
32756
  const page = pagination(limit, skip);
32685
32757
  if (usersType) {
32686
- const data = await client.gql(LIST_EVENT_USERS, {
32758
+ const data = await client2.gql(LIST_EVENT_USERS, {
32687
32759
  eventId,
32688
32760
  usersType,
32689
32761
  pagination: page
@@ -32691,8 +32763,8 @@ function registerCollaborationTools(server) {
32691
32763
  return textResult({ ...data.eventUsers, usersType });
32692
32764
  }
32693
32765
  const [hosts, guests] = await Promise.all([
32694
- client.gql(LIST_EVENT_USERS, { eventId, usersType: "host", pagination: page }),
32695
- client.gql(LIST_EVENT_USERS, { eventId, usersType: "guest", pagination: page })
32766
+ client2.gql(LIST_EVENT_USERS, { eventId, usersType: "host", pagination: page }),
32767
+ client2.gql(LIST_EVENT_USERS, { eventId, usersType: "guest", pagination: page })
32696
32768
  ]);
32697
32769
  return textResult({
32698
32770
  hosts: hosts.eventUsers.users,
@@ -32718,7 +32790,7 @@ function registerCollaborationTools(server) {
32718
32790
  async ({ eventId, type, text, emails, confirm }) => {
32719
32791
  const variables = { eventId, type, text, emails };
32720
32792
  if (!confirm) return previewResult("inviteUserViaEmail", variables);
32721
- const data = await client.gql(INVITE_USERS, variables);
32793
+ const data = await client2.gql(INVITE_USERS, variables);
32722
32794
  return textResult(data.inviteUserViaEmail);
32723
32795
  }
32724
32796
  );
@@ -32737,7 +32809,7 @@ function registerCollaborationTools(server) {
32737
32809
  async ({ eventId, userId, type, confirm }) => {
32738
32810
  const variables = { eventId, userId, type };
32739
32811
  if (!confirm) return previewResult("changeUserTypeInEvent", variables);
32740
- const data = await client.gql(CHANGE_USER_ROLE, variables);
32812
+ const data = await client2.gql(CHANGE_USER_ROLE, variables);
32741
32813
  return textResult(data.changeUserTypeInEvent);
32742
32814
  }
32743
32815
  );
@@ -32755,14 +32827,14 @@ function registerCollaborationTools(server) {
32755
32827
  async ({ eventId, userId, confirm }) => {
32756
32828
  const variables = { eventId, userId };
32757
32829
  if (!confirm) return previewResult("removeUserFromEvent", variables);
32758
- const data = await client.gql(REMOVE_USER, variables);
32830
+ const data = await client2.gql(REMOVE_USER, variables);
32759
32831
  return textResult(data.removeUserFromEvent);
32760
32832
  }
32761
32833
  );
32762
32834
  }
32763
32835
 
32764
32836
  // src/tools/section-edit.ts
32765
- function registerSectionEditTools(server) {
32837
+ function registerSectionEditTools(server, client2) {
32766
32838
  server.registerTool(
32767
32839
  "vibo_update_section",
32768
32840
  {
@@ -32791,30 +32863,38 @@ function registerSectionEditTools(server) {
32791
32863
  }
32792
32864
  const vars = { eventId, sectionId, payload };
32793
32865
  if (!confirm) return previewResult("updateSection", vars);
32794
- const data = await client.gql(UPDATE_SECTION, vars);
32866
+ const data = await client2.gql(UPDATE_SECTION, vars);
32795
32867
  return textResult(data.updateSection);
32796
32868
  }
32797
32869
  );
32798
32870
  }
32799
32871
 
32800
32872
  // src/tools/uploads.ts
32801
- function registerUploadTools(server) {
32873
+ function registerUploadTools(server, client2, resolveUpload = nodeUploadResolver) {
32802
32874
  server.registerTool(
32803
32875
  "vibo_set_profile_photo",
32804
32876
  {
32805
- description: "Set your Vibo profile photo from a local image file. Returns the uploaded image URL. Confirm-gated.",
32877
+ description: "Set your Vibo profile photo from an image. On the local (stdio) server pass a local file `path`; on the hosted connector pass the image bytes as base64 `fileData`. Returns the uploaded image URL. Confirm-gated.",
32806
32878
  annotations: toolAnnotations({ title: "Set Vibo profile photo", readOnly: false }),
32807
32879
  inputSchema: {
32808
- path: external_exports.string().describe("Absolute path to a local image file (jpg/png)."),
32880
+ path: external_exports.string().optional().describe("Absolute path to a local image file (jpg/png). Local/stdio server only."),
32881
+ fileData: external_exports.string().optional().describe("Base64-encoded image bytes (a `data:` URL prefix is allowed). Used by the hosted connector, which has no filesystem."),
32882
+ filename: external_exports.string().optional().describe('Filename for the image when using fileData (default "photo.jpg").'),
32809
32883
  confirm: schemaConfirm
32810
32884
  }
32811
32885
  },
32812
- async ({ path, confirm }) => {
32813
- if (!confirm) return previewResult("uploadUserPhoto", { photo: path });
32814
- const data = await client.gqlUpload(
32886
+ async ({ path, fileData, filename, confirm }) => {
32887
+ if (!path && !fileData) {
32888
+ throw new McpToolError("Provide an image: a local file `path` or inline base64 `fileData`.", {
32889
+ hint: "On the local server pass `path`; on the hosted connector pass `fileData` (base64)."
32890
+ });
32891
+ }
32892
+ if (!confirm) return previewResult("uploadUserPhoto", { photo: path ?? "(inline bytes)" });
32893
+ const file2 = await resolveUpload({ path, data: fileData, filename: filename ?? "photo.jpg" });
32894
+ const data = await client2.gqlUpload(
32815
32895
  UPLOAD_USER_PHOTO,
32816
32896
  { photo: null },
32817
- { "variables.photo": path }
32897
+ { "variables.photo": file2 }
32818
32898
  );
32819
32899
  return textResult(data.uploadUserPhoto);
32820
32900
  }
@@ -32868,7 +32948,7 @@ async function captureViboSession(deps = {}) {
32868
32948
  }
32869
32949
 
32870
32950
  // src/tools/session.ts
32871
- function registerSessionTools(server) {
32951
+ function registerSessionTools(server, client2) {
32872
32952
  server.registerTool(
32873
32953
  "vibo_capture_session",
32874
32954
  {
@@ -32877,8 +32957,8 @@ function registerSessionTools(server) {
32877
32957
  },
32878
32958
  async () => {
32879
32959
  const { accessToken, refreshToken } = await captureViboSession();
32880
- client.setTokens(accessToken, refreshToken);
32881
- const data = await client.gql(GET_ME);
32960
+ client2.setTokens(accessToken, refreshToken);
32961
+ const data = await client2.gql(GET_ME);
32882
32962
  saveSession({ accessToken, refreshToken });
32883
32963
  return textResult({
32884
32964
  captured: true,
@@ -32895,6 +32975,10 @@ await runMcp({
32895
32975
  name: "vibo-mcp",
32896
32976
  version: VERSION,
32897
32977
  banner: "[vibo-mcp] This project was developed and is maintained by AI (Claude Code). Use at your own discretion.",
32978
+ // runMcp invokes each registrar as `register(server, deps)`, threading this
32979
+ // singleton so the tool modules import only the ViboClient TYPE. Same client
32980
+ // as before — just passed in rather than imported by each module.
32981
+ deps: client,
32898
32982
  tools: [
32899
32983
  registerProfileTools,
32900
32984
  registerEventTools,