vibo-mcp 1.0.0 → 1.2.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
@@ -31116,7 +31116,7 @@ function toolAnnotations(opts = {}) {
31116
31116
  }
31117
31117
 
31118
31118
  // src/version.ts
31119
- var VERSION = "1.0.0";
31119
+ var VERSION = "1.2.0";
31120
31120
 
31121
31121
  // src/client.ts
31122
31122
  import { dirname, join } from "path";
@@ -31189,6 +31189,73 @@ var ViboClient = class {
31189
31189
  }
31190
31190
  return this.unwrap(res.status, res.body);
31191
31191
  }
31192
+ /**
31193
+ * Run a GraphQL operation that uploads one or more files (the `Upload` scalar),
31194
+ * using the graphql-multipart-request spec. `fileMap` maps a dotted variable
31195
+ * path (e.g. "variables.photo" or "variables.payload.answer.images.0") to a
31196
+ * local file path; `variables` must carry `null` at each of those positions.
31197
+ * Same auth + single-retry-on-expiry behavior as `gql`.
31198
+ */
31199
+ async gqlUpload(query, variables, fileMap) {
31200
+ if (this.configError) throw this.configError;
31201
+ const token = await this.ensureAccessToken();
31202
+ let res = await this.postMultipart(query, variables, fileMap, token);
31203
+ if (this.isAuthError(res.status, res.body.errors)) {
31204
+ const fresh = await this.reauthenticate();
31205
+ res = await this.postMultipart(query, variables, fileMap, fresh);
31206
+ }
31207
+ return this.unwrap(res.status, res.body);
31208
+ }
31209
+ async postMultipart(query, variables, fileMap, token) {
31210
+ const { openAsBlob } = await import("node:fs");
31211
+ const { basename } = await import("node:path");
31212
+ const form = new FormData();
31213
+ form.append("operations", JSON.stringify({ query, variables }));
31214
+ const paths = Object.keys(fileMap);
31215
+ const map2 = {};
31216
+ paths.forEach((varPath, i) => {
31217
+ map2[String(i)] = [varPath];
31218
+ });
31219
+ form.append("map", JSON.stringify(map2));
31220
+ for (let i = 0; i < paths.length; i++) {
31221
+ const filePath = fileMap[paths[i]];
31222
+ let blob;
31223
+ try {
31224
+ blob = await openAsBlob(filePath);
31225
+ } catch (err) {
31226
+ throw new McpToolError(`Could not read file for upload: ${filePath}`, {
31227
+ hint: "Provide an absolute path to a readable local file.",
31228
+ cause: err
31229
+ });
31230
+ }
31231
+ form.append(String(i), blob, basename(filePath));
31232
+ }
31233
+ const headers = { "apollo-require-preflight": "true" };
31234
+ if (token) headers["x-token"] = token;
31235
+ let response;
31236
+ try {
31237
+ response = await fetch(this.apiUrl, {
31238
+ method: "POST",
31239
+ headers,
31240
+ // NB: no content-type — fetch sets the multipart boundary
31241
+ body: form,
31242
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
31243
+ });
31244
+ } catch (err) {
31245
+ const reason = err instanceof Error && err.name === "TimeoutError" ? "timed out" : "failed";
31246
+ throw new McpToolError(`Upload to ${SERVICE} ${reason}.`, {
31247
+ hint: "The Vibo API may be unreachable \u2014 check your connection and retry.",
31248
+ cause: err
31249
+ });
31250
+ }
31251
+ let body;
31252
+ try {
31253
+ body = await response.json();
31254
+ } catch {
31255
+ body = {};
31256
+ }
31257
+ return { status: response.status, body };
31258
+ }
31192
31259
  /** Returns the current access token, performing a first login if we only have email/password. */
31193
31260
  async ensureAccessToken() {
31194
31261
  if (this.accessToken) return this.accessToken;
@@ -31506,6 +31573,118 @@ var ANSWER_SECTION_QUESTION = `
31506
31573
  }
31507
31574
  }
31508
31575
  `;
31576
+ var REMOVE_SECTION_SONGS = `
31577
+ mutation removeSectionSongsV2($eventId: ID!, $sectionId: ID!, $songIds: [ID!]!) {
31578
+ removeSectionSongsV2(eventId: $eventId, sectionId: $sectionId, songIds: $songIds) {
31579
+ success sectionsWithSongs sectionsWithSongsTotal sectionsWithSongsProgress totalProgress songsInfo
31580
+ }
31581
+ }
31582
+ `;
31583
+ var UPDATE_SECTION_SONGS = `
31584
+ mutation updateSectionSongs($eventId: ID!, $sectionId: ID!, $songIds: [ID!]!, $payload: UpdateSectionSongInput) {
31585
+ updateSectionSongs(eventId: $eventId, sectionId: $sectionId, songIds: $songIds, payload: $payload) {
31586
+ _id isMustPlay isFlagged comment
31587
+ }
31588
+ }
31589
+ `;
31590
+ var MOVE_SECTION_SONGS = `
31591
+ mutation moveSectionSongsV2($eventId: ID!, $sourceSectionId: ID!, $targetSectionId: ID!, $songIds: [ID!]!) {
31592
+ moveSectionSongsV2(eventId: $eventId, sourceSectionId: $sourceSectionId, targetSectionId: $targetSectionId, songIds: $songIds) {
31593
+ success sectionsWithSongs sectionsWithSongsTotal sectionsWithSongsProgress songsInfo
31594
+ }
31595
+ }
31596
+ `;
31597
+ var REORDER_SONGS = `
31598
+ mutation reorderSongsBatch($eventId: ID!, $sectionId: ID!, $sourceSongIds: [ID!]!, $targetSongId: ID) {
31599
+ reorderSongsBatch(eventId: $eventId, sectionId: $sectionId, sourceSongIds: $sourceSongIds, targetSongId: $targetSongId)
31600
+ }
31601
+ `;
31602
+ var CREATE_SONG_COMMENT = `
31603
+ mutation createSongComment($eventId: ID!, $sectionId: ID!, $songId: ID!, $payload: CreateCommentInput!) {
31604
+ createSongComment(eventId: $eventId, sectionId: $sectionId, songId: $songId, payload: $payload) {
31605
+ _id message date
31606
+ }
31607
+ }
31608
+ `;
31609
+ var DELETE_SONG_COMMENT = `
31610
+ mutation deleteSongComment($eventId: ID!, $sectionId: ID!, $songId: ID!, $commentId: ID!) {
31611
+ deleteSongComment(eventId: $eventId, sectionId: $sectionId, songId: $songId, commentId: $commentId)
31612
+ }
31613
+ `;
31614
+ var CREATE_SECTION_COMMENT = `
31615
+ mutation createSectionComment($eventId: ID!, $sectionId: ID!, $payload: CreateCommentInput!) {
31616
+ createSectionComment(eventId: $eventId, sectionId: $sectionId, payload: $payload) {
31617
+ _id message date
31618
+ }
31619
+ }
31620
+ `;
31621
+ var DELETE_SECTION_COMMENT = `
31622
+ mutation deleteSectionComment($eventId: ID!, $sectionId: ID!, $commentId: ID!) {
31623
+ deleteSectionComment(eventId: $eventId, sectionId: $sectionId, commentId: $commentId)
31624
+ }
31625
+ `;
31626
+ var LIST_SECTION_SONG_IDEAS = `
31627
+ query getEventSectionSongIdeas($eventId: ID!, $sectionId: ID!, $pagination: PaginationInput) {
31628
+ getEventSectionSongIdeas(eventId: $eventId, sectionId: $sectionId, pagination: $pagination) {
31629
+ songIdeas { _id title description songsCount icon isPublic isOwner }
31630
+ totalCount
31631
+ }
31632
+ }
31633
+ `;
31634
+ var LIST_SONG_IDEAS_SONGS = `
31635
+ query getEventSectionSongIdeasSongs($eventId: ID!, $sectionId: ID!, $songIdeasId: ID!, $pagination: PaginationInput) {
31636
+ getEventSectionSongIdeasSongs(eventId: $eventId, sectionId: $sectionId, songIdeasId: $songIdeasId, pagination: $pagination) {
31637
+ songs {
31638
+ viboSongId songUrl title artist isInSection isDontPlay isAddedByMe
31639
+ ${THUMBS}
31640
+ ${SONG_LINKS}
31641
+ }
31642
+ totalCount
31643
+ }
31644
+ }
31645
+ `;
31646
+ var IMPORT_PLAYLIST_TO_SECTION = `
31647
+ mutation importPlaylistToSectionWeb($eventId: ID!, $sectionId: ID!, $playlistId: ID, $source: MusicImportSource!, $tracksToAdd: [ID]!, $tracksToIgnore: [ID]!) {
31648
+ importPlaylistToSectionWeb(eventId: $eventId, sectionId: $sectionId, playlistId: $playlistId, source: $source, tracksToAdd: $tracksToAdd, tracksToIgnore: $tracksToIgnore) {
31649
+ totalCount addedCount existingCount dontPlayCount ignoredCount songsInfo sectionsWithSongs sectionsWithSongsTotal
31650
+ }
31651
+ }
31652
+ `;
31653
+ var LIST_EVENT_USERS = `
31654
+ query eventUsers($eventId: ID!, $usersType: EventUserType, $pagination: PaginationInput) {
31655
+ eventUsers(eventId: $eventId, usersType: $usersType, pagination: $pagination) {
31656
+ users { _id firstName lastName email role imageUrl }
31657
+ totalCount
31658
+ }
31659
+ }
31660
+ `;
31661
+ var INVITE_USERS = `
31662
+ mutation inviteUserViaEmail($eventId: ID!, $type: EventUserType!, $text: String!, $emails: [String!]!) {
31663
+ inviteUserViaEmail(eventId: $eventId, type: $type, text: $text, emails: $emails)
31664
+ }
31665
+ `;
31666
+ var CHANGE_USER_ROLE = `
31667
+ mutation changeUserTypeInEvent($eventId: ID!, $userId: ID!, $type: EventUserType!) {
31668
+ changeUserTypeInEvent(eventId: $eventId, userId: $userId, type: $type)
31669
+ }
31670
+ `;
31671
+ var REMOVE_USER = `
31672
+ mutation removeUserFromEvent($eventId: ID!, $userId: ID!) {
31673
+ removeUserFromEvent(eventId: $eventId, userId: $userId)
31674
+ }
31675
+ `;
31676
+ var UPDATE_SECTION = `
31677
+ mutation updateSection($eventId: ID!, $sectionId: ID!, $payload: UpdateSectionInput!) {
31678
+ updateSection(eventId: $eventId, sectionId: $sectionId, payload: $payload) {
31679
+ _id name time note description
31680
+ }
31681
+ }
31682
+ `;
31683
+ var UPLOAD_USER_PHOTO = `
31684
+ mutation uploadUserPhoto($photo: Upload!) {
31685
+ uploadUserPhoto(photo: $photo) { url mimetype filename }
31686
+ }
31687
+ `;
31509
31688
 
31510
31689
  // src/tools/profile.ts
31511
31690
  function registerProfileTools(server) {
@@ -31964,7 +32143,7 @@ function registerQuestionTools(server) {
31964
32143
  server.registerTool(
31965
32144
  "vibo_answer_question",
31966
32145
  {
31967
- description: "Answer a section planning question. Provide exactly 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. Confirm-gated.",
32146
+ 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.",
31968
32147
  annotations: toolAnnotations({ title: "Answer Vibo question", readOnly: false }),
31969
32148
  inputSchema: {
31970
32149
  eventId: external_exports.string().describe("Event id."),
@@ -31974,13 +32153,17 @@ function registerQuestionTools(server) {
31974
32153
  selectedOptions: external_exports.array(external_exports.string()).optional().describe("Option _ids to select, for radio/checkbox/select questions."),
31975
32154
  link: external_exports.array(external_exports.string()).optional().describe("URL(s), for a link question."),
31976
32155
  otherOptionTitle: external_exports.string().optional().describe(`Free-text value when selecting the question's "other" option.`),
32156
+ imagePaths: external_exports.array(external_exports.string()).optional().describe("Absolute local image file paths, for a photo question."),
32157
+ filePaths: external_exports.array(external_exports.string()).optional().describe("Absolute local file paths, for a file-attachment question."),
31977
32158
  confirm: schemaConfirm
31978
32159
  }
31979
32160
  },
31980
- async ({ eventId, sectionId, questionId, text, selectedOptions, link, otherOptionTitle, confirm }) => {
31981
- if (text === void 0 && selectedOptions === void 0 && link === void 0) {
31982
- throw new McpToolError("Provide an answer: text, selectedOptions, or link.", {
31983
- hint: "Match the question's type \u2014 text \u2192 `text`, radio/checkbox/select \u2192 `selectedOptions`, link \u2192 `link`."
32161
+ async ({ eventId, sectionId, questionId, text, selectedOptions, link, otherOptionTitle, imagePaths, filePaths, confirm }) => {
32162
+ const hasImages = (imagePaths?.length ?? 0) > 0;
32163
+ const hasFiles = (filePaths?.length ?? 0) > 0;
32164
+ if (text === void 0 && selectedOptions === void 0 && link === void 0 && !hasImages && !hasFiles) {
32165
+ throw new McpToolError("Provide an answer: text, selectedOptions, link, imagePaths, or filePaths.", {
32166
+ hint: "Match the question's type \u2014 text \u2192 `text`, radio/checkbox/select \u2192 `selectedOptions`, link \u2192 `link`, photo/file \u2192 `imagePaths`/`filePaths`."
31984
32167
  });
31985
32168
  }
31986
32169
  const answer = {};
@@ -31988,6 +32171,23 @@ function registerQuestionTools(server) {
31988
32171
  if (selectedOptions !== void 0) answer.selectedOptions = selectedOptions;
31989
32172
  if (link !== void 0) answer.link = link;
31990
32173
  if (otherOptionTitle !== void 0) answer.otherOptionTitle = otherOptionTitle;
32174
+ if (hasImages || hasFiles) {
32175
+ if (hasImages) answer.images = imagePaths.map(() => null);
32176
+ if (hasFiles) answer.files = filePaths.map(() => null);
32177
+ const payload2 = { answer };
32178
+ const fileMap = {};
32179
+ (imagePaths ?? []).forEach((p, i) => fileMap[`variables.payload.answer.images.${i}`] = p);
32180
+ (filePaths ?? []).forEach((p, i) => fileMap[`variables.payload.answer.files.${i}`] = p);
32181
+ if (!confirm) {
32182
+ return previewResult("answerEventSectionQuestionV2", { eventId, sectionId, questionId, payload: payload2, uploads: fileMap });
32183
+ }
32184
+ const data2 = await client.gqlUpload(
32185
+ ANSWER_SECTION_QUESTION,
32186
+ { eventId, sectionId, questionId, payload: payload2 },
32187
+ fileMap
32188
+ );
32189
+ return textResult(data2.answerEventSectionQuestionV2);
32190
+ }
31991
32191
  const payload = { answer };
31992
32192
  if (!confirm) return previewResult("answerEventSectionQuestionV2", { eventId, sectionId, questionId, payload });
31993
32193
  const data = await client.gql(ANSWER_SECTION_QUESTION, {
@@ -32001,6 +32201,421 @@ function registerQuestionTools(server) {
32001
32201
  );
32002
32202
  }
32003
32203
 
32204
+ // src/tools/song-management.ts
32205
+ function registerSongManagementTools(server) {
32206
+ server.registerTool(
32207
+ "vibo_remove_song_from_section",
32208
+ {
32209
+ description: "Remove one or more songs from a section. Confirm-gated.",
32210
+ annotations: toolAnnotations({ title: "Remove songs from Vibo section", readOnly: false }),
32211
+ inputSchema: {
32212
+ eventId: external_exports.string().describe("Event id."),
32213
+ sectionId: external_exports.string().describe("Section id."),
32214
+ songIds: external_exports.array(external_exports.string()).min(1).describe("Song _ids from vibo_get_section_songs."),
32215
+ confirm: schemaConfirm
32216
+ }
32217
+ },
32218
+ async ({ eventId, sectionId, songIds, confirm }) => {
32219
+ const vars = { eventId, sectionId, songIds };
32220
+ if (!confirm) return previewResult("removeSectionSongsV2", vars);
32221
+ const data = await client.gql(REMOVE_SECTION_SONGS, vars);
32222
+ return textResult(data.removeSectionSongsV2);
32223
+ }
32224
+ );
32225
+ server.registerTool(
32226
+ "vibo_update_song",
32227
+ {
32228
+ description: "Update songs in a section: mark must-play, flag as do-not-play, and/or set a comment. Provide at least one field. Confirm-gated.",
32229
+ annotations: toolAnnotations({ title: "Update Vibo section songs", readOnly: false }),
32230
+ inputSchema: {
32231
+ eventId: external_exports.string().describe("Event id."),
32232
+ sectionId: external_exports.string().describe("Section id."),
32233
+ songIds: external_exports.array(external_exports.string()).min(1).describe("Song _ids from vibo_get_section_songs."),
32234
+ isMustPlay: external_exports.boolean().optional(),
32235
+ isFlagged: external_exports.boolean().optional().describe("mark do-not-play / flagged"),
32236
+ comment: external_exports.string().optional(),
32237
+ confirm: schemaConfirm
32238
+ }
32239
+ },
32240
+ async ({ eventId, sectionId, songIds, isMustPlay, isFlagged, comment, confirm }) => {
32241
+ const payload = {};
32242
+ if (isMustPlay !== void 0) payload.isMustPlay = isMustPlay;
32243
+ if (isFlagged !== void 0) payload.isFlagged = isFlagged;
32244
+ if (comment !== void 0) payload.comment = comment;
32245
+ if (Object.keys(payload).length === 0) {
32246
+ throw new McpToolError("Provide at least one of isMustPlay, isFlagged, or comment.", {
32247
+ hint: "Pass isMustPlay, isFlagged, and/or comment to update the songs."
32248
+ });
32249
+ }
32250
+ const vars = { eventId, sectionId, songIds, payload };
32251
+ if (!confirm) return previewResult("updateSectionSongs", vars);
32252
+ const data = await client.gql(UPDATE_SECTION_SONGS, vars);
32253
+ return textResult(data.updateSectionSongs);
32254
+ }
32255
+ );
32256
+ server.registerTool(
32257
+ "vibo_move_song",
32258
+ {
32259
+ description: "Move songs from one section to another. Confirm-gated.",
32260
+ annotations: toolAnnotations({ title: "Move Vibo section songs", readOnly: false }),
32261
+ inputSchema: {
32262
+ eventId: external_exports.string().describe("Event id."),
32263
+ sourceSectionId: external_exports.string().describe("Section id the songs are currently in."),
32264
+ targetSectionId: external_exports.string().describe("Section id to move the songs to."),
32265
+ songIds: external_exports.array(external_exports.string()).min(1).describe("Song _ids from vibo_get_section_songs."),
32266
+ confirm: schemaConfirm
32267
+ }
32268
+ },
32269
+ async ({ eventId, sourceSectionId, targetSectionId, songIds, confirm }) => {
32270
+ const vars = { eventId, sourceSectionId, targetSectionId, songIds };
32271
+ if (!confirm) return previewResult("moveSectionSongsV2", vars);
32272
+ const data = await client.gql(MOVE_SECTION_SONGS, vars);
32273
+ return textResult(data.moveSectionSongsV2);
32274
+ }
32275
+ );
32276
+ server.registerTool(
32277
+ "vibo_reorder_songs",
32278
+ {
32279
+ description: "Reorder songs within a section. Confirm-gated.",
32280
+ annotations: toolAnnotations({ title: "Reorder Vibo section songs", readOnly: false }),
32281
+ inputSchema: {
32282
+ eventId: external_exports.string().describe("Event id."),
32283
+ sectionId: external_exports.string().describe("Section id."),
32284
+ sourceSongIds: external_exports.array(external_exports.string()).min(1).describe("Song _ids from vibo_get_section_songs."),
32285
+ targetSongId: external_exports.string().optional().describe("place the moved songs after this song _id; omit for start"),
32286
+ confirm: schemaConfirm
32287
+ }
32288
+ },
32289
+ async ({ eventId, sectionId, sourceSongIds, targetSongId, confirm }) => {
32290
+ const vars = { eventId, sectionId, sourceSongIds, targetSongId: targetSongId ?? null };
32291
+ if (!confirm) return previewResult("reorderSongsBatch", vars);
32292
+ const data = await client.gql(REORDER_SONGS, vars);
32293
+ return textResult(data.reorderSongsBatch);
32294
+ }
32295
+ );
32296
+ }
32297
+
32298
+ // src/tools/comments.ts
32299
+ function registerCommentTools(server) {
32300
+ server.registerTool(
32301
+ "vibo_comment_on_song",
32302
+ {
32303
+ description: "Leave a comment / note for the DJ on a specific song. Confirm-gated.",
32304
+ annotations: toolAnnotations({ title: "Comment on Vibo song", readOnly: false }),
32305
+ inputSchema: {
32306
+ eventId: external_exports.string().describe("Event id."),
32307
+ sectionId: external_exports.string().describe("Section id."),
32308
+ songId: external_exports.string().describe("Song _id (from vibo_get_section_songs)."),
32309
+ message: external_exports.string().describe("The comment text."),
32310
+ confirm: schemaConfirm
32311
+ }
32312
+ },
32313
+ async ({ eventId, sectionId, songId, message, confirm }) => {
32314
+ const vars = { eventId, sectionId, songId, payload: { message } };
32315
+ if (!confirm) return previewResult("createSongComment", vars);
32316
+ const data = await client.gql(CREATE_SONG_COMMENT, vars);
32317
+ return textResult(data.createSongComment);
32318
+ }
32319
+ );
32320
+ server.registerTool(
32321
+ "vibo_delete_song_comment",
32322
+ {
32323
+ description: "Delete a comment on a song. Confirm-gated.",
32324
+ annotations: toolAnnotations({ title: "Delete Vibo song comment", readOnly: false }),
32325
+ inputSchema: {
32326
+ eventId: external_exports.string().describe("Event id."),
32327
+ sectionId: external_exports.string().describe("Section id."),
32328
+ songId: external_exports.string().describe("Song _id (from vibo_get_section_songs)."),
32329
+ commentId: external_exports.string().describe("Comment _id to delete."),
32330
+ confirm: schemaConfirm
32331
+ }
32332
+ },
32333
+ async ({ eventId, sectionId, songId, commentId, confirm }) => {
32334
+ const vars = { eventId, sectionId, songId, commentId };
32335
+ if (!confirm) return previewResult("deleteSongComment", vars);
32336
+ const data = await client.gql(DELETE_SONG_COMMENT, vars);
32337
+ return textResult(data.deleteSongComment);
32338
+ }
32339
+ );
32340
+ server.registerTool(
32341
+ "vibo_comment_on_section",
32342
+ {
32343
+ description: "Leave a comment on a timeline section. Confirm-gated.",
32344
+ annotations: toolAnnotations({ title: "Comment on Vibo section", readOnly: false }),
32345
+ inputSchema: {
32346
+ eventId: external_exports.string().describe("Event id."),
32347
+ sectionId: external_exports.string().describe("Section id (from vibo_list_sections)."),
32348
+ message: external_exports.string().describe("The comment text."),
32349
+ confirm: schemaConfirm
32350
+ }
32351
+ },
32352
+ async ({ eventId, sectionId, message, confirm }) => {
32353
+ const vars = { eventId, sectionId, payload: { message } };
32354
+ if (!confirm) return previewResult("createSectionComment", vars);
32355
+ const data = await client.gql(CREATE_SECTION_COMMENT, vars);
32356
+ return textResult(data.createSectionComment);
32357
+ }
32358
+ );
32359
+ server.registerTool(
32360
+ "vibo_delete_section_comment",
32361
+ {
32362
+ description: "Delete a comment on a timeline section. Confirm-gated.",
32363
+ annotations: toolAnnotations({ title: "Delete Vibo section comment", readOnly: false }),
32364
+ inputSchema: {
32365
+ eventId: external_exports.string().describe("Event id."),
32366
+ sectionId: external_exports.string().describe("Section id (from vibo_list_sections)."),
32367
+ commentId: external_exports.string().describe("Comment _id to delete."),
32368
+ confirm: schemaConfirm
32369
+ }
32370
+ },
32371
+ async ({ eventId, sectionId, commentId, confirm }) => {
32372
+ const vars = { eventId, sectionId, commentId };
32373
+ if (!confirm) return previewResult("deleteSectionComment", vars);
32374
+ const data = await client.gql(DELETE_SECTION_COMMENT, vars);
32375
+ return textResult(data.deleteSectionComment);
32376
+ }
32377
+ );
32378
+ }
32379
+
32380
+ // src/tools/ideas.ts
32381
+ function registerIdeasTools(server) {
32382
+ server.registerTool(
32383
+ "vibo_list_section_song_ideas",
32384
+ {
32385
+ description: "List the DJ's suggested song-idea collections for a section (each with a title, songsCount and _id). Use a song-ideas _id with vibo_list_song_ideas_songs to see the suggested songs, then add the ones you like with vibo_add_song_to_section.",
32386
+ annotations: toolAnnotations({ title: "List Vibo section song ideas", readOnly: true }),
32387
+ inputSchema: {
32388
+ eventId: external_exports.string().describe("Event id."),
32389
+ sectionId: external_exports.string().describe("Section id (from vibo_list_sections)."),
32390
+ limit: limitSchema,
32391
+ skip: skipSchema
32392
+ }
32393
+ },
32394
+ async ({ eventId, sectionId, limit, skip }) => {
32395
+ const data = await client.gql(LIST_SECTION_SONG_IDEAS, {
32396
+ eventId,
32397
+ sectionId,
32398
+ pagination: pagination(limit, skip)
32399
+ });
32400
+ return textResult(data.getEventSectionSongIdeas);
32401
+ }
32402
+ );
32403
+ server.registerTool(
32404
+ "vibo_list_song_ideas_songs",
32405
+ {
32406
+ description: "List the suggested songs inside a song-idea collection (returns songUrl/viboSongId/title/artist to pass to vibo_add_song_to_section).",
32407
+ annotations: toolAnnotations({ title: "List Vibo song-idea songs", readOnly: true }),
32408
+ inputSchema: {
32409
+ eventId: external_exports.string().describe("Event id."),
32410
+ sectionId: external_exports.string().describe("Section id (from vibo_list_sections)."),
32411
+ songIdeasId: external_exports.string().describe("The _id from vibo_list_section_song_ideas."),
32412
+ limit: limitSchema,
32413
+ skip: skipSchema
32414
+ }
32415
+ },
32416
+ async ({ eventId, sectionId, songIdeasId, limit, skip }) => {
32417
+ const data = await client.gql(LIST_SONG_IDEAS_SONGS, {
32418
+ eventId,
32419
+ sectionId,
32420
+ songIdeasId,
32421
+ pagination: pagination(limit, skip)
32422
+ });
32423
+ return textResult(data.getEventSectionSongIdeasSongs);
32424
+ }
32425
+ );
32426
+ }
32427
+
32428
+ // src/tools/imports.ts
32429
+ function registerImportTools(server) {
32430
+ server.registerTool(
32431
+ "vibo_import_playlist_to_section",
32432
+ {
32433
+ description: "Import selected tracks from a connected Spotify/Apple Music playlist into a section. Returns counts of added/existing/ignored. Confirm-gated.",
32434
+ annotations: toolAnnotations({ title: "Import playlist to section", readOnly: false }),
32435
+ inputSchema: {
32436
+ eventId: external_exports.string().describe("Event id."),
32437
+ sectionId: external_exports.string().describe("Section id (from vibo_list_sections)."),
32438
+ source: external_exports.enum(["spotify", "appleMusic"]).describe("Streaming source \u2014 must be connected to your Vibo account."),
32439
+ playlistId: external_exports.string().optional().describe("Playlist id from vibo_get_playlists."),
32440
+ tracksToAdd: external_exports.array(external_exports.string()).min(1).describe("Track ids (from vibo_get_playlist_songs) to import."),
32441
+ tracksToIgnore: external_exports.array(external_exports.string()).optional().describe("Track ids to skip."),
32442
+ confirm: schemaConfirm
32443
+ }
32444
+ },
32445
+ async ({ eventId, sectionId, source, playlistId, tracksToAdd, tracksToIgnore, confirm }) => {
32446
+ const vars = {
32447
+ eventId,
32448
+ sectionId,
32449
+ playlistId: playlistId ?? null,
32450
+ source,
32451
+ tracksToAdd,
32452
+ tracksToIgnore: tracksToIgnore ?? []
32453
+ };
32454
+ if (!confirm) return previewResult("importPlaylistToSectionWeb", vars);
32455
+ const data = await client.gql(
32456
+ IMPORT_PLAYLIST_TO_SECTION,
32457
+ vars
32458
+ );
32459
+ return textResult(data.importPlaylistToSectionWeb);
32460
+ }
32461
+ );
32462
+ }
32463
+
32464
+ // src/tools/collaboration.ts
32465
+ function registerCollaborationTools(server) {
32466
+ server.registerTool(
32467
+ "vibo_list_event_users",
32468
+ {
32469
+ description: "List the hosts and guests on an event.",
32470
+ annotations: toolAnnotations({ title: "List Vibo event users", readOnly: true }),
32471
+ inputSchema: {
32472
+ eventId: external_exports.string().describe("Event id."),
32473
+ usersType: external_exports.enum(["host", "guest"]).optional().describe("Filter to only hosts or only guests."),
32474
+ limit: limitSchema,
32475
+ skip: skipSchema
32476
+ }
32477
+ },
32478
+ async ({ eventId, usersType, limit, skip }) => {
32479
+ const page = pagination(limit, skip);
32480
+ if (usersType) {
32481
+ const data = await client.gql(LIST_EVENT_USERS, {
32482
+ eventId,
32483
+ usersType,
32484
+ pagination: page
32485
+ });
32486
+ return textResult({ ...data.eventUsers, usersType });
32487
+ }
32488
+ const [hosts, guests] = await Promise.all([
32489
+ client.gql(LIST_EVENT_USERS, { eventId, usersType: "host", pagination: page }),
32490
+ client.gql(LIST_EVENT_USERS, { eventId, usersType: "guest", pagination: page })
32491
+ ]);
32492
+ return textResult({
32493
+ hosts: hosts.eventUsers.users,
32494
+ guests: guests.eventUsers.users,
32495
+ hostsCount: hosts.eventUsers.totalCount,
32496
+ guestsCount: guests.eventUsers.totalCount
32497
+ });
32498
+ }
32499
+ );
32500
+ server.registerTool(
32501
+ "vibo_invite_users",
32502
+ {
32503
+ description: "Invite people to an event by email (as host or guest). Confirm-gated.",
32504
+ annotations: toolAnnotations({ title: "Invite Vibo event users", readOnly: false }),
32505
+ inputSchema: {
32506
+ eventId: external_exports.string().describe("Event id."),
32507
+ type: external_exports.enum(["host", "guest"]).describe("Invite as host or guest."),
32508
+ text: external_exports.string().describe("Personal message included in the invite."),
32509
+ emails: external_exports.array(external_exports.string().email()).min(1).describe("Email addresses to invite."),
32510
+ confirm: schemaConfirm
32511
+ }
32512
+ },
32513
+ async ({ eventId, type, text, emails, confirm }) => {
32514
+ const variables = { eventId, type, text, emails };
32515
+ if (!confirm) return previewResult("inviteUserViaEmail", variables);
32516
+ const data = await client.gql(INVITE_USERS, variables);
32517
+ return textResult(data.inviteUserViaEmail);
32518
+ }
32519
+ );
32520
+ server.registerTool(
32521
+ "vibo_change_user_role",
32522
+ {
32523
+ description: "Change an event member's role between host and guest. Confirm-gated.",
32524
+ annotations: toolAnnotations({ title: "Change Vibo user role", readOnly: false }),
32525
+ inputSchema: {
32526
+ eventId: external_exports.string().describe("Event id."),
32527
+ userId: external_exports.string().describe("Id of the member to update."),
32528
+ type: external_exports.enum(["host", "guest"]).describe("New role for the member."),
32529
+ confirm: schemaConfirm
32530
+ }
32531
+ },
32532
+ async ({ eventId, userId, type, confirm }) => {
32533
+ const variables = { eventId, userId, type };
32534
+ if (!confirm) return previewResult("changeUserTypeInEvent", variables);
32535
+ const data = await client.gql(CHANGE_USER_ROLE, variables);
32536
+ return textResult(data.changeUserTypeInEvent);
32537
+ }
32538
+ );
32539
+ server.registerTool(
32540
+ "vibo_remove_user",
32541
+ {
32542
+ description: "Remove a member from an event. Confirm-gated.",
32543
+ annotations: toolAnnotations({ title: "Remove Vibo event user", readOnly: false }),
32544
+ inputSchema: {
32545
+ eventId: external_exports.string().describe("Event id."),
32546
+ userId: external_exports.string().describe("Id of the member to remove."),
32547
+ confirm: schemaConfirm
32548
+ }
32549
+ },
32550
+ async ({ eventId, userId, confirm }) => {
32551
+ const variables = { eventId, userId };
32552
+ if (!confirm) return previewResult("removeUserFromEvent", variables);
32553
+ const data = await client.gql(REMOVE_USER, variables);
32554
+ return textResult(data.removeUserFromEvent);
32555
+ }
32556
+ );
32557
+ }
32558
+
32559
+ // src/tools/section-edit.ts
32560
+ function registerSectionEditTools(server) {
32561
+ server.registerTool(
32562
+ "vibo_update_section",
32563
+ {
32564
+ description: "Edit a timeline section's name, time, note, or description. Subject to the section's host-edit permissions. Confirm-gated.",
32565
+ annotations: toolAnnotations({ title: "Update Vibo section", readOnly: false }),
32566
+ inputSchema: {
32567
+ eventId: external_exports.string(),
32568
+ sectionId: external_exports.string(),
32569
+ name: external_exports.string().optional(),
32570
+ time: external_exports.string().optional().describe('scheduled time, e.g. "05:00 pm"'),
32571
+ note: external_exports.string().optional().describe("note to the DJ for this section"),
32572
+ description: external_exports.string().optional(),
32573
+ confirm: schemaConfirm
32574
+ }
32575
+ },
32576
+ async ({ eventId, sectionId, name, time: time3, note, description, confirm }) => {
32577
+ const payload = {};
32578
+ if (name !== void 0) payload.name = name;
32579
+ if (time3 !== void 0) payload.time = time3;
32580
+ if (note !== void 0) payload.note = note;
32581
+ if (description !== void 0) payload.description = description;
32582
+ if (Object.keys(payload).length === 0) {
32583
+ throw new McpToolError("Provide at least one field to update: name, time, note, or description.", {
32584
+ hint: "Pass at least one of name, time, note, or description."
32585
+ });
32586
+ }
32587
+ const vars = { eventId, sectionId, payload };
32588
+ if (!confirm) return previewResult("updateSection", vars);
32589
+ const data = await client.gql(UPDATE_SECTION, vars);
32590
+ return textResult(data.updateSection);
32591
+ }
32592
+ );
32593
+ }
32594
+
32595
+ // src/tools/uploads.ts
32596
+ function registerUploadTools(server) {
32597
+ server.registerTool(
32598
+ "vibo_set_profile_photo",
32599
+ {
32600
+ description: "Set your Vibo profile photo from a local image file. Returns the uploaded image URL. Confirm-gated.",
32601
+ annotations: toolAnnotations({ title: "Set Vibo profile photo", readOnly: false }),
32602
+ inputSchema: {
32603
+ path: external_exports.string().describe("Absolute path to a local image file (jpg/png)."),
32604
+ confirm: schemaConfirm
32605
+ }
32606
+ },
32607
+ async ({ path, confirm }) => {
32608
+ if (!confirm) return previewResult("uploadUserPhoto", { photo: path });
32609
+ const data = await client.gqlUpload(
32610
+ UPLOAD_USER_PHOTO,
32611
+ { photo: null },
32612
+ { "variables.photo": path }
32613
+ );
32614
+ return textResult(data.uploadUserPhoto);
32615
+ }
32616
+ );
32617
+ }
32618
+
32004
32619
  // src/index.ts
32005
32620
  await runMcp({
32006
32621
  name: "vibo-mcp",
@@ -32013,6 +32628,13 @@ await runMcp({
32013
32628
  registerSongTools,
32014
32629
  registerPlaylistTools,
32015
32630
  registerNotificationTools,
32016
- registerQuestionTools
32631
+ registerQuestionTools,
32632
+ registerSongManagementTools,
32633
+ registerCommentTools,
32634
+ registerIdeasTools,
32635
+ registerImportTools,
32636
+ registerCollaborationTools,
32637
+ registerSectionEditTools,
32638
+ registerUploadTools
32017
32639
  ]
32018
32640
  });