vibo-mcp 1.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.
- package/.claude-plugin/marketplace.json +29 -0
- package/.claude-plugin/plugin.json +16 -0
- package/.mcp.json +14 -0
- package/LICENSE +21 -0
- package/README.md +62 -0
- package/SKILL.md +79 -0
- package/dist/bundle.js +32018 -0
- package/dist/client.js +219 -0
- package/dist/gql.js +210 -0
- package/dist/index.js +29 -0
- package/dist/tools/events.js +104 -0
- package/dist/tools/notifications.js +51 -0
- package/dist/tools/playlists.js +86 -0
- package/dist/tools/profile.js +19 -0
- package/dist/tools/questions.js +68 -0
- package/dist/tools/sections.js +16 -0
- package/dist/tools/shared.js +33 -0
- package/dist/tools/songs.js +109 -0
- package/dist/version.js +5 -0
- package/package.json +59 -0
- package/server.json +50 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { textResult, toolAnnotations, schemaConfirm } from '@chrischall/mcp-utils';
|
|
3
|
+
import { client } from '../client.js';
|
|
4
|
+
import { GET_PLAYLISTS, GET_PLAYLIST_SONGS, EXPORT_EVENT_TO_SPOTIFY, EXPORT_EVENT_TO_APPLE_MUSIC, } from '../gql.js';
|
|
5
|
+
import { limitSchema, skipSchema, pagination, previewResult } from './shared.js';
|
|
6
|
+
const sourceSchema = z
|
|
7
|
+
.enum(['spotify', 'appleMusic'])
|
|
8
|
+
.describe('Streaming source — must be connected to your Vibo account.');
|
|
9
|
+
export function registerPlaylistTools(server) {
|
|
10
|
+
server.registerTool('vibo_get_playlists', {
|
|
11
|
+
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).",
|
|
12
|
+
annotations: toolAnnotations({ title: 'List connected playlists', readOnly: true }),
|
|
13
|
+
inputSchema: {
|
|
14
|
+
source: sourceSchema,
|
|
15
|
+
q: z.string().optional().describe('Filter playlists by name.'),
|
|
16
|
+
limit: limitSchema,
|
|
17
|
+
skip: skipSchema,
|
|
18
|
+
},
|
|
19
|
+
}, async ({ source, q, limit, skip }) => {
|
|
20
|
+
const data = await client.gql(GET_PLAYLISTS, {
|
|
21
|
+
source,
|
|
22
|
+
pagination: pagination(limit, skip),
|
|
23
|
+
...(q ? { filter: { q } } : {}),
|
|
24
|
+
});
|
|
25
|
+
return textResult(data.getPlaylists);
|
|
26
|
+
});
|
|
27
|
+
server.registerTool('vibo_get_playlist_songs', {
|
|
28
|
+
description: 'List the tracks in one of your connected-service playlists.',
|
|
29
|
+
annotations: toolAnnotations({ title: 'Get playlist tracks', readOnly: true }),
|
|
30
|
+
inputSchema: {
|
|
31
|
+
playlistId: z.string().describe('Playlist id from vibo_get_playlists.'),
|
|
32
|
+
source: sourceSchema,
|
|
33
|
+
limit: limitSchema,
|
|
34
|
+
skip: skipSchema,
|
|
35
|
+
},
|
|
36
|
+
}, async ({ playlistId, source, limit, skip }) => {
|
|
37
|
+
const data = await client.gql(GET_PLAYLIST_SONGS, {
|
|
38
|
+
playlistId,
|
|
39
|
+
source,
|
|
40
|
+
pagination: pagination(limit, skip),
|
|
41
|
+
});
|
|
42
|
+
return textResult(data.getPlaylistSongs);
|
|
43
|
+
});
|
|
44
|
+
server.registerTool('vibo_export_event_to_spotify', {
|
|
45
|
+
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.",
|
|
46
|
+
annotations: toolAnnotations({ title: 'Export event to Spotify', readOnly: false }),
|
|
47
|
+
inputSchema: {
|
|
48
|
+
eventId: z.string().describe('Event id.'),
|
|
49
|
+
sectionIds: z.array(z.string()).min(1).describe('Section ids to include (from vibo_list_sections).'),
|
|
50
|
+
title: z.string().optional().describe('Playlist title (defaults to the event title).'),
|
|
51
|
+
onlyFlagged: z.boolean().optional().describe('Export only flagged/do-not-play songs (rarely needed).'),
|
|
52
|
+
confirm: schemaConfirm,
|
|
53
|
+
},
|
|
54
|
+
}, async ({ eventId, sectionIds, title, onlyFlagged, confirm }) => {
|
|
55
|
+
const variables = { eventId, sectionIds };
|
|
56
|
+
if (title !== undefined)
|
|
57
|
+
variables.title = title;
|
|
58
|
+
if (onlyFlagged !== undefined)
|
|
59
|
+
variables.filter = { isFlagged: onlyFlagged };
|
|
60
|
+
if (!confirm)
|
|
61
|
+
return previewResult('exportEventToSpotify', variables);
|
|
62
|
+
const data = await client.gql(EXPORT_EVENT_TO_SPOTIFY, variables);
|
|
63
|
+
return textResult(data.exportEventToSpotify);
|
|
64
|
+
});
|
|
65
|
+
server.registerTool('vibo_export_event_to_apple_music', {
|
|
66
|
+
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.",
|
|
67
|
+
annotations: toolAnnotations({ title: 'Export event to Apple Music', readOnly: false }),
|
|
68
|
+
inputSchema: {
|
|
69
|
+
eventId: z.string().describe('Event id.'),
|
|
70
|
+
sectionIds: z.array(z.string()).min(1).describe('Section ids to include (from vibo_list_sections).'),
|
|
71
|
+
title: z.string().optional().describe('Playlist title (defaults to the event title).'),
|
|
72
|
+
onlyFlagged: z.boolean().optional().describe('Export only flagged/do-not-play songs (rarely needed).'),
|
|
73
|
+
confirm: schemaConfirm,
|
|
74
|
+
},
|
|
75
|
+
}, async ({ eventId, sectionIds, title, onlyFlagged, confirm }) => {
|
|
76
|
+
const variables = { eventId, sectionIds };
|
|
77
|
+
if (title !== undefined)
|
|
78
|
+
variables.title = title;
|
|
79
|
+
if (onlyFlagged !== undefined)
|
|
80
|
+
variables.filter = { isFlagged: onlyFlagged };
|
|
81
|
+
if (!confirm)
|
|
82
|
+
return previewResult('exportEventToAppleMusic', variables);
|
|
83
|
+
const data = await client.gql(EXPORT_EVENT_TO_APPLE_MUSIC, variables);
|
|
84
|
+
return textResult(data.exportEventToAppleMusic);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { textResult, toolAnnotations } from '@chrischall/mcp-utils';
|
|
2
|
+
import { client } from '../client.js';
|
|
3
|
+
import { GET_ME } from '../gql.js';
|
|
4
|
+
export function registerProfileTools(server) {
|
|
5
|
+
server.registerTool('vibo_get_me', {
|
|
6
|
+
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
|
+
annotations: toolAnnotations({ title: 'Get my Vibo profile', readOnly: true }),
|
|
8
|
+
}, async () => {
|
|
9
|
+
const data = await client.gql(GET_ME);
|
|
10
|
+
return textResult(data.me);
|
|
11
|
+
});
|
|
12
|
+
server.registerTool('vibo_healthcheck', {
|
|
13
|
+
description: 'Verify connectivity and authentication to the Vibo API by fetching the current user. Returns ok:true with your account id when credentials work.',
|
|
14
|
+
annotations: toolAnnotations({ title: 'Vibo healthcheck', readOnly: true }),
|
|
15
|
+
}, async () => {
|
|
16
|
+
const data = await client.gql(GET_ME);
|
|
17
|
+
return textResult({ ok: true, userId: data.me._id, email: data.me.email });
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { textResult, toolAnnotations, schemaConfirm, McpToolError } from '@chrischall/mcp-utils';
|
|
3
|
+
import { client } from '../client.js';
|
|
4
|
+
import { LIST_SECTION_QUESTIONS, ANSWER_SECTION_QUESTION } from '../gql.js';
|
|
5
|
+
import { previewResult } from './shared.js';
|
|
6
|
+
export function registerQuestionTools(server) {
|
|
7
|
+
server.registerTool('vibo_list_section_questions', {
|
|
8
|
+
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.",
|
|
9
|
+
annotations: toolAnnotations({ title: 'List Vibo section questions', readOnly: true }),
|
|
10
|
+
inputSchema: {
|
|
11
|
+
eventId: z.string().describe('Event id.'),
|
|
12
|
+
sectionId: z.string().describe('Section id (from vibo_list_sections).'),
|
|
13
|
+
},
|
|
14
|
+
}, async ({ eventId, sectionId }) => {
|
|
15
|
+
const data = await client.gql(LIST_SECTION_QUESTIONS, {
|
|
16
|
+
eventId,
|
|
17
|
+
sectionId,
|
|
18
|
+
});
|
|
19
|
+
return textResult(data.getEventSectionQuestionsV2);
|
|
20
|
+
});
|
|
21
|
+
server.registerTool('vibo_answer_question', {
|
|
22
|
+
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.",
|
|
23
|
+
annotations: toolAnnotations({ title: 'Answer Vibo question', readOnly: false }),
|
|
24
|
+
inputSchema: {
|
|
25
|
+
eventId: z.string().describe('Event id.'),
|
|
26
|
+
sectionId: z.string().describe('Section id.'),
|
|
27
|
+
questionId: z.string().describe('Question _id (from vibo_list_section_questions).'),
|
|
28
|
+
text: z.string().optional().describe('Answer for a text question.'),
|
|
29
|
+
selectedOptions: z
|
|
30
|
+
.array(z.string())
|
|
31
|
+
.optional()
|
|
32
|
+
.describe('Option _ids to select, for radio/checkbox/select questions.'),
|
|
33
|
+
link: z.array(z.string()).optional().describe('URL(s), for a link question.'),
|
|
34
|
+
otherOptionTitle: z
|
|
35
|
+
.string()
|
|
36
|
+
.optional()
|
|
37
|
+
.describe('Free-text value when selecting the question\'s "other" option.'),
|
|
38
|
+
confirm: schemaConfirm,
|
|
39
|
+
},
|
|
40
|
+
}, async ({ eventId, sectionId, questionId, text, selectedOptions, link, otherOptionTitle, confirm }) => {
|
|
41
|
+
// Require at least one PRIMARY answer field. otherOptionTitle is only a
|
|
42
|
+
// modifier for selectedOptions — on its own it is not a valid answer.
|
|
43
|
+
if (text === undefined && selectedOptions === undefined && link === undefined) {
|
|
44
|
+
throw new McpToolError('Provide an answer: text, selectedOptions, or link.', {
|
|
45
|
+
hint: "Match the question's type — text → `text`, radio/checkbox/select → `selectedOptions`, link → `link`.",
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
const answer = {};
|
|
49
|
+
if (text !== undefined)
|
|
50
|
+
answer.text = text;
|
|
51
|
+
if (selectedOptions !== undefined)
|
|
52
|
+
answer.selectedOptions = selectedOptions;
|
|
53
|
+
if (link !== undefined)
|
|
54
|
+
answer.link = link;
|
|
55
|
+
if (otherOptionTitle !== undefined)
|
|
56
|
+
answer.otherOptionTitle = otherOptionTitle;
|
|
57
|
+
const payload = { answer };
|
|
58
|
+
if (!confirm)
|
|
59
|
+
return previewResult('answerEventSectionQuestionV2', { eventId, sectionId, questionId, payload });
|
|
60
|
+
const data = await client.gql(ANSWER_SECTION_QUESTION, {
|
|
61
|
+
eventId,
|
|
62
|
+
sectionId,
|
|
63
|
+
questionId,
|
|
64
|
+
payload,
|
|
65
|
+
});
|
|
66
|
+
return textResult(data.answerEventSectionQuestionV2);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { textResult, toolAnnotations } from '@chrischall/mcp-utils';
|
|
3
|
+
import { client } from '../client.js';
|
|
4
|
+
import { LIST_SECTIONS } from '../gql.js';
|
|
5
|
+
export function registerSectionTools(server) {
|
|
6
|
+
server.registerTool('vibo_list_sections', {
|
|
7
|
+
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.",
|
|
8
|
+
annotations: toolAnnotations({ title: 'List Vibo event sections', readOnly: true }),
|
|
9
|
+
inputSchema: {
|
|
10
|
+
eventId: z.string().describe('Event id (from vibo_list_events).'),
|
|
11
|
+
},
|
|
12
|
+
}, async ({ eventId }) => {
|
|
13
|
+
const data = await client.gql(LIST_SECTIONS, { eventId });
|
|
14
|
+
return textResult(data.sections);
|
|
15
|
+
});
|
|
16
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { textResult } from '@chrischall/mcp-utils';
|
|
3
|
+
/** Pagination knobs shared by the list tools (maps to Vibo's PaginationInput). */
|
|
4
|
+
export const limitSchema = z
|
|
5
|
+
.number()
|
|
6
|
+
.int()
|
|
7
|
+
.min(1)
|
|
8
|
+
.max(100)
|
|
9
|
+
.optional()
|
|
10
|
+
.describe('Max items to return (default 20).');
|
|
11
|
+
export const skipSchema = z
|
|
12
|
+
.number()
|
|
13
|
+
.int()
|
|
14
|
+
.min(0)
|
|
15
|
+
.optional()
|
|
16
|
+
.describe('Number of items to skip, for paging (default 0).');
|
|
17
|
+
/** Build a Vibo PaginationInput from optional limit/skip with sane defaults. */
|
|
18
|
+
export function pagination(limit, skip) {
|
|
19
|
+
return { skip: skip ?? 0, limit: limit ?? 20 };
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Dry-run response for a confirm-gated write. Returned WITHOUT making any
|
|
23
|
+
* network call when `confirm` is not `true`, so the caller can see exactly what
|
|
24
|
+
* would be sent before committing.
|
|
25
|
+
*/
|
|
26
|
+
export function previewResult(action, willSend) {
|
|
27
|
+
return textResult({
|
|
28
|
+
preview: true,
|
|
29
|
+
action,
|
|
30
|
+
willSend,
|
|
31
|
+
note: 'No changes were made. Re-run with confirm: true to execute.',
|
|
32
|
+
});
|
|
33
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { textResult, toolAnnotations, schemaConfirm } from '@chrischall/mcp-utils';
|
|
3
|
+
import { client } from '../client.js';
|
|
4
|
+
import { GET_SECTION_SONGS, SEARCH_SONGS, ADD_SONG_TO_SECTION, TOGGLE_LIKE } from '../gql.js';
|
|
5
|
+
import { limitSchema, skipSchema, pagination, previewResult } from './shared.js';
|
|
6
|
+
export function registerSongTools(server) {
|
|
7
|
+
server.registerTool('vibo_get_section_songs', {
|
|
8
|
+
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.",
|
|
9
|
+
annotations: toolAnnotations({ title: 'Get Vibo section songs', readOnly: true }),
|
|
10
|
+
inputSchema: {
|
|
11
|
+
eventId: z.string().describe('Event id.'),
|
|
12
|
+
sectionId: z.string().describe('Section id (from vibo_list_sections).'),
|
|
13
|
+
q: z.string().optional().describe('Filter songs by text.'),
|
|
14
|
+
isMustPlay: z.boolean().optional(),
|
|
15
|
+
isFlagged: z.boolean().optional().describe('Filter to do-not-play / flagged songs.'),
|
|
16
|
+
sortField: z.enum(['likesCount', 'createdAt', 'title']).optional(),
|
|
17
|
+
sortDirection: z.enum(['asc', 'desc']).optional(),
|
|
18
|
+
limit: limitSchema,
|
|
19
|
+
skip: skipSchema,
|
|
20
|
+
},
|
|
21
|
+
}, async ({ eventId, sectionId, q, isMustPlay, isFlagged, sortField, sortDirection, limit, skip }) => {
|
|
22
|
+
const filter = {};
|
|
23
|
+
if (q !== undefined)
|
|
24
|
+
filter.q = q;
|
|
25
|
+
if (isMustPlay !== undefined)
|
|
26
|
+
filter.isMustPlay = isMustPlay;
|
|
27
|
+
if (isFlagged !== undefined)
|
|
28
|
+
filter.isFlagged = isFlagged;
|
|
29
|
+
const variables = {
|
|
30
|
+
eventId,
|
|
31
|
+
sectionId,
|
|
32
|
+
pagination: pagination(limit, skip),
|
|
33
|
+
...(Object.keys(filter).length ? { filter } : {}),
|
|
34
|
+
...(sortField ? { sort: { field: sortField, direction: sortDirection ?? 'desc' } } : {}),
|
|
35
|
+
};
|
|
36
|
+
const data = await client.gql(GET_SECTION_SONGS, variables);
|
|
37
|
+
return textResult(data.getSectionSongs);
|
|
38
|
+
});
|
|
39
|
+
server.registerTool('vibo_search_songs', {
|
|
40
|
+
description: "Search for songs to add to a section. source 'searchField' (default) searches Vibo's catalog; 'spotify' searches your connected Spotify. Returns songUrl/viboSongId/title/artist to pass to vibo_add_song_to_section.",
|
|
41
|
+
annotations: toolAnnotations({ title: 'Search Vibo songs', readOnly: true }),
|
|
42
|
+
inputSchema: {
|
|
43
|
+
eventId: z.string().describe('Event id (search is scoped to an event/section).'),
|
|
44
|
+
sectionId: z.string().describe('Section id the search is for.'),
|
|
45
|
+
query: z.string().describe('Song or artist to search for.'),
|
|
46
|
+
source: z.enum(['searchField', 'spotify']).optional().describe("Search source (default 'searchField')."),
|
|
47
|
+
limit: z.number().int().min(1).max(50).optional().describe('Max results (default 20).'),
|
|
48
|
+
},
|
|
49
|
+
}, async ({ eventId, sectionId, query, source, limit }) => {
|
|
50
|
+
const data = await client.gql(SEARCH_SONGS, {
|
|
51
|
+
eventId,
|
|
52
|
+
sectionId,
|
|
53
|
+
filter: { q: query, source: source ?? 'searchField' },
|
|
54
|
+
limit: limit ?? 20,
|
|
55
|
+
});
|
|
56
|
+
return textResult(data.getSongs);
|
|
57
|
+
});
|
|
58
|
+
server.registerTool('vibo_add_song_to_section', {
|
|
59
|
+
description: 'Add a song to a section. Pass a song from vibo_search_songs (songUrl is required; include viboSongId/title/artist when known). Confirm-gated.',
|
|
60
|
+
annotations: toolAnnotations({ title: 'Add song to Vibo section', readOnly: false }),
|
|
61
|
+
inputSchema: {
|
|
62
|
+
eventId: z.string().describe('Event id.'),
|
|
63
|
+
sectionId: z.string().describe('Section id to add the song to.'),
|
|
64
|
+
songUrl: z.string().describe('The song URL from vibo_search_songs (required).'),
|
|
65
|
+
viboSongId: z.string().optional().describe("The song's viboSongId from search, when available."),
|
|
66
|
+
title: z.string().optional(),
|
|
67
|
+
artist: z.string().optional(),
|
|
68
|
+
confirm: schemaConfirm,
|
|
69
|
+
},
|
|
70
|
+
}, async ({ eventId, sectionId, songUrl, viboSongId, title, artist, confirm }) => {
|
|
71
|
+
const song = { songUrl };
|
|
72
|
+
if (viboSongId !== undefined)
|
|
73
|
+
song.viboSongId = viboSongId;
|
|
74
|
+
if (title !== undefined)
|
|
75
|
+
song.title = title;
|
|
76
|
+
if (artist !== undefined)
|
|
77
|
+
song.artist = artist;
|
|
78
|
+
const payload = { song };
|
|
79
|
+
if (!confirm)
|
|
80
|
+
return previewResult('addSongToSection', { eventId, sectionId, payload });
|
|
81
|
+
const data = await client.gql(ADD_SONG_TO_SECTION, {
|
|
82
|
+
eventId,
|
|
83
|
+
sectionId,
|
|
84
|
+
payload,
|
|
85
|
+
});
|
|
86
|
+
return textResult(data.addSongToSection);
|
|
87
|
+
});
|
|
88
|
+
server.registerTool('vibo_toggle_song_like', {
|
|
89
|
+
description: 'Like or unlike a song in a section. Confirm-gated.',
|
|
90
|
+
annotations: toolAnnotations({ title: 'Like/unlike Vibo song', readOnly: false }),
|
|
91
|
+
inputSchema: {
|
|
92
|
+
eventId: z.string().describe('Event id.'),
|
|
93
|
+
sectionId: z.string().describe('Section id.'),
|
|
94
|
+
songId: z.string().describe('Song _id (from vibo_get_section_songs).'),
|
|
95
|
+
liked: z.boolean().describe('true to like, false to unlike.'),
|
|
96
|
+
confirm: schemaConfirm,
|
|
97
|
+
},
|
|
98
|
+
}, async ({ eventId, sectionId, songId, liked, confirm }) => {
|
|
99
|
+
if (!confirm)
|
|
100
|
+
return previewResult('toggleLike', { eventId, sectionId, songId, liked });
|
|
101
|
+
const data = await client.gql(TOGGLE_LIKE, {
|
|
102
|
+
eventId,
|
|
103
|
+
sectionId,
|
|
104
|
+
songId,
|
|
105
|
+
liked,
|
|
106
|
+
});
|
|
107
|
+
return textResult(data.toggleLike);
|
|
108
|
+
});
|
|
109
|
+
}
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Single source of truth for the package version. release-please bumps the
|
|
2
|
+
// literal on the line carrying the release marker; every manifest and the MCP
|
|
3
|
+
// server banner import VERSION from here, so there is exactly one place to keep
|
|
4
|
+
// in sync (and one release-please extra-files entry).
|
|
5
|
+
export const VERSION = '1.0.0'; // x-release-please-version
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vibo-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"mcpName": "io.github.chrischall/vibo-mcp",
|
|
5
|
+
"description": "Vibo (vibodj.com) MCP server for Claude — host/couple event music planning. Developed and maintained by AI (Claude Code).",
|
|
6
|
+
"author": "Claude Code (AI) <https://www.anthropic.com/claude>",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/chrischall/vibo-mcp.git"
|
|
10
|
+
},
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"mcp",
|
|
14
|
+
"model-context-protocol",
|
|
15
|
+
"claude",
|
|
16
|
+
"ai",
|
|
17
|
+
"vibo",
|
|
18
|
+
"vibodj",
|
|
19
|
+
"wedding",
|
|
20
|
+
"dj",
|
|
21
|
+
"event-planning",
|
|
22
|
+
"playlist",
|
|
23
|
+
"music",
|
|
24
|
+
"spotify",
|
|
25
|
+
"apple-music"
|
|
26
|
+
],
|
|
27
|
+
"type": "module",
|
|
28
|
+
"bin": {
|
|
29
|
+
"vibo-mcp": "dist/index.js"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist",
|
|
33
|
+
".claude-plugin",
|
|
34
|
+
"SKILL.md",
|
|
35
|
+
".mcp.json",
|
|
36
|
+
"server.json"
|
|
37
|
+
],
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsc && npm run bundle",
|
|
40
|
+
"bundle": "esbuild src/index.ts --bundle --platform=node --format=esm --external:dotenv --outfile=dist/bundle.js",
|
|
41
|
+
"dev": "node dist/index.js",
|
|
42
|
+
"test": "vitest run",
|
|
43
|
+
"test:watch": "vitest",
|
|
44
|
+
"test:coverage": "vitest run --coverage"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@chrischall/mcp-utils": "^0.10.4",
|
|
48
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
49
|
+
"dotenv": "^17.4.0",
|
|
50
|
+
"zod": "^4.4.2"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@types/node": "^25.5.2",
|
|
54
|
+
"@vitest/coverage-v8": "^4.1.2",
|
|
55
|
+
"esbuild": "^0.28.0",
|
|
56
|
+
"typescript": "^6.0.2",
|
|
57
|
+
"vitest": "^4.1.2"
|
|
58
|
+
}
|
|
59
|
+
}
|
package/server.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.chrischall/vibo-mcp",
|
|
4
|
+
"description": "Vibo (vibodj.com) MCP — plan event music: events, timeline, song requests, playlist export",
|
|
5
|
+
"repository": {
|
|
6
|
+
"url": "https://github.com/chrischall/vibo-mcp",
|
|
7
|
+
"source": "github"
|
|
8
|
+
},
|
|
9
|
+
"version": "1.0.0",
|
|
10
|
+
"packages": [
|
|
11
|
+
{
|
|
12
|
+
"registryType": "npm",
|
|
13
|
+
"identifier": "vibo-mcp",
|
|
14
|
+
"version": "1.0.0",
|
|
15
|
+
"transport": {
|
|
16
|
+
"type": "stdio"
|
|
17
|
+
},
|
|
18
|
+
"environmentVariables": [
|
|
19
|
+
{
|
|
20
|
+
"name": "VIBO_EMAIL",
|
|
21
|
+
"description": "Vibo account email (or use VIBO_ACCESS_TOKEN for SSO accounts)",
|
|
22
|
+
"isRequired": false,
|
|
23
|
+
"format": "string",
|
|
24
|
+
"isSecret": false
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"name": "VIBO_PASSWORD",
|
|
28
|
+
"description": "Vibo account password",
|
|
29
|
+
"isRequired": false,
|
|
30
|
+
"format": "string",
|
|
31
|
+
"isSecret": true
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "VIBO_ACCESS_TOKEN",
|
|
35
|
+
"description": "Captured x-token from a signed-in web.vibodj.com session (SSO alternative)",
|
|
36
|
+
"isRequired": false,
|
|
37
|
+
"format": "string",
|
|
38
|
+
"isSecret": true
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"name": "VIBO_REFRESH_TOKEN",
|
|
42
|
+
"description": "Matching x-refresh-token so the session can renew",
|
|
43
|
+
"isRequired": false,
|
|
44
|
+
"format": "string",
|
|
45
|
+
"isSecret": true
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
}
|