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.
@@ -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.3"
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.3",
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.3",
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.3";
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 the Workers runtime
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 on stdio, or inline base64 on the hosted connector see
31464
- * src/upload-source.ts), so this method never touches the filesystem and runs
31465
- * unchanged in the Workers runtime (`FormData`/`Blob`/`fetch` are all
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`) on the stdio server, or inline base64 bytes (`images`/`files`) on the hosted connector. Confirm-gated.",
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 (used by the hosted connector, which has no filesystem)."),
32595
- files: external_exports.array(inlineFileSchema).optional().describe("Inline base64 files, for a file-attachment question (used by the hosted connector)."),
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` (stdio) or `images`/`files` (hosted connector)."
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. On the local (stdio) server pass a local file `path`; on the hosted connector pass the image bytes as base64 `fileData`. Returns the uploaded image URL. Confirm-gated.",
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). Used by the hosted connector, which has no filesystem."),
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: "On the local server pass `path`; on the hosted connector pass `fileData` (base64)."
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 the Cloudflare Worker runtime (src/worker.ts): there `import.meta.url`
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 the Workers runtime
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 on stdio, or inline base64 on the hosted connector see
137
- * src/upload-source.ts), so this method never touches the filesystem and runs
138
- * unchanged in the Workers runtime (`FormData`/`Blob`/`fetch` are all
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();
@@ -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
- * connector passes `inlineUploadResolver` (inline base64 `images`/`files`).
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`) on the stdio server, or inline base64 bytes (`images`/`files`) on the hosted connector. Confirm-gated.",
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 (used by the hosted connector, which has no filesystem).'),
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 (used by the hosted connector).'),
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. stdio supplies paths; the hosted connector supplies inline
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` (stdio) or `images`/`files` (hosted connector).",
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 = {};
@@ -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: this registrar is STDIO-ONLY it is deliberately NOT wired into the
6
- // hosted Cloudflare connector (src/worker.ts). vibo_capture_session needs the
7
- // fetchproxy browser bridge + a signed-in browser tab, neither of which exists
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.",
@@ -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 the hosted connector,
23
- * which has no filesystem. Mirrors the local-path upload inputs so a tool can
24
- * accept either a filesystem path (stdio) or inline bytes (Worker).
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).'),
@@ -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` (reads a local `path`), while the hosted
9
- * Cloudflare connector (src/worker.ts) passes `inlineUploadResolver` so the same
10
- * tool works from base64 `fileData` with no filesystem.
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. On the local (stdio) server pass a local file `path`; on the hosted connector pass the image bytes as base64 `fileData`. Returns the uploaded image URL. Confirm-gated.',
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). Used by the hosted connector, which has no filesystem.'),
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: 'On the local server pass `path`; on the hosted connector pass `fileData` (base64).',
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)
@@ -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
- // The stdio server reads those bytes from a local file path; the hosted
6
- // Cloudflare Worker has NO filesystem, so it receives the bytes inline as
7
- // base64 instead. Both paths converge on an in-memory {@link UploadFile} that
8
- // `ViboClient.gqlUpload` streams into a `FormData` — the client itself never
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. `nodeUploadResolver` (stdio)
13
- // resolves either; `inlineUploadResolver` (Worker) resolves only inline bytes
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.3'; // x-release-please-version
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",
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
- "worker:dev": "wrangler dev",
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.14.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.3",
9
+ "version": "1.5.5",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "vibo-mcp",
14
- "version": "1.5.3",
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
- };