@velaro/mcp-server 0.6.101 → 0.6.103

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/server.js +61 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velaro/mcp-server",
3
- "version": "0.6.101",
3
+ "version": "0.6.103",
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": {
package/server.js CHANGED
@@ -1867,6 +1867,25 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
1867
1867
  required: ['v10_site_id'],
1868
1868
  },
1869
1869
  },
1870
+ {
1871
+ name: 'livefluence_conversion_advisor',
1872
+ description: 'Velaro staff only (superadmin). Livefluence conversion advisor: reads a v10 site\'s groups and routing rules, clusters them by intent and returns a consolidated v20 design (one workflow per intent; page-path + device + geo conditions; team availability check with AI-node fallback), diffed against what already exists on the v20 site. Or pass intents (a structured list built from a customer description) instead of v10_site_id. apply=false (default) is a dry run; apply=true creates missing teams and workflows, ALWAYS DISABLED, idempotent by name. Always dry-run first. Same as CLI `velaro migrate advise`.',
1873
+ inputSchema: {
1874
+ type: 'object',
1875
+ properties: {
1876
+ v10_site_id: { type: 'number', description: 'V10 (Livefluence) site to read. Omit when passing intents.' },
1877
+ v20_site_id: { type: 'number', description: 'V20 destination site ID' },
1878
+ apply: { type: 'boolean', description: 'Create missing teams and DISABLED workflows. Default false (dry run + diff).' },
1879
+ include_non_routing: { type: 'boolean', description: 'Also read v10 rules with IsRouting=false (group-assignment rules). Default false.' },
1880
+ intents: {
1881
+ type: 'array',
1882
+ description: 'Structured intents: [{intent, paths[], device: any|mobile|desktop, countries[], excludeCountries[], target: "Service team + troubleshooting AI node" (or team / aiNode), mode: humanFirst|botFirst|aiOnly, priority}]',
1883
+ items: { type: 'object' },
1884
+ },
1885
+ },
1886
+ required: ['v20_site_id'],
1887
+ },
1888
+ },
1870
1889
  {
1871
1890
  name: 'livefluence_export',
1872
1891
  description: 'Export V10 (Livefluence) routing rules for a site and show a preview of what would be created in V20. Auto-suggests V20 team and AI config matches by name. Use this before livefluence_migrate to confirm the mapping is correct.',
@@ -3336,11 +3355,11 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
3336
3355
  },
3337
3356
  {
3338
3357
  name: 'enterprise_reports',
3339
- description: 'Enterprise Reports package (enterprise-internal, default OFF; every report returns 403 when the site does not hold that package item). report: program_breakdown (compare websites, teams, platforms, devices or entry pages: volume, missed, transfers, wait, first reply, QA score and coverage, sentiment), agent_scorecard (per agent by name: resolution, transfers, handle time, AI score, supervisor-reviewed score, sentiment impact), qa_coverage (share of resolved chats AI-scored and supervisor-reviewed by week and program), coaching_queue (low-scoring chats nobody reviewed, per agent), sentiment_breakdown (by website, team, agent or topic with change vs prior period), topic_radar (topics by volume, growth, transfer rate), weekly_brief (preview of the Monday brief sections), weekly_brief_send_test (emails this week\'s brief to the signed-in user). Scoped to the caller site.',
3358
+ description: 'Enterprise Reports package (enterprise-internal, default OFF; every report returns 403 when the site does not hold that package item). report: program_breakdown (compare websites, teams, platforms, devices or entry pages: volume, missed, transfers, wait, first reply, QA score and coverage, sentiment), agent_scorecard (per agent by name: resolution, transfers, handle time, AI score, supervisor-reviewed score, sentiment impact), qa_coverage (share of resolved chats AI-scored and supervisor-reviewed by week and program), coaching_queue (low-scoring chats nobody reviewed, per agent), sentiment_breakdown (by website, team, agent or topic with change vs prior period), topic_radar (topics by volume, growth, transfer rate), weekly_brief (preview of the Monday brief sections), weekly_brief_send_test (emails this week\'s brief to the signed-in user), year_in_review (builds the 12-month Year in review document in the background: from/to yyyy-MM-dd inclusive, default the last 12 whole Pacific months, returns a build id; call again with buildId to get status and the download link). Scoped to the caller site.',
3340
3359
  inputSchema: {
3341
3360
  type: 'object',
3342
3361
  properties: {
3343
- report: { type: 'string', enum: ['program_breakdown', 'agent_scorecard', 'qa_coverage', 'coaching_queue', 'sentiment_breakdown', 'topic_radar', 'weekly_brief', 'weekly_brief_send_test', 'repeat_contact', 'demand_coverage', 'bot_containment', 'effort_index', 'promise_ledger', 'promise_dismiss'] },
3362
+ report: { type: 'string', enum: ['program_breakdown', 'agent_scorecard', 'qa_coverage', 'coaching_queue', 'sentiment_breakdown', 'topic_radar', 'weekly_brief', 'weekly_brief_send_test', 'repeat_contact', 'demand_coverage', 'bot_containment', 'effort_index', 'promise_ledger', 'promise_dismiss', 'year_in_review'] },
3344
3363
  from: { type: 'string', description: 'yyyy-MM-dd start (default: 30 days ago).' },
3345
3364
  to: { type: 'string', description: 'yyyy-MM-dd end, inclusive (default: today).' },
3346
3365
  groupBy: { type: 'string', description: 'program_breakdown: website|team|platform|device|entrypage. sentiment_breakdown: website|team|agent|theme.' },
@@ -3358,6 +3377,7 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
3358
3377
  lowScore: { type: 'number', description: 'bot_containment: QA score below which a handed-off chat counts as regret (1-100, default 60).' },
3359
3378
  includeDemo: { type: 'boolean', description: 'true = show only the seeded sample (demo) data. Default false.' },
3360
3379
  promiseId: { type: 'string', description: 'promise_dismiss: the promise id to dismiss (managers only).' },
3380
+ buildId: { type: 'string', description: 'year_in_review: the build id returned by a start call; when given, returns that build\'s status (queued|building|completed|failed) and download link instead of starting a new build.' },
3361
3381
  },
3362
3382
  required: ['report'],
3363
3383
  },
@@ -7512,6 +7532,17 @@ async function handleTool(name, args) {
7512
7532
  return JSON.stringify(result, null, 2);
7513
7533
  }
7514
7534
 
7535
+ case 'livefluence_conversion_advisor': {
7536
+ const result = await api('POST', '/DatabaseTool/ConversionAdvisor', {
7537
+ v10SiteId: args.intents?.length ? null : (args.v10_site_id ?? null),
7538
+ v20SiteId: args.v20_site_id,
7539
+ apply: !!args.apply,
7540
+ includeNonRouting: !!args.include_non_routing,
7541
+ intents: args.intents ?? null,
7542
+ });
7543
+ return JSON.stringify(result, null, 2);
7544
+ }
7545
+
7515
7546
  case 'livefluence_migrate': {
7516
7547
  const result = await api('POST', '/DatabaseTool/V10Migrate', {
7517
7548
  v10SiteId: args.v10_site_id,
@@ -9223,8 +9254,35 @@ async function handleTool(name, args) {
9223
9254
  if (!args.promiseId) throw new Error('promiseId is required for promise_dismiss');
9224
9255
  return JSON.stringify(await messagingPost('/api/enterprise-reports/promise-ledger/' + encodeURIComponent(String(args.promiseId)) + '/dismiss', {}), null, 2);
9225
9256
  }
9257
+ if (args.report === 'year_in_review') {
9258
+ // POST starts a build (202 {id}); GET .../{id} -> {status, downloadUrl, error}. 403 when EnableEnterpriseReportsYearInReview is off.
9259
+ if (args.buildId) return JSON.stringify(await messagingGet('/api/enterprise-reports/year-in-review/' + encodeURIComponent(String(args.buildId))), null, 2);
9260
+ const zone = 'America/Los_Angeles';
9261
+ const keyRe = /^(\d{4})-(\d{2})-(\d{2})$/;
9262
+ const addDays = (k, n) => { const m = keyRe.exec(k); return new Date(Date.UTC(+m[1], +m[2] - 1, +m[3] + n)).toISOString().slice(0, 10); };
9263
+ const offsetMs = (ms) => {
9264
+ const ps = new Intl.DateTimeFormat('en-US', { timeZone: zone, hourCycle: 'h23', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' }).formatToParts(new Date(ms));
9265
+ const g = (t) => Number(ps.find((p) => p.type === t).value);
9266
+ return Date.UTC(g('year'), g('month') - 1, g('day'), g('hour'), g('minute'), g('second')) - Math.floor(ms / 1000) * 1000;
9267
+ };
9268
+ const midnightIso = (k) => {
9269
+ if (!keyRe.test(k || '')) throw new Error('from/to must be yyyy-MM-dd');
9270
+ const m = keyRe.exec(k);
9271
+ const wall = Date.UTC(+m[1], +m[2] - 1, +m[3]);
9272
+ let t = wall - offsetMs(wall);
9273
+ t = wall - offsetMs(t);
9274
+ return new Date(t).toISOString();
9275
+ };
9276
+ const today = new Intl.DateTimeFormat('en-CA', { timeZone: zone, year: 'numeric', month: '2-digit', day: '2-digit' }).format(new Date());
9277
+ const defFrom = String(Number(today.slice(0, 4)) - 1).padStart(4, '0') + '-' + today.slice(5, 7) + '-01';
9278
+ const defTo = addDays(today.slice(0, 7) + '-01', -1);
9279
+ const f = args.from || defFrom;
9280
+ const t = args.to || defTo;
9281
+ if (f > t) throw new Error('from must be on or before to');
9282
+ return JSON.stringify(await messagingPost('/api/enterprise-reports/year-in-review', { from: midnightIso(f), to: midnightIso(addDays(t, 1)) }), null, 2);
9283
+ }
9226
9284
  const seg = ER_PATHS[args.report];
9227
- if (!seg) throw new Error('report must be one of: ' + Object.keys(ER_PATHS).join(', '));
9285
+ if (!seg) throw new Error('report must be one of: ' + Object.keys(ER_PATHS).join(', ') + ', promise_dismiss, year_in_review');
9228
9286
  if (args.report === 'weekly_brief_send_test') return JSON.stringify(await messagingPost('/api/enterprise-reports/' + seg, {}), null, 2);
9229
9287
  const qs = new URLSearchParams();
9230
9288
  for (const k of ['from', 'to', 'groupBy', 'deploymentId', 'teamId', 'threshold', 'perAgent', 'limit', 'days', 'windowDays', 'weeks', 'handleMinutes', 'maxConcurrent', 'timeZone', 'lowScore', 'includeDemo']) {