vibo-mcp 1.5.3 → 1.5.5
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 +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/dist/bundle.js +87 -60
- package/dist/client.js +5 -6
- package/dist/tools/questions.js +6 -6
- package/dist/tools/session.js +3 -4
- package/dist/tools/shared.js +3 -3
- package/dist/tools/uploads.js +6 -6
- package/dist/upload-source.js +6 -25
- package/dist/version.js +1 -1
- package/mint.yaml +65 -0
- package/package.json +8 -16
- package/server.json +2 -2
- package/dist/vibo-auth.js +0 -34
|
@@ -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": "1.5.
|
|
10
|
+
"version": "1.5.5"
|
|
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": "1.5.
|
|
18
|
+
"version": "1.5.5",
|
|
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": "1.5.
|
|
4
|
+
"version": "1.5.5",
|
|
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
|
@@ -31000,53 +31000,6 @@ var StdioServerTransport = class {
|
|
|
31000
31000
|
}
|
|
31001
31001
|
};
|
|
31002
31002
|
|
|
31003
|
-
// node_modules/@chrischall/mcp-utils/dist/server/index.js
|
|
31004
|
-
async function createMcpServer(opts) {
|
|
31005
|
-
const server = new McpServer({ name: opts.name, version: opts.version });
|
|
31006
|
-
if (opts.banner !== void 0) {
|
|
31007
|
-
console.error(opts.banner);
|
|
31008
|
-
}
|
|
31009
|
-
const deps = opts.deps;
|
|
31010
|
-
for (const register of opts.tools) {
|
|
31011
|
-
await register(server, deps);
|
|
31012
|
-
}
|
|
31013
|
-
return server;
|
|
31014
|
-
}
|
|
31015
|
-
function withGracefulShutdown(server, opts = {}) {
|
|
31016
|
-
const shouldExit = opts.exit ?? true;
|
|
31017
|
-
let shuttingDown = false;
|
|
31018
|
-
const handler = (signal) => {
|
|
31019
|
-
if (shuttingDown)
|
|
31020
|
-
return;
|
|
31021
|
-
shuttingDown = true;
|
|
31022
|
-
void (async () => {
|
|
31023
|
-
try {
|
|
31024
|
-
if (opts.onSignal)
|
|
31025
|
-
await opts.onSignal(signal);
|
|
31026
|
-
await server.close();
|
|
31027
|
-
} catch (err) {
|
|
31028
|
-
console.error(`[mcp-utils] error during graceful shutdown on ${signal}: ${err instanceof Error ? err.message : String(err)}`);
|
|
31029
|
-
} finally {
|
|
31030
|
-
if (shouldExit)
|
|
31031
|
-
process.exit(0);
|
|
31032
|
-
}
|
|
31033
|
-
})();
|
|
31034
|
-
};
|
|
31035
|
-
process.on("SIGINT", () => handler("SIGINT"));
|
|
31036
|
-
process.on("SIGTERM", () => handler("SIGTERM"));
|
|
31037
|
-
}
|
|
31038
|
-
async function runMcp(opts) {
|
|
31039
|
-
const server = await createMcpServer(opts);
|
|
31040
|
-
const shutdown = opts.shutdown ?? true;
|
|
31041
|
-
if (shutdown !== false) {
|
|
31042
|
-
withGracefulShutdown(server, shutdown === true ? {} : shutdown);
|
|
31043
|
-
}
|
|
31044
|
-
const spec = opts.transport ?? "stdio";
|
|
31045
|
-
const transport = spec === "stdio" ? new StdioServerTransport() : spec;
|
|
31046
|
-
await server.connect(transport);
|
|
31047
|
-
return server;
|
|
31048
|
-
}
|
|
31049
|
-
|
|
31050
31003
|
// node_modules/@chrischall/mcp-utils/dist/errors/index.js
|
|
31051
31004
|
var DEFAULT_ERROR_MESSAGE_MAX = 500;
|
|
31052
31005
|
var McpToolError = class extends Error {
|
|
@@ -31109,6 +31062,81 @@ function textResult(data) {
|
|
|
31109
31062
|
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
31110
31063
|
};
|
|
31111
31064
|
}
|
|
31065
|
+
function errorResult(message) {
|
|
31066
|
+
return {
|
|
31067
|
+
content: [{ type: "text", text: redactSecrets(message) }],
|
|
31068
|
+
isError: true
|
|
31069
|
+
};
|
|
31070
|
+
}
|
|
31071
|
+
|
|
31072
|
+
// node_modules/@chrischall/mcp-utils/dist/server/index.js
|
|
31073
|
+
function hintResultOrRethrow(err) {
|
|
31074
|
+
if (err instanceof McpToolError && err.hint) {
|
|
31075
|
+
return errorResult(`${err.message}
|
|
31076
|
+
|
|
31077
|
+
Hint: ${err.hint}`);
|
|
31078
|
+
}
|
|
31079
|
+
throw err;
|
|
31080
|
+
}
|
|
31081
|
+
function surfaceToolHints(server) {
|
|
31082
|
+
const register = server.registerTool.bind(server);
|
|
31083
|
+
server.registerTool = (name, config2, cb) => register(name, config2, (...args) => {
|
|
31084
|
+
let result;
|
|
31085
|
+
try {
|
|
31086
|
+
result = cb(...args);
|
|
31087
|
+
} catch (err) {
|
|
31088
|
+
return hintResultOrRethrow(err);
|
|
31089
|
+
}
|
|
31090
|
+
return result instanceof Promise ? result.catch(hintResultOrRethrow) : result;
|
|
31091
|
+
});
|
|
31092
|
+
}
|
|
31093
|
+
async function createMcpServer(opts) {
|
|
31094
|
+
const server = new McpServer({ name: opts.name, version: opts.version });
|
|
31095
|
+
if (opts.surfaceHints !== false)
|
|
31096
|
+
surfaceToolHints(server);
|
|
31097
|
+
if (opts.banner !== void 0) {
|
|
31098
|
+
console.error(opts.banner);
|
|
31099
|
+
}
|
|
31100
|
+
const deps = opts.deps;
|
|
31101
|
+
for (const register of opts.tools) {
|
|
31102
|
+
await register(server, deps);
|
|
31103
|
+
}
|
|
31104
|
+
return server;
|
|
31105
|
+
}
|
|
31106
|
+
function withGracefulShutdown(server, opts = {}) {
|
|
31107
|
+
const shouldExit = opts.exit ?? true;
|
|
31108
|
+
let shuttingDown = false;
|
|
31109
|
+
const handler = (signal) => {
|
|
31110
|
+
if (shuttingDown)
|
|
31111
|
+
return;
|
|
31112
|
+
shuttingDown = true;
|
|
31113
|
+
void (async () => {
|
|
31114
|
+
try {
|
|
31115
|
+
if (opts.onSignal)
|
|
31116
|
+
await opts.onSignal(signal);
|
|
31117
|
+
await server.close();
|
|
31118
|
+
} catch (err) {
|
|
31119
|
+
console.error(`[mcp-utils] error during graceful shutdown on ${signal}: ${err instanceof Error ? err.message : String(err)}`);
|
|
31120
|
+
} finally {
|
|
31121
|
+
if (shouldExit)
|
|
31122
|
+
process.exit(0);
|
|
31123
|
+
}
|
|
31124
|
+
})();
|
|
31125
|
+
};
|
|
31126
|
+
process.on("SIGINT", () => handler("SIGINT"));
|
|
31127
|
+
process.on("SIGTERM", () => handler("SIGTERM"));
|
|
31128
|
+
}
|
|
31129
|
+
async function runMcp(opts) {
|
|
31130
|
+
const server = await createMcpServer(opts);
|
|
31131
|
+
const shutdown = opts.shutdown ?? true;
|
|
31132
|
+
if (shutdown !== false) {
|
|
31133
|
+
withGracefulShutdown(server, shutdown === true ? {} : shutdown);
|
|
31134
|
+
}
|
|
31135
|
+
const spec = opts.transport ?? "stdio";
|
|
31136
|
+
const transport = spec === "stdio" ? new StdioServerTransport() : spec;
|
|
31137
|
+
await server.connect(transport);
|
|
31138
|
+
return server;
|
|
31139
|
+
}
|
|
31112
31140
|
|
|
31113
31141
|
// node_modules/@chrischall/mcp-utils/dist/config/index.js
|
|
31114
31142
|
var PLACEHOLDER_RE = /^\$\{[^}]*\}$/;
|
|
@@ -31168,7 +31196,7 @@ function toolAnnotations(opts = {}) {
|
|
|
31168
31196
|
}
|
|
31169
31197
|
|
|
31170
31198
|
// src/version.ts
|
|
31171
|
-
var VERSION = "1.5.
|
|
31199
|
+
var VERSION = "1.5.5";
|
|
31172
31200
|
|
|
31173
31201
|
// src/client.ts
|
|
31174
31202
|
import { dirname as dirname2, join as join2 } from "path";
|
|
@@ -31402,7 +31430,7 @@ var ViboClient = class {
|
|
|
31402
31430
|
/**
|
|
31403
31431
|
* Resolve the saved-session fallback and the deferred config error on first
|
|
31404
31432
|
* use. Kept out of the constructor so construction is pure (Worker-safe):
|
|
31405
|
-
* `loadSession()` reads homedir()/the filesystem, which
|
|
31433
|
+
* `loadSession()` reads homedir()/the filesystem, which a sandboxed runtime
|
|
31406
31434
|
* forbids at global scope. Runs its body at most once.
|
|
31407
31435
|
*/
|
|
31408
31436
|
ensureConfigResolved() {
|
|
@@ -31460,10 +31488,9 @@ var ViboClient = class {
|
|
|
31460
31488
|
* path (e.g. "variables.photo" or "variables.payload.answer.images.0") to an
|
|
31461
31489
|
* in-memory {@link UploadFile} (blob + filename); `variables` must carry
|
|
31462
31490
|
* `null` at each of those positions. The bytes arrive already resolved (from
|
|
31463
|
-
* a local file
|
|
31464
|
-
* src/upload-source.ts), so this method never touches the filesystem
|
|
31465
|
-
*
|
|
31466
|
-
* available there). Same auth + single-retry-on-expiry behavior as `gql`.
|
|
31491
|
+
* a local file, or inline base64 when the caller has no filesystem to name —
|
|
31492
|
+
* see src/upload-source.ts), so this method never touches the filesystem
|
|
31493
|
+
* itself. Same auth + single-retry-on-expiry behavior as `gql`.
|
|
31467
31494
|
*/
|
|
31468
31495
|
async gqlUpload(query, variables, files) {
|
|
31469
31496
|
this.ensureConfigResolved();
|
|
@@ -32579,7 +32606,7 @@ function registerQuestionTools(server, client2, resolveUpload = nodeUploadResolv
|
|
|
32579
32606
|
server.registerTool(
|
|
32580
32607
|
"vibo_answer_question",
|
|
32581
32608
|
{
|
|
32582
|
-
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`)
|
|
32609
|
+
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.",
|
|
32583
32610
|
annotations: toolAnnotations({ title: "Answer Vibo question", readOnly: false }),
|
|
32584
32611
|
inputSchema: {
|
|
32585
32612
|
eventId: external_exports.string().describe("Event id."),
|
|
@@ -32591,8 +32618,8 @@ function registerQuestionTools(server, client2, resolveUpload = nodeUploadResolv
|
|
|
32591
32618
|
otherOptionTitle: external_exports.string().optional().describe(`Free-text value when selecting the question's "other" option.`),
|
|
32592
32619
|
imagePaths: external_exports.array(external_exports.string()).optional().describe("Absolute local image file paths, for a photo question (local/stdio server only)."),
|
|
32593
32620
|
filePaths: external_exports.array(external_exports.string()).optional().describe("Absolute local file paths, for a file-attachment question (local/stdio server only)."),
|
|
32594
|
-
images: external_exports.array(inlineFileSchema).optional().describe("Inline base64 images, for a photo question
|
|
32595
|
-
files: external_exports.array(inlineFileSchema).optional().describe("Inline base64 files, for a file-attachment question
|
|
32621
|
+
images: external_exports.array(inlineFileSchema).optional().describe("Inline base64 images, for a photo question \u2014 use these when the server cannot read your filesystem."),
|
|
32622
|
+
files: external_exports.array(inlineFileSchema).optional().describe("Inline base64 files, for a file-attachment question \u2014 use these when the server cannot read your filesystem."),
|
|
32596
32623
|
confirm: schemaConfirm
|
|
32597
32624
|
}
|
|
32598
32625
|
},
|
|
@@ -32609,7 +32636,7 @@ function registerQuestionTools(server, client2, resolveUpload = nodeUploadResolv
|
|
|
32609
32636
|
const hasFiles = fileRefs.length > 0;
|
|
32610
32637
|
if (text === void 0 && selectedOptions === void 0 && link === void 0 && !hasImages && !hasFiles) {
|
|
32611
32638
|
throw new McpToolError("Provide an answer: text, selectedOptions, link, imagePaths/images, or filePaths/files.", {
|
|
32612
|
-
hint: "Match the question's type \u2014 text \u2192 `text`, radio/checkbox/select \u2192 `selectedOptions`, link \u2192 `link`, photo/file \u2192 `imagePaths`/`filePaths`
|
|
32639
|
+
hint: "Match the question's type \u2014 text \u2192 `text`, radio/checkbox/select \u2192 `selectedOptions`, link \u2192 `link`, photo/file \u2192 `imagePaths`/`filePaths` for local files, or `images`/`files` for inline base64."
|
|
32613
32640
|
});
|
|
32614
32641
|
}
|
|
32615
32642
|
const answer = {};
|
|
@@ -33054,11 +33081,11 @@ function registerUploadTools(server, client2, resolveUpload = nodeUploadResolver
|
|
|
33054
33081
|
server.registerTool(
|
|
33055
33082
|
"vibo_set_profile_photo",
|
|
33056
33083
|
{
|
|
33057
|
-
description: "Set your Vibo profile photo from an image.
|
|
33084
|
+
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.",
|
|
33058
33085
|
annotations: toolAnnotations({ title: "Set Vibo profile photo", readOnly: false }),
|
|
33059
33086
|
inputSchema: {
|
|
33060
33087
|
path: external_exports.string().optional().describe("Absolute path to a local image file (jpg/png). Local/stdio server only."),
|
|
33061
|
-
fileData: external_exports.string().optional().describe("Base64-encoded image bytes (a `data:` URL prefix is allowed).
|
|
33088
|
+
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."),
|
|
33062
33089
|
filename: external_exports.string().optional().describe('Filename for the image when using fileData (default "photo.jpg").'),
|
|
33063
33090
|
confirm: schemaConfirm
|
|
33064
33091
|
}
|
|
@@ -33066,7 +33093,7 @@ function registerUploadTools(server, client2, resolveUpload = nodeUploadResolver
|
|
|
33066
33093
|
async ({ path, fileData, filename, confirm }) => {
|
|
33067
33094
|
if (!path && !fileData) {
|
|
33068
33095
|
throw new McpToolError("Provide an image: a local file `path` or inline base64 `fileData`.", {
|
|
33069
|
-
hint: "
|
|
33096
|
+
hint: "Pass `path` for a local file, or `fileData` (base64) if the server cannot read your filesystem."
|
|
33070
33097
|
});
|
|
33071
33098
|
}
|
|
33072
33099
|
if (!confirm) return previewResult("uploadUserPhoto", { photo: path ?? "(inline bytes)" });
|
package/dist/client.js
CHANGED
|
@@ -5,7 +5,7 @@ import { loadSession, saveSession } from './session-store.js';
|
|
|
5
5
|
// Load .env for local dev; silently skip if dotenv is unavailable (e.g. the
|
|
6
6
|
// mcpb bundle, which externalizes dotenv). `override: false` means a
|
|
7
7
|
// host-provided env var always wins over .env. The try/catch additionally
|
|
8
|
-
// guards
|
|
8
|
+
// guards non-Node runtimes, where `import.meta.url`
|
|
9
9
|
// is undefined and `fileURLToPath(undefined)` would otherwise throw at module
|
|
10
10
|
// init (Worker startup validation) — there is no filesystem / .env there anyway.
|
|
11
11
|
try {
|
|
@@ -69,7 +69,7 @@ export class ViboClient {
|
|
|
69
69
|
/**
|
|
70
70
|
* Resolve the saved-session fallback and the deferred config error on first
|
|
71
71
|
* use. Kept out of the constructor so construction is pure (Worker-safe):
|
|
72
|
-
* `loadSession()` reads homedir()/the filesystem, which
|
|
72
|
+
* `loadSession()` reads homedir()/the filesystem, which a sandboxed runtime
|
|
73
73
|
* forbids at global scope. Runs its body at most once.
|
|
74
74
|
*/
|
|
75
75
|
ensureConfigResolved() {
|
|
@@ -133,10 +133,9 @@ export class ViboClient {
|
|
|
133
133
|
* path (e.g. "variables.photo" or "variables.payload.answer.images.0") to an
|
|
134
134
|
* in-memory {@link UploadFile} (blob + filename); `variables` must carry
|
|
135
135
|
* `null` at each of those positions. The bytes arrive already resolved (from
|
|
136
|
-
* a local file
|
|
137
|
-
* src/upload-source.ts), so this method never touches the filesystem
|
|
138
|
-
*
|
|
139
|
-
* available there). Same auth + single-retry-on-expiry behavior as `gql`.
|
|
136
|
+
* a local file, or inline base64 when the caller has no filesystem to name —
|
|
137
|
+
* see src/upload-source.ts), so this method never touches the filesystem
|
|
138
|
+
* itself. Same auth + single-retry-on-expiry behavior as `gql`.
|
|
140
139
|
*/
|
|
141
140
|
async gqlUpload(query, variables, files) {
|
|
142
141
|
this.ensureConfigResolved();
|
package/dist/tools/questions.js
CHANGED
|
@@ -6,7 +6,7 @@ import { previewResult, inlineFileSchema } from './shared.js';
|
|
|
6
6
|
/**
|
|
7
7
|
* `resolveUpload` is the injectable file-source seam for photo/file answers:
|
|
8
8
|
* stdio uses the default `nodeUploadResolver` (local `*Paths`), the hosted
|
|
9
|
-
*
|
|
9
|
+
* remote caller sends inline base64 (`images`/`files`) instead.
|
|
10
10
|
*/
|
|
11
11
|
export function registerQuestionTools(server, client, resolveUpload = nodeUploadResolver) {
|
|
12
12
|
server.registerTool('vibo_list_section_questions', {
|
|
@@ -24,7 +24,7 @@ export function registerQuestionTools(server, client, resolveUpload = nodeUpload
|
|
|
24
24
|
return textResult(data.getEventSectionQuestionsV2);
|
|
25
25
|
});
|
|
26
26
|
server.registerTool('vibo_answer_question', {
|
|
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`)
|
|
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
29
|
inputSchema: {
|
|
30
30
|
eventId: z.string().describe('Event id.'),
|
|
@@ -51,16 +51,16 @@ export function registerQuestionTools(server, client, resolveUpload = nodeUpload
|
|
|
51
51
|
images: z
|
|
52
52
|
.array(inlineFileSchema)
|
|
53
53
|
.optional()
|
|
54
|
-
.describe('Inline base64 images, for a photo question
|
|
54
|
+
.describe('Inline base64 images, for a photo question — use these when the server cannot read your filesystem.'),
|
|
55
55
|
files: z
|
|
56
56
|
.array(inlineFileSchema)
|
|
57
57
|
.optional()
|
|
58
|
-
.describe('Inline base64 files, for a file-attachment question
|
|
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
|
-
// per slot.
|
|
63
|
+
// per slot. A local caller supplies paths; a remote one supplies inline
|
|
64
64
|
// bytes; the injected resolver turns each ref into an in-memory blob.
|
|
65
65
|
const imageRefs = [
|
|
66
66
|
...(imagePaths ?? []).map((path) => ({ path })),
|
|
@@ -76,7 +76,7 @@ export function registerQuestionTools(server, client, resolveUpload = nodeUpload
|
|
|
76
76
|
// modifier for selectedOptions — on its own it is not a valid answer.
|
|
77
77
|
if (text === undefined && selectedOptions === undefined && link === undefined && !hasImages && !hasFiles) {
|
|
78
78
|
throw new McpToolError('Provide an answer: text, selectedOptions, link, imagePaths/images, or filePaths/files.', {
|
|
79
|
-
hint: "Match the question's type — text → `text`, radio/checkbox/select → `selectedOptions`, link → `link`, photo/file → `imagePaths`/`filePaths`
|
|
79
|
+
hint: "Match the question's type — text → `text`, radio/checkbox/select → `selectedOptions`, link → `link`, photo/file → `imagePaths`/`filePaths` for local files, or `images`/`files` for inline base64.",
|
|
80
80
|
});
|
|
81
81
|
}
|
|
82
82
|
const answer = {};
|
package/dist/tools/session.js
CHANGED
|
@@ -2,10 +2,9 @@ import { textResult, toolAnnotations } from '@chrischall/mcp-utils';
|
|
|
2
2
|
import { captureViboSession } from '../auth.js';
|
|
3
3
|
import { saveSession } from '../session-store.js';
|
|
4
4
|
import { GET_ME } from '../gql.js';
|
|
5
|
-
// NB:
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// in the serverless Worker runtime.
|
|
5
|
+
// NB: vibo_capture_session needs the fetchproxy browser bridge and a signed-in
|
|
6
|
+
// browser tab on the same machine, so this registrar is only useful where both
|
|
7
|
+
// exist — a deployment without them should not wire it in.
|
|
9
8
|
export function registerSessionTools(server, client) {
|
|
10
9
|
server.registerTool('vibo_capture_session', {
|
|
11
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.",
|
package/dist/tools/shared.js
CHANGED
|
@@ -19,9 +19,9 @@ export function pagination(limit, skip) {
|
|
|
19
19
|
return { skip: skip ?? 0, limit: limit ?? 20 };
|
|
20
20
|
}
|
|
21
21
|
/**
|
|
22
|
-
* One inline file (base64 bytes + optional filename) for
|
|
23
|
-
*
|
|
24
|
-
*
|
|
22
|
+
* One inline file (base64 bytes + optional filename) for callers with no
|
|
23
|
+
* filesystem to name — a hosted deployment reaches the server's disk, not the
|
|
24
|
+
* user's. Mirrors the local-path upload inputs so a tool can accept either.
|
|
25
25
|
*/
|
|
26
26
|
export const inlineFileSchema = z.object({
|
|
27
27
|
data: z.string().describe('Base64-encoded file bytes (a `data:` URL prefix is allowed).'),
|
package/dist/tools/uploads.js
CHANGED
|
@@ -5,27 +5,27 @@ import { nodeUploadResolver } from '../upload-source.js';
|
|
|
5
5
|
import { previewResult } from './shared.js';
|
|
6
6
|
/**
|
|
7
7
|
* `resolveUpload` is the injectable file-source seam: the stdio server uses the
|
|
8
|
-
* default `nodeUploadResolver
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* default `nodeUploadResolver`, which resolves either a local `path` or inline
|
|
9
|
+
* base64 `fileData`. A caller that shares no filesystem with the server sends
|
|
10
|
+
* the bytes inline, and the same tool works unchanged.
|
|
11
11
|
*/
|
|
12
12
|
export function registerUploadTools(server, client, resolveUpload = nodeUploadResolver) {
|
|
13
13
|
server.registerTool('vibo_set_profile_photo', {
|
|
14
|
-
description: 'Set your Vibo profile photo from an image.
|
|
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
16
|
inputSchema: {
|
|
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()
|
|
20
20
|
.optional()
|
|
21
|
-
.describe('Base64-encoded image bytes (a `data:` URL prefix is allowed).
|
|
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`.', {
|
|
28
|
-
hint: '
|
|
28
|
+
hint: 'Pass `path` for a local file, or `fileData` (base64) if the server cannot read your filesystem.',
|
|
29
29
|
});
|
|
30
30
|
}
|
|
31
31
|
if (!confirm)
|
package/dist/upload-source.js
CHANGED
|
@@ -2,16 +2,14 @@
|
|
|
2
2
|
//
|
|
3
3
|
// The Vibo upload tools (`vibo_set_profile_photo`, `vibo_answer_question` with
|
|
4
4
|
// photo/file answers) send local media through the GraphQL `Upload` scalar.
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// base64 instead. Both
|
|
8
|
-
// `ViboClient.gqlUpload` streams into a `FormData
|
|
9
|
-
// touches `node:fs`, which keeps it loadable in the Workers runtime.
|
|
5
|
+
// A caller that shares a filesystem with the server names a local path; one
|
|
6
|
+
// that does not — anything reaching this server remotely — sends the bytes
|
|
7
|
+
// inline as base64 instead. Both converge on an in-memory {@link UploadFile}
|
|
8
|
+
// that `ViboClient.gqlUpload` streams into a `FormData`.
|
|
10
9
|
//
|
|
11
10
|
// A tool hands the resolver a {@link FileRef} (a local `path`, or inline base64
|
|
12
|
-
// `data`) and gets back a `Blob` + filename
|
|
13
|
-
//
|
|
14
|
-
// and throws an actionable error for a filesystem path.
|
|
11
|
+
// `data`) and gets back a `Blob` + filename; `nodeUploadResolver` resolves
|
|
12
|
+
// either.
|
|
15
13
|
import { McpToolError } from '@chrischall/mcp-utils';
|
|
16
14
|
const DEFAULT_FILENAME = 'upload';
|
|
17
15
|
/** Decode base64 (optionally a `data:` URL) into an {@link UploadFile}. */
|
|
@@ -60,20 +58,3 @@ export const nodeUploadResolver = async (ref) => {
|
|
|
60
58
|
hint: 'Pass a local file `path` (or inline base64 `fileData`).',
|
|
61
59
|
});
|
|
62
60
|
};
|
|
63
|
-
/**
|
|
64
|
-
* Hosted-connector resolver: the Worker has no filesystem, so it accepts ONLY
|
|
65
|
-
* inline base64 bytes. A filesystem path draws an actionable error rather than
|
|
66
|
-
* a runtime crash.
|
|
67
|
-
*/
|
|
68
|
-
export const inlineUploadResolver = async (ref) => {
|
|
69
|
-
if (ref.data)
|
|
70
|
-
return blobFromBase64(ref.data, ref.filename);
|
|
71
|
-
if (ref.path) {
|
|
72
|
-
throw new McpToolError('Local file paths are not available on the hosted Vibo connector.', {
|
|
73
|
-
hint: 'The hosted connector has no filesystem — pass the file bytes as base64 in `fileData` instead of a `path`.',
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
throw new McpToolError('No file provided for upload.', {
|
|
77
|
-
hint: 'Pass the file bytes as base64 in `fileData`.',
|
|
78
|
-
});
|
|
79
|
-
};
|
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.5.
|
|
5
|
+
export const VERSION = '1.5.5'; // x-release-please-version
|
package/mint.yaml
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
version: 1
|
|
2
|
+
name: Vibo
|
|
3
|
+
slug: vibo
|
|
4
|
+
summary: >-
|
|
5
|
+
Plan and manage your event music in Vibo (vibodj.com) — events, timeline,
|
|
6
|
+
song requests, the DJ's song ideas and planning questions,
|
|
7
|
+
must-play/do-not-play, comments, guests, and exports to Spotify/Apple
|
|
8
|
+
Music, via natural language.
|
|
9
|
+
#
|
|
10
|
+
# Hosting note (a comment, not user-facing summary text): this is a
|
|
11
|
+
# BROWSER-BRIDGE MCP — it reaches its site through the user's signed-in
|
|
12
|
+
# tab via the fetchproxy bridge. A bridged registration also needs runtime
|
|
13
|
+
# `fly-shared`, `bridge: true` and a `bridgePortEnv`, which are registration
|
|
14
|
+
# fields this manifest has no schema for (set them over the control API).
|
|
15
|
+
# `state.dataDir` below is required for `bridge`.
|
|
16
|
+
env:
|
|
17
|
+
- name: VIBO_EMAIL
|
|
18
|
+
required: false
|
|
19
|
+
help: >-
|
|
20
|
+
Vibo account email (or use VIBO_ACCESS_TOKEN for SSO accounts)
|
|
21
|
+
- name: VIBO_PASSWORD
|
|
22
|
+
secret: true
|
|
23
|
+
required: false
|
|
24
|
+
help: >-
|
|
25
|
+
Vibo account password
|
|
26
|
+
- name: VIBO_ACCESS_TOKEN
|
|
27
|
+
secret: true
|
|
28
|
+
required: false
|
|
29
|
+
help: >-
|
|
30
|
+
Captured x-token from a signed-in web.vibodj.com session (SSO alternative)
|
|
31
|
+
- name: VIBO_REFRESH_TOKEN
|
|
32
|
+
secret: true
|
|
33
|
+
required: false
|
|
34
|
+
help: >-
|
|
35
|
+
Matching x-refresh-token so the session can renew
|
|
36
|
+
- name: VIBO_API_URL
|
|
37
|
+
required: false
|
|
38
|
+
help: >-
|
|
39
|
+
Overrides the upstream API base URL. Leave unset for the default; if you
|
|
40
|
+
change it, update egress.allow to match.
|
|
41
|
+
- name: VIBO_SESSION_FILE
|
|
42
|
+
required: false
|
|
43
|
+
help: >-
|
|
44
|
+
Filesystem path/flag for saved state or output. Relates to state.dataDir;
|
|
45
|
+
leave unset for the default.
|
|
46
|
+
state:
|
|
47
|
+
dataDir: true
|
|
48
|
+
reason: >-
|
|
49
|
+
The fetchproxy identity lives at $HOME/.fetchproxy/identity/<name>.json
|
|
50
|
+
and the pair code derives from it. Without a persistent $HOME every cold
|
|
51
|
+
start mints a fresh identity and re-prompts pairing in the browser; the
|
|
52
|
+
API refuses bridge without it.
|
|
53
|
+
egress:
|
|
54
|
+
allow:
|
|
55
|
+
# Only hosts the SERVER process actually fetches. Hosts that appear
|
|
56
|
+
# solely in a URL this server BUILDS and returns are excluded.
|
|
57
|
+
#
|
|
58
|
+
# Every mode (email/password signIn, captured token, refresh, uploads)
|
|
59
|
+
# POSTs to the GraphQL endpoint from the process itself
|
|
60
|
+
# (src/client.ts DEFAULT_API_URL); no redirect hop. `vibo_capture_session`
|
|
61
|
+
# lifts x-token/x-refresh-token from the signed-in web.vibodj.com tab over
|
|
62
|
+
# the fetchproxy bridge — that is the browser's dial, not the process's, so
|
|
63
|
+
# web.vibodj.com and vibodj.com are NOT listed. A VIBO_API_URL override
|
|
64
|
+
# names a host this list cannot know; add it here if you set one.
|
|
65
|
+
- api.vibodj.com
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vibo-mcp",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.5",
|
|
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>",
|
|
@@ -33,38 +33,30 @@
|
|
|
33
33
|
".claude-plugin",
|
|
34
34
|
"skills",
|
|
35
35
|
".mcp.json",
|
|
36
|
-
"server.json"
|
|
36
|
+
"server.json",
|
|
37
|
+
"mint.yaml"
|
|
37
38
|
],
|
|
38
39
|
"scripts": {
|
|
39
40
|
"build": "tsc && npm run bundle",
|
|
40
41
|
"bundle": "esbuild src/index.ts --bundle --platform=node --format=esm --external:dotenv --external:@fetchproxy/bootstrap --outfile=dist/bundle.js",
|
|
41
42
|
"dev": "node dist/index.js",
|
|
42
|
-
"test": "vitest run",
|
|
43
|
+
"test": "npm run typecheck && vitest run",
|
|
43
44
|
"test:watch": "vitest",
|
|
44
|
-
"test:coverage": "vitest run --coverage",
|
|
45
|
-
"
|
|
46
|
-
"worker:deploy": "wrangler deploy",
|
|
47
|
-
"worker:typecheck": "tsc --noEmit -p tsconfig.worker.json",
|
|
48
|
-
"worker:test": "vitest run --config vitest.workers.config.ts"
|
|
45
|
+
"test:coverage": "npm run typecheck && vitest run --coverage",
|
|
46
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
49
47
|
},
|
|
50
48
|
"dependencies": {
|
|
51
|
-
"@chrischall/mcp-utils": "^0.
|
|
49
|
+
"@chrischall/mcp-utils": "^0.15.0",
|
|
52
50
|
"@fetchproxy/bootstrap": "^2.0.0",
|
|
53
51
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
54
52
|
"dotenv": "^17.4.0",
|
|
55
53
|
"zod": "^4.4.2"
|
|
56
54
|
},
|
|
57
55
|
"devDependencies": {
|
|
58
|
-
"@chrischall/mcp-connector": "^1.0.0",
|
|
59
|
-
"@cloudflare/vitest-pool-workers": "^0.20.1",
|
|
60
|
-
"@cloudflare/workers-oauth-provider": "^0.8.1",
|
|
61
|
-
"@cloudflare/workers-types": "^5.20260708.1",
|
|
62
56
|
"@types/node": "^26.0.0",
|
|
63
57
|
"@vitest/coverage-v8": "^4.1.2",
|
|
64
|
-
"agents": "^0.19.0",
|
|
65
58
|
"esbuild": "^0.28.0",
|
|
66
59
|
"typescript": "^7.0.2",
|
|
67
|
-
"vitest": "^4.1.2"
|
|
68
|
-
"wrangler": "^4.110.0"
|
|
60
|
+
"vitest": "^4.1.2"
|
|
69
61
|
}
|
|
70
62
|
}
|
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.5.
|
|
9
|
+
"version": "1.5.5",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "vibo-mcp",
|
|
14
|
-
"version": "1.5.
|
|
14
|
+
"version": "1.5.5",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|
package/dist/vibo-auth.js
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { ViboClient } from './client.js';
|
|
2
|
-
import { GET_ME } from './gql.js';
|
|
3
|
-
/**
|
|
4
|
-
* `ConnectorAuth` for the Vibo remote connector: the login page collects the
|
|
5
|
-
* user's Vibo email + password, VERIFIES them by constructing a `ViboClient`
|
|
6
|
-
* with the injected creds and forcing a `signIn` + `me` read (a bad
|
|
7
|
-
* email/password makes the mutation throw here, surfaced back on the login
|
|
8
|
-
* page), and stores `{ email, password }` as the OAuth props that `worker.ts`'s
|
|
9
|
-
* `buildClient` turns into a per-user client.
|
|
10
|
-
*
|
|
11
|
-
* SSO-only accounts (Apple/Google/Facebook, no password) are NOT supported on
|
|
12
|
-
* the hosted connector — they have no password to sign in with. Use a Vibo
|
|
13
|
-
* account with an email + password, or the local stdio server's
|
|
14
|
-
* `vibo_capture_session` browser-capture flow instead.
|
|
15
|
-
*/
|
|
16
|
-
export const viboAuth = {
|
|
17
|
-
service: 'Vibo',
|
|
18
|
-
accent: '#5B2AE0',
|
|
19
|
-
privacyNote: 'Your Vibo email and password are stored encrypted and used only to sign into Vibo (vibodj.com) on your behalf to mint short-lived access tokens. SSO-only Apple/Google/Facebook accounts are not supported — use an account with a password.',
|
|
20
|
-
fields: [
|
|
21
|
-
{ name: 'email', label: 'Vibo email', type: 'text' },
|
|
22
|
-
{ name: 'password', label: 'Vibo password', type: 'password' },
|
|
23
|
-
],
|
|
24
|
-
async login(fields) {
|
|
25
|
-
// Verify the creds up front: build a client with the injected email +
|
|
26
|
-
// password and force a server-side signIn by hitting a cheap authenticated
|
|
27
|
-
// read. Bad creds make `signIn` (and thus this call) throw here — surfaced
|
|
28
|
-
// back on the login page. The response is discarded; the per-user client is
|
|
29
|
-
// built fresh from the stored props by buildClient.
|
|
30
|
-
const client = new ViboClient({ email: fields.email, password: fields.password });
|
|
31
|
-
await client.gql(GET_ME);
|
|
32
|
-
return { email: fields.email, password: fields.password };
|
|
33
|
-
},
|
|
34
|
-
};
|