@velaro/mcp-server 0.6.55 → 0.6.57
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 +7 -3
- package/server.js +253 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velaro/mcp-server",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.57",
|
|
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": {
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"qs": "^6.16.0"
|
|
15
15
|
},
|
|
16
16
|
"overrides": {
|
|
17
|
-
"qs": "^6.16.0"
|
|
17
|
+
"qs": "^6.16.0",
|
|
18
|
+
"hono": "^4.13.5"
|
|
18
19
|
},
|
|
19
20
|
"scripts": {
|
|
20
21
|
"start": "node server.js"
|
|
@@ -43,5 +44,8 @@
|
|
|
43
44
|
"server.js",
|
|
44
45
|
"server.json",
|
|
45
46
|
"README.md"
|
|
46
|
-
]
|
|
47
|
+
],
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"license-checker-rseidelsohn": "^4.4.2"
|
|
50
|
+
}
|
|
47
51
|
}
|
package/server.js
CHANGED
|
@@ -62,6 +62,12 @@ const MESSAGING_API = process.env.VELARO_MESSAGING_API || null;
|
|
|
62
62
|
const MCP_KEY = process.env.VELARO_MCP_KEY;
|
|
63
63
|
const JWT = process.env.VELARO_JWT;
|
|
64
64
|
|
|
65
|
+
// mcp/staff-server.js defaults to staging instead -- the two servers' defaults intentionally
|
|
66
|
+
// differ, but a zero-result lookup gives no hint which environment was actually searched
|
|
67
|
+
// either way. See ENV_LABEL in staff-server.js for the incident this class of confusion caused
|
|
68
|
+
// (a real production customer read as "doesn't exist" from a staging-scoped search).
|
|
69
|
+
const ENV_LABEL = API_BASE.includes('staging') ? 'staging' : 'production';
|
|
70
|
+
|
|
65
71
|
// Known admin API hosts, for tools that let a caller explicitly target one environment
|
|
66
72
|
// regardless of what this server process's own API_BASE is configured to (see the `env` param
|
|
67
73
|
// on the calendly_* superadmin tools below). Deliberately NOT used as a default anywhere —
|
|
@@ -561,6 +567,18 @@ const TOOLS = [
|
|
|
561
567
|
},
|
|
562
568
|
},
|
|
563
569
|
|
|
570
|
+
{
|
|
571
|
+
name: 'site_scraper_usage',
|
|
572
|
+
description: 'Velaro staff only. Scraper/upload/chunk consumption for ANY site in one call: pages this month vs budget (both the admin and messaging ledgers), upload pages, OCR pages, jobs today vs daily cap, and chunks indexed vs MaxIndexedChunkTotal with chunks grouped by index name. Wraps GET ScraperJobs/StaffUsage/{siteId} (IsVelaroAdminAsync + super-admin enforced server-side). Read-only.',
|
|
573
|
+
inputSchema: {
|
|
574
|
+
type: 'object',
|
|
575
|
+
properties: {
|
|
576
|
+
siteId: { type: 'number', description: 'Target site ID (need not be your own)' },
|
|
577
|
+
},
|
|
578
|
+
required: ['siteId'],
|
|
579
|
+
},
|
|
580
|
+
},
|
|
581
|
+
|
|
564
582
|
// ── Startup migration diagnostics (AdminStartupDiagnostics canary table) ──
|
|
565
583
|
{
|
|
566
584
|
name: 'admin_startup_diagnostics',
|
|
@@ -1593,6 +1611,120 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
1593
1611
|
description: 'Show what is currently indexed for the site: document counts per source type (kb_article, scraper_page, bigcommerce_product, etc). Use to verify ingestion completed and data is present.',
|
|
1594
1612
|
inputSchema: { type: 'object', properties: {} },
|
|
1595
1613
|
},
|
|
1614
|
+
{
|
|
1615
|
+
name: 'index_move_preview',
|
|
1616
|
+
description: 'Preview moving (or copying) documents between two knowledge indexes on the site. Writes nothing. Returns document/chunk/page counts, per-document method (copy_vectors, rechunk, already_in_target, blocked), quota effect, and whether it can run. No rescan and no OCR is ever needed. Moving into a troubleshooting index needs the EnableTroubleshootingKnowledge entitlement. Wraps POST IndexMove/Preview (velaro-messaging); the site comes from the caller auth, never an argument.',
|
|
1617
|
+
inputSchema: {
|
|
1618
|
+
type: 'object',
|
|
1619
|
+
properties: {
|
|
1620
|
+
fromIndex: { type: 'string', description: 'Source index name' },
|
|
1621
|
+
toIndex: { type: 'string', description: 'Target index name' },
|
|
1622
|
+
kind: { type: 'string', enum: ['move', 'copy'], description: 'Default move' },
|
|
1623
|
+
skipBlocked: { type: 'boolean', description: 'Leave blocked documents in place and move the rest' },
|
|
1624
|
+
selector: {
|
|
1625
|
+
type: 'object',
|
|
1626
|
+
description: 'What to move. Needs at least one field or all:true. Fields: all (bool), jobId, urlPrefix, sourceIds[], fileNames[], docIds[] (numbers), sourceType (website_page | video_transcript | uploaded_document | onedrive_file | custom_content).',
|
|
1627
|
+
},
|
|
1628
|
+
},
|
|
1629
|
+
required: ['fromIndex', 'toIndex', 'selector'],
|
|
1630
|
+
},
|
|
1631
|
+
},
|
|
1632
|
+
{
|
|
1633
|
+
name: 'index_move_execute',
|
|
1634
|
+
description: 'Run a move/copy between knowledge indexes. Always call index_move_preview first and check canExecute. Write-then-delete and resumable: content is never missing from both indexes. Returns a moveId; poll index_move_status. Wraps POST IndexMove/Execute.',
|
|
1635
|
+
inputSchema: {
|
|
1636
|
+
type: 'object',
|
|
1637
|
+
properties: {
|
|
1638
|
+
fromIndex: { type: 'string' },
|
|
1639
|
+
toIndex: { type: 'string' },
|
|
1640
|
+
kind: { type: 'string', enum: ['move', 'copy'] },
|
|
1641
|
+
skipBlocked: { type: 'boolean' },
|
|
1642
|
+
selector: {
|
|
1643
|
+
type: 'object',
|
|
1644
|
+
description: 'What to move. Needs at least one field or all:true. Fields: all (bool), jobId, urlPrefix, sourceIds[], fileNames[], docIds[] (numbers), sourceType (website_page | video_transcript | uploaded_document | onedrive_file | custom_content).',
|
|
1645
|
+
},
|
|
1646
|
+
},
|
|
1647
|
+
required: ['fromIndex', 'toIndex', 'selector'],
|
|
1648
|
+
},
|
|
1649
|
+
},
|
|
1650
|
+
{
|
|
1651
|
+
name: 'index_split_preview',
|
|
1652
|
+
description: 'Detect manual-like documents in a general index and preview moving them into an existing troubleshooting-purpose index. Writes nothing. Wraps POST IndexMove/Split/Preview. Use index_split_execute to run it.',
|
|
1653
|
+
inputSchema: {
|
|
1654
|
+
type: 'object',
|
|
1655
|
+
properties: {
|
|
1656
|
+
fromIndex: { type: 'string' },
|
|
1657
|
+
toIndex: { type: 'string', description: 'Existing troubleshooting-purpose index' },
|
|
1658
|
+
docIds: { type: 'array', items: { type: 'number' }, description: 'Only these documents (default: every detected manual)' },
|
|
1659
|
+
skipBlocked: { type: 'boolean' },
|
|
1660
|
+
},
|
|
1661
|
+
required: ['fromIndex', 'toIndex'],
|
|
1662
|
+
},
|
|
1663
|
+
},
|
|
1664
|
+
{
|
|
1665
|
+
name: 'index_split_execute',
|
|
1666
|
+
description: 'Run a split: move detected manuals from a general index into an existing troubleshooting-purpose index. Call index_split_preview first. Resumable and undoable via index_move_control. Wraps POST IndexMove/Split/Execute.',
|
|
1667
|
+
inputSchema: {
|
|
1668
|
+
type: 'object',
|
|
1669
|
+
properties: {
|
|
1670
|
+
fromIndex: { type: 'string' },
|
|
1671
|
+
toIndex: { type: 'string' },
|
|
1672
|
+
docIds: { type: 'array', items: { type: 'number' } },
|
|
1673
|
+
skipBlocked: { type: 'boolean' },
|
|
1674
|
+
},
|
|
1675
|
+
required: ['fromIndex', 'toIndex'],
|
|
1676
|
+
},
|
|
1677
|
+
},
|
|
1678
|
+
{
|
|
1679
|
+
name: 'index_move_status',
|
|
1680
|
+
description: 'Status of one move (moveId) or the recent moves (omit moveId): status, per-document progress, canResume/canAbort/canUndo. Wraps GET IndexMove/Moves[/{id}].',
|
|
1681
|
+
inputSchema: {
|
|
1682
|
+
type: 'object',
|
|
1683
|
+
properties: { moveId: { type: 'string' } },
|
|
1684
|
+
},
|
|
1685
|
+
},
|
|
1686
|
+
{
|
|
1687
|
+
name: 'index_move_control',
|
|
1688
|
+
description: 'Resume a stopped move, abort a queued/failed move that has not started removing content, or undo a completed move. Wraps POST IndexMove/Moves/{id}/Resume|Abort|Undo.',
|
|
1689
|
+
inputSchema: {
|
|
1690
|
+
type: 'object',
|
|
1691
|
+
properties: {
|
|
1692
|
+
moveId: { type: 'string' },
|
|
1693
|
+
action: { type: 'string', enum: ['resume', 'abort', 'undo'] },
|
|
1694
|
+
},
|
|
1695
|
+
required: ['moveId', 'action'],
|
|
1696
|
+
},
|
|
1697
|
+
},
|
|
1698
|
+
{
|
|
1699
|
+
name: 'index_mixed_content',
|
|
1700
|
+
description: 'Check whether a general index holds manual-like documents that belong in a Troubleshooting Assistant index. Read-only. Wraps GET IndexMove/MixedContent.',
|
|
1701
|
+
inputSchema: {
|
|
1702
|
+
type: 'object',
|
|
1703
|
+
properties: { indexName: { type: 'string' } },
|
|
1704
|
+
required: ['indexName'],
|
|
1705
|
+
},
|
|
1706
|
+
},
|
|
1707
|
+
{
|
|
1708
|
+
name: 'index_integrity_check',
|
|
1709
|
+
description: 'Read-only integrity report for a knowledge index: orphan chunks, records without chunks, count mismatches, duplicate records. Wraps GET IndexMove/Integrity.',
|
|
1710
|
+
inputSchema: {
|
|
1711
|
+
type: 'object',
|
|
1712
|
+
properties: { indexName: { type: 'string' } },
|
|
1713
|
+
required: ['indexName'],
|
|
1714
|
+
},
|
|
1715
|
+
},
|
|
1716
|
+
{
|
|
1717
|
+
name: 'index_integrity_repair',
|
|
1718
|
+
description: 'Repair a knowledge index. Additive and idempotent: corrects counts and adopts orphan chunks into tracking rows, never deletes vectors. removeDuplicateRecords also removes duplicate tracking rows. Run index_integrity_check first. Wraps POST IndexMove/Integrity/Repair.',
|
|
1719
|
+
inputSchema: {
|
|
1720
|
+
type: 'object',
|
|
1721
|
+
properties: {
|
|
1722
|
+
indexName: { type: 'string' },
|
|
1723
|
+
removeDuplicateRecords: { type: 'boolean' },
|
|
1724
|
+
},
|
|
1725
|
+
required: ['indexName'],
|
|
1726
|
+
},
|
|
1727
|
+
},
|
|
1596
1728
|
{
|
|
1597
1729
|
name: 'index_ingest',
|
|
1598
1730
|
description: 'Trigger ingestion of a scraper job into the knowledge index. Fires in the background; use index_status after ~2 minutes to verify chunks appeared.',
|
|
@@ -2331,6 +2463,17 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
2331
2463
|
required: ['siteId'],
|
|
2332
2464
|
},
|
|
2333
2465
|
},
|
|
2466
|
+
{
|
|
2467
|
+
name: 'tour_insights',
|
|
2468
|
+
description: 'Moshky setup-tour funnel and feedback (Velaro staff only). Without siteId: per-tour, per-step counts of offered/opened/step_viewed/arrived/stuck/completed/dismissed/load_error over the last N days plus recent scrubbed feedback. With siteId: that one site\'s tour event timeline and feedback. Feedback comments are PII-scrubbed and for staff eyes only.',
|
|
2469
|
+
inputSchema: {
|
|
2470
|
+
type: 'object',
|
|
2471
|
+
properties: {
|
|
2472
|
+
siteId: { type: 'number', description: 'Optional. When set, returns that site\'s tour timeline instead of the cross-site funnel.' },
|
|
2473
|
+
days: { type: 'number', description: 'Look-back window in days (default 30, max 365)' },
|
|
2474
|
+
},
|
|
2475
|
+
},
|
|
2476
|
+
},
|
|
2334
2477
|
{
|
|
2335
2478
|
name: 'support_extend_subscription',
|
|
2336
2479
|
description: 'Extend a customer site\'s subscription expiration by N days. SuperAdmin only.',
|
|
@@ -2935,6 +3078,18 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
2935
3078
|
},
|
|
2936
3079
|
},
|
|
2937
3080
|
|
|
3081
|
+
{
|
|
3082
|
+
name: 'magento_diagnose',
|
|
3083
|
+
description: 'Run the Magento live-connection diagnostic for a site: config present, access token valid, /rest/V1 reachability, store code/base URL, and ACL scope probes. Calls messaging GET diagnostics/test-magento?siteId=<id>. Read-only.',
|
|
3084
|
+
inputSchema: {
|
|
3085
|
+
type: 'object',
|
|
3086
|
+
properties: {
|
|
3087
|
+
siteId: { type: 'number', description: 'Site to diagnose (e.g. 1032)' },
|
|
3088
|
+
},
|
|
3089
|
+
required: ['siteId'],
|
|
3090
|
+
},
|
|
3091
|
+
},
|
|
3092
|
+
|
|
2938
3093
|
// ── CallRail Attribution ──────────────────────────────────────────────────────
|
|
2939
3094
|
{
|
|
2940
3095
|
name: 'callrail_get_attributions',
|
|
@@ -2999,6 +3154,22 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
2999
3154
|
},
|
|
3000
3155
|
},
|
|
3001
3156
|
|
|
3157
|
+
{
|
|
3158
|
+
name: 'netsuite_m2m_init',
|
|
3159
|
+
description: 'Start NetSuite machine-to-machine (OAuth 2.0 Client Credentials) setup: generates the Velaro public certificate PEM to upload in NetSuite (Setup > Integration > Manage Authentication > OAuth 2.0 Client Credentials (M2M) Setup) and returns the steps. Omit configId for a new connection (company then required). Lives in velaro-messaging - requires VELARO_MESSAGING_API.',
|
|
3160
|
+
inputSchema: { type: 'object', properties: { configId: { type: 'number', description: 'Existing NetSuite config ID (rotate/switch).' }, company: { type: 'string', description: 'NetSuite account ID for a new connection (sandbox looks like 1234567_SB1).' }, displayName: { type: 'string' } } },
|
|
3161
|
+
},
|
|
3162
|
+
{
|
|
3163
|
+
name: 'netsuite_m2m_save',
|
|
3164
|
+
description: 'Save the Client ID (NetSuite integration record) and Certificate ID (NetSuite M2M mapping) for a NetSuite config after netsuite_m2m_init. Lives in velaro-messaging - requires VELARO_MESSAGING_API.',
|
|
3165
|
+
inputSchema: { type: 'object', properties: { configId: { type: 'number' }, clientId: { type: 'string' }, certificateId: { type: 'string' } }, required: ['configId', 'clientId', 'certificateId'] },
|
|
3166
|
+
},
|
|
3167
|
+
{
|
|
3168
|
+
name: 'netsuite_m2m_verify',
|
|
3169
|
+
description: 'Verify a NetSuite M2M connection with per-check results (token, REST, customer read) and specific failure reasons and fixes. Lives in velaro-messaging - requires VELARO_MESSAGING_API.',
|
|
3170
|
+
inputSchema: { type: 'object', properties: { configId: { type: 'number' } }, required: ['configId'] },
|
|
3171
|
+
},
|
|
3172
|
+
|
|
3002
3173
|
// ── Microsoft Teams Phone (config CRUD) ────────────────────────────────────
|
|
3003
3174
|
// Live in-call actions (hold/resume/end/transfer, TeamsPhoneCtiController) are
|
|
3004
3175
|
// intentionally NOT exposed here — see mcp-cli-exempt comments on that controller.
|
|
@@ -4583,6 +4754,11 @@ async function handleTool(name, args) {
|
|
|
4583
4754
|
return JSON.stringify(result, null, 2);
|
|
4584
4755
|
}
|
|
4585
4756
|
|
|
4757
|
+
case 'site_scraper_usage': {
|
|
4758
|
+
const result = await api('GET', `ScraperJobs/StaffUsage/${encodeURIComponent(args.siteId)}`);
|
|
4759
|
+
return JSON.stringify(result, null, 2);
|
|
4760
|
+
}
|
|
4761
|
+
|
|
4586
4762
|
case 'apple_health': {
|
|
4587
4763
|
const h = await messagingGet(`/SupportTools/sites/${args.siteId}/apple/health`);
|
|
4588
4764
|
return JSON.stringify(h, null, 2);
|
|
@@ -5848,6 +6024,53 @@ async function handleTool(name, args) {
|
|
|
5848
6024
|
return lines.join('\n');
|
|
5849
6025
|
}
|
|
5850
6026
|
|
|
6027
|
+
case 'index_move_preview': {
|
|
6028
|
+
const { fromIndex, toIndex, kind, skipBlocked, selector } = args;
|
|
6029
|
+
const r = await messagingPost('/IndexMove/Preview', { fromIndex, toIndex, kind, skipBlocked, selector });
|
|
6030
|
+
return JSON.stringify(r, null, 2);
|
|
6031
|
+
}
|
|
6032
|
+
case 'index_move_execute': {
|
|
6033
|
+
const { fromIndex, toIndex, kind, skipBlocked, selector } = args;
|
|
6034
|
+
const r = await messagingPost('/IndexMove/Execute', { fromIndex, toIndex, kind, skipBlocked, selector });
|
|
6035
|
+
return JSON.stringify(r, null, 2);
|
|
6036
|
+
}
|
|
6037
|
+
case 'index_split_preview': {
|
|
6038
|
+
const body = { fromIndex: args.fromIndex, toIndex: args.toIndex, docIds: args.docIds, skipBlocked: args.skipBlocked };
|
|
6039
|
+
const r = await messagingPost('/IndexMove/Split/Preview', body);
|
|
6040
|
+
return JSON.stringify(r, null, 2);
|
|
6041
|
+
}
|
|
6042
|
+
case 'index_split_execute': {
|
|
6043
|
+
const body = { fromIndex: args.fromIndex, toIndex: args.toIndex, docIds: args.docIds, skipBlocked: args.skipBlocked };
|
|
6044
|
+
const r = await messagingPost('/IndexMove/Split/Execute', body);
|
|
6045
|
+
return JSON.stringify(r, null, 2);
|
|
6046
|
+
}
|
|
6047
|
+
case 'index_move_status': {
|
|
6048
|
+
const r = await messagingGet(args.moveId
|
|
6049
|
+
? `/IndexMove/Moves/${encodeURIComponent(args.moveId)}`
|
|
6050
|
+
: '/IndexMove/Moves?take=25');
|
|
6051
|
+
return JSON.stringify(r, null, 2);
|
|
6052
|
+
}
|
|
6053
|
+
case 'index_move_control': {
|
|
6054
|
+
const verb = { resume: 'Resume', abort: 'Abort', undo: 'Undo' }[args.action];
|
|
6055
|
+
if (!verb) throw new Error('action must be resume, abort or undo');
|
|
6056
|
+
const r = await messagingPost(`/IndexMove/Moves/${encodeURIComponent(args.moveId)}/${verb}`);
|
|
6057
|
+
return JSON.stringify(r, null, 2);
|
|
6058
|
+
}
|
|
6059
|
+
case 'index_mixed_content': {
|
|
6060
|
+
const r = await messagingGet(`/IndexMove/MixedContent?index=${encodeURIComponent(args.indexName)}`);
|
|
6061
|
+
return JSON.stringify(r, null, 2);
|
|
6062
|
+
}
|
|
6063
|
+
case 'index_integrity_check': {
|
|
6064
|
+
const r = await messagingGet(`/IndexMove/Integrity?index=${encodeURIComponent(args.indexName)}`);
|
|
6065
|
+
return JSON.stringify(r, null, 2);
|
|
6066
|
+
}
|
|
6067
|
+
case 'index_integrity_repair': {
|
|
6068
|
+
const r = await messagingPost('/IndexMove/Integrity/Repair', {
|
|
6069
|
+
indexName: args.indexName,
|
|
6070
|
+
removeDuplicateRecords: !!args.removeDuplicateRecords,
|
|
6071
|
+
});
|
|
6072
|
+
return JSON.stringify(r, null, 2);
|
|
6073
|
+
}
|
|
5851
6074
|
case 'index_status': {
|
|
5852
6075
|
const sources = await api('GET', '/AzureIndexes/IntegrationSources');
|
|
5853
6076
|
if (!Array.isArray(sources) || !sources.length) return 'No indexed sources found. Run index_ingest to populate.';
|
|
@@ -6737,7 +6960,7 @@ async function handleTool(name, args) {
|
|
|
6737
6960
|
|
|
6738
6961
|
case 'support_user_lookup': {
|
|
6739
6962
|
const users = await api('GET', `/SupportTools/users/search?q=${encodeURIComponent(args.query)}`);
|
|
6740
|
-
if (!Array.isArray(users) || !users.length) return `No users found matching "${args.query}".`;
|
|
6963
|
+
if (!Array.isArray(users) || !users.length) return `No users found matching "${args.query}" in ${ENV_LABEL}. If this looks like a real customer, re-run with VELARO_ADMIN_API set to the other environment before concluding they don't exist.`;
|
|
6741
6964
|
return users.map(u => {
|
|
6742
6965
|
const roles = u.sites?.map(s =>
|
|
6743
6966
|
` site ${s.siteId} (${s.companyName}) — ${s.isAdministrator ? 'Admin' : s.isManager ? 'Manager' : 'Agent'}${s.isActive === false ? ' INACTIVE' : ''}`
|
|
@@ -6753,7 +6976,7 @@ async function handleTool(name, args) {
|
|
|
6753
6976
|
|
|
6754
6977
|
case 'support_site_lookup': {
|
|
6755
6978
|
const sites = await api('GET', `/SupportTools/sites/search?q=${encodeURIComponent(args.query)}`);
|
|
6756
|
-
if (!Array.isArray(sites) || !sites.length) return `No sites found matching "${args.query}".`;
|
|
6979
|
+
if (!Array.isArray(sites) || !sites.length) return `No sites found matching "${args.query}" in ${ENV_LABEL}. If this looks like a real customer, re-run with VELARO_ADMIN_API set to the other environment before concluding they don't exist.`;
|
|
6757
6980
|
return sites.map(s => {
|
|
6758
6981
|
const admins = s.admins?.map(a => ` ${a.userName} (${a.firstName} ${a.lastName})`).join('\n') ?? 'none';
|
|
6759
6982
|
return `[siteId=${s.siteId}] ${s.companyName}${s.isTestSite ? ' [TEST]' : ''}\n URL: ${s.companyURL ?? '—'} | Created: ${s.dateCreated ? s.dateCreated.slice(0,10) : '—'}\n Admins:\n${admins}`;
|
|
@@ -6850,6 +7073,14 @@ async function handleTool(name, args) {
|
|
|
6850
7073
|
return lines.join('\n');
|
|
6851
7074
|
}
|
|
6852
7075
|
|
|
7076
|
+
case 'tour_insights': {
|
|
7077
|
+
const days = Math.min(365, Math.max(1, args.days ?? 30));
|
|
7078
|
+
const path = args.siteId
|
|
7079
|
+
? `/SupportTools/sites/${args.siteId}/tour-events?days=${days}`
|
|
7080
|
+
: `/SupportTools/tour-insights?days=${days}`;
|
|
7081
|
+
return JSON.stringify(await api('GET', path), null, 2);
|
|
7082
|
+
}
|
|
7083
|
+
|
|
6853
7084
|
case 'support_extend_subscription': {
|
|
6854
7085
|
const res = await api('POST', `/SupportTools/sites/${args.siteId}/extend-subscription`, { days: args.days });
|
|
6855
7086
|
return res?.success
|
|
@@ -7429,6 +7660,12 @@ async function handleTool(name, args) {
|
|
|
7429
7660
|
return lines.join('\n');
|
|
7430
7661
|
}
|
|
7431
7662
|
|
|
7663
|
+
case 'magento_diagnose': {
|
|
7664
|
+
if (!args.siteId) return 'siteId is required.';
|
|
7665
|
+
const r = await api('GET', `/diagnostics/test-magento?siteId=${encodeURIComponent(args.siteId)}`);
|
|
7666
|
+
return JSON.stringify(r, null, 2);
|
|
7667
|
+
}
|
|
7668
|
+
|
|
7432
7669
|
// ── CallRail Attribution ──────────────────────────────────────────────────
|
|
7433
7670
|
case 'callrail_get_attributions': {
|
|
7434
7671
|
const limit = Math.min(100, args.limit ?? 25);
|
|
@@ -7489,6 +7726,20 @@ async function handleTool(name, args) {
|
|
|
7489
7726
|
return `CTI token generated/rotated.\nDial URL: ${urls.dialUrlTemplate}\nScreen-pop URL: ${urls.screenPopUrl}`;
|
|
7490
7727
|
}
|
|
7491
7728
|
|
|
7729
|
+
case 'netsuite_m2m_init': {
|
|
7730
|
+
const r = await messagingPost('/NetSuite/M2M/Init', { id: args.configId ?? null, company: args.company, displayName: args.displayName });
|
|
7731
|
+
return `Config ${r.id ?? args.configId ?? 'new'}. Upload this certificate in NetSuite (Setup > Integration > Manage Authentication > OAuth 2.0 Client Credentials (M2M) Setup):\n${r.certificatePem ?? '(none returned)'}\n${Array.isArray(r.steps) ? r.steps.map((x, i) => `${i + 1}. ${x}`).join('\n') : ''}`;
|
|
7732
|
+
}
|
|
7733
|
+
case 'netsuite_m2m_save': {
|
|
7734
|
+
await messagingPost('/NetSuite/M2M/Save', { id: args.configId, clientId: args.clientId, certificateId: args.certificateId });
|
|
7735
|
+
return `Saved for config ${args.configId}. Run netsuite_m2m_verify next.`;
|
|
7736
|
+
}
|
|
7737
|
+
case 'netsuite_m2m_verify': {
|
|
7738
|
+
const r = await messagingPost('/NetSuite/M2M/Verify', { id: args.configId });
|
|
7739
|
+
const checks = Array.isArray(r.checks) ? r.checks : [];
|
|
7740
|
+
return checks.map(c => `${c.ok ? 'PASS' : 'FAIL'} ${c.name}${c.ok ? '' : ` - ${c.reason || c.reasonCode || ''}${c.howToFix ? ` | Fix: ${c.howToFix}` : ''}`}`).join('\n') || 'No checks returned.';
|
|
7741
|
+
}
|
|
7742
|
+
|
|
7492
7743
|
// ── Microsoft Teams Phone (config CRUD) ───────────────────────────────────
|
|
7493
7744
|
case 'teams_phone_get_config': {
|
|
7494
7745
|
const config = await messagingGet('/TeamsPhone/Config');
|