@velaro/mcp-server 0.6.104 → 1.0.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velaro/mcp-server",
3
- "version": "0.6.104",
3
+ "version": "1.0.0",
4
4
  "description": "Velaro MCP server — connect Claude and other AI agents directly to your Velaro account: KB, workflows, bots, conversations, contacts, routing, and more.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -45,6 +45,7 @@
45
45
  "tool-annotations.js",
46
46
  "log-search.js",
47
47
  "messaging-base.js",
48
+ "workflow-schedule.js",
48
49
  "server.json",
49
50
  "README.md"
50
51
  ],
package/server.js CHANGED
@@ -51,6 +51,7 @@ import {
51
51
  } from '@modelcontextprotocol/sdk/types.js';
52
52
  import { withAnnotations } from './tool-annotations.js';
53
53
  import { resolveMessagingApi } from './messaging-base.js';
54
+ import { buildResolvePath, buildTimelinePath, buildOverrideBody, buildValidateBody, buildBannerPath, buildOverrideListPath, tolerant, VALIDATE_PATH, OVERRIDE_PATH } from './workflow-schedule.js';
54
55
 
55
56
  // Default was 'https://help.velaro.com'. Renamed to the recognizable admin API hostname for
56
57
  // clarity during the 2026-09-10 Donaldson bot Calendly status investigation
@@ -156,6 +157,25 @@ function extractLogEntries(response) {
156
157
  return [];
157
158
  }
158
159
 
160
+ /** Body for POST/PUT ApplePayPresetOrders: only passed fields; the server computes the total. */
161
+ function applePayPresetBody(args) {
162
+ const body = {};
163
+ for (const k of ['name', 'description', 'currencyCode', 'isActive', 'overrideRestrictedToManagers']) {
164
+ if (args[k] !== undefined) body[k] = args[k];
165
+ }
166
+ if (args.lineItems !== undefined) {
167
+ if (!Array.isArray(args.lineItems) || args.lineItems.length > 20) throw new Error('lineItems must be an array of at most 20 items');
168
+ body.lineItems = args.lineItems.map(li => ({ label: String(li?.label ?? ''), amount: Number(li?.amount) }));
169
+ }
170
+ return body;
171
+ }
172
+
173
+ function applePayPresetId(raw) {
174
+ const n = Number(raw);
175
+ if (!Number.isInteger(n) || n <= 0) throw new Error('id must be a positive whole number');
176
+ return n;
177
+ }
178
+
159
179
  /**
160
180
  * Body for PUT avatar/groups|workflows/{id}/override. Merge by default (only passed fields change, "" clears one);
161
181
  * `replace: true` replaces the whole override. Mirrors cli/lib/commands/avatar.js.
@@ -1114,6 +1134,66 @@ const TOOLS = [
1114
1134
  required: ['name', 'trigger'],
1115
1135
  },
1116
1136
  },
1137
+ {
1138
+ name: 'workflow_schedule_resolve',
1139
+ description: 'Which workflow runs at a given date/time? Returns { mode, first (winner), gap, winnerOverrideId, active (ordered workflows with priority), trace } (workflow schedule priority; schedule-only preview, a workflow own trigger conditions are not evaluated). Site comes from auth. Answers "why did workflow X (not) fire at 6 PM Dec 25". Read-only.',
1140
+ inputSchema: { type: 'object', properties: {
1141
+ trigger: { type: 'string', description: 'Trigger type, e.g. "conversation_opened"' },
1142
+ channel: { type: 'string', description: 'Channel, e.g. "Chat", "Sms"' },
1143
+ at: { type: 'string', description: 'ISO date-time to evaluate (default now)' },
1144
+ tz: { type: 'string', description: 'IANA time zone, e.g. "America/Los_Angeles"' },
1145
+ }, required: ['trigger'] },
1146
+ },
1147
+ {
1148
+ name: 'workflow_schedule_timeline',
1149
+ description: 'Timeline of which workflow wins over a date range for a trigger/channel: returns { segments: [{ fromUtc, toUtc, workflowId, workflowName, gap }], gaps (count) }. Read-only.',
1150
+ inputSchema: { type: 'object', properties: {
1151
+ from: { type: 'string', description: 'ISO start with offset (default now)' },
1152
+ to: { type: 'string', description: 'ISO end with offset (default from + 14 days; range max 62 days)' },
1153
+ trigger: { type: 'string' },
1154
+ channel: { type: 'string' },
1155
+ }, required: ['trigger'] },
1156
+ },
1157
+ {
1158
+ name: 'workflow_schedule_validate',
1159
+ description: 'Dry-run one draft schedule override without saving: what would the next 400 days look like. Returns { ok, errors, warnings, gapCount }; same-priority overlap and a bad target block, gaps warn. Omit override to just list current gaps. Read-only.',
1160
+ inputSchema: { type: 'object', properties: {
1161
+ trigger: { type: 'string' },
1162
+ channel: { type: 'string' },
1163
+ override: { type: 'object', description: 'Draft override: { sharedScheduleId, name, startDate, endDate, action ("SwitchWorkflow" or 3), targetWorkflowId, recurringYearly, autoExpiresAt, isActive }' },
1164
+ }, required: ['trigger'] },
1165
+ },
1166
+ {
1167
+ name: 'workflow_schedule_banner',
1168
+ description: 'The first gap (no workflow scheduled to run) in the next 7 days for a trigger/channel: { hasGap, fromUtc, toUtc, message }. Read-only.',
1169
+ inputSchema: { type: 'object', properties: { trigger: { type: 'string' }, channel: { type: 'string' } } },
1170
+ },
1171
+ {
1172
+ name: 'workflow_schedule_override_list',
1173
+ description: 'List this site\'s schedule overrides, optionally only one action ("SwitchWorkflow" or 3). Read-only.',
1174
+ inputSchema: { type: 'object', properties: { action: { description: '"SwitchWorkflow" or an int action value' } } },
1175
+ },
1176
+ {
1177
+ name: 'workflow_schedule_override_delete',
1178
+ description: 'Delete a schedule override by id (site admins only). Writes: confirm with the user first.',
1179
+ inputSchema: { type: 'object', properties: { id: { type: 'number' } }, required: ['id'] },
1180
+ },
1181
+ {
1182
+ name: 'workflow_schedule_override_save',
1183
+ description: 'Create (omit id) or update (id) a schedule override. action accepts "SwitchWorkflow" (3) with targetWorkflowId to run a different workflow on those dates, alongside the existing actions. Writes: confirm the dates and target with the user first.',
1184
+ inputSchema: { type: 'object', properties: {
1185
+ id: { type: 'number', description: 'Existing override id to update; omit to create' },
1186
+ sharedScheduleId: { type: 'number' },
1187
+ name: { type: 'string' },
1188
+ startDate: { type: 'string', description: 'YYYY-MM-DD' },
1189
+ endDate: { type: 'string', description: 'YYYY-MM-DD, optional' },
1190
+ recurringYearly: { type: 'boolean' },
1191
+ autoExpiresAt: { type: 'string', description: 'ISO date-time with offset, optional' },
1192
+ isActive: { type: 'boolean', description: 'Default true' },
1193
+ action: { description: '"SwitchWorkflow" or its int value 3 (or an existing action value)' },
1194
+ targetWorkflowId: { type: 'number', description: 'Workflow to run when action is SwitchWorkflow' },
1195
+ }, required: ['action'] },
1196
+ },
1117
1197
  {
1118
1198
  name: 'workflow_update_node',
1119
1199
  description: `Update a single node in a workflow. Use workflow_get first to read current nodes, then pass the full updated node. The node must already exist.
@@ -3706,6 +3786,51 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
3706
3786
  },
3707
3787
  },
3708
3788
 
3789
+ // ── Apple Pay pre-canned (preset) orders ───────────────────────────────────
3790
+ {
3791
+ name: 'apple_pay_preset_list',
3792
+ description: 'List the Apple Pay pre-canned (preset) orders for the authenticated site: name, line items (label + amount), currency, server-computed total, active flag, and whether only managers may change amounts at send time. Use the id or name with the AI skill (preset_order_id / preset_order_name) or the composer.',
3793
+ inputSchema: { type: 'object', properties: { activeOnly: { type: 'boolean', description: 'Only return active presets (default false).' } } },
3794
+ },
3795
+ {
3796
+ name: 'apple_pay_preset_create',
3797
+ description: 'Create an Apple Pay pre-canned order (up to 20 line items). The total is computed by the server from the line items; never send one.',
3798
+ inputSchema: {
3799
+ type: 'object',
3800
+ properties: {
3801
+ name: { type: 'string', description: 'Preset name, e.g. "Haircut and tip".' },
3802
+ description: { type: 'string', description: 'Optional description.' },
3803
+ currencyCode: { type: 'string', description: 'ISO currency code, default USD.' },
3804
+ lineItems: { type: 'array', maxItems: 20, description: 'Line items.', items: { type: 'object', properties: { label: { type: 'string' }, amount: { type: 'number' } }, required: ['label', 'amount'] } },
3805
+ isActive: { type: 'boolean', description: 'Available to agents and the AI (default true).' },
3806
+ overrideRestrictedToManagers: { type: 'boolean', description: 'Only managers can change amounts at send time.' },
3807
+ },
3808
+ required: ['name', 'lineItems'],
3809
+ },
3810
+ },
3811
+ {
3812
+ name: 'apple_pay_preset_update',
3813
+ description: 'Update an Apple Pay preset order. Only the fields you pass change; passing lineItems replaces the whole list.',
3814
+ inputSchema: {
3815
+ type: 'object',
3816
+ properties: {
3817
+ id: { type: 'number', description: 'Preset order id (from apple_pay_preset_list).' },
3818
+ name: { type: 'string' },
3819
+ description: { type: 'string' },
3820
+ currencyCode: { type: 'string' },
3821
+ lineItems: { type: 'array', maxItems: 20, items: { type: 'object', properties: { label: { type: 'string' }, amount: { type: 'number' } }, required: ['label', 'amount'] } },
3822
+ isActive: { type: 'boolean' },
3823
+ overrideRestrictedToManagers: { type: 'boolean' },
3824
+ },
3825
+ required: ['id'],
3826
+ },
3827
+ },
3828
+ {
3829
+ name: 'apple_pay_preset_delete',
3830
+ description: 'Delete an Apple Pay preset order by id.',
3831
+ inputSchema: { type: 'object', properties: { id: { type: 'number', description: 'Preset order id.' } }, required: ['id'] },
3832
+ },
3833
+
3709
3834
  // ── Velaro Avatar (first-party provider) — library, personalities, site/group inheritance ──
3710
3835
  {
3711
3836
  name: 'avatar_list_library',
@@ -5013,6 +5138,47 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
5013
5138
  required: ['id'],
5014
5139
  },
5015
5140
  },
5141
+ {
5142
+ name: 'billing_notify_settings_get',
5143
+ description: 'Billing notifications: the global settings (receipts.card, receipts.ach, receipts.other, invoices.bcc, digest.recipients). Staff only.',
5144
+ inputSchema: { type: 'object', properties: {} },
5145
+ },
5146
+ {
5147
+ name: 'billing_notify_setting_set',
5148
+ description: 'Billing notifications: set one global setting. receipts.card|receipts.ach|receipts.other = never|send; invoices.bcc = comma-separated emails (blank = none); digest.recipients = comma-separated emails. Staff only.',
5149
+ inputSchema: {
5150
+ type: 'object',
5151
+ properties: {
5152
+ key: { type: 'string', enum: ['receipts.card', 'receipts.ach', 'receipts.other', 'invoices.bcc', 'digest.recipients'], description: 'Setting key' },
5153
+ value: { type: 'string', description: 'New value' },
5154
+ },
5155
+ required: ['key', 'value'],
5156
+ },
5157
+ },
5158
+ {
5159
+ name: 'billing_receipt_overrides_set',
5160
+ description: 'Billing notifications: set receipt overrides on one subscription (never|send per channel). Channels you omit keep their current override. Staff only.',
5161
+ inputSchema: {
5162
+ type: 'object',
5163
+ properties: {
5164
+ subscriptionId: { type: 'number', description: 'Billing subscription ID' },
5165
+ card: { type: 'string', enum: ['never', 'send'], description: 'Card receipts override' },
5166
+ ach: { type: 'string', enum: ['never', 'send'], description: 'ACH receipts override' },
5167
+ other: { type: 'string', enum: ['never', 'send'], description: 'Other (check, cash, wire) receipts override' },
5168
+ },
5169
+ required: ['subscriptionId'],
5170
+ },
5171
+ },
5172
+ {
5173
+ name: 'billing_digest_send_now',
5174
+ description: 'Billing notifications: send the daily billing digest now to digest.recipients (same email the 7am run sends). Staff only.',
5175
+ inputSchema: { type: 'object', properties: {} },
5176
+ },
5177
+ {
5178
+ name: 'billing_receipts_run_now',
5179
+ description: 'Billing notifications: decide pending payment receipts now instead of at :05. Returns the counts (sent, skipped, failed). Staff only.',
5180
+ inputSchema: { type: 'object', properties: {} },
5181
+ },
5016
5182
  {
5017
5183
  name: 'billing_plan_status',
5018
5184
  description: 'Returns the current subscription state for the authenticated site: active Enable* feature flags, licensed seat count, self-serve seats toggle, and recent Stripe payment session history. Use this to answer "what plan are they on?" or "which features are active?"',
@@ -5122,6 +5288,43 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
5122
5288
  required: ['siteId', 'featureKey', 'value'],
5123
5289
  },
5124
5290
  },
5291
+ {
5292
+ name: 'entitlement_trial_get',
5293
+ description: 'Trial governance view for one site: trial status, days left, and the grant ledger (active/held/purchased/revoked). Superadmin only.',
5294
+ inputSchema: { type: 'object', properties: { siteId: { type: 'number', description: 'Site ID.' } }, required: ['siteId'] },
5295
+ },
5296
+ {
5297
+ name: 'entitlement_trial_recommend',
5298
+ description: 'Deterministic package recommendation for a trial site, from its recorded trial usage. Returns customer-safe text plus a staff-only rationale. Superadmin only.',
5299
+ inputSchema: { type: 'object', properties: { siteId: { type: 'number', description: 'Site ID.' } }, required: ['siteId'] },
5300
+ },
5301
+ {
5302
+ name: 'entitlement_trial_grant',
5303
+ description: 'Grant one feature to a trial site and record it in the trial ledger (who, when, why). Superadmin only; the site must be in a trial.',
5304
+ inputSchema: {
5305
+ type: 'object',
5306
+ properties: {
5307
+ siteId: { type: 'number', description: 'Site ID.' },
5308
+ featureKey: { type: 'string', description: 'Feature key, e.g. "EnableIvr".' },
5309
+ reason: { type: 'string', description: 'Why this feature is granted (required, recorded).' },
5310
+ },
5311
+ required: ['siteId', 'featureKey', 'reason'],
5312
+ },
5313
+ },
5314
+ {
5315
+ name: 'entitlement_trial_set_limit',
5316
+ description: 'Set a per-site trial cap (MaxIndexedChunkTotal, MaxKbArticles, MaxKnowledgeDocuments, MaxScraperPagesPerMonth). Whole number >= 0; audited. Superadmin only.',
5317
+ inputSchema: {
5318
+ type: 'object',
5319
+ properties: {
5320
+ siteId: { type: 'number', description: 'Site ID.' },
5321
+ key: { type: 'string', description: 'Cap name.' },
5322
+ value: { type: 'number', description: 'Whole number >= 0.' },
5323
+ reason: { type: 'string', description: 'Why (required, audited).' },
5324
+ },
5325
+ required: ['siteId', 'key', 'value', 'reason'],
5326
+ },
5327
+ },
5125
5328
  {
5126
5329
  name: 'entitlement_remove_override',
5127
5330
  description: 'Remove a per-site feature override, reverting the site to its plan default. Superadmin only.',
@@ -6838,6 +7041,29 @@ async function handleTool(name, args) {
6838
7041
  return await rpc('conversation_search', args);
6839
7042
 
6840
7043
  // ── Workflows ──────────────────────────────────────────────────────────
7044
+ case 'workflow_schedule_resolve':
7045
+ return await tolerant(() => messagingGet(buildResolvePath(args)));
7046
+
7047
+ case 'workflow_schedule_timeline':
7048
+ return await tolerant(() => messagingGet(buildTimelinePath(args)));
7049
+
7050
+ case 'workflow_schedule_validate':
7051
+ return await tolerant(() => messagingPost(VALIDATE_PATH, buildValidateBody(args)));
7052
+
7053
+ case 'workflow_schedule_banner':
7054
+ return await tolerant(() => messagingGet(buildBannerPath(args)));
7055
+
7056
+ case 'workflow_schedule_override_list':
7057
+ return await tolerant(() => messagingGet(buildOverrideListPath(args)));
7058
+
7059
+ case 'workflow_schedule_override_delete':
7060
+ return await tolerant(async () => { await messagingDel(`${OVERRIDE_PATH}/${args.id}`); return { success: true, deleted: args.id }; });
7061
+
7062
+ case 'workflow_schedule_override_save':
7063
+ return await tolerant(() => args.id
7064
+ ? messagingPut(`${OVERRIDE_PATH}/${args.id}`, buildOverrideBody(args))
7065
+ : messagingPost(OVERRIDE_PATH, buildOverrideBody(args)));
7066
+
6841
7067
  case 'workflow_list':
6842
7068
  return await rpc('workflow_list', args);
6843
7069
 
@@ -9803,6 +10029,15 @@ reply: ${r?.reply}`;
9803
10029
  return `✓ AI Video Avatar updated for site ${args.siteId}. Provider: ${res.providerSlug}, enabled: ${res.enabled}, maxAvatarMinutesPerMonth: ${res.maxAvatarMinutesPerMonth ?? 'n/a'}, maxAvatarTtsChars: ${res.maxAvatarTtsChars ?? 'n/a'}`;
9804
10030
  }
9805
10031
 
10032
+ case 'apple_pay_preset_list':
10033
+ return JSON.stringify(await messagingRequest('GET', `/ApplePayPresetOrders${args.activeOnly ? '?activeOnly=true' : ''}`), null, 2);
10034
+ case 'apple_pay_preset_create':
10035
+ return JSON.stringify(await messagingRequest('POST', '/ApplePayPresetOrders', applePayPresetBody(args)), null, 2);
10036
+ case 'apple_pay_preset_update':
10037
+ return JSON.stringify(await messagingRequest('PUT', `/ApplePayPresetOrders/${applePayPresetId(args.id)}`, applePayPresetBody(args)), null, 2);
10038
+ case 'apple_pay_preset_delete':
10039
+ return JSON.stringify(await messagingRequest('DELETE', `/ApplePayPresetOrders/${applePayPresetId(args.id)}`), null, 2);
10040
+
9806
10041
  case 'avatar_list_library':
9807
10042
  return JSON.stringify(await messagingRequest('GET', `/avatar/library${args.kind ? `?kind=${encodeURIComponent(args.kind)}` : ''}`), null, 2);
9808
10043
  case 'avatar_list_personalities':
@@ -11049,6 +11284,37 @@ ${JSON.stringify(result, null, 2)}`;
11049
11284
  return `✅ Billing contact ${args.id} removed.`;
11050
11285
  }
11051
11286
 
11287
+ case 'billing_notify_settings_get': {
11288
+ const all = await api('GET', '/api/BillingNotifications/settings');
11289
+ return Object.entries(all).map(([k, v]) => `${k}: ${v ?? '(not set: code default)'}`).join('\n');
11290
+ }
11291
+
11292
+ case 'billing_notify_setting_set': {
11293
+ await api('PUT', `/api/BillingNotifications/settings/${encodeURIComponent(args.key)}`, { value: args.value });
11294
+ return `✅ ${args.key} set to ${args.value}.`;
11295
+ }
11296
+
11297
+ case 'billing_receipt_overrides_set': {
11298
+ // The API replaces all three channels, so keep the ones the caller did not pass.
11299
+ const sub = await api('GET', `/api/BillingSubscription/${args.subscriptionId}`);
11300
+ const res = await api('PUT', `/api/BillingNotifications/subscriptions/${args.subscriptionId}/receipts`, {
11301
+ card: args.card ?? sub.receiptCardOverride ?? null,
11302
+ ach: args.ach ?? sub.receiptAchOverride ?? null,
11303
+ other: args.other ?? sub.receiptOtherOverride ?? null,
11304
+ });
11305
+ return `✅ Subscription ${res.id} receipts: card=${res.card ?? 'inherit'} ach=${res.ach ?? 'inherit'} other=${res.other ?? 'inherit'}.`;
11306
+ }
11307
+
11308
+ case 'billing_digest_send_now': {
11309
+ const res = await api('POST', '/api/BillingNotifications/digest/send-now', {});
11310
+ return `✅ Digest sent to ${res.recipients.join(', ')}: ${res.subject}`;
11311
+ }
11312
+
11313
+ case 'billing_receipts_run_now': {
11314
+ const res = await api('POST', '/api/BillingNotifications/receipts/run-now', {});
11315
+ return `✅ Receipts run: ${res.summary}`;
11316
+ }
11317
+
11052
11318
  case 'billing_plan_status': {
11053
11319
  const s = await api('GET', '/api/BillingPayment/plan-status');
11054
11320
  const lines = [`Site ${s.siteId} — Subscription Status`];
@@ -11220,6 +11486,32 @@ ${JSON.stringify(result, null, 2)}`;
11220
11486
  return `✅ Site ${args.siteId}: ${args.featureKey} = ${args.value}${args.reason ? ` (${args.reason})` : ''}`;
11221
11487
  }
11222
11488
 
11489
+ case 'entitlement_trial_get': {
11490
+ if (!args.siteId) throw new Error('siteId is required');
11491
+ return JSON.stringify(await api('GET', `/Entitlements/${args.siteId}/trial`), null, 2);
11492
+ }
11493
+
11494
+ case 'entitlement_trial_recommend': {
11495
+ if (!args.siteId) throw new Error('siteId is required');
11496
+ return JSON.stringify(await api('GET', `/Entitlements/${args.siteId}/trial/recommendation`), null, 2);
11497
+ }
11498
+
11499
+ case 'entitlement_trial_grant': {
11500
+ if (!args.siteId) throw new Error('siteId is required');
11501
+ if (!args.featureKey) throw new Error('featureKey is required');
11502
+ if (!args.reason) throw new Error('reason is required');
11503
+ const res = await api('POST', `/Entitlements/${args.siteId}/trial/grant`, { featureKey: args.featureKey, reason: args.reason });
11504
+ return `Site ${args.siteId}: ${res.featureKey} granted for the trial (${res.state}).`;
11505
+ }
11506
+
11507
+ case 'entitlement_trial_set_limit': {
11508
+ if (!args.siteId) throw new Error('siteId is required');
11509
+ if (!Number.isInteger(args.value) || args.value < 0) throw new Error('value must be a whole number >= 0');
11510
+ if (!args.key || !args.reason) throw new Error('key and reason are required');
11511
+ const res = await api('POST', `/Entitlements/${args.siteId}/trial/limit`, { key: args.key, value: args.value, reason: args.reason });
11512
+ return `Site ${args.siteId}: ${res.key} ${res.old} -> ${res.value}.`;
11513
+ }
11514
+
11223
11515
  case 'entitlement_remove_override': {
11224
11516
  if (!args.siteId) throw new Error('siteId is required');
11225
11517
  if (!args.featureKey) throw new Error('featureKey is required');
@@ -12128,7 +12420,7 @@ const PROMPTS = [
12128
12420
  ];
12129
12421
 
12130
12422
  const server = new Server(
12131
- { name: 'velaro', version: '0.4.0' },
12423
+ { name: 'velaro', version: JSON.parse(_readFileSync(_join(_dirname(_ftu(import.meta.url)), 'package.json'), 'utf8')).version },
12132
12424
  { capabilities: { tools: {}, prompts: {} } }
12133
12425
  );
12134
12426
 
package/server.json CHANGED
@@ -8,13 +8,13 @@
8
8
  "url": "https://github.com/velaro/velaro-admin",
9
9
  "source": "github"
10
10
  },
11
- "version": "0.6.0",
11
+ "version": "1.0.0",
12
12
  "packages": [
13
13
  {
14
14
  "registryType": "npm",
15
15
  "registryBaseUrl": "https://registry.npmjs.org",
16
16
  "identifier": "@velaro/mcp-server",
17
- "version": "0.6.0",
17
+ "version": "1.0.0",
18
18
  "transport": {
19
19
  "type": "stdio"
20
20
  },
@@ -0,0 +1,70 @@
1
+ // Shared helpers for the workflow schedule-priority tools (velaro MCP). Messaging owns the REST
2
+ // endpoints under /Workflows/schedule/*; the site always comes from auth, never a parameter.
3
+ // An older messaging without the endpoints answers 404/501 -> we return a friendly message.
4
+
5
+ export const SCHEDULE_NOT_AVAILABLE = "Workflow scheduling is not available on this site's version.";
6
+
7
+ export const SCHEDULE_ACTIONS = { SwitchWorkflow: 3 };
8
+
9
+ export function buildResolvePath({ trigger, channel, at, tz } = {}) {
10
+ const q = new URLSearchParams();
11
+ if (trigger) q.set('trigger', trigger);
12
+ if (channel) q.set('channel', channel);
13
+ if (at) q.set('at', at);
14
+ if (tz) q.set('tz', tz);
15
+ return `/Workflows/schedule/resolve?${q}`;
16
+ }
17
+
18
+ export function buildTimelinePath({ from, to, trigger, channel } = {}) {
19
+ const q = new URLSearchParams();
20
+ if (from) q.set('from', from);
21
+ if (to) q.set('to', to);
22
+ if (trigger) q.set('trigger', trigger);
23
+ if (channel) q.set('channel', channel);
24
+ return `/Workflows/schedule/timeline?${q}`;
25
+ }
26
+
27
+ export const VALIDATE_PATH = '/Workflows/schedule/validate';
28
+ export const BANNER_PATH = '/Workflows/schedule/banner';
29
+ export const OVERRIDE_PATH = '/ScheduleOverride';
30
+
31
+ // Normalizes the action to the int messaging expects (accepts 'SwitchWorkflow' or 3).
32
+ export function buildOverrideBody(args = {}) {
33
+ const { action, targetWorkflowId, ...rest } = args;
34
+ const body = { ...rest };
35
+ if (action !== undefined) body.action = typeof action === 'string' && action in SCHEDULE_ACTIONS ? SCHEDULE_ACTIONS[action] : action;
36
+ if (targetWorkflowId !== undefined) body.targetWorkflowId = targetWorkflowId;
37
+ return body;
38
+ }
39
+
40
+ // Messaging's POST /Workflows/schedule/validate takes { trigger, channel, override } (one draft override).
41
+ export function buildValidateBody({ trigger, channel, override } = {}) {
42
+ const body = { trigger: trigger || 'conversation_opened' };
43
+ if (channel) body.channel = channel;
44
+ if (override) body.override = buildOverrideBody(override);
45
+ return body;
46
+ }
47
+
48
+ export function buildBannerPath({ trigger, channel } = {}) {
49
+ const q = new URLSearchParams();
50
+ if (trigger) q.set('trigger', trigger);
51
+ if (channel) q.set('channel', channel);
52
+ return `${BANNER_PATH}?${q}`;
53
+ }
54
+
55
+ export function buildOverrideListPath({ action } = {}) {
56
+ if (action === undefined || action === '') return OVERRIDE_PATH;
57
+ const n = typeof action === 'string' && action in SCHEDULE_ACTIONS ? SCHEDULE_ACTIONS[action] : action;
58
+ return `${OVERRIDE_PATH}?action=${encodeURIComponent(n)}`;
59
+ }
60
+
61
+ // Runs fn(); a 404/501 (error message contains "-> 404:" / "-> 501:" or err.status) maps to the friendly string.
62
+ export async function tolerant(fn) {
63
+ try {
64
+ return await fn();
65
+ } catch (err) {
66
+ const status = err?.status ?? Number(/->\s*(\d{3})/.exec(err?.message ?? '')?.[1]);
67
+ if (status === 404 || status === 501) return SCHEDULE_NOT_AVAILABLE;
68
+ throw err;
69
+ }
70
+ }