@velaro/mcp-server 0.6.69 → 0.6.71
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/README.md +1 -0
- package/package.json +1 -1
- package/server.js +446 -4
package/README.md
CHANGED
|
@@ -167,6 +167,7 @@ This works with any MCP client that supports HTTP/SSE transport — including Cl
|
|
|
167
167
|
|------|-------------|
|
|
168
168
|
| `report_roi_summary` | Full ROI report — bot deflections, hours saved, cost savings, AI skill executions, resolution rate, response time, trends vs prior period, monthly breakdown, top skills by call count, channel breakdown |
|
|
169
169
|
| `report_agent_utilization` | Per-agent stats — conversation count, avg handle time, active/away breakdown for any date range |
|
|
170
|
+
| `report_visitor_traffic_overview` | Visitor Traffic report (velaro-messaging) — daily visit trend, top countries and chat-channel breakdown for any date range |
|
|
170
171
|
| `report_campaign_performance` | Email campaign KPIs — open rate, click rate, delivery rate, complaint rate per campaign + aggregate totals |
|
|
171
172
|
| `report_campaign_deliverability` | Deliverability health check — bounce rate, complaint rate, unsubscribe rate, open rate with traffic-light status and fix recommendations |
|
|
172
173
|
| `report_campaign_monthly` | Month-by-month email volume for the last 12 months |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velaro/mcp-server",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.71",
|
|
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
|
@@ -119,12 +119,18 @@ function parseDuration(s) {
|
|
|
119
119
|
return n * 24 * 3600 * 1000;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
async function messagingApi(path) {
|
|
122
|
+
async function messagingApi(path, opts = {}) {
|
|
123
123
|
if (!MESSAGING_API) throw new Error(
|
|
124
124
|
'VELARO_MESSAGING_API is not configured. Add "VELARO_MESSAGING_API": "https://velaro-messaging-api-staging.azurewebsites.net" to your MCP env config.'
|
|
125
125
|
);
|
|
126
126
|
const res = await fetch(`${MESSAGING_API}${path}`, {
|
|
127
|
-
|
|
127
|
+
method: opts.method ?? 'GET',
|
|
128
|
+
headers: {
|
|
129
|
+
Authorization: authHeader(),
|
|
130
|
+
'X-Internal-SiteId': '1032',
|
|
131
|
+
...(opts.body ? { 'Content-Type': 'application/json' } : {}),
|
|
132
|
+
},
|
|
133
|
+
body: opts.body ? JSON.stringify(opts.body) : undefined,
|
|
128
134
|
signal: AbortSignal.timeout(20000),
|
|
129
135
|
});
|
|
130
136
|
if (!res.ok) {
|
|
@@ -525,9 +531,59 @@ const TOOLS = [
|
|
|
525
531
|
required: ['id', 'title', 'slug', 'topicId', 'content'],
|
|
526
532
|
},
|
|
527
533
|
},
|
|
534
|
+
{
|
|
535
|
+
name: 'kb_list_deleted_articles',
|
|
536
|
+
description: 'List recently deleted KB articles (restorable for 30 days).',
|
|
537
|
+
inputSchema: { type: 'object', properties: {} },
|
|
538
|
+
},
|
|
539
|
+
{
|
|
540
|
+
name: 'kb_restore_article',
|
|
541
|
+
description: 'Restore a deleted KB article (and re-index it).',
|
|
542
|
+
inputSchema: { type: 'object', properties: { id: { type: 'number', description: 'Article ID' } }, required: ['id'] },
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
name: 'kb_list_deleted_topics',
|
|
546
|
+
description: 'List recently deleted KB topics (restorable for 30 days).',
|
|
547
|
+
inputSchema: { type: 'object', properties: {} },
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
name: 'kb_restore_topic',
|
|
551
|
+
description: 'Restore a deleted KB topic.',
|
|
552
|
+
inputSchema: { type: 'object', properties: { id: { type: 'number', description: 'Topic ID' } }, required: ['id'] },
|
|
553
|
+
},
|
|
554
|
+
{
|
|
555
|
+
name: 'contactlist_list_deleted',
|
|
556
|
+
description: 'List recently deleted contact lists (restorable for 30 days).',
|
|
557
|
+
inputSchema: { type: 'object', properties: {} },
|
|
558
|
+
},
|
|
559
|
+
{
|
|
560
|
+
name: 'contactlist_restore',
|
|
561
|
+
description: 'Restore a deleted contact list with its members.',
|
|
562
|
+
inputSchema: { type: 'object', properties: { id: { type: 'number', description: 'List ID' } }, required: ['id'] },
|
|
563
|
+
},
|
|
564
|
+
{
|
|
565
|
+
name: 'contactsegment_list_deleted',
|
|
566
|
+
description: 'List recently deleted contact segments (restorable for 30 days).',
|
|
567
|
+
inputSchema: { type: 'object', properties: {} },
|
|
568
|
+
},
|
|
569
|
+
{
|
|
570
|
+
name: 'contactsegment_restore',
|
|
571
|
+
description: 'Restore a deleted contact segment.',
|
|
572
|
+
inputSchema: { type: 'object', properties: { id: { type: 'number', description: 'Segment ID' } }, required: ['id'] },
|
|
573
|
+
},
|
|
574
|
+
{
|
|
575
|
+
name: 'contactlist_member_list_deleted',
|
|
576
|
+
description: 'List members recently removed from a contact list (restorable for 30 days).',
|
|
577
|
+
inputSchema: { type: 'object', properties: { listId: { type: 'number' } }, required: ['listId'] },
|
|
578
|
+
},
|
|
579
|
+
{
|
|
580
|
+
name: 'contactlist_member_restore',
|
|
581
|
+
description: 'Restore a member removed from a contact list.',
|
|
582
|
+
inputSchema: { type: 'object', properties: { listId: { type: 'number' }, memberId: { type: 'number' } }, required: ['listId', 'memberId'] },
|
|
583
|
+
},
|
|
528
584
|
{
|
|
529
585
|
name: 'kb_delete_article',
|
|
530
|
-
description: 'Delete a KB article
|
|
586
|
+
description: 'Delete a KB article (soft delete: restorable for 30 days via kb_restore_article, then purged).',
|
|
531
587
|
inputSchema: {
|
|
532
588
|
type: 'object',
|
|
533
589
|
properties: { id: { type: 'number', description: 'Article ID to delete' } },
|
|
@@ -2017,6 +2073,17 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
2017
2073
|
},
|
|
2018
2074
|
},
|
|
2019
2075
|
},
|
|
2076
|
+
{
|
|
2077
|
+
name: 'report_visitor_traffic_overview',
|
|
2078
|
+
description: 'Visitor Traffic report (velaro-messaging) — daily visit trend, top countries and chat-channel breakdown over a date range. Site is resolved server-side from the service key/JWT, never a client-supplied value. Lives in velaro-messaging — requires VELARO_MESSAGING_API.',
|
|
2079
|
+
inputSchema: {
|
|
2080
|
+
type: 'object',
|
|
2081
|
+
properties: {
|
|
2082
|
+
startDate: { type: 'string', description: 'ISO 8601 start date, e.g. 2026-01-01 (default: 30 days ago)' },
|
|
2083
|
+
endDate: { type: 'string', description: 'ISO 8601 end date, e.g. 2026-04-19 (default: now)' },
|
|
2084
|
+
},
|
|
2085
|
+
},
|
|
2086
|
+
},
|
|
2020
2087
|
{
|
|
2021
2088
|
name: 'report_schedules_get',
|
|
2022
2089
|
description: 'List the recurring report email schedules configured for this site (Reports/Schedules) — report type, frequency, recipients, enabled state.',
|
|
@@ -2197,6 +2264,37 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
2197
2264
|
},
|
|
2198
2265
|
},
|
|
2199
2266
|
|
|
2267
|
+
// ── Klaviyo migration ─────────────────────────────────────────────────────
|
|
2268
|
+
{
|
|
2269
|
+
name: 'klaviyo_migration_run',
|
|
2270
|
+
description: 'Import the customer\'s Klaviyo lists, segments and flows into Velaro (uses the key stored by the Klaviyo integration). SAFE BY DEFAULT: dryRun defaults to true and writes nothing; pass dryRun=false only after reviewing the preview. Idempotent. Only SUBSCRIBED profiles are imported; flows arrive inactive.',
|
|
2271
|
+
inputSchema: {
|
|
2272
|
+
type: 'object',
|
|
2273
|
+
properties: {
|
|
2274
|
+
dryRun: { type: 'boolean', description: 'Default true. false = apply the import.' },
|
|
2275
|
+
listIds: { type: 'array', items: { type: 'string' }, description: 'Klaviyo list ids (omit for all)' },
|
|
2276
|
+
segmentIds: { type: 'array', items: { type: 'string' }, description: 'Klaviyo segment ids (omit for all)' },
|
|
2277
|
+
flowIds: { type: 'array', items: { type: 'string' }, description: 'Klaviyo flow ids (omit for all)' },
|
|
2278
|
+
includeFlows: { type: 'boolean', description: 'Default true' },
|
|
2279
|
+
maxProfilesPerGroup: { type: 'number', description: 'Cap per list/segment (default 5000)' },
|
|
2280
|
+
},
|
|
2281
|
+
required: [],
|
|
2282
|
+
},
|
|
2283
|
+
},
|
|
2284
|
+
{
|
|
2285
|
+
name: 'klaviyo_savings_calculator',
|
|
2286
|
+
description: 'Compare a Klaviyo monthly bill to a Velaro plan\'s catalog price. Velaro price comes only from the pricing catalog. Both profiles and klaviyoMonthlyBill are REQUIRED.',
|
|
2287
|
+
inputSchema: {
|
|
2288
|
+
type: 'object',
|
|
2289
|
+
properties: {
|
|
2290
|
+
profiles: { type: 'number', description: 'Klaviyo profile count' },
|
|
2291
|
+
klaviyoMonthlyBill: { type: 'number', description: 'Current Klaviyo monthly bill in USD' },
|
|
2292
|
+
planKey: { type: 'string', description: 'Velaro plan key, default plan.professional' },
|
|
2293
|
+
},
|
|
2294
|
+
required: ['profiles', 'klaviyoMonthlyBill'],
|
|
2295
|
+
},
|
|
2296
|
+
},
|
|
2297
|
+
|
|
2200
2298
|
// ── Conversion tracking tools ─────────────────────────────────────────────
|
|
2201
2299
|
{
|
|
2202
2300
|
name: 'conversion_list_goals',
|
|
@@ -2329,6 +2427,34 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
2329
2427
|
},
|
|
2330
2428
|
},
|
|
2331
2429
|
|
|
2430
|
+
{
|
|
2431
|
+
name: 'ai_model_set_site_thinking',
|
|
2432
|
+
description: 'Set the extended-thinking level for a site (off | low | medium | high), optionally scoped to one role. Staff only. Thinking bills as output tokens, is off by default, and only takes effect when the site holds the EnableAiThinking entitlement. Opus 5.5 always thinks at model-default effort regardless.',
|
|
2433
|
+
inputSchema: {
|
|
2434
|
+
type: 'object',
|
|
2435
|
+
properties: {
|
|
2436
|
+
siteId: { type: 'number', description: 'Site ID' },
|
|
2437
|
+
effort: { type: 'string', enum: ['off', 'low', 'medium', 'high'], description: 'Thinking level' },
|
|
2438
|
+
role: { type: 'string', description: 'Optional role to scope the setting to (e.g. chatbot). Omit for the site-wide value.' },
|
|
2439
|
+
},
|
|
2440
|
+
required: ['siteId', 'effort'],
|
|
2441
|
+
},
|
|
2442
|
+
},
|
|
2443
|
+
{
|
|
2444
|
+
name: 'ai_model_set_site_pin',
|
|
2445
|
+
description: 'Set a per-site role model override and choose whether to pin its exact version (pinVersion=true keeps a still-callable legacy model like claude-opus-5 instead of auto-upgrading to the latest). Staff only. Non-pinnable ids ignore the pin.',
|
|
2446
|
+
inputSchema: {
|
|
2447
|
+
type: 'object',
|
|
2448
|
+
properties: {
|
|
2449
|
+
siteId: { type: 'number', description: 'Site ID' },
|
|
2450
|
+
role: { type: 'string', description: 'Role (e.g. chatbot, ivr, skill_synthesis)' },
|
|
2451
|
+
model: { type: 'string', description: 'Model id/alias to set for this role' },
|
|
2452
|
+
pinVersion: { type: 'boolean', description: 'true = keep this exact model, do not auto-upgrade' },
|
|
2453
|
+
},
|
|
2454
|
+
required: ['siteId', 'role', 'model', 'pinVersion'],
|
|
2455
|
+
},
|
|
2456
|
+
},
|
|
2457
|
+
|
|
2332
2458
|
// ── Page Widgets ──────────────────────────────────────────────────────────
|
|
2333
2459
|
{
|
|
2334
2460
|
name: 'page_widget_list',
|
|
@@ -2491,6 +2617,22 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
2491
2617
|
},
|
|
2492
2618
|
|
|
2493
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
|
+
},
|
|
2494
2636
|
{
|
|
2495
2637
|
name: 'support_agent_lookup',
|
|
2496
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).',
|
|
@@ -2578,6 +2720,34 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
2578
2720
|
required: ['siteId'],
|
|
2579
2721
|
},
|
|
2580
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
|
+
},
|
|
2581
2751
|
{
|
|
2582
2752
|
name: 'support_site_health',
|
|
2583
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.',
|
|
@@ -2596,6 +2766,14 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
2596
2766
|
required: ['siteId'],
|
|
2597
2767
|
},
|
|
2598
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
|
+
},
|
|
2599
2777
|
{
|
|
2600
2778
|
name: 'experience_impact_report',
|
|
2601
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.',
|
|
@@ -2638,6 +2816,30 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
2638
2816
|
required: ['siteId'],
|
|
2639
2817
|
},
|
|
2640
2818
|
},
|
|
2819
|
+
{
|
|
2820
|
+
name: 'support_create_agent',
|
|
2821
|
+
description: 'Provision a new licensed agent/admin (Entra/V20 WorkspaceUser) on an EXISTING site. ' +
|
|
2822
|
+
'Velaro staff only. Closes a real gap: site-provision only creates brand-new sites (rejects an ' +
|
|
2823
|
+
'ExplicitSiteId that already exists), and the ordinary Users/List create endpoint requires an ' +
|
|
2824
|
+
'existing site admin to call it -- impossible on a zero-agent site (a fresh review/demo site, or ' +
|
|
2825
|
+
'any site whose only admin left). Enforces the site\'s license/seat cap same as the ordinary ' +
|
|
2826
|
+
'create-user flow. "Velaro Admin"/"Velaro Support" roles are always stripped server-side -- this ' +
|
|
2827
|
+
'tool provisions customer/site agents, never internal staff privilege escalation. Entra/V20 sites ' +
|
|
2828
|
+
'only (does not cover a site still on the legacy V10 identity model).',
|
|
2829
|
+
inputSchema: {
|
|
2830
|
+
type: 'object',
|
|
2831
|
+
properties: {
|
|
2832
|
+
siteId: { type: 'number', description: 'Target site ID (must already exist)' },
|
|
2833
|
+
email: { type: 'string', description: 'New agent email' },
|
|
2834
|
+
firstName: { type: 'string', description: 'First name' },
|
|
2835
|
+
lastName: { type: 'string', description: 'Last name' },
|
|
2836
|
+
roles: { type: 'array', items: { type: 'string' }, description: 'Roles to grant (default: ["Administrator"])' },
|
|
2837
|
+
teamIds: { type: 'array', items: { type: 'number' }, description: 'Team IDs on the target site (default: the site\'s first team)' },
|
|
2838
|
+
maxChats: { type: 'number', description: 'Concurrent chat capacity (default: site ceiling)' },
|
|
2839
|
+
},
|
|
2840
|
+
required: ['siteId', 'email'],
|
|
2841
|
+
},
|
|
2842
|
+
},
|
|
2641
2843
|
{
|
|
2642
2844
|
name: 'support_site_activity',
|
|
2643
2845
|
description: 'Get recent activity log entries for a customer site — who did what and when (workflows, logins, settings changes, etc.). Velaro staff only.',
|
|
@@ -4649,6 +4851,37 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
4649
4851
|
required: ['ruleId'],
|
|
4650
4852
|
},
|
|
4651
4853
|
},
|
|
4854
|
+
{
|
|
4855
|
+
name: 'booking_list_appointments',
|
|
4856
|
+
description: 'List actual bookings (Phase 1c) made against the booking engine for this site, optionally filtered by date range or service.',
|
|
4857
|
+
inputSchema: {
|
|
4858
|
+
type: 'object',
|
|
4859
|
+
properties: {
|
|
4860
|
+
fromUtc: { type: 'string', description: 'Only bookings starting at or after this ISO 8601 UTC timestamp' },
|
|
4861
|
+
toUtc: { type: 'string', description: 'Only bookings starting at or before this ISO 8601 UTC timestamp' },
|
|
4862
|
+
bookableServiceId: { type: 'number', description: 'Only bookings for this bookable service id' },
|
|
4863
|
+
},
|
|
4864
|
+
required: [],
|
|
4865
|
+
},
|
|
4866
|
+
},
|
|
4867
|
+
{
|
|
4868
|
+
name: 'booking_get_appointment',
|
|
4869
|
+
description: 'Get a single booking (Phase 1c) by id.',
|
|
4870
|
+
inputSchema: {
|
|
4871
|
+
type: 'object',
|
|
4872
|
+
properties: { bookingId: { type: 'number', description: 'Booking ID. Required.' } },
|
|
4873
|
+
required: ['bookingId'],
|
|
4874
|
+
},
|
|
4875
|
+
},
|
|
4876
|
+
{
|
|
4877
|
+
name: 'booking_cancel_appointment',
|
|
4878
|
+
description: 'Cancel a booking (Phase 1c), staff-initiated. Releases the held slot capacity.',
|
|
4879
|
+
inputSchema: {
|
|
4880
|
+
type: 'object',
|
|
4881
|
+
properties: { bookingId: { type: 'number', description: 'Booking ID. Required.' } },
|
|
4882
|
+
required: ['bookingId'],
|
|
4883
|
+
},
|
|
4884
|
+
},
|
|
4652
4885
|
|
|
4653
4886
|
// Focus Inbox -- draft pregeneration usage/cap report ("Always Learning" addon,
|
|
4654
4887
|
// issue #582 Phase D). Entitlements-gated (EnableFocusDraftPregeneration), not a
|
|
@@ -4996,6 +5229,61 @@ async function handleTool(name, args) {
|
|
|
4996
5229
|
return `Updated KB settings. ${JSON.stringify(args)}`;
|
|
4997
5230
|
}
|
|
4998
5231
|
|
|
5232
|
+
case 'kb_list_deleted_articles': {
|
|
5233
|
+
const rows = await api('GET', `/api/kb/articles/deleted`);
|
|
5234
|
+
if (!Array.isArray(rows) || !rows.length) return 'Nothing in Recently deleted.';
|
|
5235
|
+
return rows.map(r => `#${r.id} ${r.name} (deleted ${r.deletedAt}, purges in ${r.daysUntilPurge} day(s))`).join('\n');
|
|
5236
|
+
}
|
|
5237
|
+
|
|
5238
|
+
case 'kb_restore_article': {
|
|
5239
|
+
await api('POST', `/api/kb/articles/${args.id}/restore`);
|
|
5240
|
+
return `Restored #${args.id}.`;
|
|
5241
|
+
}
|
|
5242
|
+
|
|
5243
|
+
case 'kb_list_deleted_topics': {
|
|
5244
|
+
const rows = await api('GET', `/api/kb/topics/deleted`);
|
|
5245
|
+
if (!Array.isArray(rows) || !rows.length) return 'Nothing in Recently deleted.';
|
|
5246
|
+
return rows.map(r => `#${r.id} ${r.name} (deleted ${r.deletedAt}, purges in ${r.daysUntilPurge} day(s))`).join('\n');
|
|
5247
|
+
}
|
|
5248
|
+
|
|
5249
|
+
case 'kb_restore_topic': {
|
|
5250
|
+
await api('POST', `/api/kb/topics/${args.id}/restore`);
|
|
5251
|
+
return `Restored #${args.id}.`;
|
|
5252
|
+
}
|
|
5253
|
+
|
|
5254
|
+
case 'contactlist_list_deleted': {
|
|
5255
|
+
const rows = await api('GET', `/ContactLists/deleted`);
|
|
5256
|
+
if (!Array.isArray(rows) || !rows.length) return 'Nothing in Recently deleted.';
|
|
5257
|
+
return rows.map(r => `#${r.id} ${r.name} (deleted ${r.deletedAt}, purges in ${r.daysUntilPurge} day(s))`).join('\n');
|
|
5258
|
+
}
|
|
5259
|
+
|
|
5260
|
+
case 'contactlist_restore': {
|
|
5261
|
+
await api('POST', `/ContactLists/${args.id}/restore`);
|
|
5262
|
+
return `Restored #${args.id}.`;
|
|
5263
|
+
}
|
|
5264
|
+
|
|
5265
|
+
case 'contactsegment_list_deleted': {
|
|
5266
|
+
const rows = await api('GET', `/ContactSegments/deleted`);
|
|
5267
|
+
if (!Array.isArray(rows) || !rows.length) return 'Nothing in Recently deleted.';
|
|
5268
|
+
return rows.map(r => `#${r.id} ${r.name} (deleted ${r.deletedAt}, purges in ${r.daysUntilPurge} day(s))`).join('\n');
|
|
5269
|
+
}
|
|
5270
|
+
|
|
5271
|
+
case 'contactsegment_restore': {
|
|
5272
|
+
await api('POST', `/ContactSegments/${args.id}/restore`);
|
|
5273
|
+
return `Restored #${args.id}.`;
|
|
5274
|
+
}
|
|
5275
|
+
|
|
5276
|
+
case 'contactlist_member_list_deleted': {
|
|
5277
|
+
const rows = await api('GET', `/ContactLists/${args.listId}/Members/deleted`);
|
|
5278
|
+
if (!Array.isArray(rows) || !rows.length) return 'Nothing in Recently deleted.';
|
|
5279
|
+
return rows.map(r => `#${r.id} ${r.name} (purges in ${r.daysUntilPurge} day(s))`).join('\n');
|
|
5280
|
+
}
|
|
5281
|
+
|
|
5282
|
+
case 'contactlist_member_restore': {
|
|
5283
|
+
await api('POST', `/ContactLists/${args.listId}/Members/${args.memberId}/restore`);
|
|
5284
|
+
return `Restored member #${args.memberId}.`;
|
|
5285
|
+
}
|
|
5286
|
+
|
|
4999
5287
|
case 'kb_delete_article': {
|
|
5000
5288
|
await api('DELETE', `/api/kb/articles?id=${args.id}`);
|
|
5001
5289
|
return `Article ${args.id} deleted.`;
|
|
@@ -5057,6 +5345,18 @@ async function handleTool(name, args) {
|
|
|
5057
5345
|
return JSON.stringify(h, null, 2);
|
|
5058
5346
|
}
|
|
5059
5347
|
|
|
5348
|
+
case 'support_create_agent': {
|
|
5349
|
+
const r = await messagingPost(`/SupportTools/sites/${args.siteId}/agents`, {
|
|
5350
|
+
email: args.email,
|
|
5351
|
+
firstName: args.firstName,
|
|
5352
|
+
lastName: args.lastName,
|
|
5353
|
+
roles: args.roles,
|
|
5354
|
+
teamIds: args.teamIds,
|
|
5355
|
+
maxChats: args.maxChats ?? 0,
|
|
5356
|
+
});
|
|
5357
|
+
return JSON.stringify(r, null, 2);
|
|
5358
|
+
}
|
|
5359
|
+
|
|
5060
5360
|
case 'apple_test_control': {
|
|
5061
5361
|
const r = await messagingPost(`/SupportTools/sites/${args.siteId}/apple/test-control`, {
|
|
5062
5362
|
control: args.control,
|
|
@@ -6534,6 +6834,31 @@ async function handleTool(name, args) {
|
|
|
6534
6834
|
return lines.join('\n');
|
|
6535
6835
|
}
|
|
6536
6836
|
|
|
6837
|
+
case 'report_visitor_traffic_overview': {
|
|
6838
|
+
const end = args.endDate ? new Date(args.endDate) : new Date();
|
|
6839
|
+
const start = args.startDate ? new Date(args.startDate) : new Date(end.getTime() - 30 * 24 * 3600 * 1000);
|
|
6840
|
+
const r = await messagingPost('/reports/visitor-traffic-overview', {
|
|
6841
|
+
start: start.toISOString(),
|
|
6842
|
+
end: end.toISOString(),
|
|
6843
|
+
});
|
|
6844
|
+
const k = r?.kpis ?? {};
|
|
6845
|
+
const lines = [
|
|
6846
|
+
`=== Visitor Traffic (${r?.start ?? start.toISOString().slice(0, 10)} – ${r?.end ?? end.toISOString().slice(0, 10)}) ===`,
|
|
6847
|
+
`Total Visits: ${k.totalVisits ?? 0} | Avg Daily: ${k.avgDailyVisits ?? 0} | Countries Reached: ${k.countriesReached ?? 0} | Chats Started: ${k.chatsStarted ?? 0}`,
|
|
6848
|
+
'',
|
|
6849
|
+
'--- Top Countries ---',
|
|
6850
|
+
...(Array.isArray(r?.countryBreakdown) && r.countryBreakdown.length
|
|
6851
|
+
? r.countryBreakdown.map((c) => ` ${c.country}: ${c.count}`)
|
|
6852
|
+
: [' (no data)']),
|
|
6853
|
+
'',
|
|
6854
|
+
'--- By Channel ---',
|
|
6855
|
+
...(Array.isArray(r?.channelBreakdown) && r.channelBreakdown.length
|
|
6856
|
+
? r.channelBreakdown.map((c) => ` ${c.name}: ${c.count} (${c.pct}%)`)
|
|
6857
|
+
: [' (no data)']),
|
|
6858
|
+
];
|
|
6859
|
+
return lines.join('\n');
|
|
6860
|
+
}
|
|
6861
|
+
|
|
6537
6862
|
case 'report_schedules_get': {
|
|
6538
6863
|
const rows = await api('GET', '/Reports/Schedules');
|
|
6539
6864
|
if (!Array.isArray(rows) || !rows.length) return 'No scheduled reports configured for this site.';
|
|
@@ -6973,6 +7298,25 @@ async function handleTool(name, args) {
|
|
|
6973
7298
|
return JSON.stringify(r, null, 2);
|
|
6974
7299
|
}
|
|
6975
7300
|
|
|
7301
|
+
// ── Klaviyo migration ──────────────────────────────────────────────────
|
|
7302
|
+
case 'klaviyo_migration_run': {
|
|
7303
|
+
// Anything other than an explicit boolean false stays a dry run.
|
|
7304
|
+
const dryRun = args.dryRun !== false;
|
|
7305
|
+
const r = await api('POST', `/KlaviyoMigration/${dryRun ? 'Preview' : 'Apply'}`, {
|
|
7306
|
+
listIds: args.listIds, segmentIds: args.segmentIds, flowIds: args.flowIds,
|
|
7307
|
+
includeFlows: args.includeFlows, maxProfilesPerGroup: args.maxProfilesPerGroup,
|
|
7308
|
+
});
|
|
7309
|
+
return JSON.stringify(r, null, 2);
|
|
7310
|
+
}
|
|
7311
|
+
|
|
7312
|
+
case 'klaviyo_savings_calculator': {
|
|
7313
|
+
if (args.profiles == null || args.klaviyoMonthlyBill == null) return 'Error: profiles and klaviyoMonthlyBill are required';
|
|
7314
|
+
const p = new URLSearchParams({ profiles: String(args.profiles), klaviyoMonthlyBill: String(args.klaviyoMonthlyBill) });
|
|
7315
|
+
if (args.planKey) p.set('planKey', args.planKey);
|
|
7316
|
+
const r = await api('GET', `/KlaviyoMigration/SavingsCalculator?${p}`);
|
|
7317
|
+
return JSON.stringify(r, null, 2);
|
|
7318
|
+
}
|
|
7319
|
+
|
|
6976
7320
|
// ── Unified Outbound Activity ──────────────────────────────────────────
|
|
6977
7321
|
case 'outbound_activity_list':
|
|
6978
7322
|
case 'outbound_activity_for_contact': {
|
|
@@ -7179,6 +7523,22 @@ async function handleTool(name, args) {
|
|
|
7179
7523
|
return lines.join('\n');
|
|
7180
7524
|
}
|
|
7181
7525
|
|
|
7526
|
+
case 'ai_model_set_site_thinking': {
|
|
7527
|
+
const data = await messagingApi(`/superadmin/ai-models/site/${Number(args.siteId)}/thinking`, {
|
|
7528
|
+
method: 'POST',
|
|
7529
|
+
body: { effort: args.effort, ...(args.role ? { role: args.role } : {}) },
|
|
7530
|
+
});
|
|
7531
|
+
return `Site ${data.siteId}: ${data.key} = ${data.effort}. (Only active if the site holds the EnableAiThinking entitlement.)`;
|
|
7532
|
+
}
|
|
7533
|
+
|
|
7534
|
+
case 'ai_model_set_site_pin': {
|
|
7535
|
+
const data = await messagingApi(`/superadmin/ai-models/site/${Number(args.siteId)}`, {
|
|
7536
|
+
method: 'POST',
|
|
7537
|
+
body: { role: args.role, model: args.model, pinVersion: args.pinVersion === true, updatedBy: 'velaro-mcp' },
|
|
7538
|
+
});
|
|
7539
|
+
return `Site ${data.siteId} role ${data.role}: model ${data.model}, pinVersion=${data.pinVersion}`;
|
|
7540
|
+
}
|
|
7541
|
+
|
|
7182
7542
|
// ── Page Widgets ──────────────────────────────────────────────────────
|
|
7183
7543
|
case 'page_widget_list': {
|
|
7184
7544
|
const data = await api('GET', '/PageWidgets');
|
|
@@ -7314,6 +7674,28 @@ async function handleTool(name, args) {
|
|
|
7314
7674
|
}
|
|
7315
7675
|
|
|
7316
7676
|
// ── Support Tools ─────────────────────────────────────────────────────────
|
|
7677
|
+
case 'support_add_site_user': {
|
|
7678
|
+
const roleMap = { agent: 'Agent', administrator: 'Administrator' };
|
|
7679
|
+
const role = String(args.role || 'agent').toLowerCase();
|
|
7680
|
+
if (!roleMap[role]) throw new Error('role must be agent or administrator.');
|
|
7681
|
+
const email = String(args.email || '').trim().toLowerCase();
|
|
7682
|
+
if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)) throw new Error('A valid email is required.');
|
|
7683
|
+
if (typeof args.password !== 'string' || args.password.length < 8) throw new Error('password is required (min 8 characters).');
|
|
7684
|
+
const listed = await messagingGet(`/SuperAdmin/users/${args.siteId}`);
|
|
7685
|
+
const users = Array.isArray(listed) ? listed : (listed?.users || []);
|
|
7686
|
+
let u = users.find(x => String(x.email || '').toLowerCase() === email);
|
|
7687
|
+
let created = false;
|
|
7688
|
+
if (!u) {
|
|
7689
|
+
const c = await messagingPost(`/SuperAdmin/users/${args.siteId}`, { email, firstName: args.firstName || '', lastName: args.lastName || '', roles: [roleMap[role]], sendEmail: false });
|
|
7690
|
+
if (!c?.success) throw new Error(`Create failed: ${c?.error || c?.message || 'unknown error'}`);
|
|
7691
|
+
u = { id: c.user.id ?? c.user.Id };
|
|
7692
|
+
created = true;
|
|
7693
|
+
}
|
|
7694
|
+
const sp = await messagingPost(`/SuperAdmin/users/${args.siteId}/${u.id}/set-password`, { newPassword: args.password });
|
|
7695
|
+
if (sp && sp.success === false) throw new Error(`set-password failed: ${sp.error || 'unknown error'}`);
|
|
7696
|
+
return JSON.stringify({ success: true, siteId: args.siteId, userId: u.id, email, created, passwordSet: true });
|
|
7697
|
+
}
|
|
7698
|
+
|
|
7317
7699
|
case 'support_agent_lookup': {
|
|
7318
7700
|
if (!args.id) return 'id is required (WorkspaceUser.Id / AgentId).';
|
|
7319
7701
|
const res = await messagingApi(`/SuperAdmin/agents/${args.id}/lookup`);
|
|
@@ -7383,6 +7765,29 @@ async function handleTool(name, args) {
|
|
|
7383
7765
|
return lines.join('\n');
|
|
7384
7766
|
}
|
|
7385
7767
|
|
|
7768
|
+
case 'support_connection_timeline': {
|
|
7769
|
+
if (!args.siteId || !args.workspaceUserId) return 'siteId and workspaceUserId are required.';
|
|
7770
|
+
const q = new URLSearchParams();
|
|
7771
|
+
if (args.from) q.set('from', args.from);
|
|
7772
|
+
if (args.to) q.set('to', args.to);
|
|
7773
|
+
if (args.limit) q.set('limit', String(args.limit));
|
|
7774
|
+
const qs = q.toString() ? `?${q}` : '';
|
|
7775
|
+
const res = await messagingApi(`/SupportTools/sites/${args.siteId}/agents/${args.workspaceUserId}/connection-timeline${qs}`);
|
|
7776
|
+
if (res?.error) return `Error: ${res.error}`;
|
|
7777
|
+
return JSON.stringify(res, null, 2);
|
|
7778
|
+
}
|
|
7779
|
+
|
|
7780
|
+
case 'support_connection_evidence': {
|
|
7781
|
+
if (!args.siteId) return 'siteId is required.';
|
|
7782
|
+
const q = new URLSearchParams();
|
|
7783
|
+
if (args.from) q.set('from', args.from);
|
|
7784
|
+
if (args.to) q.set('to', args.to);
|
|
7785
|
+
const qs = q.toString() ? `?${q}` : '';
|
|
7786
|
+
const res = await messagingApi(`/SupportTools/sites/${args.siteId}/connection-evidence${qs}`);
|
|
7787
|
+
if (res?.error) return `Error: ${res.error}`;
|
|
7788
|
+
return JSON.stringify(res, null, 2);
|
|
7789
|
+
}
|
|
7790
|
+
|
|
7386
7791
|
case 'support_site_health': {
|
|
7387
7792
|
const h = await api('GET', `/SupportTools/sites/${args.siteId}/health`);
|
|
7388
7793
|
const sub = h.subscription;
|
|
@@ -7409,6 +7814,15 @@ async function handleTool(name, args) {
|
|
|
7409
7814
|
).join('\n');
|
|
7410
7815
|
}
|
|
7411
7816
|
|
|
7817
|
+
case 'report_explain': {
|
|
7818
|
+
const all = JSON.parse(_readFileSync(_join(_dirname(_ftu(import.meta.url)), '..', 'cli', 'lib', 'report-explain.json'), 'utf8'));
|
|
7819
|
+
const key = String(args.report ?? '').trim().toLowerCase();
|
|
7820
|
+
if (!key) return Object.entries(all).map(([k, r]) => `${k}: ${r.name}`).join('\n');
|
|
7821
|
+
const r = all[key];
|
|
7822
|
+
if (!r) return `Unknown report "${key}". Valid keys: ${Object.keys(all).join(', ')}`;
|
|
7823
|
+
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');
|
|
7824
|
+
}
|
|
7825
|
+
|
|
7412
7826
|
case 'experience_impact_report': {
|
|
7413
7827
|
const qs = new URLSearchParams();
|
|
7414
7828
|
for (const k of ['from', 'to', 'teamId']) if (args[k] != null && args[k] !== '') qs.set(k, String(args[k]));
|
|
@@ -7431,6 +7845,12 @@ async function handleTool(name, args) {
|
|
|
7431
7845
|
lines.push(` ${label}: ${x.chats} chats | avg wait ${x.avgWaitSec}s | missed ${x.missed} | CSAT ${x.csatResponses ? x.csatAvg : '-'} (${x.csatResponses ?? 0})`);
|
|
7432
7846
|
}
|
|
7433
7847
|
}
|
|
7848
|
+
// Additive fields from the rebuilt report (absent on older backends): CSAT coverage + per-KPI reasons.
|
|
7849
|
+
if (r.coverage) {
|
|
7850
|
+
const c = r.coverage;
|
|
7851
|
+
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'}`);
|
|
7852
|
+
}
|
|
7853
|
+
for (const x of r.reasons ?? []) lines.push(` Why ${x.kpi} is low/empty [${x.code}]: ${x.message}${x.action ? ' Action: ' + x.action : ''}`);
|
|
7434
7854
|
for (const a of r.agents ?? []) lines.push(` ${a.name}: index ${a.experienceIndex} | adherence ${a.adherencePct}% | CSAT ${a.csatAvg ?? '-'} | handled ${a.handled}`);
|
|
7435
7855
|
return lines.join('\n');
|
|
7436
7856
|
}
|
|
@@ -9253,6 +9673,28 @@ async function handleTool(name, args) {
|
|
|
9253
9673
|
return JSON.stringify(result, null, 2);
|
|
9254
9674
|
}
|
|
9255
9675
|
|
|
9676
|
+
case 'booking_list_appointments': {
|
|
9677
|
+
const params = [];
|
|
9678
|
+
if (args.fromUtc != null) params.push(`fromUtc=${encodeURIComponent(args.fromUtc)}`);
|
|
9679
|
+
if (args.toUtc != null) params.push(`toUtc=${encodeURIComponent(args.toUtc)}`);
|
|
9680
|
+
if (args.bookableServiceId != null) params.push(`bookableServiceId=${encodeURIComponent(args.bookableServiceId)}`);
|
|
9681
|
+
const qs = params.length ? `?${params.join('&')}` : '';
|
|
9682
|
+
const result = await messagingGet(`/Bookings${qs}`);
|
|
9683
|
+
return JSON.stringify(result, null, 2);
|
|
9684
|
+
}
|
|
9685
|
+
|
|
9686
|
+
case 'booking_get_appointment': {
|
|
9687
|
+
if (args.bookingId == null) return 'bookingId is required.';
|
|
9688
|
+
const result = await messagingGet(`/Bookings/${encodeURIComponent(args.bookingId)}`);
|
|
9689
|
+
return JSON.stringify(result, null, 2);
|
|
9690
|
+
}
|
|
9691
|
+
|
|
9692
|
+
case 'booking_cancel_appointment': {
|
|
9693
|
+
if (args.bookingId == null) return 'bookingId is required.';
|
|
9694
|
+
const result = await messagingPost(`/Bookings/${encodeURIComponent(args.bookingId)}/cancel`, {});
|
|
9695
|
+
return JSON.stringify(result, null, 2);
|
|
9696
|
+
}
|
|
9697
|
+
|
|
9256
9698
|
default:
|
|
9257
9699
|
throw new Error(`Unknown tool: ${name}`);
|
|
9258
9700
|
}
|
|
@@ -9353,4 +9795,4 @@ if (mode === 'http') {
|
|
|
9353
9795
|
} else {
|
|
9354
9796
|
const transport = new StdioServerTransport();
|
|
9355
9797
|
await server.connect(transport);
|
|
9356
|
-
}
|
|
9798
|
+
}
|