vibo-mcp 1.8.2 → 2.0.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.
@@ -7,13 +7,13 @@ export function registerCollaborationTools(server, client) {
7
7
  server.registerTool('vibo_list_event_users', {
8
8
  description: "List the hosts and guests on an event. With no usersType, returns both groups merged ({hosts, guests, hostsCount, guestsCount}) and `limit`/`skip` apply per group; with usersType, returns that one group's page.",
9
9
  annotations: toolAnnotations({ title: 'List Vibo event users', readOnly: true }),
10
- inputSchema: {
10
+ inputSchema: z.object({
11
11
  eventId: z.string().describe('Event id.'),
12
12
  usersType: z.enum(['host', 'guest']).optional().describe('Filter to only hosts or only guests.'),
13
13
  view: viewArg(),
14
14
  limit: limitSchema.describe('Max items to return (default 20). Applies per group when usersType is omitted.'),
15
15
  skip: skipSchema,
16
- },
16
+ }),
17
17
  }, async ({ eventId, usersType, limit, skip, view }) => {
18
18
  const page = pagination(limit, skip);
19
19
  if (usersType) {
@@ -43,13 +43,13 @@ export function registerCollaborationTools(server, client) {
43
43
  server.registerTool('vibo_invite_users', {
44
44
  description: 'Invite people to an event by email (as host or guest). Confirm-gated.',
45
45
  annotations: toolAnnotations({ title: 'Invite Vibo event users', readOnly: false }),
46
- inputSchema: {
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.'),
49
49
  text: z.string().describe('Personal message included in the invite.'),
50
50
  emails: z.array(z.string().email()).min(1).describe('Email addresses to invite.'),
51
51
  confirm: schemaConfirm,
52
- },
52
+ }),
53
53
  }, async ({ eventId, type, text, emails, confirm }) => {
54
54
  const variables = { eventId, type, text, emails };
55
55
  if (!confirm)
@@ -60,12 +60,12 @@ export function registerCollaborationTools(server, client) {
60
60
  server.registerTool('vibo_change_user_role', {
61
61
  description: "Change an event member's role between host and guest. Confirm-gated.",
62
62
  annotations: toolAnnotations({ title: 'Change Vibo user role', readOnly: false }),
63
- inputSchema: {
63
+ inputSchema: z.object({
64
64
  eventId: z.string().describe('Event id.'),
65
65
  userId: z.string().describe('Id of the member to update.'),
66
66
  type: z.enum(['host', 'guest']).describe('New role for the member.'),
67
67
  confirm: schemaConfirm,
68
- },
68
+ }),
69
69
  }, async ({ eventId, userId, type, confirm }) => {
70
70
  const variables = { eventId, userId, type };
71
71
  if (!confirm)
@@ -76,11 +76,11 @@ export function registerCollaborationTools(server, client) {
76
76
  server.registerTool('vibo_remove_user', {
77
77
  description: 'Remove a member from an event. Confirm-gated.',
78
78
  annotations: toolAnnotations({ title: 'Remove Vibo event user', readOnly: false }),
79
- inputSchema: {
79
+ inputSchema: z.object({
80
80
  eventId: z.string().describe('Event id.'),
81
81
  userId: z.string().describe('Id of the member to remove.'),
82
82
  confirm: schemaConfirm,
83
- },
83
+ }),
84
84
  }, async ({ eventId, userId, confirm }) => {
85
85
  const variables = { eventId, userId };
86
86
  if (!confirm)
@@ -6,13 +6,13 @@ 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
8
  annotations: toolAnnotations({ title: 'Comment on Vibo song', readOnly: false }),
9
- inputSchema: {
9
+ inputSchema: z.object({
10
10
  eventId: z.string().describe('Event id.'),
11
11
  sectionId: z.string().describe('Section id.'),
12
12
  songId: z.string().describe('Song _id (from vibo_get_section_songs).'),
13
13
  message: z.string().describe('The comment text.'),
14
14
  confirm: schemaConfirm,
15
- },
15
+ }),
16
16
  }, async ({ eventId, sectionId, songId, message, confirm }) => {
17
17
  const vars = { eventId, sectionId, songId, payload: { message } };
18
18
  if (!confirm)
@@ -23,13 +23,13 @@ export function registerCommentTools(server, client) {
23
23
  server.registerTool('vibo_delete_song_comment', {
24
24
  description: 'Delete a comment on a song. Confirm-gated.',
25
25
  annotations: toolAnnotations({ title: 'Delete Vibo song comment', readOnly: false }),
26
- inputSchema: {
26
+ inputSchema: z.object({
27
27
  eventId: z.string().describe('Event id.'),
28
28
  sectionId: z.string().describe('Section id.'),
29
29
  songId: z.string().describe('Song _id (from vibo_get_section_songs).'),
30
30
  commentId: z.string().describe('Comment _id to delete.'),
31
31
  confirm: schemaConfirm,
32
- },
32
+ }),
33
33
  }, async ({ eventId, sectionId, songId, commentId, confirm }) => {
34
34
  const vars = { eventId, sectionId, songId, commentId };
35
35
  if (!confirm)
@@ -40,12 +40,12 @@ export function registerCommentTools(server, client) {
40
40
  server.registerTool('vibo_comment_on_section', {
41
41
  description: 'Leave a comment on a timeline section. Confirm-gated.',
42
42
  annotations: toolAnnotations({ title: 'Comment on Vibo section', readOnly: false }),
43
- inputSchema: {
43
+ inputSchema: z.object({
44
44
  eventId: z.string().describe('Event id.'),
45
45
  sectionId: z.string().describe('Section id (from vibo_list_sections).'),
46
46
  message: z.string().describe('The comment text.'),
47
47
  confirm: schemaConfirm,
48
- },
48
+ }),
49
49
  }, async ({ eventId, sectionId, message, confirm }) => {
50
50
  const vars = { eventId, sectionId, payload: { message } };
51
51
  if (!confirm)
@@ -56,12 +56,12 @@ export function registerCommentTools(server, client) {
56
56
  server.registerTool('vibo_delete_section_comment', {
57
57
  description: 'Delete a comment on a timeline section. Confirm-gated.',
58
58
  annotations: toolAnnotations({ title: 'Delete Vibo section comment', readOnly: false }),
59
- inputSchema: {
59
+ inputSchema: z.object({
60
60
  eventId: z.string().describe('Event id.'),
61
61
  sectionId: z.string().describe('Section id (from vibo_list_sections).'),
62
62
  commentId: z.string().describe('Comment _id to delete.'),
63
63
  confirm: schemaConfirm,
64
- },
64
+ }),
65
65
  }, async ({ eventId, sectionId, commentId, confirm }) => {
66
66
  const vars = { eventId, sectionId, commentId };
67
67
  if (!confirm)
@@ -6,12 +6,12 @@ export function registerEventTools(server, client) {
6
6
  server.registerTool('vibo_list_events', {
7
7
  description: "List the events you're part of (as host or guest). Defaults to upcoming events; pass past:true for events that have already happened. Optionally filter by a search query.",
8
8
  annotations: toolAnnotations({ title: 'List Vibo events', readOnly: true }),
9
- inputSchema: {
9
+ inputSchema: z.object({
10
10
  past: z.boolean().optional().describe('Return past events instead of upcoming (default false).'),
11
11
  q: z.string().optional().describe('Search events by title.'),
12
12
  limit: limitSchema,
13
13
  skip: skipSchema,
14
- },
14
+ }),
15
15
  }, async ({ past, q, limit, skip }) => {
16
16
  const variables = {
17
17
  pagination: pagination(limit, skip),
@@ -24,9 +24,9 @@ export function registerEventTools(server, client) {
24
24
  server.registerTool('vibo_get_event', {
25
25
  description: 'Get full details for one event: title, date/timezone, location, your role, lock status, playlist size, and section/question progress. Use vibo_list_sections for the timeline.',
26
26
  annotations: toolAnnotations({ title: 'Get Vibo event', readOnly: true }),
27
- inputSchema: {
27
+ inputSchema: z.object({
28
28
  eventId: z.string().describe('Event id (the _id from vibo_list_events).'),
29
- },
29
+ }),
30
30
  }, async ({ eventId }) => {
31
31
  const data = await client.gql(GET_EVENT, { eventId });
32
32
  return minifiedResult(data.event);
@@ -34,12 +34,12 @@ export function registerEventTools(server, client) {
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
36
  annotations: toolAnnotations({ title: 'Join Vibo event', readOnly: false }),
37
- inputSchema: {
37
+ inputSchema: z.object({
38
38
  link: z
39
39
  .string()
40
40
  .describe('The full event share URL (vibodj.app.link/... or web.vibodj.com/...) or the bare join hash.'),
41
41
  confirm: schemaConfirm,
42
- },
42
+ }),
43
43
  }, async ({ link, confirm }) => {
44
44
  const isUrl = /^https?:\/\//i.test(link);
45
45
  if (!confirm) {
@@ -57,10 +57,10 @@ export function registerEventTools(server, client) {
57
57
  server.registerTool('vibo_leave_event', {
58
58
  description: 'Leave an event you previously joined. Confirm-gated.',
59
59
  annotations: toolAnnotations({ title: 'Leave Vibo event', readOnly: false }),
60
- inputSchema: {
60
+ inputSchema: z.object({
61
61
  eventId: z.string().describe('Event id to leave.'),
62
62
  confirm: schemaConfirm,
63
- },
63
+ }),
64
64
  }, async ({ eventId, confirm }) => {
65
65
  if (!confirm)
66
66
  return previewResult('leaveEvent', { eventId });
@@ -70,7 +70,7 @@ export function registerEventTools(server, client) {
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
72
  annotations: toolAnnotations({ title: 'Add Vibo event contact', readOnly: false }),
73
- inputSchema: {
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."),
76
76
  email: z.string().email().describe('Contact email (required by Vibo).'),
@@ -79,7 +79,7 @@ export function registerEventTools(server, client) {
79
79
  phoneCode: z.string().optional().describe('Country calling code, e.g. "1".'),
80
80
  phoneNumber: z.string().optional(),
81
81
  confirm: schemaConfirm,
82
- },
82
+ }),
83
83
  }, async ({ eventId, role, email, firstName, lastName, phoneCode, phoneNumber, confirm }) => {
84
84
  const payload = { role, email };
85
85
  if (firstName !== undefined)
@@ -7,12 +7,12 @@ export function registerIdeasTools(server, client) {
7
7
  server.registerTool('vibo_list_section_song_ideas', {
8
8
  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.",
9
9
  annotations: toolAnnotations({ title: 'List Vibo section song ideas', readOnly: true }),
10
- inputSchema: {
10
+ inputSchema: z.object({
11
11
  eventId: z.string().describe('Event id.'),
12
12
  sectionId: z.string().describe('Section id (from vibo_list_sections).'),
13
13
  limit: limitSchema,
14
14
  skip: skipSchema,
15
- },
15
+ }),
16
16
  }, async ({ eventId, sectionId, limit, skip }) => {
17
17
  const data = await client.gql(LIST_SECTION_SONG_IDEAS, {
18
18
  eventId,
@@ -24,14 +24,14 @@ export function registerIdeasTools(server, client) {
24
24
  server.registerTool('vibo_list_song_ideas_songs', {
25
25
  description: 'List the suggested songs inside a song-idea collection (returns songUrl/viboSongId/title/artist to pass to vibo_add_song_to_section).',
26
26
  annotations: toolAnnotations({ title: 'List Vibo song-idea songs', readOnly: true }),
27
- inputSchema: {
27
+ inputSchema: z.object({
28
28
  eventId: z.string().describe('Event id.'),
29
29
  sectionId: z.string().describe('Section id (from vibo_list_sections).'),
30
30
  songIdeasId: z.string().describe('The _id from vibo_list_section_song_ideas.'),
31
31
  view: viewArg(),
32
32
  limit: limitSchema,
33
33
  skip: skipSchema,
34
- },
34
+ }),
35
35
  }, async ({ eventId, sectionId, songIdeasId, limit, skip, view }) => {
36
36
  const data = await client.gql(LIST_SONG_IDEAS_SONGS, {
37
37
  eventId,
@@ -6,7 +6,7 @@ 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
8
  annotations: toolAnnotations({ title: 'Import playlist to section', readOnly: false }),
9
- inputSchema: {
9
+ inputSchema: z.object({
10
10
  eventId: z.string().describe('Event id.'),
11
11
  sectionId: z.string().describe('Section id (from vibo_list_sections).'),
12
12
  source: z
@@ -19,7 +19,7 @@ export function registerImportTools(server, client) {
19
19
  .describe('Track ids (from vibo_get_playlist_songs) to import.'),
20
20
  tracksToIgnore: z.array(z.string()).optional().describe('Track ids to skip.'),
21
21
  confirm: schemaConfirm,
22
- },
22
+ }),
23
23
  }, async ({ eventId, sectionId, source, playlistId, tracksToAdd, tracksToIgnore, confirm }) => {
24
24
  const vars = {
25
25
  eventId,
@@ -7,11 +7,11 @@ export function registerNotificationTools(server, client) {
7
7
  server.registerTool('vibo_list_notifications', {
8
8
  description: 'List your Vibo notifications (song additions, comments, DJ updates, etc.) with read state and linked event/section ids.',
9
9
  annotations: toolAnnotations({ title: 'List Vibo notifications', readOnly: true }),
10
- inputSchema: {
10
+ inputSchema: z.object({
11
11
  view: viewArg(),
12
12
  limit: limitSchema,
13
13
  skip: skipSchema,
14
- },
14
+ }),
15
15
  }, async ({ limit, skip, view }) => {
16
16
  const data = await client.gql(GET_NOTIFICATIONS, {
17
17
  pagination: pagination(limit, skip),
@@ -28,11 +28,11 @@ export function registerNotificationTools(server, client) {
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
30
  annotations: toolAnnotations({ title: 'Mark notifications read', readOnly: false }),
31
- inputSchema: {
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.'),
34
34
  confirm: schemaConfirm,
35
- },
35
+ }),
36
36
  }, async ({ notificationIds, readAll, confirm }) => {
37
37
  if (!notificationIds?.length && !readAll) {
38
38
  throw new McpToolError('Provide notificationIds or set readAll:true.', {
@@ -9,12 +9,12 @@ export function registerPlaylistTools(server, client) {
9
9
  server.registerTool('vibo_get_playlists', {
10
10
  description: "List your playlists from a connected streaming service (Spotify or Apple Music) so you can import songs from them. Requires that source to be connected (see vibo_get_me).",
11
11
  annotations: toolAnnotations({ title: 'List connected playlists', readOnly: true }),
12
- inputSchema: {
12
+ inputSchema: z.object({
13
13
  source: sourceSchema,
14
14
  q: z.string().optional().describe('Filter playlists by name.'),
15
15
  limit: limitSchema,
16
16
  skip: skipSchema,
17
- },
17
+ }),
18
18
  }, async ({ source, q, limit, skip }) => {
19
19
  const data = await client.gql(GET_PLAYLISTS, {
20
20
  source,
@@ -26,12 +26,12 @@ export function registerPlaylistTools(server, client) {
26
26
  server.registerTool('vibo_get_playlist_songs', {
27
27
  description: 'List the tracks in one of your connected-service playlists.',
28
28
  annotations: toolAnnotations({ title: 'Get playlist tracks', readOnly: true }),
29
- inputSchema: {
29
+ inputSchema: z.object({
30
30
  playlistId: z.string().describe('Playlist id from vibo_get_playlists.'),
31
31
  source: sourceSchema,
32
32
  limit: limitSchema,
33
33
  skip: skipSchema,
34
- },
34
+ }),
35
35
  }, async ({ playlistId, source, limit, skip }) => {
36
36
  const data = await client.gql(GET_PLAYLIST_SONGS, {
37
37
  playlistId,
@@ -43,13 +43,13 @@ export function registerPlaylistTools(server, client) {
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
45
  annotations: toolAnnotations({ title: 'Export event to Spotify', readOnly: false }),
46
- inputSchema: {
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).'),
49
49
  title: z.string().optional().describe('Playlist title (defaults to the event title).'),
50
50
  onlyFlagged: z.boolean().optional().describe('Export only flagged/do-not-play songs (rarely needed).'),
51
51
  confirm: schemaConfirm,
52
- },
52
+ }),
53
53
  }, async ({ eventId, sectionIds, title, onlyFlagged, confirm }) => {
54
54
  const variables = { eventId, sectionIds };
55
55
  if (title !== undefined)
@@ -64,13 +64,13 @@ export function registerPlaylistTools(server, client) {
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
66
  annotations: toolAnnotations({ title: 'Export event to Apple Music', readOnly: false }),
67
- inputSchema: {
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).'),
70
70
  title: z.string().optional().describe('Playlist title (defaults to the event title).'),
71
71
  onlyFlagged: z.boolean().optional().describe('Export only flagged/do-not-play songs (rarely needed).'),
72
72
  confirm: schemaConfirm,
73
- },
73
+ }),
74
74
  }, async ({ eventId, sectionIds, title, onlyFlagged, confirm }) => {
75
75
  const variables = { eventId, sectionIds };
76
76
  if (title !== undefined)
@@ -1,3 +1,4 @@
1
+ import { z } from 'zod';
1
2
  import { minifiedResult, toolAnnotations } from '@chrischall/mcp-utils';
2
3
  import { GET_ME } from '../gql.js';
3
4
  import { viewArg, viewResponse } from '../view.js';
@@ -5,7 +6,7 @@ export function registerProfileTools(server, client) {
5
6
  server.registerTool('vibo_get_me', {
6
7
  description: "Get the signed-in Vibo user's profile (id, name, email, phone, locale, and whether Spotify/Apple Music are connected). Use the returned _id to recognize your own songs/contacts.",
7
8
  annotations: toolAnnotations({ title: 'Get my Vibo profile', readOnly: true }),
8
- inputSchema: { view: viewArg() },
9
+ inputSchema: z.object({ view: viewArg() }),
9
10
  }, async ({ view }) => {
10
11
  const data = await client.gql(GET_ME);
11
12
  return viewResponse(view, data.me);
@@ -12,10 +12,10 @@ export function registerQuestionTools(server, client, resolveUpload = nodeUpload
12
12
  server.registerTool('vibo_list_section_questions', {
13
13
  description: "List the DJ's planning questions for a section, with each question's type (text/radio/checkbox/select/link/header), available options, whether it's answered, the current answer, and overall progress. Use the question _id (and option _ids) with vibo_answer_question.",
14
14
  annotations: toolAnnotations({ title: 'List Vibo section questions', readOnly: true }),
15
- inputSchema: {
15
+ inputSchema: z.object({
16
16
  eventId: z.string().describe('Event id.'),
17
17
  sectionId: z.string().describe('Section id (from vibo_list_sections).'),
18
- },
18
+ }),
19
19
  }, async ({ eventId, sectionId }) => {
20
20
  const data = await client.gql(LIST_SECTION_QUESTIONS, {
21
21
  eventId,
@@ -26,7 +26,7 @@ export function registerQuestionTools(server, client, resolveUpload = nodeUpload
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
28
  annotations: toolAnnotations({ title: 'Answer Vibo question', readOnly: false }),
29
- inputSchema: {
29
+ inputSchema: z.object({
30
30
  eventId: z.string().describe('Event id.'),
31
31
  sectionId: z.string().describe('Section id.'),
32
32
  questionId: z.string().describe('Question _id (from vibo_list_section_questions).'),
@@ -57,7 +57,7 @@ export function registerQuestionTools(server, client, resolveUpload = nodeUpload
57
57
  .optional()
58
58
  .describe('Inline base64 files, for a file-attachment question — use these when the server cannot read your filesystem.'),
59
59
  confirm: schemaConfirm,
60
- },
60
+ }),
61
61
  }, async ({ eventId, sectionId, questionId, text, selectedOptions, link, otherOptionTitle, imagePaths, filePaths, images, files, confirm }) => {
62
62
  // Merge local-path and inline-byte file refs (in that order) into one list
63
63
  // per slot. A local caller supplies paths; a remote one supplies inline
@@ -6,7 +6,7 @@ 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
8
  annotations: toolAnnotations({ title: 'Update Vibo section', readOnly: false }),
9
- inputSchema: {
9
+ inputSchema: z.object({
10
10
  eventId: z.string(),
11
11
  sectionId: z.string(),
12
12
  name: z.string().optional(),
@@ -14,7 +14,7 @@ export function registerSectionEditTools(server, client) {
14
14
  note: z.string().optional().describe('note to the DJ for this section'),
15
15
  description: z.string().optional(),
16
16
  confirm: schemaConfirm,
17
- },
17
+ }),
18
18
  }, async ({ eventId, sectionId, name, time, note, description, confirm }) => {
19
19
  const payload = {};
20
20
  if (name !== undefined)
@@ -5,9 +5,9 @@ export function registerSectionTools(server, client) {
5
5
  server.registerTool('vibo_list_sections', {
6
6
  description: "List an event's timeline sections (e.g. Ceremony, First Dance, Dinner, Dancing) with each section's id, name, scheduled time, note, song count and progress. Use a section _id with vibo_get_section_songs / vibo_add_song_to_section.",
7
7
  annotations: toolAnnotations({ title: 'List Vibo event sections', readOnly: true }),
8
- inputSchema: {
8
+ inputSchema: z.object({
9
9
  eventId: z.string().describe('Event id (from vibo_list_events).'),
10
- },
10
+ }),
11
11
  }, async ({ eventId }) => {
12
12
  const data = await client.gql(LIST_SECTIONS, { eventId });
13
13
  return minifiedResult(data.sections);
@@ -6,7 +6,7 @@ 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
8
  annotations: toolAnnotations({ title: 'Remove songs from Vibo section', readOnly: false }),
9
- inputSchema: {
9
+ inputSchema: z.object({
10
10
  eventId: z.string().describe('Event id.'),
11
11
  sectionId: z.string().describe('Section id.'),
12
12
  songIds: z
@@ -14,7 +14,7 @@ export function registerSongManagementTools(server, client) {
14
14
  .min(1)
15
15
  .describe('Song _ids from vibo_get_section_songs.'),
16
16
  confirm: schemaConfirm,
17
- },
17
+ }),
18
18
  }, async ({ eventId, sectionId, songIds, confirm }) => {
19
19
  const vars = { eventId, sectionId, songIds };
20
20
  if (!confirm)
@@ -25,7 +25,7 @@ export function registerSongManagementTools(server, client) {
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
27
  annotations: toolAnnotations({ title: 'Update Vibo section songs', readOnly: false }),
28
- inputSchema: {
28
+ inputSchema: z.object({
29
29
  eventId: z.string().describe('Event id.'),
30
30
  sectionId: z.string().describe('Section id.'),
31
31
  songIds: z
@@ -36,7 +36,7 @@ export function registerSongManagementTools(server, client) {
36
36
  isFlagged: z.boolean().optional().describe('mark do-not-play / flagged'),
37
37
  comment: z.string().optional(),
38
38
  confirm: schemaConfirm,
39
- },
39
+ }),
40
40
  }, async ({ eventId, sectionId, songIds, isMustPlay, isFlagged, comment, confirm }) => {
41
41
  const payload = {};
42
42
  if (isMustPlay !== undefined)
@@ -59,7 +59,7 @@ export function registerSongManagementTools(server, client) {
59
59
  server.registerTool('vibo_move_song', {
60
60
  description: 'Move songs from one section to another. Confirm-gated.',
61
61
  annotations: toolAnnotations({ title: 'Move Vibo section songs', readOnly: false }),
62
- inputSchema: {
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.'),
65
65
  targetSectionId: z.string().describe('Section id to move the songs to.'),
@@ -68,7 +68,7 @@ export function registerSongManagementTools(server, client) {
68
68
  .min(1)
69
69
  .describe('Song _ids from vibo_get_section_songs.'),
70
70
  confirm: schemaConfirm,
71
- },
71
+ }),
72
72
  }, async ({ eventId, sourceSectionId, targetSectionId, songIds, confirm }) => {
73
73
  const vars = { eventId, sourceSectionId, targetSectionId, songIds };
74
74
  if (!confirm)
@@ -79,7 +79,7 @@ export function registerSongManagementTools(server, client) {
79
79
  server.registerTool('vibo_reorder_songs', {
80
80
  description: 'Reorder songs within a section. Confirm-gated.',
81
81
  annotations: toolAnnotations({ title: 'Reorder Vibo section songs', readOnly: false }),
82
- inputSchema: {
82
+ inputSchema: z.object({
83
83
  eventId: z.string().describe('Event id.'),
84
84
  sectionId: z.string().describe('Section id.'),
85
85
  sourceSongIds: z
@@ -91,7 +91,7 @@ export function registerSongManagementTools(server, client) {
91
91
  .optional()
92
92
  .describe('place the moved songs after this song _id; omit for start'),
93
93
  confirm: schemaConfirm,
94
- },
94
+ }),
95
95
  }, async ({ eventId, sectionId, sourceSongIds, targetSongId, confirm }) => {
96
96
  const vars = { eventId, sectionId, sourceSongIds, targetSongId: targetSongId ?? null };
97
97
  if (!confirm)
@@ -8,7 +8,7 @@ export function registerSongTools(server, client) {
8
8
  server.registerTool('vibo_get_section_songs', {
9
9
  description: "List the songs requested in a section, with who added each, like counts, must-play / do-not-play flags, comments, and streaming links. Sort by likesCount, createdAt, or title.",
10
10
  annotations: toolAnnotations({ title: 'Get Vibo section songs', readOnly: true }),
11
- inputSchema: {
11
+ inputSchema: z.object({
12
12
  eventId: z.string().describe('Event id.'),
13
13
  sectionId: z.string().describe('Section id (from vibo_list_sections).'),
14
14
  q: z.string().optional().describe('Filter songs by text.'),
@@ -19,7 +19,7 @@ export function registerSongTools(server, client) {
19
19
  view: viewArg(),
20
20
  limit: limitSchema,
21
21
  skip: skipSchema,
22
- },
22
+ }),
23
23
  }, async ({ eventId, sectionId, q, isMustPlay, isFlagged, sortField, sortDirection, limit, skip, view }) => {
24
24
  const filter = {};
25
25
  if (q !== undefined)
@@ -51,7 +51,7 @@ export function registerSongTools(server, client) {
51
51
  "source 'spotify' searches your connected Spotify (a structured catalog, so the hyphen " +
52
52
  'matters less). Returns songUrl/viboSongId/title/artist for vibo_add_song_to_section.',
53
53
  annotations: toolAnnotations({ title: 'Search Vibo songs', readOnly: true }),
54
- inputSchema: {
54
+ inputSchema: z.object({
55
55
  view: viewArg(),
56
56
  eventId: z.string().describe('Event id (search is scoped to an event/section).'),
57
57
  sectionId: z.string().describe('Section id the search is for.'),
@@ -66,7 +66,7 @@ export function registerSongTools(server, client) {
66
66
  'then the title alone filtered by artist.'),
67
67
  source: z.enum(['searchField', 'spotify']).optional().describe("Search source (default 'searchField')."),
68
68
  limit: z.number().int().min(1).max(50).optional().describe('Max results (default 20).'),
69
- },
69
+ }),
70
70
  }, async ({ eventId, sectionId, query, source, limit, view }) => {
71
71
  const resolvedSource = source ?? 'searchField';
72
72
  const data = await client.gql(SEARCH_SONGS, {
@@ -97,7 +97,7 @@ export function registerSongTools(server, client) {
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
99
  annotations: toolAnnotations({ title: 'Add song to Vibo section', readOnly: false }),
100
- inputSchema: {
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.'),
103
103
  songUrl: z.string().describe('The song URL from vibo_search_songs (required).'),
@@ -105,7 +105,7 @@ export function registerSongTools(server, client) {
105
105
  title: z.string().optional(),
106
106
  artist: z.string().optional(),
107
107
  confirm: schemaConfirm,
108
- },
108
+ }),
109
109
  }, async ({ eventId, sectionId, songUrl, viboSongId, title, artist, confirm }) => {
110
110
  const song = { songUrl };
111
111
  if (viboSongId !== undefined)
@@ -127,13 +127,13 @@ export function registerSongTools(server, client) {
127
127
  server.registerTool('vibo_toggle_song_like', {
128
128
  description: 'Like or unlike a song in a section. Confirm-gated.',
129
129
  annotations: toolAnnotations({ title: 'Like/unlike Vibo song', readOnly: false }),
130
- inputSchema: {
130
+ inputSchema: z.object({
131
131
  eventId: z.string().describe('Event id.'),
132
132
  sectionId: z.string().describe('Section id.'),
133
133
  songId: z.string().describe('Song _id (from vibo_get_section_songs).'),
134
134
  liked: z.boolean().describe('true to like, false to unlike.'),
135
135
  confirm: schemaConfirm,
136
- },
136
+ }),
137
137
  }, async ({ eventId, sectionId, songId, liked, confirm }) => {
138
138
  if (!confirm)
139
139
  return previewResult('toggleLike', { eventId, sectionId, songId, liked });
@@ -13,7 +13,7 @@ export function registerUploadTools(server, client, resolveUpload = nodeUploadRe
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
15
  annotations: toolAnnotations({ title: 'Set Vibo profile photo', readOnly: false }),
16
- inputSchema: {
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
19
19
  .string()
@@ -21,7 +21,7 @@ export function registerUploadTools(server, client, resolveUpload = nodeUploadRe
21
21
  .describe('Base64-encoded image bytes (a `data:` URL prefix is allowed). Use this when the server cannot read your filesystem.'),
22
22
  filename: z.string().optional().describe('Filename for the image when using fileData (default "photo.jpg").'),
23
23
  confirm: schemaConfirm,
24
- },
24
+ }),
25
25
  }, async ({ path, fileData, filename, confirm }) => {
26
26
  if (!path && !fileData) {
27
27
  throw new McpToolError('Provide an image: a local file `path` or inline base64 `fileData`.', {
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 = '1.8.2'; // x-release-please-version
5
+ export const VERSION = '2.0.0'; // x-release-please-version
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibo-mcp",
3
- "version": "1.8.2",
3
+ "version": "2.0.0",
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>",
@@ -38,7 +38,7 @@
38
38
  ],
39
39
  "scripts": {
40
40
  "build": "tsc && npm run bundle",
41
- "bundle": "esbuild src/index.ts --bundle --platform=node --format=esm --external:dotenv --external:@fetchproxy/bootstrap --outfile=dist/bundle.js",
41
+ "bundle": "esbuild src/index.ts --bundle --alias:zod/v4=./node_modules/zod/v4/index.cjs --platform=node --format=esm --external:dotenv --external:@fetchproxy/bootstrap --outfile=dist/bundle.js",
42
42
  "dev": "node dist/index.js",
43
43
  "test": "npm run typecheck && vitest run",
44
44
  "test:watch": "vitest",
@@ -46,13 +46,14 @@
46
46
  "typecheck": "tsc -p tsconfig.json --noEmit"
47
47
  },
48
48
  "dependencies": {
49
- "@chrischall/mcp-utils": "^0.27.1",
49
+ "@chrischall/mcp-utils": "^0.28.0",
50
50
  "@fetchproxy/bootstrap": "^3.0.1",
51
- "@modelcontextprotocol/sdk": "^1.30.0",
51
+ "@modelcontextprotocol/server": "^2.0.0",
52
52
  "dotenv": "^17.4.0",
53
- "zod": "^4.5.4"
53
+ "zod": "^4.6.2"
54
54
  },
55
55
  "devDependencies": {
56
+ "@modelcontextprotocol/client": "^2.0.0",
56
57
  "@types/node": "^26.0.0",
57
58
  "@vitest/coverage-v8": "^5.0.0",
58
59
  "esbuild": "^0.28.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": "1.8.2",
9
+ "version": "2.0.0",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "vibo-mcp",
14
- "version": "1.8.2",
14
+ "version": "2.0.0",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },