@velaro/mcp-server 0.6.73 → 0.6.75

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 +176 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velaro/mcp-server",
3
- "version": "0.6.73",
3
+ "version": "0.6.75",
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
@@ -179,8 +179,26 @@ function teamRoutingConfigPath(teamId) {
179
179
  return `/RoutingConfig?teamId=${n}`;
180
180
  }
181
181
  const messagingPatch = (path, body) => messagingRequest('PATCH', path, body);
182
+ function execInsightsRange(days) {
183
+ const d = Math.min(Math.max(Number(days) || 7, 1), 92);
184
+ const to = new Date();
185
+ return new URLSearchParams({ from: new Date(to.getTime() - d * 86400000).toISOString(), to: to.toISOString() });
186
+ }
187
+
182
188
  const messagingDel = (path) => messagingRequest('DELETE', path);
183
189
 
190
+ // Skill-based routing CRUD helper for SkillRouting/{Skills|Assignments|Requirements}.
191
+ async function skillRoutingCrud(resource, args, fields) {
192
+ const body = Object.fromEntries(Object.entries(fields).filter(([, v]) => v !== undefined));
193
+ switch (args.action) {
194
+ case 'list': return await messagingGet('/SkillRouting/' + resource);
195
+ case 'create': return await messagingPost('/SkillRouting/' + resource, body);
196
+ case 'update': return await messagingPut('/SkillRouting/' + resource + '/' + args.id, body);
197
+ case 'delete': return await messagingDel('/SkillRouting/' + resource + '/' + args.id);
198
+ default: throw new Error('action must be list|create|update|delete');
199
+ }
200
+ }
201
+
184
202
  async function api(method, path, body, base = API_BASE) {
185
203
  // Normalize: a path without a leading slash would glue onto the host ("...comScraperJobs/...") and fail as "fetch failed".
186
204
  if (!path.startsWith('/')) path = '/' + path;
@@ -765,6 +783,8 @@ const TOOLS = [
765
783
  startDate: { type: 'string', description: 'ISO date (defaults to 90 days ago)' },
766
784
  endDate: { type: 'string', description: 'ISO date (defaults to today)' },
767
785
  groupBy: { type: 'string', description: '"month" or "week" (default month)' },
786
+ deploymentId: { type: 'number', description: 'Optional website (deployment row id) filter' },
787
+ teamId: { type: 'number', description: 'Optional team filter' },
768
788
  },
769
789
  },
770
790
  },
@@ -2801,6 +2821,46 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
2801
2821
  properties: { report: { type: 'string', description: 'Report key. Omit to list all.' } },
2802
2822
  },
2803
2823
  },
2824
+ {
2825
+ name: 'friction_radar',
2826
+ description: 'Friction Radar: conversation topics ranked by estimated agent cost (handle time plus a fixed penalty per escalation and abandoned chat), with volume, week-over-week growth versus the prior period of equal length and up to 3 example conversation ids per topic. Demo data excluded. Chat Intelligence entitlement required. Scoped to the caller site.',
2827
+ inputSchema: {
2828
+ type: 'object',
2829
+ properties: {
2830
+ days: { type: 'number', description: 'Look-back window in days, 1-92 (default 7).' },
2831
+ limit: { type: 'number', description: 'Max topics, 1-100 (default 20).' },
2832
+ },
2833
+ },
2834
+ },
2835
+ {
2836
+ name: 'executive_brief',
2837
+ description: 'Executive brief for the last N days: conversation volume and growth, sentiment split, average QA rubric score, Experience Impact (index, CSAT, service level, missed chats), fastest rising keywords and top friction topics. Same data as the Monday executive-brief email. Chat Intelligence entitlement required.',
2838
+ inputSchema: {
2839
+ type: 'object',
2840
+ properties: { days: { type: 'number', description: 'Look-back window in days, 1-92 (default 7).' } },
2841
+ },
2842
+ },
2843
+ {
2844
+ name: 'report_data_export',
2845
+ description: 'Export conversations as CSV (sentiment, QA score, tag, escalations, handle seconds, abandoned, keywords) for the last N days; max 20,000 rows. To schedule it by email use executive_insights_subscribe with reportType data-export. Chat Intelligence entitlement required.',
2846
+ inputSchema: {
2847
+ type: 'object',
2848
+ properties: { days: { type: 'number', description: 'Look-back window in days, 1-92 (default 7).' } },
2849
+ },
2850
+ },
2851
+ {
2852
+ name: 'executive_insights_subscribe',
2853
+ description: 'Email the caller the executive-brief (weekly sends on Monday at deliveryHour site time, default 7 = 7:00 AM) or a scheduled data-export CSV. Creates or updates the caller report subscription.',
2854
+ inputSchema: {
2855
+ type: 'object',
2856
+ properties: {
2857
+ reportType: { type: 'string', enum: ['executive-brief', 'data-export'] },
2858
+ frequency: { type: 'string', enum: ['daily', 'biweekly', 'weekly', 'monthly'], description: 'Default weekly.' },
2859
+ deliveryHour: { type: 'number', description: 'Site-local hour 0-23 (default 7).' },
2860
+ },
2861
+ required: ['reportType'],
2862
+ },
2863
+ },
2804
2864
  {
2805
2865
  name: 'experience_impact_report',
2806
2866
  description: 'Experience Impact report: shows how staffing and schedule adherence affect customer experience. Returns a 0-100 experience index (adherence 25, service level 30, missed-chat rate 20, CSAT 25), per-interval staffing vs chat demand (scheduled vs actual agents, missed chats, wait time, CSAT), per-agent rows, a bot-vs-human breakdown (bot only, bot-to-human handoff, human only, with wait, missed, CSAT and bot containment rate), and plain-language insights. Scoped to the caller site.',
@@ -4940,6 +5000,21 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
4940
5000
  description: 'Get the Focus Inbox draft-pregeneration usage/cap report for this site -- whether the "Always Learning" background draft-pregeneration addon is entitled (EnableFocusDraftPregeneration), plus the live usage snapshot (used/max/percent) against the site\'s monthly draft-pregeneration cap. Use to check whether the addon is active and how close the site is to its cap.',
4941
5001
  inputSchema: { type: 'object', properties: {} },
4942
5002
  },
5003
+ {"name":"sentiment_report","description":"Sentiment report (v20). kind = overview | breakdown | conversation-overview | conversation-breakdown | contact-overview | agent-breakdown. Optional deploymentId (website) and teamId filters, verified server-side against your site.","inputSchema":{"type":"object","properties":{"kind":{"type":"string","description":"overview (default), breakdown, conversation-overview, conversation-breakdown, contact-overview, agent-breakdown"},"startDate":{"type":"string","description":"ISO date (default 30 days ago)"},"endDate":{"type":"string","description":"ISO date (default now)"},"groupBy":{"type":"string","description":"day (default), week, month"},"excludeBots":{"type":"boolean"},"deploymentId":{"type":"number","description":"Optional website (deployment row id) filter; must belong to your site"},"teamId":{"type":"number","description":"Optional team filter"}}}},
5004
+ {"name":"quality_agent_performance","description":"Conversation Quality agent performance (AI scores per agent). Optional deploymentId (website) and teamId filters.","inputSchema":{"type":"object","properties":{"days":{"type":"number","description":"Window in days (default 30)"},"deploymentId":{"type":"number","description":"Optional website (deployment row id) filter; must belong to your site"},"teamId":{"type":"number","description":"Optional team filter"}}}},
5005
+ {"name":"keyword_intelligence_top","description":"Top keywords/phrases customers use over a date range. Optional groupBy, ngram size, limit, teamId, agentId, website.","inputSchema":{"type":"object","properties":{"from":{"type":"string"},"to":{"type":"string"},"groupBy":{"type":"string","description":"none (default) or a grouping dimension"},"ngram":{"type":"number","description":"Phrase length 1-3 (default 3)"},"limit":{"type":"number","description":"Default 50"},"teamId":{"type":"number"},"agentId":{"type":"number"},"website":{"type":"string"}},"required":["from","to"]}},
5006
+ {"name":"keyword_intelligence_conversations","description":"Drill-down: conversations containing a keyword in a date range.","inputSchema":{"type":"object","properties":{"keyword":{"type":"string"},"from":{"type":"string"},"to":{"type":"string"},"teamId":{"type":"number"},"agentId":{"type":"number"}},"required":["keyword","from","to"]}},
5007
+ {"name":"skill_routing_skills","description":"Skill-based routing: manage the skill catalog. action = list | create | update | delete. Requires the EnableSkillBasedRouting entitlement (403 otherwise).","inputSchema":{"type":"object","properties":{"action":{"type":"string"},"id":{"type":"number"},"name":{"type":"string"},"description":{"type":"string"}},"required":["action"]}},
5008
+ {"name":"skill_routing_assignments","description":"Skill-based routing: assign skills to agents with proficiency 1-5. action = list | create | update | delete.","inputSchema":{"type":"object","properties":{"action":{"type":"string"},"id":{"type":"number"},"workspaceUserId":{"type":"number"},"agentSkillId":{"type":"number"},"proficiency":{"type":"number"}},"required":["action"]}},
5009
+ {"name":"skill_routing_requirements","description":"Skill-based routing: required/preferred skills per deployment and/or team. action = list | create | update | delete. Update is partial. Requirements are disabled by default; set isEnabled true to turn on.","inputSchema":{"type":"object","properties":{"action":{"type":"string"},"id":{"type":"number"},"deploymentId":{"type":"number"},"teamId":{"type":"number"},"requiredSkillIds":{"type":"array","items":{"type":"number"}},"preferredSkillIds":{"type":"array","items":{"type":"number"}},"overflowWaitSeconds":{"type":"number"},"isEnabled":{"type":"boolean"}},"required":["action"]}},
5010
+ {"name":"skill_routing_simulate","description":"Dry-run skill routing for a team: shows which agents would be narrowed to and why. Never assigns a conversation or writes the decision log.","inputSchema":{"type":"object","properties":{"teamId":{"type":"number"},"waitedSeconds":{"type":"number"}}}},
5011
+ {"name":"qa_rubric","description":"Conversation Quality QA rubrics. action = list | get | default | create | update | delete. Pass rubric fields in \"rubric\". Requires EnableConversationQuality.","inputSchema":{"type":"object","properties":{"action":{"type":"string"},"id":{"type":"number"},"rubric":{"type":"object"}},"required":["action"]}},
5012
+ {"name":"qa_low_scores","description":"Conversations scoring below the rubric threshold, for QA review.","inputSchema":{"type":"object","properties":{"threshold":{"type":"number"},"days":{"type":"number"},"agentId":{"type":"number"},"skip":{"type":"number"},"take":{"type":"number"}}}},
5013
+ {"name":"ai_field_extraction_get_config","description":"Get the AI field extraction config (opt-in, default off; monthly cap). Requires the ai_field_extraction entitlement.","inputSchema":{"type":"object","properties":{}}},
5014
+ {"name":"ai_field_extraction_update_config","description":"Update AI field extraction config (partial). enabled, monthlyCap (null = 500 default, hard ceiling 50000), createThemeTags.","inputSchema":{"type":"object","properties":{"enabled":{"type":"boolean"},"monthlyCap":{"type":"number"},"createThemeTags":{"type":"boolean"}}}},
5015
+ {"name":"ai_field_extraction_correct_field","description":"Agent correction of an extracted field on a conversation.","inputSchema":{"type":"object","properties":{"conversationId":{"type":"number"},"fieldName":{"type":"string"},"value":{"type":"string"}},"required":["conversationId","fieldName","value"]}},
5016
+ {"name":"qa_calibration","description":"QA calibration report (AI score vs human review) for the last N days.","inputSchema":{"type":"object","properties":{"days":{"type":"number"}}}},
5017
+ {"name":"qa_alert_settings","description":"Get or set quality alert settings. Omit \"settings\" to read; pass it to update.","inputSchema":{"type":"object","properties":{"settings":{"type":"object"}}}},
4943
5018
  ];
4944
5019
 
4945
5020
  // ── IVR business-hours helper ─────────────────────────────────────────────────
@@ -5423,6 +5498,8 @@ async function handleTool(name, args) {
5423
5498
  if (args.startDate) qs.set('startDate', args.startDate);
5424
5499
  if (args.endDate) qs.set('endDate', args.endDate);
5425
5500
  qs.set('groupBy', args.groupBy ?? 'month');
5501
+ if (args.deploymentId != null) qs.set('deploymentId', String(args.deploymentId));
5502
+ if (args.teamId != null) qs.set('teamId', String(args.teamId));
5426
5503
  return await messagingGet(`/Reports/chat-intelligence/themes?${qs.toString()}`);
5427
5504
  }
5428
5505
  case 'chat_intelligence_recommendations':
@@ -7897,6 +7974,39 @@ async function handleTool(name, args) {
7897
7974
  return [`=== ${r.name} ===`, `Answers: ${r.answers}`, `Sources: ${r.sources}`, `Setup: ${r.prerequisites}`, `How to read: ${r.interpret}`, `Why zero/empty: ${r.whyZero}`, `What to do: ${r.fix}`, `Related: ${r.related}`].join('\n');
7898
7975
  }
7899
7976
 
7977
+ case 'friction_radar': {
7978
+ const qs = execInsightsRange(args.days);
7979
+ qs.set('limit', String(Math.min(Math.max(Number(args.limit) || 20, 1), 100)));
7980
+ const r = await messagingGet(`/executive-insights/friction-radar?${qs}`);
7981
+ const lines = [`=== Friction Radar (${r.conversationsScanned} conversations${r.truncated ? ', truncated' : ''}) ===`];
7982
+ for (const t of r.topics ?? []) {
7983
+ const growth = t.isNew ? 'new' : t.growthPct == null ? 'n/a' : `${t.growthPct}%`;
7984
+ lines.push(`#${t.costRank} ${t.topic}: ${t.volume} chats (volume rank ${t.volumeRank}, ${growth}), ${t.escalations} escalated, ${t.abandoned} abandoned, ~${Math.round(t.estimatedCostMinutes)} agent min, examples ${(t.exampleConversationIds ?? []).join(', ') || '-'}`);
7985
+ }
7986
+ if (!(r.topics ?? []).length) lines.push('No tagged topics in this period.');
7987
+ return lines.join('\n');
7988
+ }
7989
+
7990
+ case 'executive_brief': {
7991
+ const r = await messagingGet(`/executive-insights/brief?${execInsightsRange(args.days)}`);
7992
+ return JSON.stringify(r.brief, null, 2);
7993
+ }
7994
+
7995
+ case 'report_data_export': {
7996
+ const r = await messagingGet(`/executive-insights/export?${execInsightsRange(args.days)}`);
7997
+ return `${r.rows} rows${r.truncated ? ' (truncated at the row cap)' : ''}\n${r.csv}`;
7998
+ }
7999
+
8000
+ case 'executive_insights_subscribe': {
8001
+ if (!['executive-brief', 'data-export'].includes(args.reportType)) return 'Error: reportType must be executive-brief or data-export.';
8002
+ await messagingPost('/report-subscriptions', {
8003
+ reportType: args.reportType,
8004
+ frequency: args.frequency ?? 'weekly',
8005
+ deliveryHour: args.deliveryHour ?? 7,
8006
+ });
8007
+ return `Subscribed to ${args.reportType} (${args.frequency ?? 'weekly'}, ${args.deliveryHour ?? 7}:00 site time).`;
8008
+ }
8009
+
7900
8010
  case 'experience_impact_report': {
7901
8011
  const qs = new URLSearchParams();
7902
8012
  for (const k of ['from', 'to', 'teamId']) if (args[k] != null && args[k] !== '') qs.set(k, String(args[k]));
@@ -9780,6 +9890,72 @@ async function handleTool(name, args) {
9780
9890
  return JSON.stringify(result, null, 2);
9781
9891
  }
9782
9892
 
9893
+
9894
+ // -- Wave: sentiment / quality / keyword intelligence / skill routing / QA rubric --
9895
+ case 'sentiment_report': {
9896
+ const routes = { 'overview': 'sentiment-overview', 'breakdown': 'sentiment-breakdown', 'conversation-overview': 'conversation-sentiment-overview', 'conversation-breakdown': 'conversation-sentiment-breakdown', 'contact-overview': 'contact-sentiment-overview', 'agent-breakdown': 'agent-sentiment-breakdown' };
9897
+ const kind = args.kind ?? 'overview';
9898
+ if (!routes[kind]) throw new Error('kind must be one of: ' + Object.keys(routes).join(', '));
9899
+ const end = args.endDate ? new Date(args.endDate) : new Date();
9900
+ const start = args.startDate ? new Date(args.startDate) : new Date(end.getTime() - 30 * 86400000);
9901
+ const body = { Start: start.toISOString(), End: end.toISOString(), GroupBy: args.groupBy ?? 'day', ExcludeBots: args.excludeBots === true };
9902
+ if (args.deploymentId != null) body.DeploymentId = args.deploymentId;
9903
+ if (args.teamId != null) body.TeamId = args.teamId;
9904
+ return await messagingPost('/Reports/' + routes[kind], body);
9905
+ }
9906
+ case 'quality_agent_performance': {
9907
+ const qs = new URLSearchParams({ days: String(args.days ?? 30) });
9908
+ if (args.deploymentId != null) qs.set('deploymentId', String(args.deploymentId));
9909
+ if (args.teamId != null) qs.set('teamId', String(args.teamId));
9910
+ return await messagingGet('/api/admin/Reports/AgentPerformance?' + qs.toString());
9911
+ }
9912
+ case 'keyword_intelligence_top': {
9913
+ const qs = new URLSearchParams({ from: args.from, to: args.to, groupBy: args.groupBy ?? 'none', ngram: String(args.ngram ?? 3), limit: String(args.limit ?? 50) });
9914
+ for (const k of ['teamId', 'agentId', 'website']) if (args[k] != null) qs.set(k, String(args[k]));
9915
+ return await messagingGet('/api/keyword-intelligence/top?' + qs.toString());
9916
+ }
9917
+ case 'keyword_intelligence_conversations': {
9918
+ const qs = new URLSearchParams({ keyword: args.keyword, from: args.from, to: args.to });
9919
+ for (const k of ['teamId', 'agentId']) if (args[k] != null) qs.set(k, String(args[k]));
9920
+ return await messagingGet('/api/keyword-intelligence/conversations?' + qs.toString());
9921
+ }
9922
+ case 'skill_routing_skills':
9923
+ return await skillRoutingCrud('Skills', args, { Name: args.name, Description: args.description });
9924
+ case 'skill_routing_assignments':
9925
+ return await skillRoutingCrud('Assignments', args, { WorkspaceUserId: args.workspaceUserId, AgentSkillId: args.agentSkillId, Proficiency: args.proficiency });
9926
+ case 'skill_routing_requirements':
9927
+ return await skillRoutingCrud('Requirements', args, { DeploymentId: args.deploymentId, TeamId: args.teamId, RequiredSkillIds: args.requiredSkillIds, PreferredSkillIds: args.preferredSkillIds, OverflowWaitSeconds: args.overflowWaitSeconds, IsEnabled: args.isEnabled });
9928
+ case 'skill_routing_simulate':
9929
+ return await messagingPost('/SkillRouting/Simulate', { TeamId: args.teamId ?? null, WaitedSeconds: args.waitedSeconds ?? null });
9930
+ case 'qa_rubric': {
9931
+ const a = args.action;
9932
+ if (a === 'list') return await messagingGet('/api/admin/QualityQa/rubrics');
9933
+ if (a === 'default') return await messagingGet('/api/admin/QualityQa/rubrics/default');
9934
+ if (a === 'get') return await messagingGet('/api/admin/QualityQa/rubrics/' + args.id);
9935
+ if (a === 'create') return await messagingPost('/api/admin/QualityQa/rubrics', args.rubric ?? {});
9936
+ if (a === 'update') return await messagingPut('/api/admin/QualityQa/rubrics/' + args.id, args.rubric ?? {});
9937
+ if (a === 'delete') return await messagingDel('/api/admin/QualityQa/rubrics/' + args.id);
9938
+ throw new Error('action must be list|get|default|create|update|delete');
9939
+ }
9940
+ case 'qa_low_scores': {
9941
+ const qs = new URLSearchParams({ days: String(args.days ?? 30), skip: String(args.skip ?? 0), take: String(args.take ?? 50) });
9942
+ if (args.threshold != null) qs.set('threshold', String(args.threshold));
9943
+ if (args.agentId != null) qs.set('agentId', String(args.agentId));
9944
+ return await messagingGet('/api/admin/QualityQa/low-scores?' + qs.toString());
9945
+ }
9946
+ case 'ai_field_extraction_get_config':
9947
+ return await messagingGet('/AiFieldExtraction/Config');
9948
+ case 'ai_field_extraction_update_config': {
9949
+ const body = Object.fromEntries(Object.entries({ Enabled: args.enabled, MonthlyCap: args.monthlyCap, CreateThemeTags: args.createThemeTags }).filter(([, v]) => v !== undefined));
9950
+ return await messagingPut('/AiFieldExtraction/Config', body);
9951
+ }
9952
+ case 'ai_field_extraction_correct_field':
9953
+ return await messagingPut('/AiFieldExtraction/Conversation/' + args.conversationId + '/Fields/' + encodeURIComponent(args.fieldName), { Value: args.value });
9954
+ case 'qa_calibration':
9955
+ return await messagingGet('/api/admin/QualityQa/calibration?days=' + (args.days ?? 90));
9956
+ case 'qa_alert_settings':
9957
+ return args.settings ? await messagingPut('/api/admin/QualityQa/alert-settings', args.settings) : await messagingGet('/api/admin/QualityQa/alert-settings');
9958
+
9783
9959
  default:
9784
9960
  throw new Error(`Unknown tool: ${name}`);
9785
9961
  }