vibo-mcp 2.3.0 → 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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +50 -1
- package/dist/auth.js +7 -3
- package/dist/bundle.js +693 -86
- package/dist/client.js +13 -1
- package/dist/gql.js +50 -0
- package/dist/index.js +2 -0
- package/dist/reorder.js +27 -0
- package/dist/tools/collaboration.js +19 -7
- package/dist/tools/lookups.js +59 -0
- package/dist/tools/section-manage.js +288 -0
- package/dist/tools/session.js +1 -1
- package/dist/tools/shared.js +8 -3
- package/dist/tools/song-management.js +70 -10
- package/dist/tools/songs.js +44 -3
- package/dist/version.js +1 -1
- package/dist/view.js +13 -0
- package/package.json +3 -3
- package/server.json +2 -2
- package/skills/vibo-mcp/SKILL.md +17 -5
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-
|
|
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-
|
|
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
|
-
|
|
20838
|
-
|
|
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
|
-
|
|
20850
|
-
|
|
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 (
|
|
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)
|
|
25002
|
-
|
|
25003
|
-
|
|
25004
|
-
|
|
25005
|
-
|
|
25006
|
-
|
|
25007
|
-
|
|
25008
|
-
|
|
25009
|
-
|
|
25010
|
-
|
|
25011
|
-
|
|
25012
|
-
|
|
25013
|
-
|
|
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 &&
|
|
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-
|
|
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)
|
|
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
|
|
33605
|
-
|
|
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.
|
|
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 =
|
|
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*)([
|
|
34426
|
-
var COOKIE_HEADER_RE = /((?<!set-)\bcookie\s*:\s*)((?:[
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
|
35023
|
+
function readConfirmTtl(env) {
|
|
34924
35024
|
const raw = readEnvVar("MCP_CONFIRM_TTL_SECONDS", { env })?.trim();
|
|
34925
|
-
|
|
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, ...
|
|
35056
|
+
const { tool, account, confirmToken, subject, instruction, spent, args, env = process.env, ...rest } = options;
|
|
34939
35057
|
const mode = readConfirmMode(env);
|
|
34940
|
-
|
|
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} ${
|
|
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
|
|
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
|
-
|
|
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.
|
|
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) =>
|
|
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) {
|
|
@@ -36000,7 +36182,9 @@ var UPLOAD_USER_PHOTO = `
|
|
|
36000
36182
|
// src/view.ts
|
|
36001
36183
|
var VIBO_VIEWS = ["compact", "full"];
|
|
36002
36184
|
var NOTE = `compact strips image/avatar URLs from the response; "full" returns Vibo's payload untouched. No field projection: this server has no verified record of which Vibo fields matter, and inventing one would risk dropping a field a caller needs.`;
|
|
36185
|
+
var EVENT_USERS_NOTE = `compact (default) returns each member as {_id, firstName, lastName, role} only \u2014 no email addresses or avatar URLs; "full" returns Vibo's payload untouched, emails included. This document's field list is fixed by its query, so the projection cannot drop an unknown field.`;
|
|
36003
36186
|
var viewArg = () => viewParam(VIBO_VIEWS, { note: NOTE });
|
|
36187
|
+
var eventUsersViewArg = () => viewParam(VIBO_VIEWS, { note: EVENT_USERS_NOTE });
|
|
36004
36188
|
function viewResponse(view, data) {
|
|
36005
36189
|
const rung = resolveView(view, VIBO_VIEWS);
|
|
36006
36190
|
return minifiedResult(rung === "compact" ? stripMediaUrls(data) : data);
|
|
@@ -36045,7 +36229,11 @@ var inlineFileSchema = external_exports.object({
|
|
|
36045
36229
|
});
|
|
36046
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).";
|
|
36047
36231
|
function confirmWrite(ctx, options) {
|
|
36048
|
-
const preview = {
|
|
36232
|
+
const preview = {
|
|
36233
|
+
action: options.mutation,
|
|
36234
|
+
willSend: options.willSend,
|
|
36235
|
+
...options.context ? { context: options.context } : {}
|
|
36236
|
+
};
|
|
36049
36237
|
return requireConfirmationWithFallback(
|
|
36050
36238
|
ctx,
|
|
36051
36239
|
confirmationFromEnv({
|
|
@@ -36056,7 +36244,7 @@ function confirmWrite(ctx, options) {
|
|
|
36056
36244
|
confirmToken: options.confirmToken,
|
|
36057
36245
|
subject: () => ({
|
|
36058
36246
|
target: options.target,
|
|
36059
|
-
payload: options.payload ?? options.willSend,
|
|
36247
|
+
payload: options.payload ?? (options.context ? { willSend: options.willSend, context: options.context } : options.willSend),
|
|
36060
36248
|
preview
|
|
36061
36249
|
})
|
|
36062
36250
|
})
|
|
@@ -36343,7 +36531,61 @@ function annotateSearchResults(results, query, source = "searchField") {
|
|
|
36343
36531
|
return { query: parsed, ...hint ? { hint } : {}, summary, results: annotated };
|
|
36344
36532
|
}
|
|
36345
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
|
+
|
|
36346
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();
|
|
36347
36589
|
function registerSongTools(server, client2) {
|
|
36348
36590
|
server.registerTool(
|
|
36349
36591
|
"vibo_get_section_songs",
|
|
@@ -36411,7 +36653,7 @@ function registerSongTools(server, client2) {
|
|
|
36411
36653
|
server.registerTool(
|
|
36412
36654
|
"vibo_add_song_to_section",
|
|
36413
36655
|
{
|
|
36414
|
-
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,
|
|
36415
36657
|
annotations: toolAnnotations({ title: "Add song to Vibo section", readOnly: false, destructive: false }),
|
|
36416
36658
|
inputSchema: external_exports.object({
|
|
36417
36659
|
eventId: external_exports.string().describe("Event id."),
|
|
@@ -36443,7 +36685,35 @@ function registerSongTools(server, client2) {
|
|
|
36443
36685
|
sectionId,
|
|
36444
36686
|
payload
|
|
36445
36687
|
});
|
|
36446
|
-
|
|
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 });
|
|
36447
36717
|
}
|
|
36448
36718
|
);
|
|
36449
36719
|
server.registerTool(
|
|
@@ -36859,12 +37129,35 @@ function registerQuestionTools(server, client2, resolveUpload = nodeUploadResolv
|
|
|
36859
37129
|
);
|
|
36860
37130
|
}
|
|
36861
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
|
+
|
|
36862
37154
|
// src/tools/song-management.ts
|
|
37155
|
+
var SONG_COMMENT_MAX = 90;
|
|
36863
37156
|
function registerSongManagementTools(server, client2) {
|
|
36864
37157
|
server.registerTool(
|
|
36865
37158
|
"vibo_remove_song_from_section",
|
|
36866
37159
|
{
|
|
36867
|
-
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,
|
|
36868
37161
|
annotations: toolAnnotations({ title: "Remove songs from Vibo section", readOnly: false, destructive: true }),
|
|
36869
37162
|
inputSchema: external_exports.object({
|
|
36870
37163
|
eventId: external_exports.string().describe("Event id."),
|
|
@@ -36874,6 +37167,8 @@ function registerSongManagementTools(server, client2) {
|
|
|
36874
37167
|
})
|
|
36875
37168
|
},
|
|
36876
37169
|
async ({ eventId, sectionId, songIds, confirmToken }, ctx) => {
|
|
37170
|
+
const songs = await fetchSectionSongs(client2, eventId, sectionId);
|
|
37171
|
+
assertSongsInSection(songIds, songs);
|
|
36877
37172
|
const vars = { eventId, sectionId, songIds };
|
|
36878
37173
|
const gate = await confirmWrite(ctx, {
|
|
36879
37174
|
tool: "vibo_remove_song_from_section",
|
|
@@ -36881,7 +37176,13 @@ function registerSongManagementTools(server, client2) {
|
|
|
36881
37176
|
message: "Review and confirm removing these songs:",
|
|
36882
37177
|
confirmToken,
|
|
36883
37178
|
target: sectionId,
|
|
36884
|
-
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
|
+
}
|
|
36885
37186
|
});
|
|
36886
37187
|
if (gate) return gate;
|
|
36887
37188
|
const data = await client2.gql(REMOVE_SECTION_SONGS, vars);
|
|
@@ -36891,7 +37192,7 @@ function registerSongManagementTools(server, client2) {
|
|
|
36891
37192
|
server.registerTool(
|
|
36892
37193
|
"vibo_update_song",
|
|
36893
37194
|
{
|
|
36894
|
-
description:
|
|
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,
|
|
36895
37196
|
annotations: toolAnnotations({ title: "Update Vibo section songs", readOnly: false, destructive: false }),
|
|
36896
37197
|
inputSchema: external_exports.object({
|
|
36897
37198
|
eventId: external_exports.string().describe("Event id."),
|
|
@@ -36899,7 +37200,7 @@ function registerSongManagementTools(server, client2) {
|
|
|
36899
37200
|
songIds: external_exports.array(external_exports.string()).min(1).describe("Song _ids from vibo_get_section_songs."),
|
|
36900
37201
|
isMustPlay: external_exports.boolean().optional(),
|
|
36901
37202
|
isFlagged: external_exports.boolean().optional().describe("mark do-not-play / flagged"),
|
|
36902
|
-
comment: external_exports.string().optional(),
|
|
37203
|
+
comment: external_exports.string().optional().describe(`Comment for the DJ, at most ${SONG_COMMENT_MAX} characters.`),
|
|
36903
37204
|
confirmToken: confirmTokenParam
|
|
36904
37205
|
})
|
|
36905
37206
|
},
|
|
@@ -36913,6 +37214,12 @@ function registerSongManagementTools(server, client2) {
|
|
|
36913
37214
|
hint: "Pass isMustPlay, isFlagged, and/or comment to update the songs."
|
|
36914
37215
|
});
|
|
36915
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
|
+
}
|
|
36916
37223
|
const vars = { eventId, sectionId, songIds, payload };
|
|
36917
37224
|
const gate = await confirmWrite(ctx, {
|
|
36918
37225
|
tool: "vibo_update_song",
|
|
@@ -36958,29 +37265,58 @@ function registerSongManagementTools(server, client2) {
|
|
|
36958
37265
|
server.registerTool(
|
|
36959
37266
|
"vibo_reorder_songs",
|
|
36960
37267
|
{
|
|
36961
|
-
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,
|
|
36962
37269
|
annotations: toolAnnotations({ title: "Reorder Vibo section songs", readOnly: false, destructive: false }),
|
|
36963
37270
|
inputSchema: external_exports.object({
|
|
36964
37271
|
eventId: external_exports.string().describe("Event id."),
|
|
36965
37272
|
sectionId: external_exports.string().describe("Section id."),
|
|
36966
|
-
sourceSongIds: external_exports.array(external_exports.string()).min(1).describe("Song _ids from vibo_get_section_songs."),
|
|
36967
|
-
targetSongId: external_exports.string().optional().describe("place the moved songs after this song _id; omit for
|
|
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"),
|
|
36968
37275
|
confirmToken: confirmTokenParam
|
|
36969
37276
|
})
|
|
36970
37277
|
},
|
|
36971
37278
|
async ({ eventId, sectionId, sourceSongIds, targetSongId, confirmToken }, ctx) => {
|
|
36972
|
-
|
|
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 }));
|
|
36973
37307
|
const gate = await confirmWrite(ctx, {
|
|
36974
37308
|
tool: "vibo_reorder_songs",
|
|
36975
37309
|
mutation: "reorderSongsBatch",
|
|
36976
37310
|
message: "Review and confirm this reorder:",
|
|
36977
37311
|
confirmToken,
|
|
36978
37312
|
target: sectionId,
|
|
36979
|
-
willSend:
|
|
37313
|
+
willSend: { eventId, sectionId, calls }
|
|
36980
37314
|
});
|
|
36981
37315
|
if (gate) return gate;
|
|
36982
|
-
const
|
|
36983
|
-
|
|
37316
|
+
for (const c of calls) {
|
|
37317
|
+
await client2.gql(REORDER_SONGS, { eventId, sectionId, ...c });
|
|
37318
|
+
}
|
|
37319
|
+
return minifiedResult({ changed: true, callsSent: calls.length });
|
|
36984
37320
|
}
|
|
36985
37321
|
);
|
|
36986
37322
|
}
|
|
@@ -37193,16 +37529,23 @@ function registerImportTools(server, client2) {
|
|
|
37193
37529
|
}
|
|
37194
37530
|
|
|
37195
37531
|
// src/tools/collaboration.ts
|
|
37532
|
+
function membersForView(view, users) {
|
|
37533
|
+
if (resolveView(view, VIBO_VIEWS) !== "compact") return users;
|
|
37534
|
+
return users.map((u) => {
|
|
37535
|
+
const { _id, firstName, lastName, role } = u ?? {};
|
|
37536
|
+
return { _id, firstName, lastName, role };
|
|
37537
|
+
});
|
|
37538
|
+
}
|
|
37196
37539
|
function registerCollaborationTools(server, client2) {
|
|
37197
37540
|
server.registerTool(
|
|
37198
37541
|
"vibo_list_event_users",
|
|
37199
37542
|
{
|
|
37200
|
-
description: "List the hosts and guests on an event. With no usersType, returns both groups merged ({hosts, guests, hostsCount, guestsCount}) and `limit`/`skip` apply per group; with usersType, returns that one group's page.",
|
|
37543
|
+
description: "List the hosts and guests on an event. With no usersType, returns both groups merged ({hosts, guests, hostsCount, guestsCount}) and `limit`/`skip` apply per group; with usersType, returns that one group's page. Default (compact) view returns each member's id, name and role only; pass view:'full' for email addresses and avatars.",
|
|
37201
37544
|
annotations: toolAnnotations({ title: "List Vibo event users", readOnly: true }),
|
|
37202
37545
|
inputSchema: external_exports.object({
|
|
37203
37546
|
eventId: external_exports.string().describe("Event id."),
|
|
37204
37547
|
usersType: external_exports.enum(["host", "guest"]).optional().describe("Filter to only hosts or only guests."),
|
|
37205
|
-
view:
|
|
37548
|
+
view: eventUsersViewArg(),
|
|
37206
37549
|
limit: limitSchema.describe("Max items to return (default 20). Applies per group when usersType is omitted."),
|
|
37207
37550
|
skip: skipSchema
|
|
37208
37551
|
})
|
|
@@ -37215,15 +37558,19 @@ function registerCollaborationTools(server, client2) {
|
|
|
37215
37558
|
usersType,
|
|
37216
37559
|
pagination: page
|
|
37217
37560
|
});
|
|
37218
|
-
return viewResponse(view, {
|
|
37561
|
+
return viewResponse(view, {
|
|
37562
|
+
...data.eventUsers,
|
|
37563
|
+
users: membersForView(view, data.eventUsers.users),
|
|
37564
|
+
usersType
|
|
37565
|
+
});
|
|
37219
37566
|
}
|
|
37220
37567
|
const [hosts, guests] = await Promise.all([
|
|
37221
37568
|
client2.gql(LIST_EVENT_USERS, { eventId, usersType: "host", pagination: page }),
|
|
37222
37569
|
client2.gql(LIST_EVENT_USERS, { eventId, usersType: "guest", pagination: page })
|
|
37223
37570
|
]);
|
|
37224
37571
|
return viewResponse(view, {
|
|
37225
|
-
hosts: hosts.eventUsers.users,
|
|
37226
|
-
guests: guests.eventUsers.users,
|
|
37572
|
+
hosts: membersForView(view, hosts.eventUsers.users),
|
|
37573
|
+
guests: membersForView(view, guests.eventUsers.users),
|
|
37227
37574
|
hostsCount: hosts.eventUsers.totalCount,
|
|
37228
37575
|
guestsCount: guests.eventUsers.totalCount
|
|
37229
37576
|
});
|
|
@@ -37356,6 +37703,264 @@ function registerSectionEditTools(server, client2) {
|
|
|
37356
37703
|
);
|
|
37357
37704
|
}
|
|
37358
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
|
+
|
|
37359
37964
|
// src/tools/uploads.ts
|
|
37360
37965
|
function registerUploadTools(server, client2, resolveUpload = nodeUploadResolver) {
|
|
37361
37966
|
server.registerTool(
|
|
@@ -37428,8 +38033,9 @@ async function captureViboSession(deps = {}) {
|
|
|
37428
38033
|
});
|
|
37429
38034
|
} catch (err) {
|
|
37430
38035
|
const msg = err instanceof Error ? err.message : String(err);
|
|
38036
|
+
const bridgeHint = err?.hint;
|
|
37431
38037
|
throw new McpToolError(`Vibo browser token capture failed: ${msg}`, {
|
|
37432
|
-
hint: "Install the
|
|
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.",
|
|
37433
38039
|
cause: err
|
|
37434
38040
|
});
|
|
37435
38041
|
}
|
|
@@ -37437,7 +38043,7 @@ async function captureViboSession(deps = {}) {
|
|
|
37437
38043
|
const refreshToken = session.localStorage?.["x-refresh-token"] ?? null;
|
|
37438
38044
|
if (!accessToken) {
|
|
37439
38045
|
throw new McpToolError("No Vibo token found in the signed-in browser tab.", {
|
|
37440
|
-
hint: "Make sure you are signed into https://web.vibodj.com in the browser with
|
|
38046
|
+
hint: "Make sure you are signed into https://web.vibodj.com in the browser with ContextMint Bridge, then retry."
|
|
37441
38047
|
});
|
|
37442
38048
|
}
|
|
37443
38049
|
return { accessToken, refreshToken };
|
|
@@ -37448,7 +38054,7 @@ function registerSessionTools(server, client2) {
|
|
|
37448
38054
|
server.registerTool(
|
|
37449
38055
|
"vibo_capture_session",
|
|
37450
38056
|
{
|
|
37451
|
-
description: "Capture your Vibo login from a signed-in web.vibodj.com browser tab via
|
|
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.",
|
|
37452
38058
|
annotations: toolAnnotations({ title: "Capture Vibo session (SSO)", readOnly: false, destructive: false })
|
|
37453
38059
|
},
|
|
37454
38060
|
async () => {
|
|
@@ -37489,13 +38095,14 @@ await runMcp({
|
|
|
37489
38095
|
registerImportTools,
|
|
37490
38096
|
registerCollaborationTools,
|
|
37491
38097
|
registerSectionEditTools,
|
|
38098
|
+
registerSectionManageTools,
|
|
37492
38099
|
registerUploadTools,
|
|
37493
38100
|
registerSessionTools
|
|
37494
38101
|
]
|
|
37495
38102
|
});
|
|
37496
38103
|
/*! Bundled license information:
|
|
37497
38104
|
|
|
37498
|
-
@modelcontextprotocol/server/dist/src-
|
|
38105
|
+
@modelcontextprotocol/server/dist/src-D-y6h4N7.mjs:
|
|
37499
38106
|
(*!
|
|
37500
38107
|
* content-type
|
|
37501
38108
|
* Copyright(c) 2015 Douglas Christopher Wilson
|