vibo-mcp 2.3.1 → 2.4.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/dist/bundle.js CHANGED
@@ -19782,7 +19782,7 @@ function date4(params) {
19782
19782
  return _coercedDate(ZodDate, params);
19783
19783
  }
19784
19784
 
19785
- // node_modules/@modelcontextprotocol/core/dist/auth-CUe6YdwF.mjs
19785
+ // node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core/dist/auth-CGP0BDVq.mjs
19786
19786
  var LATEST_PROTOCOL_VERSION = "2025-11-25";
19787
19787
  var SUPPORTED_PROTOCOL_VERSIONS = [
19788
19788
  LATEST_PROTOCOL_VERSION,
@@ -20619,7 +20619,8 @@ var OAuthMetadataSchema = looseObject({
20619
20619
  introspection_endpoint_auth_signing_alg_values_supported: array(string2()).optional(),
20620
20620
  code_challenge_methods_supported: array(string2()).optional(),
20621
20621
  client_id_metadata_document_supported: boolean2().optional(),
20622
- authorization_response_iss_parameter_supported: boolean2().optional().catch(void 0)
20622
+ authorization_response_iss_parameter_supported: boolean2().optional().catch(void 0),
20623
+ dpop_signing_alg_values_supported: array(string2()).optional()
20623
20624
  });
20624
20625
  var OpenIdProviderMetadataSchema = looseObject({
20625
20626
  issuer: string2(),
@@ -20720,7 +20721,7 @@ var OAuthTokenRevocationRequestSchema = object({
20720
20721
  token_type_hint: string2().optional()
20721
20722
  }).strip();
20722
20723
 
20723
- // node_modules/@modelcontextprotocol/server/dist/src-CX2iR2pK.mjs
20724
+ // node_modules/@modelcontextprotocol/server/dist/src-D-y6h4N7.mjs
20724
20725
  var BRANDS = /* @__PURE__ */ Symbol.for("mcp.sdk.errorBrands");
20725
20726
  function stampErrorBrands(instance, ctor) {
20726
20727
  const brands = /* @__PURE__ */ new Set();
@@ -20834,8 +20835,18 @@ var SdkError = class extends Error {
20834
20835
  if (typeof this !== "function") throw new TypeError("isInstance must be called on the class (e.g. `SdkError.isInstance(value)`); for callbacks use `v => SdkError.isInstance(v)`");
20835
20836
  return brandedHasInstance(this, value);
20836
20837
  }
20837
- constructor(code, message, data) {
20838
- super(message);
20838
+ /**
20839
+ * @param code - Stable string code identifying the failure ({@linkcode SdkErrorCode}).
20840
+ * @param message - Human-readable description.
20841
+ * @param data - Optional structured payload (for example the HTTP status carried by
20842
+ * {@linkcode SdkHttpError}). Opaque to the SDK: a `cause` key inside `data` is not
20843
+ * promoted to `Error.cause`.
20844
+ * @param options - Standard `ErrorOptions`, forwarded to `Error`. Pass the underlying
20845
+ * failure as `{ cause }` so it is reachable through the `Error.cause` chain that
20846
+ * loggers and error trackers walk.
20847
+ */
20848
+ constructor(code, message, data, options) {
20849
+ super(message, options);
20839
20850
  this.code = code;
20840
20851
  this.data = data;
20841
20852
  this.name = "SdkError";
@@ -20846,8 +20857,11 @@ var SdkHttpError = class extends SdkError {
20846
20857
  static {
20847
20858
  Object.defineProperty(this, "mcpBrand", { value: "mcp.SdkHttpError" });
20848
20859
  }
20849
- constructor(code, message, data) {
20850
- super(code, message, data);
20860
+ /**
20861
+ * @param options - Standard `ErrorOptions`, forwarded to `Error` (see {@linkcode SdkError}).
20862
+ */
20863
+ constructor(code, message, data, options) {
20864
+ super(code, message, data, options);
20851
20865
  this.name = "SdkHttpError";
20852
20866
  }
20853
20867
  get status() {
@@ -23798,7 +23812,7 @@ var INBOUND_VALIDATION_LADDER = [
23798
23812
  evaluatedAt: "pre-dispatch",
23799
23813
  codes: [HEADER_MISMATCH_ERROR_CODE],
23800
23814
  conformance: ["http-header-validation"],
23801
- rationale: "SEP-2243 standard `Mcp-Method` / `Mcp-Name` headers \u2014 presence, sentinel decoding, and `Mcp-Name` \u2194 body cross-check \u2014 are validated by the HTTP entry on a modern-classified request after the supported-revision gate and before dispatch. The classifier\u2019s own header-mismatch cells (protocol-version, `Mcp-Method` mismatch) stay on the edge `era-classification` rung; this rung carries the entry-layer presence/`Mcp-Name` half. Evaluated before the capability gate, the factory call, and the `Mcp-Param-*` rung so a request that fails several rungs is answered by the standard-header rung first. The documented order (after method-registry 5 and request-params 6) is NOT the observed precedence: serveModern evaluates this rung immediately after the supported-revision gate, so a request that also fails a dispatch rung is answered here before the dispatch rungs (5\u20136) are consulted."
23815
+ rationale: "SEP-2243 standard `MCP-Protocol-Version` / `Mcp-Method` / `Mcp-Name` headers \u2014 presence, sentinel decoding, and `Mcp-Name` \u2194 body cross-check \u2014 are validated by the HTTP entry on a modern-classified request after the supported-revision gate and before dispatch. The spec requires `MCP-Protocol-Version` and `Mcp-Method` on every modern *request* POST (`Mcp-Name` only for the methods that mirror `params.name` / `params.uri` / `params.taskId`, see `MCP_NAME_HEADER_SOURCE`) and names them in that order, so a request missing several is answered by the earliest. Notification POSTs are exempt: the presence half runs on requests only, so a modern-enveloped notification is dispatched even with no standard headers at all. The classifier\u2019s own header-mismatch cells (protocol-version, `Mcp-Method` mismatch) stay on the edge `era-classification` rung; this rung carries the entry-layer presence/`Mcp-Name` half \u2014 including the missing `MCP-Protocol-Version` cell, which cannot live on the edge rung without breaking body-primary classification. Evaluated before the capability gate, the factory call, and the `Mcp-Param-*` rung so a request that fails several rungs is answered by the standard-header rung first. The documented order (after method-registry 5 and request-params 6) is NOT the observed precedence: serveModern evaluates this rung immediately after the supported-revision gate, so a request that also fails a dispatch rung is answered here before the dispatch rungs (5\u20136) are consulted."
23802
23816
  },
23803
23817
  {
23804
23818
  rung: "client-capabilities",
@@ -24599,7 +24613,7 @@ var Protocol = class {
24599
24613
  return this._requestHandlers.get(method);
24600
24614
  }
24601
24615
  async _oncancel(notification) {
24602
- if (!notification.params.requestId) return;
24616
+ if (notification.params.requestId === void 0) return;
24603
24617
  this._requestHandlerAbortControllers.get(notification.params.requestId)?.abort(notification.params.reason);
24604
24618
  }
24605
24619
  _setupTimeout(messageId, timeout, maxTotalTimeout, onTimeout, resetTimeoutOnProgress = false) {
@@ -24998,19 +25012,20 @@ var Protocol = class {
24998
25012
  const cancel = (reason) => {
24999
25013
  if (responseReceived) return;
25000
25014
  this._progressHandlers.delete(messageId);
25001
- if (requestAbort === void 0) this._transport?.send(this._envelopeOutbound({
25002
- jsonrpc: "2.0",
25003
- method: "notifications/cancelled",
25004
- params: {
25005
- requestId: messageId,
25006
- reason: String(reason)
25007
- }
25008
- }), {
25009
- relatedRequestId,
25010
- resumptionToken,
25011
- onresumptiontoken
25012
- }).catch((error62) => this._onerror(/* @__PURE__ */ new Error(`Failed to send cancellation: ${error62}`)));
25013
- else requestAbort.abort();
25015
+ if (requestAbort === void 0) {
25016
+ if (request.method !== "initialize") this._transport?.send(this._envelopeOutbound({
25017
+ jsonrpc: "2.0",
25018
+ method: "notifications/cancelled",
25019
+ params: {
25020
+ requestId: messageId,
25021
+ reason: String(reason)
25022
+ }
25023
+ }), {
25024
+ relatedRequestId,
25025
+ resumptionToken,
25026
+ onresumptiontoken
25027
+ }).catch((error62) => this._onerror(/* @__PURE__ */ new Error(`Failed to send cancellation: ${error62}`)));
25028
+ } else requestAbort.abort();
25014
25029
  reject(reason instanceof SdkError ? reason : new SdkError(SdkErrorCode.RequestTimeout, String(reason)));
25015
25030
  };
25016
25031
  this._responseHandlers.set(messageId, (response) => {
@@ -25090,7 +25105,7 @@ var Protocol = class {
25090
25105
  jsonrpc: "2.0",
25091
25106
  ...notification
25092
25107
  });
25093
- if ((this._options?.debouncedNotificationMethods ?? []).includes(notification.method) && !notification.params && !options?.relatedRequestId) {
25108
+ if ((this._options?.debouncedNotificationMethods ?? []).includes(notification.method) && !notification.params && options?.relatedRequestId === void 0) {
25094
25109
  if (this._pendingDebouncedNotifications.has(notification.method)) return;
25095
25110
  this._pendingDebouncedNotifications.add(notification.method);
25096
25111
  Promise.resolve().then(() => {
@@ -32393,7 +32408,7 @@ var Ajv = import_ajv.Ajv;
32393
32408
  // node_modules/@modelcontextprotocol/server/dist/shimsNode.mjs
32394
32409
  import process2 from "node:process";
32395
32410
 
32396
- // node_modules/@modelcontextprotocol/server/dist/mcp-DXXb3Vv3.mjs
32411
+ // node_modules/@modelcontextprotocol/server/dist/mcp-Dw2OlZ1f.mjs
32397
32412
  var COMPLETABLE_SYMBOL = /* @__PURE__ */ Symbol.for("mcp.completable");
32398
32413
  function isCompletable(schema) {
32399
32414
  return !!schema && typeof schema === "object" && COMPLETABLE_SYMBOL in schema;
@@ -32586,6 +32601,9 @@ function notificationToServerEvent(method, uri) {
32586
32601
  };
32587
32602
  }
32588
32603
  }
32604
+ function supportsScopeChallengeResolver(transport) {
32605
+ return typeof transport === "object" && transport !== null && "setScopeChallengeResolver" in transport && typeof transport.setScopeChallengeResolver === "function";
32606
+ }
32589
32607
  var DEFAULT_LEGACY_SHIM_MAX_ROUNDS = 8;
32590
32608
  var DEFAULT_LEGACY_SHIM_ROUND_TIMEOUT_MS = 6e5;
32591
32609
  function resolveLegacyShimOptions(options) {
@@ -33396,6 +33414,7 @@ var McpServer = class {
33396
33414
  * ```
33397
33415
  */
33398
33416
  async connect(transport) {
33417
+ if (supportsScopeChallengeResolver(transport)) transport.setScopeChallengeResolver((context) => this.resolveScopeChallenge(context));
33399
33418
  return await this.server.connect(transport);
33400
33419
  }
33401
33420
  /**
@@ -33404,6 +33423,41 @@ var McpServer = class {
33404
33423
  async close() {
33405
33424
  await this.server.close();
33406
33425
  }
33426
+ /** @internal */
33427
+ resolveScopeChallenge = (context) => {
33428
+ switch (context.request.method) {
33429
+ case "tools/call": {
33430
+ const toolName = context.request.params?.name;
33431
+ if (typeof toolName !== "string") return;
33432
+ const tool = this._registeredTools[toolName];
33433
+ if (tool === void 0 || !tool.enabled) return;
33434
+ return tool.scopeChallenge?.(context);
33435
+ }
33436
+ case "resources/read": {
33437
+ const resourceUri = context.request.params?.uri;
33438
+ if (typeof resourceUri !== "string") return;
33439
+ let uri;
33440
+ try {
33441
+ uri = new URL(resourceUri);
33442
+ } catch {
33443
+ return;
33444
+ }
33445
+ const resource = this._registeredResources[uri.toString()];
33446
+ if (resource !== void 0) return resource.enabled ? resource.scopeChallenge?.(context) : void 0;
33447
+ for (const template of Object.values(this._registeredResourceTemplates)) if (template.resourceTemplate.uriTemplate.match(uri.toString())) return template.enabled ? template.scopeChallenge?.(context) : void 0;
33448
+ return;
33449
+ }
33450
+ case "prompts/get": {
33451
+ const promptName = context.request.params?.name;
33452
+ if (typeof promptName !== "string") return;
33453
+ const prompt = this._registeredPrompts[promptName];
33454
+ if (prompt === void 0 || !prompt.enabled) return;
33455
+ return prompt.scopeChallenge?.(context);
33456
+ }
33457
+ default:
33458
+ return;
33459
+ }
33460
+ };
33407
33461
  _toolHandlersInitialized = false;
33408
33462
  setToolRequestHandlers() {
33409
33463
  if (this._toolHandlersInitialized) return;
@@ -33570,7 +33624,10 @@ var McpServer = class {
33570
33624
  }
33571
33625
  for (const template of Object.values(this._registeredResourceTemplates)) {
33572
33626
  const variables = template.resourceTemplate.uriTemplate.match(uri.toString());
33573
- if (variables) return attachCacheHintFallback(await template.readCallback(uri, variables, ctx), template.cacheHint);
33627
+ if (variables) {
33628
+ if (!template.enabled) throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Resource template ${template.resourceTemplate.uriTemplate} disabled`);
33629
+ return attachCacheHintFallback(await template.readCallback(uri, variables, ctx), template.cacheHint);
33630
+ }
33574
33631
  }
33575
33632
  throw new ResourceNotFoundError(request.params.uri);
33576
33633
  });
@@ -33601,36 +33658,32 @@ var McpServer = class {
33601
33658
  this._promptHandlersInitialized = true;
33602
33659
  }
33603
33660
  registerResource(name, uriOrTemplate, config2, readCallback) {
33604
- const cacheHint = config2.cacheHint;
33605
- let metadata = config2;
33606
- if (cacheHint !== void 0) {
33607
- assertValidCacheHint(cacheHint, `resource ${name}`);
33608
- const rest = { ...config2 };
33609
- delete rest.cacheHint;
33610
- metadata = rest;
33611
- }
33661
+ const { cacheHint, scopeChallenge, ...resourceMetadata } = config2;
33662
+ const metadata = resourceMetadata;
33663
+ if (cacheHint !== void 0) assertValidCacheHint(cacheHint, `resource ${name}`);
33612
33664
  if (typeof uriOrTemplate === "string") {
33613
33665
  if (this._registeredResources[uriOrTemplate]) throw new Error(`Resource ${uriOrTemplate} is already registered`);
33614
- const registeredResource = this._createRegisteredResource(name, config2.title, uriOrTemplate, metadata, readCallback);
33666
+ const registeredResource = this._createRegisteredResource(name, config2.title, uriOrTemplate, metadata, scopeChallenge, readCallback);
33615
33667
  if (cacheHint !== void 0) registeredResource.cacheHint = cacheHint;
33616
33668
  this.setResourceRequestHandlers();
33617
33669
  this.sendResourceListChanged();
33618
33670
  return registeredResource;
33619
33671
  } else {
33620
33672
  if (this._registeredResourceTemplates[name]) throw new Error(`Resource template ${name} is already registered`);
33621
- const registeredResourceTemplate = this._createRegisteredResourceTemplate(name, config2.title, uriOrTemplate, metadata, readCallback);
33673
+ const registeredResourceTemplate = this._createRegisteredResourceTemplate(name, config2.title, uriOrTemplate, metadata, scopeChallenge, readCallback);
33622
33674
  if (cacheHint !== void 0) registeredResourceTemplate.cacheHint = cacheHint;
33623
33675
  this.setResourceRequestHandlers();
33624
33676
  this.sendResourceListChanged();
33625
33677
  return registeredResourceTemplate;
33626
33678
  }
33627
33679
  }
33628
- _createRegisteredResource(name, title, uri, metadata, readCallback) {
33680
+ _createRegisteredResource(name, title, uri, metadata, scopeChallenge, readCallback) {
33629
33681
  const registeredResource = {
33630
33682
  name,
33631
33683
  title,
33632
33684
  metadata,
33633
33685
  readCallback,
33686
+ scopeChallenge,
33634
33687
  enabled: true,
33635
33688
  disable: () => registeredResource.update({ enabled: false }),
33636
33689
  enable: () => registeredResource.update({ enabled: true }),
@@ -33644,6 +33697,7 @@ var McpServer = class {
33644
33697
  if (updates.title !== void 0) registeredResource.title = updates.title;
33645
33698
  if (updates.metadata !== void 0) registeredResource.metadata = updates.metadata;
33646
33699
  if (updates.callback !== void 0) registeredResource.readCallback = updates.callback;
33700
+ if (updates.scopeChallenge !== void 0) registeredResource.scopeChallenge = updates.scopeChallenge === null ? void 0 : updates.scopeChallenge;
33647
33701
  if (updates.enabled !== void 0) registeredResource.enabled = updates.enabled;
33648
33702
  this.sendResourceListChanged();
33649
33703
  }
@@ -33651,12 +33705,13 @@ var McpServer = class {
33651
33705
  this._registeredResources[uri] = registeredResource;
33652
33706
  return registeredResource;
33653
33707
  }
33654
- _createRegisteredResourceTemplate(name, title, template, metadata, readCallback) {
33708
+ _createRegisteredResourceTemplate(name, title, template, metadata, scopeChallenge, readCallback) {
33655
33709
  const registeredResourceTemplate = {
33656
33710
  resourceTemplate: template,
33657
33711
  title,
33658
33712
  metadata,
33659
33713
  readCallback,
33714
+ scopeChallenge,
33660
33715
  enabled: true,
33661
33716
  disable: () => registeredResourceTemplate.update({ enabled: false }),
33662
33717
  enable: () => registeredResourceTemplate.update({ enabled: true }),
@@ -33670,6 +33725,7 @@ var McpServer = class {
33670
33725
  if (updates.template !== void 0) registeredResourceTemplate.resourceTemplate = updates.template;
33671
33726
  if (updates.metadata !== void 0) registeredResourceTemplate.metadata = updates.metadata;
33672
33727
  if (updates.callback !== void 0) registeredResourceTemplate.readCallback = updates.callback;
33728
+ if (updates.scopeChallenge !== void 0) registeredResourceTemplate.scopeChallenge = updates.scopeChallenge === null ? void 0 : updates.scopeChallenge;
33673
33729
  if (updates.enabled !== void 0) registeredResourceTemplate.enabled = updates.enabled;
33674
33730
  this.sendResourceListChanged();
33675
33731
  }
@@ -33679,7 +33735,7 @@ var McpServer = class {
33679
33735
  if (Array.isArray(variableNames) && variableNames.some((v) => !!template.completeCallback(v))) this.setCompletionRequestHandler();
33680
33736
  return registeredResourceTemplate;
33681
33737
  }
33682
- _createRegisteredPrompt(name, title, description, argsSchema, callback, icons, _meta) {
33738
+ _createRegisteredPrompt(name, title, description, argsSchema, callback, icons, scopeChallenge, _meta) {
33683
33739
  let currentArgsSchema = argsSchema;
33684
33740
  let currentCallback = callback;
33685
33741
  const registeredPrompt = {
@@ -33687,6 +33743,7 @@ var McpServer = class {
33687
33743
  description,
33688
33744
  argsSchema,
33689
33745
  icons,
33746
+ scopeChallenge,
33690
33747
  _meta,
33691
33748
  handler: createPromptHandler(name, argsSchema, callback),
33692
33749
  enabled: true,
@@ -33701,6 +33758,7 @@ var McpServer = class {
33701
33758
  if (updates.title !== void 0) registeredPrompt.title = updates.title;
33702
33759
  if (updates.description !== void 0) registeredPrompt.description = updates.description;
33703
33760
  if (updates.icons !== void 0) registeredPrompt.icons = updates.icons;
33761
+ if (updates.scopeChallenge !== void 0) registeredPrompt.scopeChallenge = updates.scopeChallenge === null ? void 0 : updates.scopeChallenge;
33704
33762
  if (updates._meta !== void 0) registeredPrompt._meta = updates._meta;
33705
33763
  let needsHandlerRegen = false;
33706
33764
  if (updates.argsSchema !== void 0) {
@@ -33728,7 +33786,7 @@ var McpServer = class {
33728
33786
  }
33729
33787
  return registeredPrompt;
33730
33788
  }
33731
- _createRegisteredTool(name, title, description, inputSchema, outputSchema, annotations, icons, execution, _meta, handler) {
33789
+ _createRegisteredTool(name, title, description, inputSchema, outputSchema, annotations, icons, execution, scopeChallenge, _meta, handler) {
33732
33790
  validateAndWarnToolName(name);
33733
33791
  if (inputSchema !== void 0) try {
33734
33792
  const json2 = standardSchemaToJsonSchema(inputSchema, "input");
@@ -33747,6 +33805,7 @@ var McpServer = class {
33747
33805
  annotations,
33748
33806
  icons,
33749
33807
  execution,
33808
+ scopeChallenge,
33750
33809
  _meta,
33751
33810
  handler,
33752
33811
  executor: createToolExecutor(inputSchema, handler),
@@ -33785,6 +33844,7 @@ var McpServer = class {
33785
33844
  }
33786
33845
  if (updates.annotations !== void 0) registeredTool.annotations = updates.annotations;
33787
33846
  if (updates.icons !== void 0) registeredTool.icons = updates.icons;
33847
+ if (updates.scopeChallenge !== void 0) registeredTool.scopeChallenge = updates.scopeChallenge === null ? void 0 : updates.scopeChallenge;
33788
33848
  if (updates._meta !== void 0) registeredTool._meta = updates._meta;
33789
33849
  if (updates.enabled !== void 0) registeredTool.enabled = updates.enabled;
33790
33850
  this.sendToolListChanged();
@@ -33797,13 +33857,13 @@ var McpServer = class {
33797
33857
  }
33798
33858
  registerTool(name, config2, cb) {
33799
33859
  if (this._registeredTools[name]) throw new Error(`Tool ${name} is already registered`);
33800
- const { title, description, inputSchema, outputSchema, annotations, icons, _meta } = config2;
33801
- return this._createRegisteredTool(name, title, description, normalizeRawShapeSchema(inputSchema), normalizeRawShapeSchema(outputSchema), annotations, icons, void 0, _meta, cb);
33860
+ const { title, description, inputSchema, outputSchema, annotations, icons, scopeChallenge, _meta } = config2;
33861
+ return this._createRegisteredTool(name, title, description, normalizeRawShapeSchema(inputSchema), normalizeRawShapeSchema(outputSchema), annotations, icons, void 0, scopeChallenge, _meta, cb);
33802
33862
  }
33803
33863
  registerPrompt(name, config2, cb) {
33804
33864
  if (this._registeredPrompts[name]) throw new Error(`Prompt ${name} is already registered`);
33805
- const { title, description, argsSchema, icons, _meta } = config2;
33806
- const registeredPrompt = this._createRegisteredPrompt(name, title, description, normalizeRawShapeSchema(argsSchema), cb, icons, _meta);
33865
+ const { title, description, argsSchema, icons, scopeChallenge, _meta } = config2;
33866
+ const registeredPrompt = this._createRegisteredPrompt(name, title, description, normalizeRawShapeSchema(argsSchema), cb, icons, scopeChallenge, _meta);
33807
33867
  this.setPromptRequestHandlers();
33808
33868
  this.sendPromptListChanged();
33809
33869
  return registeredPrompt;
@@ -33915,6 +33975,7 @@ function unwrapOptionalSchema(schema) {
33915
33975
  }
33916
33976
 
33917
33977
  // node_modules/@modelcontextprotocol/server/dist/stdio.mjs
33978
+ var swallowsErrorsAfterClose = /* @__PURE__ */ Symbol("swallowsErrorsAfterClose");
33918
33979
  var StdioServerTransport = class {
33919
33980
  _readBuffer;
33920
33981
  _started = false;
@@ -33941,18 +34002,27 @@ var StdioServerTransport = class {
33941
34002
  this.onerror?.(error62);
33942
34003
  };
33943
34004
  _onstdouterror = (error62) => {
34005
+ if (this._closed) return;
33944
34006
  this.onerror?.(error62);
33945
34007
  this.close().catch(() => {
33946
34008
  });
33947
34009
  };
34010
+ _onstdinclose = () => {
34011
+ this.close().catch(() => {
34012
+ });
34013
+ };
33948
34014
  /**
33949
34015
  * Starts listening for messages on `stdin`.
33950
34016
  */
33951
34017
  async start() {
33952
34018
  if (this._started) throw new Error("StdioServerTransport already started! If using Server class, note that connect() calls start() automatically.");
33953
34019
  this._started = true;
34020
+ for (const listener of this._stdout.listeners("error")) if (listener[swallowsErrorsAfterClose]) this._stdout.off("error", listener);
34021
+ if (this._stdin.readableEnded || this._stdin.destroyed) setImmediate(this._onstdinclose);
33954
34022
  this._stdin.on("data", this._ondata);
33955
34023
  this._stdin.on("error", this._onerror);
34024
+ this._stdin.on("end", this._onstdinclose);
34025
+ this._stdin.on("close", this._onstdinclose);
33956
34026
  this._stdout.on("error", this._onstdouterror);
33957
34027
  }
33958
34028
  processReadBuffer() {
@@ -33969,7 +34039,9 @@ var StdioServerTransport = class {
33969
34039
  this._closed = true;
33970
34040
  this._stdin.off("data", this._ondata);
33971
34041
  this._stdin.off("error", this._onerror);
33972
- this._stdout.off("error", this._onstdouterror);
34042
+ this._stdin.off("end", this._onstdinclose);
34043
+ this._stdin.off("close", this._onstdinclose);
34044
+ this._onstdouterror[swallowsErrorsAfterClose] = true;
33973
34045
  if (this._stdin.listenerCount("data") === 0) this._stdin.pause();
33974
34046
  this._readBuffer.clear();
33975
34047
  this.onclose?.();
@@ -34246,6 +34318,7 @@ function serveStdio(factory, options = {}) {
34246
34318
  return;
34247
34319
  }
34248
34320
  if (state.era === "modern" && await tryServeListen(message)) return;
34321
+ if (isTornDown()) return;
34249
34322
  state.instance.channel.deliver(message);
34250
34323
  return;
34251
34324
  }
@@ -34312,6 +34385,7 @@ function serveStdio(factory, options = {}) {
34312
34385
  };
34313
34386
  }
34314
34387
  if (await tryServeListen(message)) return;
34388
+ if (isTornDown()) return;
34315
34389
  state.instance.channel.deliver(message, { classification: opening.classification });
34316
34390
  return;
34317
34391
  case "legacy": {
@@ -34398,6 +34472,9 @@ function toError(value) {
34398
34472
  return value instanceof Error ? value : new Error(String(value));
34399
34473
  }
34400
34474
 
34475
+ // node_modules/@modelcontextprotocol/server/dist/index.mjs
34476
+ var DEFAULT_MAX_REQUEST_BODY_SIZE = 4 * 1024 * 1024;
34477
+
34401
34478
  // node_modules/@chrischall/mcp-utils/dist/errors/index.js
34402
34479
  var DEFAULT_ERROR_MESSAGE_MAX = 500;
34403
34480
  var McpToolError = class extends Error {
@@ -34419,11 +34496,12 @@ var SessionNotAuthenticatedError = class extends McpToolError {
34419
34496
  this.name = "SessionNotAuthenticatedError";
34420
34497
  }
34421
34498
  };
34499
+ var HEADER_NAME_RUN = "[a-z0-9_-]{0,40}";
34422
34500
  var BEARER_RE = /(bearer\s+)[A-Za-z0-9._~+/=-]{8,}/gi;
34423
- var JWT_RE = /\b[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{8,}\b/g;
34501
+ var JWT_RE = /(?<![A-Za-z0-9_-])[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{8,}(?![A-Za-z0-9_-])/g;
34424
34502
  var BASIC_AUTH_RE = /(authorization\s*:\s*basic\s+)[A-Za-z0-9+/=_-]{6,}/gi;
34425
- var SET_COOKIE_RE = /(\bset-cookie\s*:\s*)([^=;,\s]+)=[^;,\s]*/gi;
34426
- var COOKIE_HEADER_RE = /((?<!set-)\bcookie\s*:\s*)((?:[^=;,\s]+=[^;,\s]*)(?:;\s*[^=;,\s]+=[^;,\s]*)*)/gi;
34503
+ var SET_COOKIE_RE = /(\bset-cookie\s*:\s*)([^=;,:\s]+)=[^;,\s]*/gi;
34504
+ var COOKIE_HEADER_RE = /((?<!set-)\bcookie\s*:\s*)((?:[^=;,:\s]+=[^;,\s]*)(?:;\s*[^=;,:\s]+=[^;,\s]*)*)/gi;
34427
34505
  var API_KEY_RE = new RegExp([
34428
34506
  "sk-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])",
34429
34507
  // OpenAI / Anthropic (incl. sk-ant-…)
@@ -34438,10 +34516,10 @@ var API_KEY_RE = new RegExp([
34438
34516
  "whsec_[A-Za-z0-9]{16,}\\b"
34439
34517
  // webhook signing secret (Stripe-style)
34440
34518
  ].map((p) => `\\b${p}`).join("|"), "g");
34441
- var QUERY_SECRET_RE = /([?&](?:(?:access|refresh|id|auth|session|csrf|xsrf)[_-]?token|client[_-]?secret|api[_-]?secret|api[_-]?key|password|passwd|signature|token|key|sig)=)[^&#\s"'<>`]+/gi;
34519
+ var QUERY_SECRET_RE = new RegExp(`([?&](?:(?:access|refresh|id|auth|session|csrf|xsrf)[_-]?token|client[_-]?secret|api[_-]?secret|api[_-]?key|password|passwd|signature|token|key|sig|(?:php|j)?sess(?:ion)?[_-]?id|sid|x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN})=)[^&#\\s"'<>\`]+`, "gi");
34442
34520
  var OAUTH_CODE_RE = /([?&]code=)(?=[^&#\s"'<>`]{16,})[^&#\s"'<>`]+/gi;
34443
34521
  var FORM_PASSWORD_RE = /((?:^|[\s,;:"'(\[{])(?:password|passwd)=)[^&#\s"'<>`]+/gim;
34444
- var HEADER_SECRET_RE = /(\b(?:x[-_][a-z0-9_-]*?(?:api[-_]?key|token|secret|auth)[a-z0-9_-]*|api[-_]?key)\s*:\s*)[^\s,;"'<>`]+/gi;
34522
+ var HEADER_SECRET_RE = new RegExp(`(\\b(?:x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN}|api[-_]?key)\\s*:\\s*)[^\\s,;"'<>\`]+`, "gi");
34445
34523
  var AWS_SIGV4_RE = /([?&]X-Amz-(?:Signature|Security-Token|Credential)=)[^&#\s"'<>`]+/gi;
34446
34524
  var JSON_SECRET_KEYS = [
34447
34525
  "(?:access|refresh|id|auth|session|bearer|csrf|xsrf)[_-]?token",
@@ -34449,7 +34527,8 @@ var JSON_SECRET_KEYS = [
34449
34527
  "api[_-]?secret",
34450
34528
  "(?:x[_-])?api[_-]?key",
34451
34529
  "private[_-]?key",
34452
- "x[_-][a-z0-9_-]*?(?:api[_-]?key|token|secret|auth)[a-z0-9_-]*",
34530
+ `x[_-]${HEADER_NAME_RUN}?(?:api[_-]?key|token|secret|auth)${HEADER_NAME_RUN}`,
34531
+ // bounded like HEADER_SECRET_RE
34453
34532
  "(?:proxy-)?authorization",
34454
34533
  "password",
34455
34534
  "passwd",
@@ -34458,12 +34537,30 @@ var JSON_SECRET_KEYS = [
34458
34537
  ].join("|");
34459
34538
  var JSON_SECRET_DQ_RE = new RegExp(`("(?:${JSON_SECRET_KEYS})"\\s*:\\s*")(?:[^"\\\\]|\\\\.)*(")`, "gi");
34460
34539
  var JSON_SECRET_SQ_RE = new RegExp(`('(?:${JSON_SECRET_KEYS})'\\s*:\\s*')(?:[^'\\\\]|\\\\.)*(')`, "gi");
34540
+ var JSON_SECRET_NUM_RE = new RegExp(`(["'](?:${JSON_SECRET_KEYS})["']\\s*:\\s*)-?\\d+(?:\\.\\d+)?(?![\\d.])`, "gi");
34541
+ var JSON_COOKIE_DQ_RE = /("((?:set-)?cookie)"\s*:\s*)("(?:[^"\\]|\\.)*"|\[\s*"(?:[^"\\]|\\.)*"(?:\s*,\s*"(?:[^"\\]|\\.)*")*)/gi;
34542
+ var JSON_COOKIE_SQ_RE = /('((?:set-)?cookie)'\s*:\s*)('(?:[^'\\]|\\.)*'|\[\s*'(?:[^'\\]|\\.)*'(?:\s*,\s*'(?:[^'\\]|\\.)*')*)/gi;
34543
+ var DQ_STRING_RE = /"((?:[^"\\]|\\.)*)"/g;
34544
+ var SQ_STRING_RE = /'((?:[^'\\]|\\.)*)'/g;
34545
+ function redactJsonCookie(quote) {
34546
+ const stringRe = quote === '"' ? DQ_STRING_RE : SQ_STRING_RE;
34547
+ return (_m, prefix, key, value) => {
34548
+ const setCookie = key.toLowerCase() === "set-cookie";
34549
+ const redacted = value.replace(stringRe, (_s, body) => {
34550
+ const out = setCookie ? body.replace(/^(\s*[^=;]+)=[^;]*/, "$1=[REDACTED]") : body.replace(/=[^;]*/g, "=[REDACTED]");
34551
+ return `${quote}${out}${quote}`;
34552
+ });
34553
+ return `${prefix}${redacted}`;
34554
+ };
34555
+ }
34461
34556
  function redactSecrets(text) {
34462
- return text.replace(BEARER_RE, "$1[REDACTED]").replace(BASIC_AUTH_RE, "$1[REDACTED]").replace(SET_COOKIE_RE, "$1$2=[REDACTED]").replace(COOKIE_HEADER_RE, (_m, prefix, pairs) => `${prefix}${pairs.replace(/=[^;,\s]*/g, "=[REDACTED]")}`).replace(API_KEY_RE, "[REDACTED]").replace(HEADER_SECRET_RE, "$1[REDACTED]").replace(QUERY_SECRET_RE, "$1[REDACTED]").replace(OAUTH_CODE_RE, "$1[REDACTED]").replace(FORM_PASSWORD_RE, "$1[REDACTED]").replace(AWS_SIGV4_RE, "$1[REDACTED]").replace(JSON_SECRET_DQ_RE, "$1[REDACTED]$2").replace(JSON_SECRET_SQ_RE, "$1[REDACTED]$2").replace(JWT_RE, "[REDACTED]");
34557
+ return text.replace(BEARER_RE, "$1[REDACTED]").replace(BASIC_AUTH_RE, "$1[REDACTED]").replace(SET_COOKIE_RE, "$1$2=[REDACTED]").replace(COOKIE_HEADER_RE, (_m, prefix, pairs) => `${prefix}${pairs.replace(/=[^;,\s]*/g, "=[REDACTED]")}`).replace(API_KEY_RE, "[REDACTED]").replace(HEADER_SECRET_RE, "$1[REDACTED]").replace(QUERY_SECRET_RE, "$1[REDACTED]").replace(OAUTH_CODE_RE, "$1[REDACTED]").replace(FORM_PASSWORD_RE, "$1[REDACTED]").replace(AWS_SIGV4_RE, "$1[REDACTED]").replace(JSON_COOKIE_DQ_RE, redactJsonCookie('"')).replace(JSON_COOKIE_SQ_RE, redactJsonCookie("'")).replace(JSON_SECRET_DQ_RE, "$1[REDACTED]$2").replace(JSON_SECRET_SQ_RE, "$1[REDACTED]$2").replace(JSON_SECRET_NUM_RE, "$1[REDACTED]").replace(JWT_RE, "[REDACTED]");
34463
34558
  }
34559
+ var ERROR_REDACTION_INPUT_MAX = 64 * 1024;
34464
34560
  function truncateErrorMessage(text, max = DEFAULT_ERROR_MESSAGE_MAX) {
34465
34561
  const str = text === null || text === void 0 ? "" : String(text);
34466
- const redacted = redactSecrets(str);
34562
+ const cap = Math.max(ERROR_REDACTION_INPUT_MAX, max);
34563
+ const redacted = redactSecrets(str.length > cap ? str.slice(0, cap) : str);
34467
34564
  if (redacted.length <= max)
34468
34565
  return redacted;
34469
34566
  return `${redacted.slice(0, max)}\u2026 [truncated]`;
@@ -34828,6 +34925,9 @@ async function tokenConfirmation(action, fb) {
34828
34925
  const subject = await fb.subject();
34829
34926
  if (isToolResult(subject))
34830
34927
  return subject;
34928
+ if (subject.payload === void 0) {
34929
+ throw new TypeError(`confirm token: ${fb.tool}'s subject() returned no payload. Bind what the call will do (the arguments or the request body), or pass \`args\` to confirmationFromEnv.`);
34930
+ }
34831
34931
  const binding = {
34832
34932
  tool: fb.tool,
34833
34933
  ...fb.account === void 0 ? {} : { account: fb.account },
@@ -34920,9 +35020,19 @@ function readConfirmMode(env = process.env) {
34920
35020
  }
34921
35021
  return "refuse";
34922
35022
  }
34923
- function confirmTtlFromEnv(env = process.env) {
35023
+ function readConfirmTtl(env) {
34924
35024
  const raw = readEnvVar("MCP_CONFIRM_TTL_SECONDS", { env })?.trim();
34925
- return raw && /^[1-9]\d*$/.test(raw) ? Number(raw) : DEFAULT_TTL_SECONDS2;
35025
+ if (!raw)
35026
+ return DEFAULT_TTL_SECONDS2;
35027
+ if (/^[1-9]\d*$/.test(raw))
35028
+ return Number(raw);
35029
+ const key = `ttl:${raw}`;
35030
+ if (!warned.has(key)) {
35031
+ warned.add(key);
35032
+ process.stderr.write(`MCP_CONFIRM_TTL_SECONDS="${raw}" is not a positive whole number of seconds; gated writes will refuse on clients that cannot show a confirmation prompt until it is fixed.
35033
+ `);
35034
+ }
35035
+ return void 0;
34926
35036
  }
34927
35037
  var processKey;
34928
35038
  function confirmKeyFromEnv(env = process.env) {
@@ -34934,15 +35044,33 @@ function confirmKeyFromEnv(env = process.env) {
34934
35044
  }
34935
35045
  var CONFIRM_TOKEN_AUTO_INSTRUCTION = "Nothing has been done yet. Review this preview; if it is what was intended, call again with the same arguments plus confirmToken. (This server runs with MCP_CONFIRM_MODE=auto, so the user's approval in chat is not required.)";
34936
35046
  var REFUSE_HINT = "Set MCP_CONFIRM_MODE=ask-user on the server to allow two-step confirmation instead.";
35047
+ var BAD_TTL_HINT = "MCP_CONFIRM_TTL_SECONDS on the server is not a positive whole number of seconds; fix it to allow two-step confirmation.";
35048
+ function boundArgs(args) {
35049
+ if (args === null || typeof args !== "object" || Array.isArray(args) || !("confirmToken" in args))
35050
+ return args;
35051
+ const { confirmToken, ...rest } = args;
35052
+ void confirmToken;
35053
+ return rest;
35054
+ }
34937
35055
  function confirmationFromEnv(options) {
34938
- const { tool, account, confirmToken, subject, instruction, spent, env = process.env, ...confirmation } = options;
35056
+ const { tool, account, confirmToken, subject, instruction, spent, args, env = process.env, ...rest } = options;
34939
35057
  const mode = readConfirmMode(env);
34940
- if (mode === "refuse") {
35058
+ const ttlSeconds = readConfirmTtl(env);
35059
+ const bound = args === void 0 ? void 0 : boundArgs(args);
35060
+ const confirmation = bound === void 0 || rest.binding ? rest : { ...rest, binding: { key: confirmKeyFromEnv(env), args: bound, ttlSeconds: ttlSeconds ?? DEFAULT_TTL_SECONDS2 } };
35061
+ if (mode === "refuse" || ttlSeconds === void 0) {
35062
+ const hint = mode === "refuse" ? REFUSE_HINT : BAD_TTL_HINT;
34941
35063
  return {
34942
35064
  ...confirmation,
34943
- unsupportedNote: confirmation.unsupportedNote ? `${confirmation.unsupportedNote} ${REFUSE_HINT}` : REFUSE_HINT
35065
+ unsupportedNote: confirmation.unsupportedNote ? `${confirmation.unsupportedNote} ${hint}` : hint
34944
35066
  };
34945
35067
  }
35068
+ const boundSubject = bound === void 0 ? subject : async () => {
35069
+ const read = await subject();
35070
+ if ("content" in read)
35071
+ return read;
35072
+ return { ...read, payload: { payload: read.payload, args: bound } };
35073
+ };
34946
35074
  return {
34947
35075
  ...confirmation,
34948
35076
  tokenFallback: {
@@ -34950,8 +35078,8 @@ function confirmationFromEnv(options) {
34950
35078
  tool,
34951
35079
  ...account === void 0 ? {} : { account },
34952
35080
  ...confirmToken === void 0 ? {} : { confirmToken },
34953
- subject,
34954
- ttlSeconds: confirmTtlFromEnv(env),
35081
+ subject: boundSubject,
35082
+ ttlSeconds,
34955
35083
  instruction: mode === "auto" ? CONFIRM_TOKEN_AUTO_INSTRUCTION : instruction ?? CONFIRM_TOKEN_INSTRUCTION,
34956
35084
  ...spent === void 0 ? {} : { spent }
34957
35085
  }
@@ -35075,6 +35203,8 @@ function withGracefulShutdown(target, opts = {}) {
35075
35203
  };
35076
35204
  const handler = (signal) => {
35077
35205
  if (shuttingDown) {
35206
+ if (signal === "stdin-eof")
35207
+ return;
35078
35208
  if (Date.now() - firstSignalAt < repeatGraceMs)
35079
35209
  return;
35080
35210
  if (shouldExit) {
@@ -35110,6 +35240,10 @@ function withGracefulShutdown(target, opts = {}) {
35110
35240
  };
35111
35241
  process.on("SIGINT", () => handler("SIGINT"));
35112
35242
  process.on("SIGTERM", () => handler("SIGTERM"));
35243
+ if (opts.stdin) {
35244
+ opts.stdin.once("end", () => handler("stdin-eof"));
35245
+ opts.stdin.once("close", () => handler("stdin-eof"));
35246
+ }
35113
35247
  }
35114
35248
  function runMcp(opts) {
35115
35249
  if (opts.banner !== void 0) {
@@ -35128,7 +35262,8 @@ function runMcp(opts) {
35128
35262
  });
35129
35263
  const shutdown = opts.shutdown ?? true;
35130
35264
  if (shutdown !== false) {
35131
- withGracefulShutdown(handle, shutdown === true ? {} : shutdown);
35265
+ const stdinDefault = spec === "stdio" ? { stdin: process.stdin } : {};
35266
+ withGracefulShutdown(handle, { ...stdinDefault, ...shutdown === true ? {} : shutdown });
35132
35267
  }
35133
35268
  return handle;
35134
35269
  }
@@ -35165,7 +35300,7 @@ function toolAnnotations(opts = {}) {
35165
35300
  }
35166
35301
 
35167
35302
  // src/version.ts
35168
- var VERSION = "2.3.1";
35303
+ var VERSION = "2.4.0";
35169
35304
 
35170
35305
  // src/client.ts
35171
35306
  import { createHash as createHash4 } from "crypto";
@@ -35374,6 +35509,7 @@ var REFRESH = `
35374
35509
  `;
35375
35510
  var AUTH_ERROR_CODES = /* @__PURE__ */ new Set(["UNAUTHORIZED", "UNAUTHENTICATED"]);
35376
35511
  var PERMISSION_ERROR_CODES = /* @__PURE__ */ new Set(["FORBIDDEN"]);
35512
+ var PERMISSION_MESSAGE_PATTERN = /\bnot allowed for user\b/i;
35377
35513
  var AUTH_MESSAGE_PATTERN = /not authoriz|unauthoriz|unauthenticated|invalid token|token (has )?expired|expired token|jwt (expired|malformed)|try to log ?in/i;
35378
35514
  function requestSignal() {
35379
35515
  return withAmbientCancellation(AbortSignal.timeout(REQUEST_TIMEOUT_MS));
@@ -35654,7 +35790,11 @@ var ViboClient = class {
35654
35790
  /** Signed in, but not allowed to do this (HTTP 403 / FORBIDDEN). */
35655
35791
  isPermissionError(status, errors) {
35656
35792
  if (status === 403) return true;
35657
- return (errors ?? []).some((e) => PERMISSION_ERROR_CODES.has(e.code ?? e.extensions?.code ?? ""));
35793
+ return (errors ?? []).some((e) => {
35794
+ const code = e.code ?? e.extensions?.code ?? "";
35795
+ if (PERMISSION_ERROR_CODES.has(code)) return true;
35796
+ return !AUTH_ERROR_CODES.has(code) && PERMISSION_MESSAGE_PATTERN.test(e.message ?? "");
35797
+ });
35658
35798
  }
35659
35799
  unwrap(status, body) {
35660
35800
  if (this.isPermissionError(status, body.errors)) {
@@ -35766,6 +35906,39 @@ var LIST_SECTIONS = `
35766
35906
  }
35767
35907
  }
35768
35908
  `;
35909
+ var SECTIONS_FOR_WRITE = `
35910
+ query sectionsForWrite($eventId: ID!) {
35911
+ sections(eventId: $eventId) {
35912
+ _id name type songsCount questionsCount answeredCount canRemove visibility
35913
+ settings { canHostsOrderSongs canHostDeleteSection }
35914
+ }
35915
+ }
35916
+ `;
35917
+ var EVENT_PERMISSIONS = `
35918
+ query eventPermissions($eventId: ID!) {
35919
+ event(eventId: $eventId) {
35920
+ _id role isLocked
35921
+ settings { canHostCreateSections canHostReorderSections sectionSongsLimit sectionMustPlayLimit }
35922
+ }
35923
+ }
35924
+ `;
35925
+ var CREATE_SECTION = `
35926
+ mutation createSection($eventId: ID!, $payload: CreateSectionInput!) {
35927
+ createSection(eventId: $eventId, payload: $payload) {
35928
+ _id name type time description visibility
35929
+ }
35930
+ }
35931
+ `;
35932
+ var REMOVE_SECTION = `
35933
+ mutation removeSection($eventId: ID!, $sectionId: ID!) {
35934
+ removeSection(eventId: $eventId, sectionId: $sectionId)
35935
+ }
35936
+ `;
35937
+ var REORDER_SECTIONS = `
35938
+ mutation reorderSections($eventId: ID!, $sourceSectionId: ID!, $targetSectionId: ID) {
35939
+ reorderSections(eventId: $eventId, sourceSectionId: $sourceSectionId, targetSectionId: $targetSectionId)
35940
+ }
35941
+ `;
35769
35942
  var GET_SECTION_SONGS = `
35770
35943
  query getSectionSongs($eventId: ID!, $sectionId: ID!, $filter: SectionSongsFilter, $pagination: PaginationInput, $sort: SongsSortInput) {
35771
35944
  getSectionSongs(eventId: $eventId, sectionId: $sectionId, filter: $filter, pagination: $pagination, sort: $sort) {
@@ -35781,6 +35954,15 @@ var GET_SECTION_SONGS = `
35781
35954
  }
35782
35955
  }
35783
35956
  `;
35957
+ var SECTION_SONG_IDS = `
35958
+ query sectionSongIds($eventId: ID!, $sectionId: ID!, $pagination: PaginationInput) {
35959
+ getSectionSongs(eventId: $eventId, sectionId: $sectionId, pagination: $pagination) {
35960
+ songs { _id viboSongId artist title }
35961
+ next { skip limit }
35962
+ totalCount
35963
+ }
35964
+ }
35965
+ `;
35784
35966
  var SEARCH_SONGS = `
35785
35967
  query getSongs($eventId: ID!, $sectionId: ID!, $filter: SongsFilter!, $limit: Int!) {
35786
35968
  getSongs(eventId: $eventId, sectionId: $sectionId, filter: $filter, limit: $limit) {
@@ -36047,7 +36229,11 @@ var inlineFileSchema = external_exports.object({
36047
36229
  });
36048
36230
  var CONFIRM_NOTE = "Asks the user to confirm first: a confirmation prompt where the client supports one; otherwise the first call returns a preview and a confirmToken, and only a repeat call with that token proceeds (see MCP_CONFIRM_MODE).";
36049
36231
  function confirmWrite(ctx, options) {
36050
- const preview = { action: options.mutation, willSend: options.willSend };
36232
+ const preview = {
36233
+ action: options.mutation,
36234
+ willSend: options.willSend,
36235
+ ...options.context ? { context: options.context } : {}
36236
+ };
36051
36237
  return requireConfirmationWithFallback(
36052
36238
  ctx,
36053
36239
  confirmationFromEnv({
@@ -36058,7 +36244,7 @@ function confirmWrite(ctx, options) {
36058
36244
  confirmToken: options.confirmToken,
36059
36245
  subject: () => ({
36060
36246
  target: options.target,
36061
- payload: options.payload ?? options.willSend,
36247
+ payload: options.payload ?? (options.context ? { willSend: options.willSend, context: options.context } : options.willSend),
36062
36248
  preview
36063
36249
  })
36064
36250
  })
@@ -36345,7 +36531,61 @@ function annotateSearchResults(results, query, source = "searchField") {
36345
36531
  return { query: parsed, ...hint ? { hint } : {}, summary, results: annotated };
36346
36532
  }
36347
36533
 
36534
+ // src/tools/lookups.ts
36535
+ async function fetchSections(client2, eventId) {
36536
+ const data = await client2.gql(SECTIONS_FOR_WRITE, { eventId });
36537
+ return data.sections ?? [];
36538
+ }
36539
+ async function fetchEventPermissions(client2, eventId) {
36540
+ const data = await client2.gql(EVENT_PERMISSIONS, { eventId });
36541
+ return data.event;
36542
+ }
36543
+ function findSection(sections, sectionId) {
36544
+ const section = sections.find((s) => s._id === sectionId);
36545
+ if (!section) {
36546
+ throw new McpToolError(`Section ${sectionId} is not in this event's timeline.`, {
36547
+ hint: "Get section ids from vibo_list_sections for the same eventId."
36548
+ });
36549
+ }
36550
+ return section;
36551
+ }
36552
+ async function fetchSectionSongs(client2, eventId, sectionId) {
36553
+ const songs = [];
36554
+ const limit = 100;
36555
+ for (let page = 0; page < 50; page++) {
36556
+ const data = await client2.gql(
36557
+ SECTION_SONG_IDS,
36558
+ { eventId, sectionId, pagination: { skip: songs.length, limit } }
36559
+ );
36560
+ const batch = data.getSectionSongs?.songs ?? [];
36561
+ songs.push(...batch);
36562
+ const total = data.getSectionSongs?.totalCount;
36563
+ if (batch.length < limit || typeof total === "number" && songs.length >= total) break;
36564
+ }
36565
+ return songs;
36566
+ }
36567
+ function assertSongsInSection(songIds, songs) {
36568
+ const have = new Set(songs.map((s) => s._id));
36569
+ const missing = songIds.filter((id) => !have.has(id));
36570
+ if (missing.length === 0) return;
36571
+ const hints = missing.map((id) => {
36572
+ const byVibo = songs.find((s) => s.viboSongId === id);
36573
+ return byVibo ? `${id} is a viboSongId \u2014 its section-song _id is ${byVibo._id}` : null;
36574
+ }).filter(Boolean);
36575
+ throw new McpToolError(
36576
+ `${missing.length} of ${songIds.length} song id(s) are not in this section: ${missing.join(", ")}. Nothing was sent.`,
36577
+ {
36578
+ hint: (hints.length ? hints.join("; ") + ". " : "") + "Use the song _id values from vibo_get_section_songs for this same section."
36579
+ }
36580
+ );
36581
+ }
36582
+ function isHost(perms) {
36583
+ return perms.role === "host";
36584
+ }
36585
+
36348
36586
  // src/tools/songs.ts
36587
+ var addVerifyDelaysMs = [0, 700, 1500];
36588
+ var sleep2 = (ms) => ms > 0 ? new Promise((r) => setTimeout(r, ms)) : Promise.resolve();
36349
36589
  function registerSongTools(server, client2) {
36350
36590
  server.registerTool(
36351
36591
  "vibo_get_section_songs",
@@ -36413,7 +36653,7 @@ function registerSongTools(server, client2) {
36413
36653
  server.registerTool(
36414
36654
  "vibo_add_song_to_section",
36415
36655
  {
36416
- description: "Add a song to a section. Pass a song from vibo_search_songs (songUrl is required; include viboSongId/title/artist when known). Before adding, check that result's `quality.confidence`: adding a `likely-not-original` result puts a cover, karaoke track or junk-metadata re-upload in front of a live DJ. If nothing looks original, report the closest matches back rather than adding a best guess. " + CONFIRM_NOTE,
36656
+ description: "Add a song to a section. Pass a song from vibo_search_songs (songUrl is required; include viboSongId/title/artist when known). Before adding, check that result's `quality.confidence`: adding a `likely-not-original` result puts a cover, karaoke track or junk-metadata re-upload in front of a live DJ. If nothing looks original, report the closest matches back rather than adding a best guess. After adding, re-reads the section and reports an error if the song is not actually there (Vibo has answered added:true without adding). " + CONFIRM_NOTE,
36417
36657
  annotations: toolAnnotations({ title: "Add song to Vibo section", readOnly: false, destructive: false }),
36418
36658
  inputSchema: external_exports.object({
36419
36659
  eventId: external_exports.string().describe("Event id."),
@@ -36445,7 +36685,35 @@ function registerSongTools(server, client2) {
36445
36685
  sectionId,
36446
36686
  payload
36447
36687
  });
36448
- return minifiedResult(data.addSongToSection);
36688
+ const res = data.addSongToSection ?? {};
36689
+ if (res.added === false) {
36690
+ throw new McpToolError("Vibo did not add the song (added: false).", {
36691
+ hint: "It may already be in this section, be on the do-not-play list, or the section may be full. Check with vibo_get_section_songs."
36692
+ });
36693
+ }
36694
+ if (!res.songId && !viboSongId) {
36695
+ return minifiedResult({
36696
+ ...res,
36697
+ verified: false,
36698
+ note: "Vibo returned no songId and no viboSongId was passed, so the add could not be checked by re-reading. Check with vibo_get_section_songs before retrying."
36699
+ });
36700
+ }
36701
+ const isOurs = (s) => !!res.songId && s._id === res.songId || !!viboSongId && s.viboSongId === viboSongId;
36702
+ let found;
36703
+ for (const delay of addVerifyDelaysMs) {
36704
+ await sleep2(delay);
36705
+ found = (await fetchSectionSongs(client2, eventId, sectionId)).find(isOurs);
36706
+ if (found) break;
36707
+ }
36708
+ if (!found) {
36709
+ throw new McpToolError(
36710
+ "Vibo answered added:true, but the song is not in the section when re-read, so it was NOT added.",
36711
+ {
36712
+ hint: "Retry once; if it still fails, add it in the Vibo app. Details: " + JSON.stringify({ songId: res.songId ?? null, viboSongId: viboSongId ?? null, totalCount: res.totalCount })
36713
+ }
36714
+ );
36715
+ }
36716
+ return minifiedResult({ ...res, verified: true, song: found });
36449
36717
  }
36450
36718
  );
36451
36719
  server.registerTool(
@@ -36861,12 +37129,35 @@ function registerQuestionTools(server, client2, resolveUpload = nodeUploadResolv
36861
37129
  );
36862
37130
  }
36863
37131
 
37132
+ // src/reorder.ts
37133
+ function planMoves(order, sources, after) {
37134
+ let current = [...order];
37135
+ let anchor2 = after;
37136
+ const moves = [];
37137
+ for (const source of sources) {
37138
+ const from = current.indexOf(source);
37139
+ const rest = current.filter((id) => id !== source);
37140
+ const to = anchor2 === null ? 0 : rest.indexOf(anchor2) + 1;
37141
+ if (to !== from) {
37142
+ moves.push({ source, target: anchor2 });
37143
+ current = [...rest.slice(0, to), source, ...rest.slice(to)];
37144
+ }
37145
+ anchor2 = source;
37146
+ }
37147
+ return { moves, finalOrder: current };
37148
+ }
37149
+ function missingIds(wanted, have) {
37150
+ const set2 = new Set(have);
37151
+ return wanted.filter((id) => !set2.has(id));
37152
+ }
37153
+
36864
37154
  // src/tools/song-management.ts
37155
+ var SONG_COMMENT_MAX = 90;
36865
37156
  function registerSongManagementTools(server, client2) {
36866
37157
  server.registerTool(
36867
37158
  "vibo_remove_song_from_section",
36868
37159
  {
36869
- description: "Remove one or more songs from a section. " + CONFIRM_NOTE,
37160
+ description: "Remove one or more songs from a section. Every id is checked against the section first; if any is not there, nothing is sent and the missing ids are listed. " + CONFIRM_NOTE,
36870
37161
  annotations: toolAnnotations({ title: "Remove songs from Vibo section", readOnly: false, destructive: true }),
36871
37162
  inputSchema: external_exports.object({
36872
37163
  eventId: external_exports.string().describe("Event id."),
@@ -36876,6 +37167,8 @@ function registerSongManagementTools(server, client2) {
36876
37167
  })
36877
37168
  },
36878
37169
  async ({ eventId, sectionId, songIds, confirmToken }, ctx) => {
37170
+ const songs = await fetchSectionSongs(client2, eventId, sectionId);
37171
+ assertSongsInSection(songIds, songs);
36879
37172
  const vars = { eventId, sectionId, songIds };
36880
37173
  const gate = await confirmWrite(ctx, {
36881
37174
  tool: "vibo_remove_song_from_section",
@@ -36883,7 +37176,13 @@ function registerSongManagementTools(server, client2) {
36883
37176
  message: "Review and confirm removing these songs:",
36884
37177
  confirmToken,
36885
37178
  target: sectionId,
36886
- willSend: vars
37179
+ willSend: vars,
37180
+ context: {
37181
+ songs: songIds.map((id) => {
37182
+ const s = songs.find((x) => x._id === id);
37183
+ return `${s.title ?? "?"} \u2014 ${s.artist ?? "?"}`;
37184
+ })
37185
+ }
36887
37186
  });
36888
37187
  if (gate) return gate;
36889
37188
  const data = await client2.gql(REMOVE_SECTION_SONGS, vars);
@@ -36893,7 +37192,7 @@ function registerSongManagementTools(server, client2) {
36893
37192
  server.registerTool(
36894
37193
  "vibo_update_song",
36895
37194
  {
36896
- description: "Update songs in a section: mark must-play, flag as do-not-play, and/or set a comment. Provide at least one field. " + CONFIRM_NOTE,
37195
+ description: `Update songs in a section: mark must-play, flag as do-not-play, and/or set a comment. Provide at least one field. A comment can be at most ${SONG_COMMENT_MAX} characters (Vibo's limit; emoji count double; checked before anything is sent). ` + CONFIRM_NOTE,
36897
37196
  annotations: toolAnnotations({ title: "Update Vibo section songs", readOnly: false, destructive: false }),
36898
37197
  inputSchema: external_exports.object({
36899
37198
  eventId: external_exports.string().describe("Event id."),
@@ -36901,7 +37200,7 @@ function registerSongManagementTools(server, client2) {
36901
37200
  songIds: external_exports.array(external_exports.string()).min(1).describe("Song _ids from vibo_get_section_songs."),
36902
37201
  isMustPlay: external_exports.boolean().optional(),
36903
37202
  isFlagged: external_exports.boolean().optional().describe("mark do-not-play / flagged"),
36904
- comment: external_exports.string().optional(),
37203
+ comment: external_exports.string().optional().describe(`Comment for the DJ, at most ${SONG_COMMENT_MAX} characters.`),
36905
37204
  confirmToken: confirmTokenParam
36906
37205
  })
36907
37206
  },
@@ -36915,6 +37214,12 @@ function registerSongManagementTools(server, client2) {
36915
37214
  hint: "Pass isMustPlay, isFlagged, and/or comment to update the songs."
36916
37215
  });
36917
37216
  }
37217
+ if (comment !== void 0 && comment.length > SONG_COMMENT_MAX) {
37218
+ throw new McpToolError(
37219
+ `Comment is ${comment.length} characters; Vibo allows at most ${SONG_COMMENT_MAX}. Nothing was sent.`,
37220
+ { hint: `Shorten it to ${SONG_COMMENT_MAX} characters or fewer, or put the longer text in a section note.` }
37221
+ );
37222
+ }
36918
37223
  const vars = { eventId, sectionId, songIds, payload };
36919
37224
  const gate = await confirmWrite(ctx, {
36920
37225
  tool: "vibo_update_song",
@@ -36960,29 +37265,58 @@ function registerSongManagementTools(server, client2) {
36960
37265
  server.registerTool(
36961
37266
  "vibo_reorder_songs",
36962
37267
  {
36963
- description: "Reorder songs within a section. " + CONFIRM_NOTE,
37268
+ description: "Reorder songs within a section: move sourceSongIds (in the given order) to directly after targetSongId, or to the top when it is omitted. Checks the ids against the section and, for a host, that the DJ allows hosts to order this section's songs. That setting is OFF for every section a host creates and only the DJ can turn it on, so a host usually cannot reorder songs in sections they added. Sends one reorderSongsBatch call per song that actually moves. " + CONFIRM_NOTE,
36964
37269
  annotations: toolAnnotations({ title: "Reorder Vibo section songs", readOnly: false, destructive: false }),
36965
37270
  inputSchema: external_exports.object({
36966
37271
  eventId: external_exports.string().describe("Event id."),
36967
37272
  sectionId: external_exports.string().describe("Section id."),
36968
- sourceSongIds: external_exports.array(external_exports.string()).min(1).describe("Song _ids from vibo_get_section_songs."),
36969
- targetSongId: external_exports.string().optional().describe("place the moved songs after this song _id; omit for start"),
37273
+ sourceSongIds: external_exports.array(external_exports.string()).min(1).describe("Song _ids from vibo_get_section_songs, in the order they should end up."),
37274
+ targetSongId: external_exports.string().optional().describe("place the moved songs directly after this song _id; omit for the top"),
36970
37275
  confirmToken: confirmTokenParam
36971
37276
  })
36972
37277
  },
36973
37278
  async ({ eventId, sectionId, sourceSongIds, targetSongId, confirmToken }, ctx) => {
36974
- const vars = { eventId, sectionId, sourceSongIds, targetSongId: targetSongId ?? null };
37279
+ if (new Set(sourceSongIds).size !== sourceSongIds.length) {
37280
+ throw new McpToolError("sourceSongIds contains a duplicate.", { hint: "List each song once." });
37281
+ }
37282
+ if (targetSongId !== void 0 && sourceSongIds.includes(targetSongId)) {
37283
+ throw new McpToolError("targetSongId is also one of the songs being moved.", {
37284
+ hint: "Pick a target outside sourceSongIds."
37285
+ });
37286
+ }
37287
+ const [perms, sections, songs] = await Promise.all([
37288
+ fetchEventPermissions(client2, eventId),
37289
+ fetchSections(client2, eventId),
37290
+ fetchSectionSongs(client2, eventId, sectionId)
37291
+ ]);
37292
+ const section = findSection(sections, sectionId);
37293
+ if (isHost(perms) && section.settings?.canHostsOrderSongs === false) {
37294
+ throw new McpToolError(
37295
+ `The DJ has turned off host song ordering for "${section.name}", so Vibo refuses reorders from hosts there. Nothing was sent.`,
37296
+ {
37297
+ hint: "It's the section's canHostsOrderSongs setting. Vibo turns it off for sections a host creates, and only the DJ can turn it on (a host update is ignored). Ask the DJ, or leave a comment on the song instead."
37298
+ }
37299
+ );
37300
+ }
37301
+ assertSongsInSection([...sourceSongIds, ...targetSongId ? [targetSongId] : []], songs);
37302
+ const { moves } = planMoves(songs.map((s) => s._id), sourceSongIds, targetSongId ?? null);
37303
+ if (moves.length === 0) {
37304
+ return minifiedResult({ changed: false, message: "Those songs are already in that order." });
37305
+ }
37306
+ const calls = moves.map((m) => ({ sourceSongIds: [m.source], targetSongId: m.target }));
36975
37307
  const gate = await confirmWrite(ctx, {
36976
37308
  tool: "vibo_reorder_songs",
36977
37309
  mutation: "reorderSongsBatch",
36978
37310
  message: "Review and confirm this reorder:",
36979
37311
  confirmToken,
36980
37312
  target: sectionId,
36981
- willSend: vars
37313
+ willSend: { eventId, sectionId, calls }
36982
37314
  });
36983
37315
  if (gate) return gate;
36984
- const data = await client2.gql(REORDER_SONGS, vars);
36985
- return minifiedResult(data.reorderSongsBatch);
37316
+ for (const c of calls) {
37317
+ await client2.gql(REORDER_SONGS, { eventId, sectionId, ...c });
37318
+ }
37319
+ return minifiedResult({ changed: true, callsSent: calls.length });
36986
37320
  }
36987
37321
  );
36988
37322
  }
@@ -37369,6 +37703,264 @@ function registerSectionEditTools(server, client2) {
37369
37703
  );
37370
37704
  }
37371
37705
 
37706
+ // src/tools/section-manage.ts
37707
+ var SECTION_NAME_MAX = 45;
37708
+ var DEFAULT_SECTION_SETTINGS = {
37709
+ timeEnabled: true,
37710
+ songsEnabled: true,
37711
+ notesEnabled: true,
37712
+ canHostsOrderSongs: true,
37713
+ canHostDeleteSection: true,
37714
+ notesVisibleForHosts: true,
37715
+ canHostChangeSectionName: true,
37716
+ canHostChangeSectionTime: true
37717
+ };
37718
+ var VISIBILITY_SETTINGS = {
37719
+ host: { visibleForGuests: false, visibleForHosts: true },
37720
+ // "Me and DJ"
37721
+ public: { visibleForGuests: true, visibleForHosts: true }
37722
+ // "Guests"
37723
+ };
37724
+ var PROTECTED_TYPES = /* @__PURE__ */ new Set(["dontPlay", "headline"]);
37725
+ var TIME_PATTERN = /^(0?[1-9]|1[0-2]):([0-5]\d)\s*([ap])\.?m\.?$/i;
37726
+ function normalizeTime(time3) {
37727
+ const m = TIME_PATTERN.exec(time3.trim());
37728
+ if (!m) return void 0;
37729
+ return `${m[1].padStart(2, "0")}:${m[2]} ${m[3].toLowerCase()}m`;
37730
+ }
37731
+ function assertCanWriteTimeline(perms, flag, what) {
37732
+ if (!isHost(perms)) return;
37733
+ if (perms.isLocked) {
37734
+ throw new McpToolError(`This event is locked, so hosts can't ${what}. Nothing was sent.`, {
37735
+ hint: "The DJ locks an event close to the date; ask them to unlock it or make the change."
37736
+ });
37737
+ }
37738
+ if (perms.settings?.[flag] === false) {
37739
+ throw new McpToolError(`The DJ has turned off "${flag}" for this event, so hosts can't ${what}. Nothing was sent.`, {
37740
+ hint: "It's an event setting only the DJ can change \u2014 ask them to enable it or make the change."
37741
+ });
37742
+ }
37743
+ }
37744
+ var sectionLabel = (s) => ({ _id: s._id, name: s.name });
37745
+ function registerSectionManageTools(server, client2) {
37746
+ server.registerTool(
37747
+ "vibo_create_section",
37748
+ {
37749
+ description: `Create a new timeline section. Name is required, max ${SECTION_NAME_MAX} characters (Vibo's limit, checked before anything is sent). Place it with afterSectionId (directly after that section) or position (0-based index; 0 = first); with neither it goes at the end of the timeline, like the web app. visibility "host" (default) = "Me and DJ", "public" = visible to guests too. Vibo gives a section a host creates "hosts can order songs" = off, which only the DJ can turn on. Returns the new section _id. ` + CONFIRM_NOTE,
37750
+ annotations: toolAnnotations({ title: "Create Vibo section", readOnly: false, destructive: false }),
37751
+ inputSchema: external_exports.object({
37752
+ eventId: external_exports.string().describe("Event id."),
37753
+ name: external_exports.string().describe(`Section name, 1\u2013${SECTION_NAME_MAX} characters.`),
37754
+ visibility: external_exports.enum(["host", "public"]).optional().describe('"host" (default) = Me and DJ only; "public" = guests can see it too.'),
37755
+ time: external_exports.string().optional().describe('Scheduled time as "hh:mm am/pm", e.g. "05:30 pm".'),
37756
+ note: external_exports.string().optional().describe("Note to the DJ (set right after creating, via updateSection)."),
37757
+ afterSectionId: external_exports.string().optional().describe("Put the new section directly after this section _id."),
37758
+ position: external_exports.number().int().min(0).optional().describe("0-based timeline index for the new section; past the end appends. Use instead of afterSectionId."),
37759
+ confirmToken: confirmTokenParam
37760
+ })
37761
+ },
37762
+ async ({ eventId, name, visibility, time: time3, note, afterSectionId, position, confirmToken }, ctx) => {
37763
+ const trimmed = name.trim();
37764
+ if (!trimmed) {
37765
+ throw new McpToolError("Section name is empty.", { hint: "Pass a name of 1\u201345 characters." });
37766
+ }
37767
+ if (trimmed.length > SECTION_NAME_MAX) {
37768
+ throw new McpToolError(
37769
+ `Section name is ${trimmed.length} characters; Vibo allows at most ${SECTION_NAME_MAX}. Nothing was sent.`,
37770
+ { hint: `Shorten the name to ${SECTION_NAME_MAX} characters or fewer.` }
37771
+ );
37772
+ }
37773
+ let normalizedTime;
37774
+ if (time3 !== void 0 && time3.trim() !== "") {
37775
+ normalizedTime = normalizeTime(time3);
37776
+ if (!normalizedTime) {
37777
+ throw new McpToolError(`"${time3}" is not a 12-hour time.`, { hint: 'Use "hh:mm am/pm", e.g. "05:30 pm".' });
37778
+ }
37779
+ }
37780
+ if (afterSectionId !== void 0 && position !== void 0) {
37781
+ throw new McpToolError("Pass afterSectionId or position, not both.", {
37782
+ hint: "afterSectionId places it after a named section; position places it at a 0-based index."
37783
+ });
37784
+ }
37785
+ const [perms, sections] = await Promise.all([fetchEventPermissions(client2, eventId), fetchSections(client2, eventId)]);
37786
+ assertCanWriteTimeline(perms, "canHostCreateSections", "add sections");
37787
+ let insertBefore;
37788
+ let placedAfter;
37789
+ if (afterSectionId !== void 0) {
37790
+ const anchor2 = findSection(sections, afterSectionId);
37791
+ insertBefore = sections[sections.indexOf(anchor2) + 1];
37792
+ placedAfter = anchor2;
37793
+ } else if (position !== void 0) {
37794
+ insertBefore = sections[position];
37795
+ placedAfter = position === 0 ? null : sections[Math.min(position, sections.length) - 1] ?? null;
37796
+ } else {
37797
+ placedAfter = sections[sections.length - 1] ?? null;
37798
+ }
37799
+ const payload = {
37800
+ type: "simple",
37801
+ name: trimmed,
37802
+ settings: {
37803
+ ...DEFAULT_SECTION_SETTINGS,
37804
+ ...perms.settings?.sectionSongsLimit != null ? { songsLimit: perms.settings.sectionSongsLimit } : {},
37805
+ ...perms.settings?.sectionMustPlayLimit != null ? { mustPlayLimit: perms.settings.sectionMustPlayLimit } : {},
37806
+ ...VISIBILITY_SETTINGS[visibility ?? "host"]
37807
+ }
37808
+ };
37809
+ if (normalizedTime) payload.time = normalizedTime;
37810
+ if (insertBefore) payload.insertBeforeSectionId = insertBefore._id;
37811
+ const willSend = { eventId, payload };
37812
+ if (note !== void 0) willSend.thenUpdateSection = { payload: { note } };
37813
+ const gate = await confirmWrite(ctx, {
37814
+ tool: "vibo_create_section",
37815
+ mutation: note !== void 0 ? "createSection, then updateSection (note)" : "createSection",
37816
+ message: `Review and confirm creating section "${trimmed}":`,
37817
+ confirmToken,
37818
+ target: eventId,
37819
+ willSend,
37820
+ context: { placedAfter: placedAfter ? sectionLabel(placedAfter) : "start of timeline" }
37821
+ });
37822
+ if (gate) return gate;
37823
+ const created = (await client2.gql(CREATE_SECTION, {
37824
+ eventId,
37825
+ payload
37826
+ })).createSection;
37827
+ let noteResult;
37828
+ if (note !== void 0) {
37829
+ noteResult = (await client2.gql(UPDATE_SECTION, {
37830
+ eventId,
37831
+ sectionId: created._id,
37832
+ payload: { note }
37833
+ })).updateSection?.note;
37834
+ }
37835
+ let placement = "unverified";
37836
+ const after = await fetchSections(client2, eventId);
37837
+ const order = after.map((s) => s._id);
37838
+ if (order.includes(created._id) && (!placedAfter || order.includes(placedAfter._id))) {
37839
+ const { moves } = planMoves(order, [created._id], placedAfter?._id ?? null);
37840
+ for (const m of moves) {
37841
+ await client2.gql(REORDER_SECTIONS, { eventId, sourceSectionId: m.source, targetSectionId: m.target });
37842
+ }
37843
+ placement = moves.length ? "fixed-by-reorder" : "as-requested";
37844
+ }
37845
+ return minifiedResult({
37846
+ ...created,
37847
+ ...note !== void 0 ? { note: noteResult ?? note } : {},
37848
+ placedAfter: placedAfter ? sectionLabel(placedAfter) : null,
37849
+ placement
37850
+ });
37851
+ }
37852
+ );
37853
+ server.registerTool(
37854
+ "vibo_delete_section",
37855
+ {
37856
+ description: `Delete a timeline section, with every song, answer and comment in it \u2014 this cannot be undone. The preview names the section and how many songs and answered questions it holds. Sections of type "dontPlay" (the DJ's do-not-play list) and "headline" (timeline dividers) are refused unless force:true. ` + CONFIRM_NOTE,
37857
+ annotations: toolAnnotations({ title: "Delete Vibo section", readOnly: false, destructive: true }),
37858
+ inputSchema: external_exports.object({
37859
+ eventId: external_exports.string().describe("Event id."),
37860
+ sectionId: external_exports.string().describe("Section _id from vibo_list_sections."),
37861
+ force: external_exports.boolean().optional().describe('Required to delete a "dontPlay" or "headline" section.'),
37862
+ confirmToken: confirmTokenParam
37863
+ })
37864
+ },
37865
+ async ({ eventId, sectionId, force, confirmToken }, ctx) => {
37866
+ const section = findSection(await fetchSections(client2, eventId), sectionId);
37867
+ if (PROTECTED_TYPES.has(section.type) && !force) {
37868
+ throw new McpToolError(
37869
+ `"${section.name}" is a ${section.type} section${section.type === "dontPlay" ? " (the DJ's do-not-play list)" : " (a timeline divider)"}; refusing to delete it. Nothing was sent.`,
37870
+ { hint: "Pass force:true only if the user explicitly wants this section gone." }
37871
+ );
37872
+ }
37873
+ if (section.canRemove === false) {
37874
+ throw new McpToolError(`Vibo reports you can't delete "${section.name}". Nothing was sent.`, {
37875
+ hint: 'The DJ controls who may delete each section ("Who can delete section"); ask them to allow it or delete it.'
37876
+ });
37877
+ }
37878
+ const summary = {
37879
+ name: section.name,
37880
+ type: section.type,
37881
+ songsCount: section.songsCount,
37882
+ questionsAnswered: `${section.answeredCount}/${section.questionsCount}`
37883
+ };
37884
+ const vars = { eventId, sectionId };
37885
+ const gate = await confirmWrite(ctx, {
37886
+ tool: "vibo_delete_section",
37887
+ mutation: "removeSection",
37888
+ message: `Delete section "${section.name}"? It holds ${section.songsCount} song(s) and ${section.answeredCount} of ${section.questionsCount} answered question(s). This cannot be undone.`,
37889
+ confirmToken,
37890
+ target: sectionId,
37891
+ willSend: vars,
37892
+ context: summary
37893
+ });
37894
+ if (gate) return gate;
37895
+ const data = await client2.gql(REMOVE_SECTION, vars);
37896
+ if (data.removeSection !== true) {
37897
+ throw new McpToolError(`Vibo did not confirm deleting "${section.name}" (removeSection returned ${String(data.removeSection)}).`, {
37898
+ hint: "Re-check with vibo_list_sections before retrying."
37899
+ });
37900
+ }
37901
+ return minifiedResult({ deleted: true, section: { _id: sectionId, ...summary } });
37902
+ }
37903
+ );
37904
+ server.registerTool(
37905
+ "vibo_reorder_sections",
37906
+ {
37907
+ description: "Move one or more timeline sections to directly after targetSectionId (omit it to move them to the start), keeping the given order. Mirrors vibo_reorder_songs. Sends one reorderSections call per section that actually moves. " + CONFIRM_NOTE,
37908
+ annotations: toolAnnotations({ title: "Reorder Vibo sections", readOnly: false, destructive: false }),
37909
+ inputSchema: external_exports.object({
37910
+ eventId: external_exports.string().describe("Event id."),
37911
+ sourceSectionIds: external_exports.array(external_exports.string()).min(1).describe("Section _ids to move, in the order they should end up."),
37912
+ targetSectionId: external_exports.string().optional().describe("Place the moved sections right after this section _id; omit for the start."),
37913
+ confirmToken: confirmTokenParam
37914
+ })
37915
+ },
37916
+ async ({ eventId, sourceSectionIds, targetSectionId, confirmToken }, ctx) => {
37917
+ if (new Set(sourceSectionIds).size !== sourceSectionIds.length) {
37918
+ throw new McpToolError("sourceSectionIds contains a duplicate.", { hint: "List each section once." });
37919
+ }
37920
+ if (targetSectionId !== void 0 && sourceSectionIds.includes(targetSectionId)) {
37921
+ throw new McpToolError("targetSectionId is also one of the sections being moved.", {
37922
+ hint: "Pick a target outside sourceSectionIds (the moved sections keep their given order)."
37923
+ });
37924
+ }
37925
+ const [perms, sections] = await Promise.all([fetchEventPermissions(client2, eventId), fetchSections(client2, eventId)]);
37926
+ const order = sections.map((s) => s._id);
37927
+ const missing = missingIds([...sourceSectionIds, ...targetSectionId ? [targetSectionId] : []], order);
37928
+ if (missing.length) {
37929
+ throw new McpToolError(`Not in this event's timeline: ${missing.join(", ")}. Nothing was sent.`, {
37930
+ hint: "Get section ids from vibo_list_sections for the same eventId."
37931
+ });
37932
+ }
37933
+ assertCanWriteTimeline(perms, "canHostReorderSections", "reorder sections");
37934
+ const byId = new Map(sections.map((s) => [s._id, s]));
37935
+ const { moves, finalOrder } = planMoves(order, sourceSectionIds, targetSectionId ?? null);
37936
+ const after = targetSectionId ? sectionLabel(byId.get(targetSectionId)) : "start of timeline";
37937
+ if (moves.length === 0) {
37938
+ return minifiedResult({ changed: false, message: "Those sections are already in that order.", after });
37939
+ }
37940
+ const calls = moves.map((m) => ({ sourceSectionId: m.source, targetSectionId: m.target }));
37941
+ const gate = await confirmWrite(ctx, {
37942
+ tool: "vibo_reorder_sections",
37943
+ mutation: "reorderSections",
37944
+ message: `Review and confirm moving ${sourceSectionIds.length} section(s):`,
37945
+ confirmToken,
37946
+ target: eventId,
37947
+ willSend: { eventId, calls },
37948
+ context: { moving: sourceSectionIds.map((id) => byId.get(id).name), after }
37949
+ });
37950
+ if (gate) return gate;
37951
+ for (const c of calls) {
37952
+ await client2.gql(REORDER_SECTIONS, { eventId, ...c });
37953
+ }
37954
+ return minifiedResult({
37955
+ changed: true,
37956
+ callsSent: calls.length,
37957
+ after,
37958
+ newPositions: sourceSectionIds.map((id) => ({ ...sectionLabel(byId.get(id)), index: finalOrder.indexOf(id) }))
37959
+ });
37960
+ }
37961
+ );
37962
+ }
37963
+
37372
37964
  // src/tools/uploads.ts
37373
37965
  function registerUploadTools(server, client2, resolveUpload = nodeUploadResolver) {
37374
37966
  server.registerTool(
@@ -37441,8 +38033,9 @@ async function captureViboSession(deps = {}) {
37441
38033
  });
37442
38034
  } catch (err) {
37443
38035
  const msg = err instanceof Error ? err.message : String(err);
38036
+ const bridgeHint = err?.hint;
37444
38037
  throw new McpToolError(`Vibo browser token capture failed: ${msg}`, {
37445
- hint: "Install the fetchproxy browser extension, sign into https://web.vibodj.com, approve the pair code, then retry.",
38038
+ hint: typeof bridgeHint === "string" && bridgeHint ? bridgeHint : "Install the ContextMint Bridge browser extension (https://github.com/nullnet-app/contextmint-bridge/releases), sign into https://web.vibodj.com, approve the pair code, then retry.",
37446
38039
  cause: err
37447
38040
  });
37448
38041
  }
@@ -37450,7 +38043,7 @@ async function captureViboSession(deps = {}) {
37450
38043
  const refreshToken = session.localStorage?.["x-refresh-token"] ?? null;
37451
38044
  if (!accessToken) {
37452
38045
  throw new McpToolError("No Vibo token found in the signed-in browser tab.", {
37453
- hint: "Make sure you are signed into https://web.vibodj.com in the browser with the fetchproxy extension, then retry."
38046
+ hint: "Make sure you are signed into https://web.vibodj.com in the browser with ContextMint Bridge, then retry."
37454
38047
  });
37455
38048
  }
37456
38049
  return { accessToken, refreshToken };
@@ -37461,7 +38054,7 @@ function registerSessionTools(server, client2) {
37461
38054
  server.registerTool(
37462
38055
  "vibo_capture_session",
37463
38056
  {
37464
- description: "Capture your Vibo login from a signed-in web.vibodj.com browser tab via the fetchproxy bridge \u2014 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.",
38057
+ description: "Capture your Vibo login from a signed-in web.vibodj.com browser tab via ContextMint Bridge \u2014 for accounts that sign in with Apple/Google/Facebook (no password). Requires the ContextMint Bridge 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.",
37465
38058
  annotations: toolAnnotations({ title: "Capture Vibo session (SSO)", readOnly: false, destructive: false })
37466
38059
  },
37467
38060
  async () => {
@@ -37502,13 +38095,14 @@ await runMcp({
37502
38095
  registerImportTools,
37503
38096
  registerCollaborationTools,
37504
38097
  registerSectionEditTools,
38098
+ registerSectionManageTools,
37505
38099
  registerUploadTools,
37506
38100
  registerSessionTools
37507
38101
  ]
37508
38102
  });
37509
38103
  /*! Bundled license information:
37510
38104
 
37511
- @modelcontextprotocol/server/dist/src-CX2iR2pK.mjs:
38105
+ @modelcontextprotocol/server/dist/src-D-y6h4N7.mjs:
37512
38106
  (*!
37513
38107
  * content-type
37514
38108
  * Copyright(c) 2015 Douglas Christopher Wilson