vibo-mcp 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,7 +7,7 @@
7
7
  },
8
8
  "metadata": {
9
9
  "description": "MCP server for Vibo (vibodj.com) — plan & manage event music, song requests, ideas, guests, and playlists via natural language",
10
- "version": "2.1.0"
10
+ "version": "2.2.0"
11
11
  },
12
12
  "plugins": [
13
13
  {
@@ -15,7 +15,7 @@
15
15
  "displayName": "Vibo",
16
16
  "source": "./",
17
17
  "description": "MCP server for Vibo — browse & manage events, timeline, songs, the DJ song ideas/questions, guests, and exports to Spotify/Apple Music",
18
- "version": "2.1.0",
18
+ "version": "2.2.0",
19
19
  "author": {
20
20
  "name": "Chris Hall"
21
21
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "vibo-mcp",
3
3
  "displayName": "Vibo",
4
- "version": "2.1.0",
4
+ "version": "2.2.0",
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
@@ -5,6 +5,24 @@ var __export = (target, all) => {
5
5
  __defProp(target, name, { get: all[name], enumerable: true });
6
6
  };
7
7
 
8
+ // node_modules/@chrischall/mcp-utils/dist/cancel/index.js
9
+ import { AsyncLocalStorage } from "node:async_hooks";
10
+ var storage = new AsyncLocalStorage();
11
+ function withCallSignal(signal, fn) {
12
+ return signal ? storage.run({ signal }, fn) : fn();
13
+ }
14
+ function currentCallSignal() {
15
+ return storage.getStore()?.signal;
16
+ }
17
+ function withAmbientCancellation(own2) {
18
+ const ambient = currentCallSignal();
19
+ if (!ambient)
20
+ return own2;
21
+ if (!own2)
22
+ return ambient;
23
+ return AbortSignal.any([own2, ambient]);
24
+ }
25
+
8
26
  // node_modules/@modelcontextprotocol/server/dist/chunk-Br0eD_fh.mjs
9
27
  var __create = Object.create;
10
28
  var __defProp2 = Object.defineProperty;
@@ -34474,17 +34492,41 @@ Hint: ${err.hint}`);
34474
34492
  }
34475
34493
  throw err;
34476
34494
  }
34495
+ function callSignalFrom(args) {
34496
+ const ctx = args.at(-1);
34497
+ if (typeof ctx !== "object" || ctx === null)
34498
+ return void 0;
34499
+ const req = ctx.mcpReq;
34500
+ if (typeof req !== "object" || req === null)
34501
+ return void 0;
34502
+ const signal = req.signal;
34503
+ return signal instanceof AbortSignal ? signal : void 0;
34504
+ }
34477
34505
  function surfaceToolHints(server) {
34478
34506
  const register2 = server.registerTool.bind(server);
34479
- server.registerTool = (name, config2, cb) => register2(name, config2, (...args) => {
34480
- let result;
34481
- try {
34482
- result = cb(...args);
34483
- } catch (err) {
34484
- return hintResultOrRethrow(err);
34485
- }
34486
- return result instanceof Promise ? result.catch(hintResultOrRethrow) : result;
34487
- });
34507
+ server.registerTool = (name, config2, cb) => register2(name, config2, (...args) => (
34508
+ // THE CALLER'S CANCELLATION, made ambient for the whole handler
34509
+ // (`cancel/index.ts`). The SDK delivers it and the fleet ignored it:
34510
+ // measured against @modelcontextprotocol/server 2.0.0, a cancelled
34511
+ // call aborts `ctx.mcpReq.signal` with the caller's reason and the
34512
+ // handler runs to completion regardless — so the HTTP request stays
34513
+ // in flight, the child keeps burning metered CPU, and the upstream
34514
+ // keeps being hit for somebody who has gone. claude.ai sent 101
34515
+ // cancellations in the week to 2026-09-20.
34516
+ //
34517
+ // Here because this is the one wrapper every tool already passes
34518
+ // through: threading the signal by hand would mean editing several
34519
+ // hundred handlers and missing exactly the ones nobody edits.
34520
+ withCallSignal(callSignalFrom(args), () => {
34521
+ let result;
34522
+ try {
34523
+ result = cb(...args);
34524
+ } catch (err) {
34525
+ return hintResultOrRethrow(err);
34526
+ }
34527
+ return result instanceof Promise ? result.catch(hintResultOrRethrow) : result;
34528
+ })
34529
+ ));
34488
34530
  }
34489
34531
  async function createMcpServer(opts) {
34490
34532
  const server = new McpServer({ name: opts.name, version: opts.version }, { supportedProtocolVersions: [...SERVER_PROTOCOL_VERSIONS] });
@@ -34602,7 +34644,7 @@ function toolAnnotations(opts = {}) {
34602
34644
  }
34603
34645
 
34604
34646
  // src/version.ts
34605
- var VERSION = "2.1.0";
34647
+ var VERSION = "2.2.0";
34606
34648
 
34607
34649
  // src/client.ts
34608
34650
  import { dirname as dirname2, join as join3 } from "path";
@@ -34807,6 +34849,9 @@ var REFRESH = `
34807
34849
  }
34808
34850
  `;
34809
34851
  var AUTH_ERROR_CODES = /* @__PURE__ */ new Set(["UNAUTHORIZED", "UNAUTHENTICATED", "FORBIDDEN"]);
34852
+ function requestSignal() {
34853
+ return withAmbientCancellation(AbortSignal.timeout(REQUEST_TIMEOUT_MS));
34854
+ }
34810
34855
  var ViboClient = class {
34811
34856
  apiUrl;
34812
34857
  email;
@@ -34931,7 +34976,7 @@ var ViboClient = class {
34931
34976
  headers,
34932
34977
  // NB: no content-type — fetch sets the multipart boundary
34933
34978
  body: form,
34934
- signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
34979
+ signal: requestSignal()
34935
34980
  });
34936
34981
  } catch (err) {
34937
34982
  const reason = err instanceof Error && err.name === "TimeoutError" ? "timed out" : "failed";
@@ -35021,7 +35066,7 @@ var ViboClient = class {
35021
35066
  method: "POST",
35022
35067
  headers,
35023
35068
  body: JSON.stringify({ query, variables }),
35024
- signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
35069
+ signal: requestSignal()
35025
35070
  });
35026
35071
  } catch (err) {
35027
35072
  const reason = err instanceof Error && err.name === "TimeoutError" ? "timed out" : "failed";
package/dist/client.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { dirname, join } from 'path';
2
2
  import { fileURLToPath } from 'url';
3
- import { loadDotenvSafely, readEnvVar, McpToolError, SessionNotAuthenticatedError, truncateErrorMessage, } from '@chrischall/mcp-utils';
3
+ import { loadDotenvSafely, readEnvVar, McpToolError, SessionNotAuthenticatedError, truncateErrorMessage, withAmbientCancellation, } from '@chrischall/mcp-utils';
4
4
  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
@@ -40,6 +40,25 @@ const REFRESH = `
40
40
  // Error codes Vibo (and conventional GraphQL servers) use for an expired /
41
41
  // missing session — these should trigger a token refresh + replay.
42
42
  const AUTH_ERROR_CODES = new Set(['UNAUTHORIZED', 'UNAUTHENTICATED', 'FORBIDDEN']);
43
+ /**
44
+ * The signal every Vibo request is made with: our timeout, and the caller's
45
+ * cancellation if there is one (mcp-utils `cancel`).
46
+ *
47
+ * Until this, only the timeout could stop a request — a cancelled tool call
48
+ * held it open for the full budget while the child burned the CPU mcp-host
49
+ * meters it on. Measured on that fleet: claude.ai sent 101 cancellations in
50
+ * the week to 2026-09-20.
51
+ *
52
+ * ONE definition for both request paths, which is not tidiness: the two are
53
+ * the multipart upload and the plain query, they had the same seven lines
54
+ * copied between them, and the next person to add a third path is the one
55
+ * this saves. The `TimeoutError` checks at both call sites still name a real
56
+ * timeout — an abort from the caller arrives as `AbortError` and falls
57
+ * through to 'failed'.
58
+ */
59
+ function requestSignal() {
60
+ return withAmbientCancellation(AbortSignal.timeout(REQUEST_TIMEOUT_MS));
61
+ }
43
62
  export class ViboClient {
44
63
  apiUrl;
45
64
  email;
@@ -172,7 +191,7 @@ export class ViboClient {
172
191
  method: 'POST',
173
192
  headers, // NB: no content-type — fetch sets the multipart boundary
174
193
  body: form,
175
- signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
194
+ signal: requestSignal(),
176
195
  });
177
196
  }
178
197
  catch (err) {
@@ -265,7 +284,7 @@ export class ViboClient {
265
284
  method: 'POST',
266
285
  headers,
267
286
  body: JSON.stringify({ query, variables }),
268
- signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
287
+ signal: requestSignal(),
269
288
  });
270
289
  }
271
290
  catch (err) {
package/dist/version.js CHANGED
@@ -2,4 +2,4 @@
2
2
  // literal on the line carrying the release marker; every manifest and the MCP
3
3
  // server banner import VERSION from here, so there is exactly one place to keep
4
4
  // in sync (and one release-please extra-files entry).
5
- export const VERSION = '2.1.0'; // x-release-please-version
5
+ export const VERSION = '2.2.0'; // x-release-please-version
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibo-mcp",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "mcpName": "io.github.chrischall/vibo-mcp",
5
5
  "description": "Vibo (vibodj.com) MCP server for Claude — host/couple event music planning & management. Developed and maintained by AI (Claude Code).",
6
6
  "author": "Claude Code (AI) <https://www.anthropic.com/claude>",
@@ -46,7 +46,7 @@
46
46
  "typecheck": "tsc -p tsconfig.json --noEmit"
47
47
  },
48
48
  "dependencies": {
49
- "@chrischall/mcp-utils": "^1.0.0",
49
+ "@chrischall/mcp-utils": "^2.0.0",
50
50
  "@fetchproxy/bootstrap": "^3.0.1",
51
51
  "@modelcontextprotocol/server": "^2.0.0",
52
52
  "dotenv": "^17.4.0",
package/server.json CHANGED
@@ -6,12 +6,12 @@
6
6
  "url": "https://github.com/chrischall/vibo-mcp",
7
7
  "source": "github"
8
8
  },
9
- "version": "2.1.0",
9
+ "version": "2.2.0",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "vibo-mcp",
14
- "version": "2.1.0",
14
+ "version": "2.2.0",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },