vibo-mcp 2.2.0 → 2.2.1

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.
@@ -7,7 +7,7 @@
7
7
  },
8
8
  "metadata": {
9
9
  "description": "MCP server for Vibo (vibodj.com) — plan & manage event music, song requests, ideas, guests, and playlists via natural language",
10
- "version": "2.2.0"
10
+ "version": "2.2.1"
11
11
  },
12
12
  "plugins": [
13
13
  {
@@ -15,7 +15,7 @@
15
15
  "displayName": "Vibo",
16
16
  "source": "./",
17
17
  "description": "MCP server for Vibo — browse & manage events, timeline, songs, the DJ song ideas/questions, guests, and exports to Spotify/Apple Music",
18
- "version": "2.2.0",
18
+ "version": "2.2.1",
19
19
  "author": {
20
20
  "name": "Chris Hall"
21
21
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "vibo-mcp",
3
3
  "displayName": "Vibo",
4
- "version": "2.2.0",
4
+ "version": "2.2.1",
5
5
  "description": "MCP server for Vibo (vibodj.com) — plan & manage event music, song requests, ideas, guests, and playlists via natural language",
6
6
  "author": {
7
7
  "name": "Chris Hall",
package/dist/bundle.js CHANGED
@@ -8,8 +8,8 @@ var __export = (target, all) => {
8
8
  // node_modules/@chrischall/mcp-utils/dist/cancel/index.js
9
9
  import { AsyncLocalStorage } from "node:async_hooks";
10
10
  var storage = new AsyncLocalStorage();
11
- function withCallSignal(signal, fn) {
12
- return signal ? storage.run({ signal }, fn) : fn();
11
+ function withCallSignal(signal, fn, request) {
12
+ return signal ? storage.run({ signal, ...request ? { request } : {} }, fn) : fn();
13
13
  }
14
14
  function currentCallSignal() {
15
15
  return storage.getStore()?.signal;
@@ -23,6 +23,13 @@ function withAmbientCancellation(own2) {
23
23
  return AbortSignal.any([own2, ambient]);
24
24
  }
25
25
 
26
+ // node_modules/@chrischall/mcp-utils/dist/caller/index.js
27
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
28
+ var storage2 = new AsyncLocalStorage2();
29
+ function withCallerCapabilities(capabilities, fn) {
30
+ return capabilities ? storage2.run(capabilities, fn) : fn();
31
+ }
32
+
26
33
  // node_modules/@modelcontextprotocol/server/dist/chunk-Br0eD_fh.mjs
27
34
  var __create = Object.create;
28
35
  var __defProp2 = Object.defineProperty;
@@ -34492,6 +34499,15 @@ Hint: ${err.hint}`);
34492
34499
  }
34493
34500
  throw err;
34494
34501
  }
34502
+ function mcpRequestFrom(args) {
34503
+ const ctx = args.at(-1);
34504
+ if (typeof ctx !== "object" || ctx === null)
34505
+ return void 0;
34506
+ const req = ctx.mcpReq;
34507
+ if (typeof req !== "object" || req === null)
34508
+ return void 0;
34509
+ return typeof req.notify === "function" ? req : void 0;
34510
+ }
34495
34511
  function callSignalFrom(args) {
34496
34512
  const ctx = args.at(-1);
34497
34513
  if (typeof ctx !== "object" || ctx === null)
@@ -34502,6 +34518,20 @@ function callSignalFrom(args) {
34502
34518
  const signal = req.signal;
34503
34519
  return signal instanceof AbortSignal ? signal : void 0;
34504
34520
  }
34521
+ function declaredCapabilitiesFrom(server) {
34522
+ const inner = server.server;
34523
+ if (typeof inner !== "object" || inner === null)
34524
+ return void 0;
34525
+ const read = inner.getClientCapabilities;
34526
+ if (typeof read !== "function")
34527
+ return void 0;
34528
+ try {
34529
+ const declared = read.call(inner);
34530
+ return typeof declared === "object" && declared !== null ? declared : void 0;
34531
+ } catch {
34532
+ return void 0;
34533
+ }
34534
+ }
34505
34535
  function surfaceToolHints(server) {
34506
34536
  const register2 = server.registerTool.bind(server);
34507
34537
  server.registerTool = (name, config2, cb) => register2(name, config2, (...args) => (
@@ -34517,15 +34547,24 @@ function surfaceToolHints(server) {
34517
34547
  // Here because this is the one wrapper every tool already passes
34518
34548
  // through: threading the signal by hand would mean editing several
34519
34549
  // hundred handlers and missing exactly the ones nobody edits.
34520
- withCallSignal(callSignalFrom(args), () => {
34521
- let result;
34522
- try {
34523
- result = cb(...args);
34524
- } catch (err) {
34525
- return hintResultOrRethrow(err);
34526
- }
34527
- return result instanceof Promise ? result.catch(hintResultOrRethrow) : result;
34528
- })
34550
+ withCallSignal(callSignalFrom(args), () => (
34551
+ // WHAT THE CALLER CAN DO, made ambient for the same reason and in
34552
+ // the same place (`caller/index.ts`). Only the 2025-era half needs
34553
+ // the wrapper — a 2026-07-28 request answers for itself off its own
34554
+ // envelope — but a guarded tool that cannot tell whether the caller
34555
+ // can be shown a prompt returns one the SDK then refuses to deliver,
34556
+ // and that refusal reaches the caller as an unexplained protocol
34557
+ // error it had no chance to catch.
34558
+ withCallerCapabilities(declaredCapabilitiesFrom(server), () => {
34559
+ let result;
34560
+ try {
34561
+ result = cb(...args);
34562
+ } catch (err) {
34563
+ return hintResultOrRethrow(err);
34564
+ }
34565
+ return result instanceof Promise ? result.catch(hintResultOrRethrow) : result;
34566
+ })
34567
+ ), mcpRequestFrom(args))
34529
34568
  ));
34530
34569
  }
34531
34570
  async function createMcpServer(opts) {
@@ -34639,12 +34678,13 @@ function toolAnnotations(opts = {}) {
34639
34678
  ...opts.title !== void 0 ? { title: opts.title } : {},
34640
34679
  readOnlyHint: opts.readOnly ?? true,
34641
34680
  ...opts.idempotent !== void 0 ? { idempotentHint: opts.idempotent } : {},
34681
+ ...opts.destructive !== void 0 ? { destructiveHint: opts.destructive } : {},
34642
34682
  ...opts.openWorld !== void 0 ? { openWorldHint: opts.openWorld } : {}
34643
34683
  };
34644
34684
  }
34645
34685
 
34646
34686
  // src/version.ts
34647
- var VERSION = "2.2.0";
34687
+ var VERSION = "2.2.1";
34648
34688
 
34649
34689
  // src/client.ts
34650
34690
  import { dirname as dirname2, join as join3 } from "path";
@@ -35523,7 +35563,7 @@ function registerEventTools(server, client2) {
35523
35563
  "vibo_join_event",
35524
35564
  {
35525
35565
  description: "Join an event you were invited to, via its share link or hash (e.g. a vibodj.app.link/... URL someone sent you). Returns the joined event's id. Confirm-gated.",
35526
- annotations: toolAnnotations({ title: "Join Vibo event", readOnly: false }),
35566
+ annotations: toolAnnotations({ title: "Join Vibo event", readOnly: false, destructive: false }),
35527
35567
  inputSchema: external_exports.object({
35528
35568
  link: external_exports.string().describe("The full event share URL (vibodj.app.link/... or web.vibodj.com/...) or the bare join hash."),
35529
35569
  confirm: schemaConfirm
@@ -35548,7 +35588,7 @@ function registerEventTools(server, client2) {
35548
35588
  "vibo_leave_event",
35549
35589
  {
35550
35590
  description: "Leave an event you previously joined. Confirm-gated.",
35551
- annotations: toolAnnotations({ title: "Leave Vibo event", readOnly: false }),
35591
+ annotations: toolAnnotations({ title: "Leave Vibo event", readOnly: false, destructive: true }),
35552
35592
  inputSchema: external_exports.object({
35553
35593
  eventId: external_exports.string().describe("Event id to leave."),
35554
35594
  confirm: schemaConfirm
@@ -35564,7 +35604,7 @@ function registerEventTools(server, client2) {
35564
35604
  "vibo_create_event_contact",
35565
35605
  {
35566
35606
  description: "Add a contact (host or guest) to an event with their name/email/phone. Confirm-gated.",
35567
- annotations: toolAnnotations({ title: "Add Vibo event contact", readOnly: false }),
35607
+ annotations: toolAnnotations({ title: "Add Vibo event contact", readOnly: false, destructive: false }),
35568
35608
  inputSchema: external_exports.object({
35569
35609
  eventId: external_exports.string().describe("Event id."),
35570
35610
  role: external_exports.enum(["host", "guest"]).describe("The contact's role in the event."),
@@ -35808,7 +35848,7 @@ function registerSongTools(server, client2) {
35808
35848
  "vibo_add_song_to_section",
35809
35849
  {
35810
35850
  description: "Add a song to a section. Pass a song from vibo_search_songs (songUrl is required; include viboSongId/title/artist when known). Before adding, check that result's `quality.confidence`: adding a `likely-not-original` result puts a cover, karaoke track or junk-metadata re-upload in front of a live DJ. If nothing looks original, report the closest matches back rather than adding a best guess. Confirm-gated.",
35811
- annotations: toolAnnotations({ title: "Add song to Vibo section", readOnly: false }),
35851
+ annotations: toolAnnotations({ title: "Add song to Vibo section", readOnly: false, destructive: false }),
35812
35852
  inputSchema: external_exports.object({
35813
35853
  eventId: external_exports.string().describe("Event id."),
35814
35854
  sectionId: external_exports.string().describe("Section id to add the song to."),
@@ -35838,7 +35878,7 @@ function registerSongTools(server, client2) {
35838
35878
  "vibo_toggle_song_like",
35839
35879
  {
35840
35880
  description: "Like or unlike a song in a section. Confirm-gated.",
35841
- annotations: toolAnnotations({ title: "Like/unlike Vibo song", readOnly: false }),
35881
+ annotations: toolAnnotations({ title: "Like/unlike Vibo song", readOnly: false, destructive: false }),
35842
35882
  inputSchema: external_exports.object({
35843
35883
  eventId: external_exports.string().describe("Event id."),
35844
35884
  sectionId: external_exports.string().describe("Section id."),
@@ -35909,7 +35949,7 @@ function registerPlaylistTools(server, client2) {
35909
35949
  "vibo_export_event_to_spotify",
35910
35950
  {
35911
35951
  description: "Export an event's song selections to a new Spotify playlist (Spotify must be connected). Returns the playlist URL plus how many tracks exported / failed. Confirm-gated.",
35912
- annotations: toolAnnotations({ title: "Export event to Spotify", readOnly: false }),
35952
+ annotations: toolAnnotations({ title: "Export event to Spotify", readOnly: false, destructive: false }),
35913
35953
  inputSchema: external_exports.object({
35914
35954
  eventId: external_exports.string().describe("Event id."),
35915
35955
  sectionIds: external_exports.array(external_exports.string()).min(1).describe("Section ids to include (from vibo_list_sections)."),
@@ -35931,7 +35971,7 @@ function registerPlaylistTools(server, client2) {
35931
35971
  "vibo_export_event_to_apple_music",
35932
35972
  {
35933
35973
  description: "Export an event's song selections to a new Apple Music playlist (Apple Music must be connected). Returns the playlist URL plus how many tracks exported / failed. Confirm-gated.",
35934
- annotations: toolAnnotations({ title: "Export event to Apple Music", readOnly: false }),
35974
+ annotations: toolAnnotations({ title: "Export event to Apple Music", readOnly: false, destructive: false }),
35935
35975
  inputSchema: external_exports.object({
35936
35976
  eventId: external_exports.string().describe("Event id."),
35937
35977
  sectionIds: external_exports.array(external_exports.string()).min(1).describe("Section ids to include (from vibo_list_sections)."),
@@ -35986,7 +36026,7 @@ function registerNotificationTools(server, client2) {
35986
36026
  "vibo_mark_notifications_read",
35987
36027
  {
35988
36028
  description: "Mark notifications as read \u2014 pass specific notificationIds, or readAll:true to clear everything. Confirm-gated.",
35989
- annotations: toolAnnotations({ title: "Mark notifications read", readOnly: false }),
36029
+ annotations: toolAnnotations({ title: "Mark notifications read", readOnly: false, destructive: false }),
35990
36030
  inputSchema: external_exports.object({
35991
36031
  notificationIds: external_exports.array(external_exports.string()).optional().describe("Specific notification ids to mark read."),
35992
36032
  readAll: external_exports.boolean().optional().describe("Mark every notification as read."),
@@ -36071,7 +36111,7 @@ function registerQuestionTools(server, client2, resolveUpload = nodeUploadResolv
36071
36111
  "vibo_answer_question",
36072
36112
  {
36073
36113
  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`) when the server can read your disk, or inline base64 bytes (`images`/`files`) otherwise. Confirm-gated.",
36074
- annotations: toolAnnotations({ title: "Answer Vibo question", readOnly: false }),
36114
+ annotations: toolAnnotations({ title: "Answer Vibo question", readOnly: false, destructive: false }),
36075
36115
  inputSchema: external_exports.object({
36076
36116
  eventId: external_exports.string().describe("Event id."),
36077
36117
  sectionId: external_exports.string().describe("Section id."),
@@ -36155,7 +36195,7 @@ function registerSongManagementTools(server, client2) {
36155
36195
  "vibo_remove_song_from_section",
36156
36196
  {
36157
36197
  description: "Remove one or more songs from a section. Confirm-gated.",
36158
- annotations: toolAnnotations({ title: "Remove songs from Vibo section", readOnly: false }),
36198
+ annotations: toolAnnotations({ title: "Remove songs from Vibo section", readOnly: false, destructive: true }),
36159
36199
  inputSchema: external_exports.object({
36160
36200
  eventId: external_exports.string().describe("Event id."),
36161
36201
  sectionId: external_exports.string().describe("Section id."),
@@ -36174,7 +36214,7 @@ function registerSongManagementTools(server, client2) {
36174
36214
  "vibo_update_song",
36175
36215
  {
36176
36216
  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.",
36177
- annotations: toolAnnotations({ title: "Update Vibo section songs", readOnly: false }),
36217
+ annotations: toolAnnotations({ title: "Update Vibo section songs", readOnly: false, destructive: false }),
36178
36218
  inputSchema: external_exports.object({
36179
36219
  eventId: external_exports.string().describe("Event id."),
36180
36220
  sectionId: external_exports.string().describe("Section id."),
@@ -36205,7 +36245,7 @@ function registerSongManagementTools(server, client2) {
36205
36245
  "vibo_move_song",
36206
36246
  {
36207
36247
  description: "Move songs from one section to another. Confirm-gated.",
36208
- annotations: toolAnnotations({ title: "Move Vibo section songs", readOnly: false }),
36248
+ annotations: toolAnnotations({ title: "Move Vibo section songs", readOnly: false, destructive: false }),
36209
36249
  inputSchema: external_exports.object({
36210
36250
  eventId: external_exports.string().describe("Event id."),
36211
36251
  sourceSectionId: external_exports.string().describe("Section id the songs are currently in."),
@@ -36225,7 +36265,7 @@ function registerSongManagementTools(server, client2) {
36225
36265
  "vibo_reorder_songs",
36226
36266
  {
36227
36267
  description: "Reorder songs within a section. Confirm-gated.",
36228
- annotations: toolAnnotations({ title: "Reorder Vibo section songs", readOnly: false }),
36268
+ annotations: toolAnnotations({ title: "Reorder Vibo section songs", readOnly: false, destructive: false }),
36229
36269
  inputSchema: external_exports.object({
36230
36270
  eventId: external_exports.string().describe("Event id."),
36231
36271
  sectionId: external_exports.string().describe("Section id."),
@@ -36249,7 +36289,7 @@ function registerCommentTools(server, client2) {
36249
36289
  "vibo_comment_on_song",
36250
36290
  {
36251
36291
  description: "Leave a comment / note for the DJ on a specific song. Confirm-gated.",
36252
- annotations: toolAnnotations({ title: "Comment on Vibo song", readOnly: false }),
36292
+ annotations: toolAnnotations({ title: "Comment on Vibo song", readOnly: false, destructive: false }),
36253
36293
  inputSchema: external_exports.object({
36254
36294
  eventId: external_exports.string().describe("Event id."),
36255
36295
  sectionId: external_exports.string().describe("Section id."),
@@ -36269,7 +36309,7 @@ function registerCommentTools(server, client2) {
36269
36309
  "vibo_delete_song_comment",
36270
36310
  {
36271
36311
  description: "Delete a comment on a song. Confirm-gated.",
36272
- annotations: toolAnnotations({ title: "Delete Vibo song comment", readOnly: false }),
36312
+ annotations: toolAnnotations({ title: "Delete Vibo song comment", readOnly: false, destructive: true }),
36273
36313
  inputSchema: external_exports.object({
36274
36314
  eventId: external_exports.string().describe("Event id."),
36275
36315
  sectionId: external_exports.string().describe("Section id."),
@@ -36289,7 +36329,7 @@ function registerCommentTools(server, client2) {
36289
36329
  "vibo_comment_on_section",
36290
36330
  {
36291
36331
  description: "Leave a comment on a timeline section. Confirm-gated.",
36292
- annotations: toolAnnotations({ title: "Comment on Vibo section", readOnly: false }),
36332
+ annotations: toolAnnotations({ title: "Comment on Vibo section", readOnly: false, destructive: false }),
36293
36333
  inputSchema: external_exports.object({
36294
36334
  eventId: external_exports.string().describe("Event id."),
36295
36335
  sectionId: external_exports.string().describe("Section id (from vibo_list_sections)."),
@@ -36308,7 +36348,7 @@ function registerCommentTools(server, client2) {
36308
36348
  "vibo_delete_section_comment",
36309
36349
  {
36310
36350
  description: "Delete a comment on a timeline section. Confirm-gated.",
36311
- annotations: toolAnnotations({ title: "Delete Vibo section comment", readOnly: false }),
36351
+ annotations: toolAnnotations({ title: "Delete Vibo section comment", readOnly: false, destructive: true }),
36312
36352
  inputSchema: external_exports.object({
36313
36353
  eventId: external_exports.string().describe("Event id."),
36314
36354
  sectionId: external_exports.string().describe("Section id (from vibo_list_sections)."),
@@ -36380,7 +36420,7 @@ function registerImportTools(server, client2) {
36380
36420
  "vibo_import_playlist_to_section",
36381
36421
  {
36382
36422
  description: "Import selected tracks from a connected Spotify/Apple Music playlist into a section. Returns counts of added/existing/ignored. Confirm-gated.",
36383
- annotations: toolAnnotations({ title: "Import playlist to section", readOnly: false }),
36423
+ annotations: toolAnnotations({ title: "Import playlist to section", readOnly: false, destructive: false }),
36384
36424
  inputSchema: external_exports.object({
36385
36425
  eventId: external_exports.string().describe("Event id."),
36386
36426
  sectionId: external_exports.string().describe("Section id (from vibo_list_sections)."),
@@ -36451,7 +36491,7 @@ function registerCollaborationTools(server, client2) {
36451
36491
  "vibo_invite_users",
36452
36492
  {
36453
36493
  description: "Invite people to an event by email (as host or guest). Confirm-gated.",
36454
- annotations: toolAnnotations({ title: "Invite Vibo event users", readOnly: false }),
36494
+ annotations: toolAnnotations({ title: "Invite Vibo event users", readOnly: false, destructive: true }),
36455
36495
  inputSchema: external_exports.object({
36456
36496
  eventId: external_exports.string().describe("Event id."),
36457
36497
  type: external_exports.enum(["host", "guest"]).describe("Invite as host or guest."),
@@ -36471,7 +36511,7 @@ function registerCollaborationTools(server, client2) {
36471
36511
  "vibo_change_user_role",
36472
36512
  {
36473
36513
  description: "Change an event member's role between host and guest. Confirm-gated.",
36474
- annotations: toolAnnotations({ title: "Change Vibo user role", readOnly: false }),
36514
+ annotations: toolAnnotations({ title: "Change Vibo user role", readOnly: false, destructive: true }),
36475
36515
  inputSchema: external_exports.object({
36476
36516
  eventId: external_exports.string().describe("Event id."),
36477
36517
  userId: external_exports.string().describe("Id of the member to update."),
@@ -36490,7 +36530,7 @@ function registerCollaborationTools(server, client2) {
36490
36530
  "vibo_remove_user",
36491
36531
  {
36492
36532
  description: "Remove a member from an event. Confirm-gated.",
36493
- annotations: toolAnnotations({ title: "Remove Vibo event user", readOnly: false }),
36533
+ annotations: toolAnnotations({ title: "Remove Vibo event user", readOnly: false, destructive: true }),
36494
36534
  inputSchema: external_exports.object({
36495
36535
  eventId: external_exports.string().describe("Event id."),
36496
36536
  userId: external_exports.string().describe("Id of the member to remove."),
@@ -36512,7 +36552,7 @@ function registerSectionEditTools(server, client2) {
36512
36552
  "vibo_update_section",
36513
36553
  {
36514
36554
  description: "Edit a timeline section's name, time, note, or description. Subject to the section's host-edit permissions. Confirm-gated.",
36515
- annotations: toolAnnotations({ title: "Update Vibo section", readOnly: false }),
36555
+ annotations: toolAnnotations({ title: "Update Vibo section", readOnly: false, destructive: false }),
36516
36556
  inputSchema: external_exports.object({
36517
36557
  eventId: external_exports.string(),
36518
36558
  sectionId: external_exports.string(),
@@ -36548,7 +36588,7 @@ function registerUploadTools(server, client2, resolveUpload = nodeUploadResolver
36548
36588
  "vibo_set_profile_photo",
36549
36589
  {
36550
36590
  description: "Set your Vibo profile photo from an image. Pass a local file `path` if the server shares your filesystem; otherwise pass the image bytes as base64 `fileData`. Returns the uploaded image URL. Confirm-gated.",
36551
- annotations: toolAnnotations({ title: "Set Vibo profile photo", readOnly: false }),
36591
+ annotations: toolAnnotations({ title: "Set Vibo profile photo", readOnly: false, destructive: false }),
36552
36592
  inputSchema: external_exports.object({
36553
36593
  path: external_exports.string().optional().describe("Absolute path to a local image file (jpg/png). Local/stdio server only."),
36554
36594
  fileData: external_exports.string().optional().describe("Base64-encoded image bytes (a `data:` URL prefix is allowed). Use this when the server cannot read your filesystem."),
@@ -36626,7 +36666,7 @@ function registerSessionTools(server, client2) {
36626
36666
  "vibo_capture_session",
36627
36667
  {
36628
36668
  description: "Capture your Vibo login from a signed-in web.vibodj.com browser tab via the fetchproxy bridge \u2014 for accounts that sign in with Apple/Google/Facebook (no password). Requires the fetchproxy browser extension installed and you signed into https://web.vibodj.com; approve the pair code shown on first use. The token is saved locally and reused on future calls.",
36629
- annotations: toolAnnotations({ title: "Capture Vibo session (SSO)", readOnly: false })
36669
+ annotations: toolAnnotations({ title: "Capture Vibo session (SSO)", readOnly: false, destructive: false })
36630
36670
  },
36631
36671
  async () => {
36632
36672
  const { accessToken, refreshToken } = await captureViboSession();
@@ -42,7 +42,7 @@ export function registerCollaborationTools(server, client) {
42
42
  });
43
43
  server.registerTool('vibo_invite_users', {
44
44
  description: 'Invite people to an event by email (as host or guest). Confirm-gated.',
45
- annotations: toolAnnotations({ title: 'Invite Vibo event users', readOnly: false }),
45
+ annotations: toolAnnotations({ title: 'Invite Vibo event users', readOnly: false, destructive: true }),
46
46
  inputSchema: z.object({
47
47
  eventId: z.string().describe('Event id.'),
48
48
  type: z.enum(['host', 'guest']).describe('Invite as host or guest.'),
@@ -59,7 +59,7 @@ export function registerCollaborationTools(server, client) {
59
59
  });
60
60
  server.registerTool('vibo_change_user_role', {
61
61
  description: "Change an event member's role between host and guest. Confirm-gated.",
62
- annotations: toolAnnotations({ title: 'Change Vibo user role', readOnly: false }),
62
+ annotations: toolAnnotations({ title: 'Change Vibo user role', readOnly: false, destructive: true }),
63
63
  inputSchema: z.object({
64
64
  eventId: z.string().describe('Event id.'),
65
65
  userId: z.string().describe('Id of the member to update.'),
@@ -75,7 +75,7 @@ export function registerCollaborationTools(server, client) {
75
75
  });
76
76
  server.registerTool('vibo_remove_user', {
77
77
  description: 'Remove a member from an event. Confirm-gated.',
78
- annotations: toolAnnotations({ title: 'Remove Vibo event user', readOnly: false }),
78
+ annotations: toolAnnotations({ title: 'Remove Vibo event user', readOnly: false, destructive: true }),
79
79
  inputSchema: z.object({
80
80
  eventId: z.string().describe('Event id.'),
81
81
  userId: z.string().describe('Id of the member to remove.'),
@@ -5,7 +5,7 @@ import { previewResult } from './shared.js';
5
5
  export function registerCommentTools(server, client) {
6
6
  server.registerTool('vibo_comment_on_song', {
7
7
  description: 'Leave a comment / note for the DJ on a specific song. Confirm-gated.',
8
- annotations: toolAnnotations({ title: 'Comment on Vibo song', readOnly: false }),
8
+ annotations: toolAnnotations({ title: 'Comment on Vibo song', readOnly: false, destructive: false }),
9
9
  inputSchema: z.object({
10
10
  eventId: z.string().describe('Event id.'),
11
11
  sectionId: z.string().describe('Section id.'),
@@ -22,7 +22,7 @@ export function registerCommentTools(server, client) {
22
22
  });
23
23
  server.registerTool('vibo_delete_song_comment', {
24
24
  description: 'Delete a comment on a song. Confirm-gated.',
25
- annotations: toolAnnotations({ title: 'Delete Vibo song comment', readOnly: false }),
25
+ annotations: toolAnnotations({ title: 'Delete Vibo song comment', readOnly: false, destructive: true }),
26
26
  inputSchema: z.object({
27
27
  eventId: z.string().describe('Event id.'),
28
28
  sectionId: z.string().describe('Section id.'),
@@ -39,7 +39,7 @@ export function registerCommentTools(server, client) {
39
39
  });
40
40
  server.registerTool('vibo_comment_on_section', {
41
41
  description: 'Leave a comment on a timeline section. Confirm-gated.',
42
- annotations: toolAnnotations({ title: 'Comment on Vibo section', readOnly: false }),
42
+ annotations: toolAnnotations({ title: 'Comment on Vibo section', readOnly: false, destructive: false }),
43
43
  inputSchema: z.object({
44
44
  eventId: z.string().describe('Event id.'),
45
45
  sectionId: z.string().describe('Section id (from vibo_list_sections).'),
@@ -55,7 +55,7 @@ export function registerCommentTools(server, client) {
55
55
  });
56
56
  server.registerTool('vibo_delete_section_comment', {
57
57
  description: 'Delete a comment on a timeline section. Confirm-gated.',
58
- annotations: toolAnnotations({ title: 'Delete Vibo section comment', readOnly: false }),
58
+ annotations: toolAnnotations({ title: 'Delete Vibo section comment', readOnly: false, destructive: true }),
59
59
  inputSchema: z.object({
60
60
  eventId: z.string().describe('Event id.'),
61
61
  sectionId: z.string().describe('Section id (from vibo_list_sections).'),
@@ -33,7 +33,7 @@ export function registerEventTools(server, client) {
33
33
  });
34
34
  server.registerTool('vibo_join_event', {
35
35
  description: "Join an event you were invited to, via its share link or hash (e.g. a vibodj.app.link/... URL someone sent you). Returns the joined event's id. Confirm-gated.",
36
- annotations: toolAnnotations({ title: 'Join Vibo event', readOnly: false }),
36
+ annotations: toolAnnotations({ title: 'Join Vibo event', readOnly: false, destructive: false }),
37
37
  inputSchema: z.object({
38
38
  link: z
39
39
  .string()
@@ -56,7 +56,7 @@ export function registerEventTools(server, client) {
56
56
  });
57
57
  server.registerTool('vibo_leave_event', {
58
58
  description: 'Leave an event you previously joined. Confirm-gated.',
59
- annotations: toolAnnotations({ title: 'Leave Vibo event', readOnly: false }),
59
+ annotations: toolAnnotations({ title: 'Leave Vibo event', readOnly: false, destructive: true }),
60
60
  inputSchema: z.object({
61
61
  eventId: z.string().describe('Event id to leave.'),
62
62
  confirm: schemaConfirm,
@@ -69,7 +69,7 @@ export function registerEventTools(server, client) {
69
69
  });
70
70
  server.registerTool('vibo_create_event_contact', {
71
71
  description: 'Add a contact (host or guest) to an event with their name/email/phone. Confirm-gated.',
72
- annotations: toolAnnotations({ title: 'Add Vibo event contact', readOnly: false }),
72
+ annotations: toolAnnotations({ title: 'Add Vibo event contact', readOnly: false, destructive: false }),
73
73
  inputSchema: z.object({
74
74
  eventId: z.string().describe('Event id.'),
75
75
  role: z.enum(['host', 'guest']).describe("The contact's role in the event."),
@@ -5,7 +5,7 @@ import { previewResult } from './shared.js';
5
5
  export function registerImportTools(server, client) {
6
6
  server.registerTool('vibo_import_playlist_to_section', {
7
7
  description: 'Import selected tracks from a connected Spotify/Apple Music playlist into a section. Returns counts of added/existing/ignored. Confirm-gated.',
8
- annotations: toolAnnotations({ title: 'Import playlist to section', readOnly: false }),
8
+ annotations: toolAnnotations({ title: 'Import playlist to section', readOnly: false, destructive: false }),
9
9
  inputSchema: z.object({
10
10
  eventId: z.string().describe('Event id.'),
11
11
  sectionId: z.string().describe('Section id (from vibo_list_sections).'),
@@ -27,7 +27,7 @@ export function registerNotificationTools(server, client) {
27
27
  });
28
28
  server.registerTool('vibo_mark_notifications_read', {
29
29
  description: 'Mark notifications as read — pass specific notificationIds, or readAll:true to clear everything. Confirm-gated.',
30
- annotations: toolAnnotations({ title: 'Mark notifications read', readOnly: false }),
30
+ annotations: toolAnnotations({ title: 'Mark notifications read', readOnly: false, destructive: false }),
31
31
  inputSchema: z.object({
32
32
  notificationIds: z.array(z.string()).optional().describe('Specific notification ids to mark read.'),
33
33
  readAll: z.boolean().optional().describe('Mark every notification as read.'),
@@ -42,7 +42,7 @@ export function registerPlaylistTools(server, client) {
42
42
  });
43
43
  server.registerTool('vibo_export_event_to_spotify', {
44
44
  description: "Export an event's song selections to a new Spotify playlist (Spotify must be connected). Returns the playlist URL plus how many tracks exported / failed. Confirm-gated.",
45
- annotations: toolAnnotations({ title: 'Export event to Spotify', readOnly: false }),
45
+ annotations: toolAnnotations({ title: 'Export event to Spotify', readOnly: false, destructive: false }),
46
46
  inputSchema: z.object({
47
47
  eventId: z.string().describe('Event id.'),
48
48
  sectionIds: z.array(z.string()).min(1).describe('Section ids to include (from vibo_list_sections).'),
@@ -63,7 +63,7 @@ export function registerPlaylistTools(server, client) {
63
63
  });
64
64
  server.registerTool('vibo_export_event_to_apple_music', {
65
65
  description: "Export an event's song selections to a new Apple Music playlist (Apple Music must be connected). Returns the playlist URL plus how many tracks exported / failed. Confirm-gated.",
66
- annotations: toolAnnotations({ title: 'Export event to Apple Music', readOnly: false }),
66
+ annotations: toolAnnotations({ title: 'Export event to Apple Music', readOnly: false, destructive: false }),
67
67
  inputSchema: z.object({
68
68
  eventId: z.string().describe('Event id.'),
69
69
  sectionIds: z.array(z.string()).min(1).describe('Section ids to include (from vibo_list_sections).'),
@@ -25,7 +25,7 @@ export function registerQuestionTools(server, client, resolveUpload = nodeUpload
25
25
  });
26
26
  server.registerTool('vibo_answer_question', {
27
27
  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`) when the server can read your disk, or inline base64 bytes (`images`/`files`) otherwise. Confirm-gated.",
28
- annotations: toolAnnotations({ title: 'Answer Vibo question', readOnly: false }),
28
+ annotations: toolAnnotations({ title: 'Answer Vibo question', readOnly: false, destructive: false }),
29
29
  inputSchema: z.object({
30
30
  eventId: z.string().describe('Event id.'),
31
31
  sectionId: z.string().describe('Section id.'),
@@ -5,7 +5,7 @@ import { previewResult } from './shared.js';
5
5
  export function registerSectionEditTools(server, client) {
6
6
  server.registerTool('vibo_update_section', {
7
7
  description: "Edit a timeline section's name, time, note, or description. Subject to the section's host-edit permissions. Confirm-gated.",
8
- annotations: toolAnnotations({ title: 'Update Vibo section', readOnly: false }),
8
+ annotations: toolAnnotations({ title: 'Update Vibo section', readOnly: false, destructive: false }),
9
9
  inputSchema: z.object({
10
10
  eventId: z.string(),
11
11
  sectionId: z.string(),
@@ -8,7 +8,7 @@ import { GET_ME } from '../gql.js';
8
8
  export function registerSessionTools(server, client) {
9
9
  server.registerTool('vibo_capture_session', {
10
10
  description: "Capture your Vibo login from a signed-in web.vibodj.com browser tab via the fetchproxy bridge — for accounts that sign in with Apple/Google/Facebook (no password). Requires the fetchproxy browser extension installed and you signed into https://web.vibodj.com; approve the pair code shown on first use. The token is saved locally and reused on future calls.",
11
- annotations: toolAnnotations({ title: 'Capture Vibo session (SSO)', readOnly: false }),
11
+ annotations: toolAnnotations({ title: 'Capture Vibo session (SSO)', readOnly: false, destructive: false }),
12
12
  }, async () => {
13
13
  const { accessToken, refreshToken } = await captureViboSession();
14
14
  client.setTokens(accessToken, refreshToken);
@@ -5,7 +5,7 @@ import { previewResult } from './shared.js';
5
5
  export function registerSongManagementTools(server, client) {
6
6
  server.registerTool('vibo_remove_song_from_section', {
7
7
  description: 'Remove one or more songs from a section. Confirm-gated.',
8
- annotations: toolAnnotations({ title: 'Remove songs from Vibo section', readOnly: false }),
8
+ annotations: toolAnnotations({ title: 'Remove songs from Vibo section', readOnly: false, destructive: true }),
9
9
  inputSchema: z.object({
10
10
  eventId: z.string().describe('Event id.'),
11
11
  sectionId: z.string().describe('Section id.'),
@@ -24,7 +24,7 @@ export function registerSongManagementTools(server, client) {
24
24
  });
25
25
  server.registerTool('vibo_update_song', {
26
26
  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.',
27
- annotations: toolAnnotations({ title: 'Update Vibo section songs', readOnly: false }),
27
+ annotations: toolAnnotations({ title: 'Update Vibo section songs', readOnly: false, destructive: false }),
28
28
  inputSchema: z.object({
29
29
  eventId: z.string().describe('Event id.'),
30
30
  sectionId: z.string().describe('Section id.'),
@@ -58,7 +58,7 @@ export function registerSongManagementTools(server, client) {
58
58
  });
59
59
  server.registerTool('vibo_move_song', {
60
60
  description: 'Move songs from one section to another. Confirm-gated.',
61
- annotations: toolAnnotations({ title: 'Move Vibo section songs', readOnly: false }),
61
+ annotations: toolAnnotations({ title: 'Move Vibo section songs', readOnly: false, destructive: false }),
62
62
  inputSchema: z.object({
63
63
  eventId: z.string().describe('Event id.'),
64
64
  sourceSectionId: z.string().describe('Section id the songs are currently in.'),
@@ -78,7 +78,7 @@ export function registerSongManagementTools(server, client) {
78
78
  });
79
79
  server.registerTool('vibo_reorder_songs', {
80
80
  description: 'Reorder songs within a section. Confirm-gated.',
81
- annotations: toolAnnotations({ title: 'Reorder Vibo section songs', readOnly: false }),
81
+ annotations: toolAnnotations({ title: 'Reorder Vibo section songs', readOnly: false, destructive: false }),
82
82
  inputSchema: z.object({
83
83
  eventId: z.string().describe('Event id.'),
84
84
  sectionId: z.string().describe('Section id.'),
@@ -96,7 +96,7 @@ export function registerSongTools(server, client) {
96
96
  '`quality.confidence`: adding a `likely-not-original` result puts a cover, karaoke ' +
97
97
  'track or junk-metadata re-upload in front of a live DJ. If nothing looks original, ' +
98
98
  'report the closest matches back rather than adding a best guess. Confirm-gated.',
99
- annotations: toolAnnotations({ title: 'Add song to Vibo section', readOnly: false }),
99
+ annotations: toolAnnotations({ title: 'Add song to Vibo section', readOnly: false, destructive: false }),
100
100
  inputSchema: z.object({
101
101
  eventId: z.string().describe('Event id.'),
102
102
  sectionId: z.string().describe('Section id to add the song to.'),
@@ -126,7 +126,7 @@ export function registerSongTools(server, client) {
126
126
  });
127
127
  server.registerTool('vibo_toggle_song_like', {
128
128
  description: 'Like or unlike a song in a section. Confirm-gated.',
129
- annotations: toolAnnotations({ title: 'Like/unlike Vibo song', readOnly: false }),
129
+ annotations: toolAnnotations({ title: 'Like/unlike Vibo song', readOnly: false, destructive: false }),
130
130
  inputSchema: z.object({
131
131
  eventId: z.string().describe('Event id.'),
132
132
  sectionId: z.string().describe('Section id.'),
@@ -12,7 +12,7 @@ import { previewResult } from './shared.js';
12
12
  export function registerUploadTools(server, client, resolveUpload = nodeUploadResolver) {
13
13
  server.registerTool('vibo_set_profile_photo', {
14
14
  description: 'Set your Vibo profile photo from an image. Pass a local file `path` if the server shares your filesystem; otherwise pass the image bytes as base64 `fileData`. Returns the uploaded image URL. Confirm-gated.',
15
- annotations: toolAnnotations({ title: 'Set Vibo profile photo', readOnly: false }),
15
+ annotations: toolAnnotations({ title: 'Set Vibo profile photo', readOnly: false, destructive: false }),
16
16
  inputSchema: z.object({
17
17
  path: z.string().optional().describe('Absolute path to a local image file (jpg/png). Local/stdio server only.'),
18
18
  fileData: z
package/dist/version.js CHANGED
@@ -2,4 +2,4 @@
2
2
  // literal on the line carrying the release marker; every manifest and the MCP
3
3
  // server banner import VERSION from here, so there is exactly one place to keep
4
4
  // in sync (and one release-please extra-files entry).
5
- export const VERSION = '2.2.0'; // x-release-please-version
5
+ export const VERSION = '2.2.1'; // x-release-please-version
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibo-mcp",
3
- "version": "2.2.0",
3
+ "version": "2.2.1",
4
4
  "mcpName": "io.github.chrischall/vibo-mcp",
5
5
  "description": "Vibo (vibodj.com) MCP server for Claude — host/couple event music planning & management. Developed and maintained by AI (Claude Code).",
6
6
  "author": "Claude Code (AI) <https://www.anthropic.com/claude>",
@@ -46,7 +46,7 @@
46
46
  "typecheck": "tsc -p tsconfig.json --noEmit"
47
47
  },
48
48
  "dependencies": {
49
- "@chrischall/mcp-utils": "^2.0.0",
49
+ "@chrischall/mcp-utils": "^2.2.0",
50
50
  "@fetchproxy/bootstrap": "^3.0.1",
51
51
  "@modelcontextprotocol/server": "^2.0.0",
52
52
  "dotenv": "^17.4.0",
package/server.json CHANGED
@@ -6,12 +6,12 @@
6
6
  "url": "https://github.com/chrischall/vibo-mcp",
7
7
  "source": "github"
8
8
  },
9
- "version": "2.2.0",
9
+ "version": "2.2.1",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "vibo-mcp",
14
- "version": "2.2.0",
14
+ "version": "2.2.1",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },