@velaro/mcp-server 0.6.70 → 0.6.72

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 +143 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velaro/mcp-server",
3
- "version": "0.6.70",
3
+ "version": "0.6.72",
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
@@ -2617,6 +2617,22 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
2617
2617
  },
2618
2618
 
2619
2619
  // ── Support Tools (Velaro staff only) ────────────────────────────────────────
2620
+ {
2621
+ name: 'support_add_site_user',
2622
+ description: 'Velaro staff only. Create or ensure a NORMAL user (role agent or administrator, never staff/superadmin) on ANY customer site with an explicit non-forcing password, usable immediately. Idempotent: an existing user just gets the password set. Password goes in the HTTPS body only and is never logged or returned. Composes velaro-messaging SuperAdmin users create + set-password (same staff-WorkspaceUser auth caveat as site_provision: an MCP-key-only session may 403; use `velaro support add-site-user` then).',
2623
+ inputSchema: {
2624
+ type: 'object',
2625
+ properties: {
2626
+ siteId: { type: 'number', description: 'Target customer site ID' },
2627
+ email: { type: 'string' },
2628
+ password: { type: 'string', description: 'Min 8 characters' },
2629
+ role: { type: 'string', enum: ['agent', 'administrator'], description: 'Default agent' },
2630
+ firstName: { type: 'string' },
2631
+ lastName: { type: 'string' },
2632
+ },
2633
+ required: ['siteId', 'email', 'password'],
2634
+ },
2635
+ },
2620
2636
  {
2621
2637
  name: 'support_agent_lookup',
2622
2638
  description: 'Resolve a bare messaging WorkspaceUser.Id (AgentId, e.g. from an AgentPresenceFlapping WARN or AgentConnectionLog entry that carries no SiteId) to its Email/DisplayName/SiteId across ALL sites. Read-only, Velaro staff only. Different ID space from support_user_lookup (which searches ApplicationUsers/login identity, not the per-agent messaging record).',
@@ -2704,6 +2720,34 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
2704
2720
  required: ['siteId'],
2705
2721
  },
2706
2722
  },
2723
+ {
2724
+ name: 'support_connection_timeline',
2725
+ description: 'Raw ordered connection events (connected, disconnected, reconnecting) for ONE agent at a site over a time window, from velaro-messaging AgentConnectionLogs. Velaro staff only, read-only. Defaults to the last 24 hours.',
2726
+ inputSchema: {
2727
+ type: 'object',
2728
+ properties: {
2729
+ siteId: { type: 'number', description: 'Target site ID' },
2730
+ workspaceUserId: { type: 'number', description: 'WorkspaceUser.Id of the agent (see support_site_agents)' },
2731
+ from: { type: 'string', description: 'ISO start (default: 24h before "to")' },
2732
+ to: { type: 'string', description: 'ISO end (default: now)' },
2733
+ limit: { type: 'number', description: 'Max events, 1-5000 (default 2000)' },
2734
+ },
2735
+ required: ['siteId', 'workspaceUserId'],
2736
+ },
2737
+ },
2738
+ {
2739
+ name: 'support_connection_evidence',
2740
+ description: 'Dispute-grade connection evidence for a site and window: Velaro independent probe uptime %, incidents (including ones excluded from uptime, with the reason), per-agent disconnect counts, and client-vs-server reconciliation (disconnects with no unhealthy Velaro probe within 5 minutes are client/network side). Velaro staff only, read-only. Defaults to the last 30 days.',
2741
+ inputSchema: {
2742
+ type: 'object',
2743
+ properties: {
2744
+ siteId: { type: 'number', description: 'Target site ID' },
2745
+ from: { type: 'string', description: 'ISO start (default: 30 days before "to")' },
2746
+ to: { type: 'string', description: 'ISO end (default: now)' },
2747
+ },
2748
+ required: ['siteId'],
2749
+ },
2750
+ },
2707
2751
  {
2708
2752
  name: 'support_site_health',
2709
2753
  description: 'Get a full health snapshot for a customer site: subscription status/expiry, active agent count, last login, 7-day activity count, and integration error count. Velaro staff only.',
@@ -2722,6 +2766,14 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
2722
2766
  required: ['siteId'],
2723
2767
  },
2724
2768
  },
2769
+ {
2770
+ name: 'report_explain',
2771
+ description: 'How to use and interpret a Velaro report: what it answers, data sources, setup prerequisites, how to read the numbers, why a value could be zero or empty, what to do, and related reports. Pass a report key (e.g. experience-impact, missed, surveys, schedule-adherence) or omit to list all keys. Static guidance, no site data.',
2772
+ inputSchema: {
2773
+ type: 'object',
2774
+ properties: { report: { type: 'string', description: 'Report key. Omit to list all.' } },
2775
+ },
2776
+ },
2725
2777
  {
2726
2778
  name: 'experience_impact_report',
2727
2779
  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.',
@@ -2735,6 +2787,20 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
2735
2787
  },
2736
2788
  },
2737
2789
  },
2790
+ {
2791
+ name: 'report_backfill',
2792
+ description: 'Backfill report data for the caller site. Preview by default (reports "would fill N", writes nothing); apply=true writes. Idempotent. type "csat" fills missing survey ratings (SurveySubmission.Rating) from existing data; it does NOT touch ticket CSAT. Site managers only; scoped to the caller site.',
2793
+ inputSchema: {
2794
+ type: 'object',
2795
+ properties: {
2796
+ type: { type: 'string', description: 'Backfill type, e.g. csat.' },
2797
+ from: { type: 'string', description: 'yyyy-MM-dd start.' },
2798
+ to: { type: 'string', description: 'yyyy-MM-dd end.' },
2799
+ apply: { type: 'boolean', description: 'true = write. Default false (preview only).' },
2800
+ },
2801
+ required: ['type', 'from', 'to'],
2802
+ },
2803
+ },
2738
2804
  // -- Staff: schedule adherence by site + role (SupportTools-style: explicit siteId, never auth-derived) --
2739
2805
  // Backend: velaro-messaging ReportsController.ScheduleAdherenceDiagnosticsAsync, gated by
2740
2806
  // IsSuperAdminAsync(allowSupport: true) -> 403 for non-staff. role=agent REQUIRES agentId (fails closed).
@@ -4253,6 +4319,12 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
4253
4319
  },
4254
4320
  },
4255
4321
 
4322
+ {
4323
+ name: 'focus_get_triage_economy_report',
4324
+ description: 'Triage Economy report for this site: conversations triaged, rules vs Haiku counts, rules hit rate, Haiku calls avoided, estimated USD saved, latency, correction rates (rules vs Haiku quality), effective mode and its source (force|site|default). Skill selection is unaffected by economy mode. siteId comes from the session, never an argument.',
4325
+ inputSchema: { type: 'object', properties: { days: { type: 'number', description: 'Lookback window in days (default 30).' } } },
4326
+ },
4327
+
4256
4328
  // Feature Discovery -- non-nag "features on other plans this account doesn't have yet" nudge
4257
4329
  // siteId is never accepted as a tool argument -- FeatureDiscoveryController derives it
4258
4330
  // server-side from the caller's auth context (MCP key / JWT site scope), same pattern as the
@@ -7622,6 +7694,28 @@ async function handleTool(name, args) {
7622
7694
  }
7623
7695
 
7624
7696
  // ── Support Tools ─────────────────────────────────────────────────────────
7697
+ case 'support_add_site_user': {
7698
+ const roleMap = { agent: 'Agent', administrator: 'Administrator' };
7699
+ const role = String(args.role || 'agent').toLowerCase();
7700
+ if (!roleMap[role]) throw new Error('role must be agent or administrator.');
7701
+ const email = String(args.email || '').trim().toLowerCase();
7702
+ if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)) throw new Error('A valid email is required.');
7703
+ if (typeof args.password !== 'string' || args.password.length < 8) throw new Error('password is required (min 8 characters).');
7704
+ const listed = await messagingGet(`/SuperAdmin/users/${args.siteId}`);
7705
+ const users = Array.isArray(listed) ? listed : (listed?.users || []);
7706
+ let u = users.find(x => String(x.email || '').toLowerCase() === email);
7707
+ let created = false;
7708
+ if (!u) {
7709
+ const c = await messagingPost(`/SuperAdmin/users/${args.siteId}`, { email, firstName: args.firstName || '', lastName: args.lastName || '', roles: [roleMap[role]], sendEmail: false });
7710
+ if (!c?.success) throw new Error(`Create failed: ${c?.error || c?.message || 'unknown error'}`);
7711
+ u = { id: c.user.id ?? c.user.Id };
7712
+ created = true;
7713
+ }
7714
+ const sp = await messagingPost(`/SuperAdmin/users/${args.siteId}/${u.id}/set-password`, { newPassword: args.password });
7715
+ if (sp && sp.success === false) throw new Error(`set-password failed: ${sp.error || 'unknown error'}`);
7716
+ return JSON.stringify({ success: true, siteId: args.siteId, userId: u.id, email, created, passwordSet: true });
7717
+ }
7718
+
7625
7719
  case 'support_agent_lookup': {
7626
7720
  if (!args.id) return 'id is required (WorkspaceUser.Id / AgentId).';
7627
7721
  const res = await messagingApi(`/SuperAdmin/agents/${args.id}/lookup`);
@@ -7691,6 +7785,29 @@ async function handleTool(name, args) {
7691
7785
  return lines.join('\n');
7692
7786
  }
7693
7787
 
7788
+ case 'support_connection_timeline': {
7789
+ if (!args.siteId || !args.workspaceUserId) return 'siteId and workspaceUserId are required.';
7790
+ const q = new URLSearchParams();
7791
+ if (args.from) q.set('from', args.from);
7792
+ if (args.to) q.set('to', args.to);
7793
+ if (args.limit) q.set('limit', String(args.limit));
7794
+ const qs = q.toString() ? `?${q}` : '';
7795
+ const res = await messagingApi(`/SupportTools/sites/${args.siteId}/agents/${args.workspaceUserId}/connection-timeline${qs}`);
7796
+ if (res?.error) return `Error: ${res.error}`;
7797
+ return JSON.stringify(res, null, 2);
7798
+ }
7799
+
7800
+ case 'support_connection_evidence': {
7801
+ if (!args.siteId) return 'siteId is required.';
7802
+ const q = new URLSearchParams();
7803
+ if (args.from) q.set('from', args.from);
7804
+ if (args.to) q.set('to', args.to);
7805
+ const qs = q.toString() ? `?${q}` : '';
7806
+ const res = await messagingApi(`/SupportTools/sites/${args.siteId}/connection-evidence${qs}`);
7807
+ if (res?.error) return `Error: ${res.error}`;
7808
+ return JSON.stringify(res, null, 2);
7809
+ }
7810
+
7694
7811
  case 'support_site_health': {
7695
7812
  const h = await api('GET', `/SupportTools/sites/${args.siteId}/health`);
7696
7813
  const sub = h.subscription;
@@ -7717,6 +7834,15 @@ async function handleTool(name, args) {
7717
7834
  ).join('\n');
7718
7835
  }
7719
7836
 
7837
+ case 'report_explain': {
7838
+ const all = JSON.parse(_readFileSync(_join(_dirname(_ftu(import.meta.url)), '..', 'cli', 'lib', 'report-explain.json'), 'utf8'));
7839
+ const key = String(args.report ?? '').trim().toLowerCase();
7840
+ if (!key) return Object.entries(all).map(([k, r]) => `${k}: ${r.name}`).join('\n');
7841
+ const r = all[key];
7842
+ if (!r) return `Unknown report "${key}". Valid keys: ${Object.keys(all).join(', ')}`;
7843
+ 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');
7844
+ }
7845
+
7720
7846
  case 'experience_impact_report': {
7721
7847
  const qs = new URLSearchParams();
7722
7848
  for (const k of ['from', 'to', 'teamId']) if (args[k] != null && args[k] !== '') qs.set(k, String(args[k]));
@@ -7739,10 +7865,22 @@ async function handleTool(name, args) {
7739
7865
  lines.push(` ${label}: ${x.chats} chats | avg wait ${x.avgWaitSec}s | missed ${x.missed} | CSAT ${x.csatResponses ? x.csatAvg : '-'} (${x.csatResponses ?? 0})`);
7740
7866
  }
7741
7867
  }
7868
+ // Additive fields from the rebuilt report (absent on older backends): CSAT coverage + per-KPI reasons.
7869
+ if (r.coverage) {
7870
+ const c = r.coverage;
7871
+ lines.push(`CSAT coverage: ${(c.sources ?? []).map(x => `${x.label} ${x.responses} (avg ${x.avg ?? '-'})`).join(' | ')} | survey submissions ${c.surveySubmissions ?? 0}, rated ${c.surveyRated ?? 0}, response rate ${c.responseRatePct ?? 0}% | CSAT survey configured: ${c.csatSurveyConfigured ? 'yes' : 'no'}`);
7872
+ }
7873
+ for (const x of r.reasons ?? []) lines.push(` Why ${x.kpi} is low/empty [${x.code}]: ${x.message}${x.action ? ' Action: ' + x.action : ''}`);
7742
7874
  for (const a of r.agents ?? []) lines.push(` ${a.name}: index ${a.experienceIndex} | adherence ${a.adherencePct}% | CSAT ${a.csatAvg ?? '-'} | handled ${a.handled}`);
7743
7875
  return lines.join('\n');
7744
7876
  }
7745
7877
 
7878
+ case 'report_backfill': {
7879
+ const qs = new URLSearchParams({ from: String(args.from), to: String(args.to), apply: String(args.apply === true) });
7880
+ const r = await messagingPost(`/Reports/backfill/${encodeURIComponent(args.type)}?${qs}`);
7881
+ return (args.apply === true ? 'Backfill APPLIED\n' : 'Backfill PREVIEW (nothing written; pass apply=true to write)\n') + JSON.stringify(r, null, 2);
7882
+ }
7883
+
7746
7884
  case 'support_schedule_adherence': {
7747
7885
  if (args.role === 'agent' && !args.agentId) throw new Error('agentId is required when role=agent');
7748
7886
  const qs = new URLSearchParams({ detail: 'true' });
@@ -9201,6 +9339,11 @@ async function handleTool(name, args) {
9201
9339
  return await messagingGet('/Focus/DraftPregeneration/Report');
9202
9340
 
9203
9341
  // -- Focus Inbox AI triage rules (#582 Phase B) --
9342
+ case 'focus_get_triage_economy_report': {
9343
+ const d = args.days !== undefined ? Number(args.days) : 30;
9344
+ if (!Number.isFinite(d) || d < 1) throw new Error('days must be a positive number.');
9345
+ return await messagingGet('/Focus/Triage/EconomyReport?days=' + Math.floor(d));
9346
+ }
9204
9347
  case 'focus_get_triage_rules':
9205
9348
  return await messagingGet('/Focus/triage-rules');
9206
9349
  case 'focus_update_triage_rules': {