@velaro/mcp-server 0.6.51 → 0.6.53
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 +1 -1
- package/server.js +305 -29
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velaro/mcp-server",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.53",
|
|
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
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* VELARO_JWT=<token> short-lived Velaro JWT (from velaro login, for dev use)
|
|
10
10
|
*
|
|
11
11
|
* API base:
|
|
12
|
-
* VELARO_ADMIN_API=https://
|
|
12
|
+
* VELARO_ADMIN_API=https://api-admin-us-east.velaro.com (default, production)
|
|
13
13
|
* VELARO_ADMIN_API=https://velaro-admin-staging.azurewebsites.net (staging)
|
|
14
14
|
*
|
|
15
15
|
* Claude Desktop config (~/.claude/claude_desktop_config.json):
|
|
@@ -49,11 +49,45 @@ import {
|
|
|
49
49
|
ListPromptsRequestSchema,
|
|
50
50
|
} from '@modelcontextprotocol/sdk/types.js';
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
// Default was 'https://help.velaro.com'. Renamed to the recognizable admin API hostname for
|
|
53
|
+
// clarity during the 2026-09-10 Donaldson bot Calendly status investigation
|
|
54
|
+
// (docs/qa/chatbot-hallucinated-booking-donaldson-bot.md) — but this is NOT a functional fix and
|
|
55
|
+
// does NOT explain that incident: live-verified same day that 'https://help.velaro.com' and
|
|
56
|
+
// 'https://api-admin-us-east.velaro.com' are the SAME deployment (identical /api/health build SHA,
|
|
57
|
+
// and /Calendly/superadmin/status returns 401 — auth-gated, not 404 — on both hosts). Whatever
|
|
58
|
+
// caused that investigation's status/reality contradiction, it was NOT this default resolving to a
|
|
59
|
+
// broken or different host; that theory is disproven. Left as a naming clarity improvement only.
|
|
60
|
+
const API_BASE = process.env.VELARO_ADMIN_API || 'https://api-admin-us-east.velaro.com';
|
|
53
61
|
const MESSAGING_API = process.env.VELARO_MESSAGING_API || null;
|
|
54
62
|
const MCP_KEY = process.env.VELARO_MCP_KEY;
|
|
55
63
|
const JWT = process.env.VELARO_JWT;
|
|
56
64
|
|
|
65
|
+
// Known admin API hosts, for tools that let a caller explicitly target one environment
|
|
66
|
+
// regardless of what this server process's own API_BASE is configured to (see the `env` param
|
|
67
|
+
// on the calendly_* superadmin tools below). Deliberately NOT used as a default anywhere —
|
|
68
|
+
// defaulting env-selectable tools to one hardcoded environment while every other tool in this
|
|
69
|
+
// file follows the single global API_BASE would recreate the exact bug being fixed here: an
|
|
70
|
+
// operator configured for one environment would silently get a DIFFERENT environment's answer
|
|
71
|
+
// from just these tools with no indication anything was different.
|
|
72
|
+
const ADMIN_API_HOSTS = {
|
|
73
|
+
staging: 'https://velaro-admin-staging.azurewebsites.net',
|
|
74
|
+
production: 'https://api-admin-us-east.velaro.com',
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/// Resolves an optional `env` tool argument to a target admin API base. Throws on an unrecognized
|
|
78
|
+
/// value instead of silently falling through to API_BASE (the JS default-parameter behavior of
|
|
79
|
+
/// `api(..., base)` when `base` is `undefined`) — a typo like `env: 'prod'` must not silently run
|
|
80
|
+
/// a DESTRUCTIVE call (calendly_disconnect) against whatever this server defaults to while telling
|
|
81
|
+
/// the caller it targeted a different environment. `hasOwnProperty` (not truthiness) also closes
|
|
82
|
+
/// the prototype-chain edge case (`env: 'constructor'`, `env: 'toString'`, etc.).
|
|
83
|
+
function resolveAdminBase(env) {
|
|
84
|
+
if (env === undefined || env === null) return API_BASE;
|
|
85
|
+
if (!Object.prototype.hasOwnProperty.call(ADMIN_API_HOSTS, env)) {
|
|
86
|
+
throw new Error(`Unknown env "${env}" — use "staging" or "production" (or omit it to use this server's configured default).`);
|
|
87
|
+
}
|
|
88
|
+
return ADMIN_API_HOSTS[env];
|
|
89
|
+
}
|
|
90
|
+
|
|
57
91
|
if (!MCP_KEY && !JWT) {
|
|
58
92
|
process.stderr.write('ERROR: Set VELARO_MCP_KEY=vel_live_... or VELARO_JWT=<token>\n');
|
|
59
93
|
process.exit(1);
|
|
@@ -94,6 +128,17 @@ async function messagingApi(path) {
|
|
|
94
128
|
return res.json();
|
|
95
129
|
}
|
|
96
130
|
|
|
131
|
+
// FIXED 2026-09-24: velaro-messaging's /superadmin/logs/search used to return a bare JSON array
|
|
132
|
+
// for some queries and {entries, nextCursor} for others, depending on which internal code path
|
|
133
|
+
// served the request (see that repo's LogsController.cs ENVELOPE SHAPE comment). It now always
|
|
134
|
+
// returns {entries, nextCursor} -- this helper also accepts a bare array so callers degrade
|
|
135
|
+
// gracefully rather than throwing if ever pointed at an older deploy.
|
|
136
|
+
function extractLogEntries(response) {
|
|
137
|
+
if (Array.isArray(response)) return response;
|
|
138
|
+
if (response && Array.isArray(response.entries)) return response.entries;
|
|
139
|
+
return [];
|
|
140
|
+
}
|
|
141
|
+
|
|
97
142
|
async function messagingRequest(method, path, body) {
|
|
98
143
|
if (!MESSAGING_API) throw new Error(
|
|
99
144
|
'VELARO_MESSAGING_API is not configured. Add "VELARO_MESSAGING_API": "https://velaro-messaging-api-staging.azurewebsites.net" to your MCP env config.'
|
|
@@ -118,8 +163,8 @@ const messagingPut = (path, body) => messagingRequest('PUT', path, body);
|
|
|
118
163
|
const messagingPatch = (path, body) => messagingRequest('PATCH', path, body);
|
|
119
164
|
const messagingDel = (path) => messagingRequest('DELETE', path);
|
|
120
165
|
|
|
121
|
-
async function api(method, path, body) {
|
|
122
|
-
const res = await fetch(`${
|
|
166
|
+
async function api(method, path, body, base = API_BASE) {
|
|
167
|
+
const res = await fetch(`${base}${path}`, {
|
|
123
168
|
method,
|
|
124
169
|
headers: { Authorization: authHeader(), 'Content-Type': 'application/json' },
|
|
125
170
|
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
@@ -127,6 +172,19 @@ async function api(method, path, body) {
|
|
|
127
172
|
});
|
|
128
173
|
if (!res.ok) {
|
|
129
174
|
const text = await res.text().catch(() => '');
|
|
175
|
+
if ((res.status === 401 || res.status === 403) && base !== API_BASE) {
|
|
176
|
+
// Cross-environment call with the single global credential (MCP_KEY/JWT) — this session's
|
|
177
|
+
// credential is issued for one environment, so pointing a tool at the OTHER one via an
|
|
178
|
+
// explicit `env` override will look like a real auth failure rather than "wrong environment
|
|
179
|
+
// selected." Surface that explicitly instead of a bare HTTP error, so a caller doesn't
|
|
180
|
+
// misread it as "not configured" or a generic outage.
|
|
181
|
+
throw new Error(
|
|
182
|
+
`Velaro API ${method} ${path} -> ${res.status} against ${base}: this session's credentials ` +
|
|
183
|
+
`may not be valid for that environment (VELARO_MCP_KEY/VELARO_JWT are single, per-environment ` +
|
|
184
|
+
`credentials — re-run with credentials issued for that environment, or omit "env" to use ` +
|
|
185
|
+
`whichever environment this server is currently configured for). Raw response: ${text.slice(0, 300)}`
|
|
186
|
+
);
|
|
187
|
+
}
|
|
130
188
|
throw new Error(`Velaro API ${method} ${path} -> ${res.status}: ${text.slice(0, 300)}`);
|
|
131
189
|
}
|
|
132
190
|
const text = await res.text();
|
|
@@ -449,14 +507,16 @@ const TOOLS = [
|
|
|
449
507
|
},
|
|
450
508
|
{
|
|
451
509
|
name: 'kb_capture_screenshot',
|
|
452
|
-
description: 'Log into admin or
|
|
510
|
+
description: 'Log into admin, messaging, or livefluence (v10) staging, navigate to a route, optionally click something (e.g. to open an export menu or a popup window), capture a screenshot, optionally polish/annotate it, upload it, and insert it into a KB article (after a matching <h2>/<h3> heading, or appended to the end). Requires E2E_USER_EMAIL/E2E_USER_PASSWORD env vars and the optional "playwright" package installed.',
|
|
453
511
|
inputSchema: {
|
|
454
512
|
type: 'object',
|
|
455
513
|
properties: {
|
|
456
|
-
app: { type: 'string', description: 'Which app to capture from', enum: ['admin', 'messaging'] },
|
|
514
|
+
app: { type: 'string', description: 'Which app to capture from', enum: ['admin', 'messaging', 'livefluence'] },
|
|
457
515
|
route: { type: 'string', description: 'Route to navigate to, e.g. "/Settings/Routing"' },
|
|
458
516
|
articleId: { type: 'number', description: 'Target article ID' },
|
|
459
517
|
heading: { type: 'string', description: 'Insert the image immediately after the <h2>/<h3> whose text contains this (case-insensitive). Falls back to appending at the end.' },
|
|
518
|
+
click: { type: 'string', description: 'Selector to click after the route loads, before capturing (CSS, or Playwright\'s "text=..."/"role=..." engines) — e.g. "text=Export"' },
|
|
519
|
+
popup: { type: 'boolean', description: 'The click target opens a NEW browser window (window.open/target=_blank) rather than something in the current page — e.g. a per-transcript export action. Capture that window instead of the original page. Requires click.' },
|
|
460
520
|
annotations: {
|
|
461
521
|
type: 'array',
|
|
462
522
|
description: 'Optional numbered/labeled circle annotations, in raw screenshot pixel coordinates.',
|
|
@@ -1195,6 +1255,47 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
1195
1255
|
required: ['message'],
|
|
1196
1256
|
},
|
|
1197
1257
|
},
|
|
1258
|
+
{
|
|
1259
|
+
name: 'teams_push_settings_get',
|
|
1260
|
+
description: 'Get the Velaro Bot proactive-alert settings for this site: per-alert switches (new chat, missed chat, ticket created, ticket assigned, daily digest), the effective daily cap and the push count for today. Site is resolved server-side.',
|
|
1261
|
+
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
1262
|
+
},
|
|
1263
|
+
{
|
|
1264
|
+
name: 'teams_push_settings_update',
|
|
1265
|
+
description: 'Change the Velaro Bot proactive-alert settings for this site. Only fields you pass are changed; omitted fields keep their value. Set resetDailyPushCap=true to clear the per-site cap and use the platform default.',
|
|
1266
|
+
inputSchema: {
|
|
1267
|
+
type: 'object',
|
|
1268
|
+
properties: {
|
|
1269
|
+
pushNewChat: { type: 'boolean', description: 'New chat alerts' },
|
|
1270
|
+
pushMissed: { type: 'boolean', description: 'Missed chat alerts' },
|
|
1271
|
+
pushTicketCreated: { type: 'boolean', description: 'Ticket created alerts' },
|
|
1272
|
+
pushTicketAssigned: { type: 'boolean', description: 'Ticket assigned alerts' },
|
|
1273
|
+
pushDailyDigest: { type: 'boolean', description: 'Daily digest (off by default)' },
|
|
1274
|
+
dailyPushCap: { type: 'number', description: 'Max alerts per day for this site' },
|
|
1275
|
+
resetDailyPushCap: { type: 'boolean', description: 'Clear the per-site cap (use platform default)' },
|
|
1276
|
+
},
|
|
1277
|
+
required: [],
|
|
1278
|
+
},
|
|
1279
|
+
},
|
|
1280
|
+
{
|
|
1281
|
+
name: 'teams_channel_config_get',
|
|
1282
|
+
description: 'Get this site\'s Teams Channel (customer chat) settings: whether it\'s enabled, the welcome message sent to a Teams user opening a new conversation, and the team new conversations route to (null = site default team). Site is resolved server-side.',
|
|
1283
|
+
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
1284
|
+
},
|
|
1285
|
+
{
|
|
1286
|
+
name: 'teams_channel_config_update',
|
|
1287
|
+
description: 'Change this site\'s Teams Channel settings. Only fields you pass are changed. Enabling this is what makes free-text messages to the Velaro Teams bot open a real, agent-routed conversation instead of the default staff-command-only reply.',
|
|
1288
|
+
inputSchema: {
|
|
1289
|
+
type: 'object',
|
|
1290
|
+
properties: {
|
|
1291
|
+
enabled: { type: 'boolean', description: 'Turn Teams Channel (customer chat) on or off for this site' },
|
|
1292
|
+
welcomeMessage: { type: 'string', description: 'Sent to the Teams user when their message opens a new conversation. Use {{user.name}} for their display name. Max 1000 characters.' },
|
|
1293
|
+
teamId: { type: 'number', description: 'Route new conversations to this team id (must be a real, non-deleted team on this site)' },
|
|
1294
|
+
clearTeamId: { type: 'boolean', description: 'Clear the team override and fall back to the site default team' },
|
|
1295
|
+
},
|
|
1296
|
+
required: [],
|
|
1297
|
+
},
|
|
1298
|
+
},
|
|
1198
1299
|
{
|
|
1199
1300
|
name: 'teams_notify_card',
|
|
1200
1301
|
description: 'Send a rich Adaptive Card (title + fact/value fields + optional action button) to a Teams webhook — use this for structured summaries (CI results, QA report pass/fail counts, ops status) instead of teams_notify\'s plain message.',
|
|
@@ -1727,6 +1828,38 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
1727
1828
|
},
|
|
1728
1829
|
},
|
|
1729
1830
|
|
|
1831
|
+
// ── Unified Outbound Activity (Klaviyo-parity Gap 6) ──────────────────────
|
|
1832
|
+
{
|
|
1833
|
+
name: 'outbound_activity_list',
|
|
1834
|
+
description: 'Unified outbound email feed across ALL THREE send systems at once: transactional/system mail (password resets, alerts), sequence/drip campaign steps, and one-off broadcasts. Newest first. Use this instead of checking the broadcast, sequence and transactional views separately. Site scoping is enforced server-side.',
|
|
1835
|
+
inputSchema: {
|
|
1836
|
+
type: 'object',
|
|
1837
|
+
properties: {
|
|
1838
|
+
take: { type: 'number', description: 'Page size, 1-100 (default 25)' },
|
|
1839
|
+
skip: { type: 'number', description: 'Rows to skip (default 0)' },
|
|
1840
|
+
source: { type: 'string', enum: ['Transactional', 'Campaign', 'Broadcast'], description: 'Limit to one send system. Omit for all three.' },
|
|
1841
|
+
status: { type: 'string', enum: ['Sent', 'Pending', 'Failed', 'Unsubscribed', 'Skipped'], description: 'Normalized status shared across all three systems' },
|
|
1842
|
+
search: { type: 'string', description: 'Match recipient address or subject' },
|
|
1843
|
+
},
|
|
1844
|
+
required: [],
|
|
1845
|
+
},
|
|
1846
|
+
},
|
|
1847
|
+
{
|
|
1848
|
+
name: 'outbound_activity_for_contact',
|
|
1849
|
+
description: 'Every email this account has sent to ONE specific address, across transactional, sequence and broadcast sends — the "what have we sent this customer" answer that previously required checking three separate screens. The email address is REQUIRED and matched exactly; it is never optional, because an omitted address must not widen this into a site-wide listing.',
|
|
1850
|
+
inputSchema: {
|
|
1851
|
+
type: 'object',
|
|
1852
|
+
properties: {
|
|
1853
|
+
email: { type: 'string', description: 'Exact recipient email address (required)' },
|
|
1854
|
+
take: { type: 'number', description: 'Page size, 1-100 (default 25)' },
|
|
1855
|
+
skip: { type: 'number', description: 'Rows to skip (default 0)' },
|
|
1856
|
+
source: { type: 'string', enum: ['Transactional', 'Campaign', 'Broadcast'], description: 'Limit to one send system. Omit for all three.' },
|
|
1857
|
+
status: { type: 'string', enum: ['Sent', 'Pending', 'Failed', 'Unsubscribed', 'Skipped'], description: 'Normalized status shared across all three systems' },
|
|
1858
|
+
},
|
|
1859
|
+
required: ['email'],
|
|
1860
|
+
},
|
|
1861
|
+
},
|
|
1862
|
+
|
|
1730
1863
|
// ── Conversion tracking tools ─────────────────────────────────────────────
|
|
1731
1864
|
{
|
|
1732
1865
|
name: 'conversion_list_goals',
|
|
@@ -1822,13 +1955,18 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
1822
1955
|
inputSchema: {
|
|
1823
1956
|
type: 'object',
|
|
1824
1957
|
properties: {
|
|
1825
|
-
q:
|
|
1826
|
-
level:
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1958
|
+
q: { type: 'string', description: 'Text to search for in log messages (plain or regex)' },
|
|
1959
|
+
level: { type: 'string', enum: ['ERROR', 'WARN', 'INFO', 'DEBUG'], description: 'Filter by log level' },
|
|
1960
|
+
logger: { type: 'string', description: 'Logger name filter (substring match, or regex when regex=true)' },
|
|
1961
|
+
integration: { type: 'string', description: 'Integration tag filter (HubSpot, [Skill], NetSuite, etc.)' },
|
|
1962
|
+
siteId: { type: 'number', description: 'Filter by site ID' },
|
|
1963
|
+
source: { type: 'string', description: 'Source/app-tag filter, e.g. velaro-messaging-Staging' },
|
|
1964
|
+
platform: { type: 'string', enum: ['v10', 'v20', 'admin'], description: 'Filter by platform' },
|
|
1965
|
+
environment: { type: 'string', enum: ['Production', 'Staging', 'Development'] },
|
|
1966
|
+
correlationId: { type: 'string', description: 'Exact correlation/trace ID filter' },
|
|
1967
|
+
last: { type: 'string', description: 'Time window: 30m, 2h, 1d (default: 2h)' },
|
|
1968
|
+
take: { type: 'number', description: 'Max results (default: 50, max: 500)' },
|
|
1969
|
+
regex: { type: 'boolean', description: 'Treat q/logger as a regex pattern (default: false)' },
|
|
1832
1970
|
},
|
|
1833
1971
|
},
|
|
1834
1972
|
},
|
|
@@ -2112,6 +2250,15 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
2112
2250
|
required: ['siteId'],
|
|
2113
2251
|
},
|
|
2114
2252
|
},
|
|
2253
|
+
{
|
|
2254
|
+
name: 'support_site_deployments',
|
|
2255
|
+
description: 'List active widget deployment embed keys and assigned survey IDs for one site. Staff-only and read-only. Use deploymentId (the string embed key) in the widget test harness, not the numeric database id.',
|
|
2256
|
+
inputSchema: {
|
|
2257
|
+
type: 'object',
|
|
2258
|
+
properties: { siteId: { type: 'number', description: 'Target site ID' } },
|
|
2259
|
+
required: ['siteId'],
|
|
2260
|
+
},
|
|
2261
|
+
},
|
|
2115
2262
|
{
|
|
2116
2263
|
name: 'support_site_agents',
|
|
2117
2264
|
description: 'List all agents/admins on a customer site with their roles, active status, and last login date. Velaro staff only.',
|
|
@@ -2231,11 +2378,12 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
2231
2378
|
// ── Calendly integration management (superadmin) ──────────────────────────
|
|
2232
2379
|
{
|
|
2233
2380
|
name: 'calendly_get_status',
|
|
2234
|
-
description: 'Check whether Calendly is configured for a site and show the current settings. Velaro admin only.',
|
|
2381
|
+
description: 'Check whether Calendly is configured for a site and show the current settings. Velaro admin only. Queries whichever admin API environment this server is currently configured for (VELARO_ADMIN_API) unless "env" is given explicitly.',
|
|
2235
2382
|
inputSchema: {
|
|
2236
2383
|
type: 'object',
|
|
2237
2384
|
properties: {
|
|
2238
2385
|
siteId: { type: 'number', description: 'Site ID to check' },
|
|
2386
|
+
env: { type: 'string', enum: ['staging', 'production'], description: 'Explicitly target one environment instead of this server\'s configured default (VELARO_ADMIN_API). Use this when a site is known to live in one specific environment (e.g. a staging-only test site) — omitting it can silently report "not configured" for a site that is only set up in the OTHER environment from the one this server defaults to. Requires credentials valid for that environment.' },
|
|
2239
2387
|
},
|
|
2240
2388
|
required: ['siteId'],
|
|
2241
2389
|
},
|
|
@@ -2252,6 +2400,7 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
2252
2400
|
defaultEventTypeName: { type: 'string', description: 'Human-readable name for the default event type. Optional.' },
|
|
2253
2401
|
displayName: { type: 'string', description: 'Label shown in admin UI, e.g. "Acme Demo Booking". Optional.' },
|
|
2254
2402
|
useSingleUseLinks:{ type: 'boolean', description: 'true = generate single-use links per visitor (requires scheduling_links:write). false = reusable event-type URL. Default: true.' },
|
|
2403
|
+
env: { type: 'string', enum: ['staging', 'production'], description: 'Explicitly target one environment instead of this server\'s configured default (VELARO_ADMIN_API). Requires credentials valid for that environment.' },
|
|
2255
2404
|
},
|
|
2256
2405
|
required: ['siteId', 'accessToken'],
|
|
2257
2406
|
},
|
|
@@ -2263,6 +2412,7 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
2263
2412
|
type: 'object',
|
|
2264
2413
|
properties: {
|
|
2265
2414
|
siteId: { type: 'number', description: 'Site ID to disconnect' },
|
|
2415
|
+
env: { type: 'string', enum: ['staging', 'production'], description: 'Explicitly target one environment instead of this server\'s configured default (VELARO_ADMIN_API). Requires credentials valid for that environment.' },
|
|
2266
2416
|
},
|
|
2267
2417
|
required: ['siteId'],
|
|
2268
2418
|
},
|
|
@@ -3316,6 +3466,18 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
3316
3466
|
required: ['siteId', 'planKey'],
|
|
3317
3467
|
},
|
|
3318
3468
|
},
|
|
3469
|
+
{ // wires EntitlementsController.SetEntitlementLock (PUT Entitlements/admin/lock/{siteId})
|
|
3470
|
+
name: 'entitlement_set_lock',
|
|
3471
|
+
description: 'Lock a site\'s entitlement resolution to a point-in-time PlanEntitlement version (freezes what plan changes the site sees), or clear an existing lock so it resolves against the current plan version again. Pass lockedAt=null to unlock. Refuses to lock to a timestamp with no defined PlanEntitlement row for the site\'s current plan. Superadmin only.',
|
|
3472
|
+
inputSchema: {
|
|
3473
|
+
type: 'object',
|
|
3474
|
+
properties: {
|
|
3475
|
+
siteId: { type: 'number', description: 'Site ID to lock or unlock.' },
|
|
3476
|
+
lockedAt: { type: 'string', description: 'ISO 8601 timestamp to lock entitlement resolution to. Pass null (or omit) to clear an existing lock.' },
|
|
3477
|
+
},
|
|
3478
|
+
required: ['siteId'],
|
|
3479
|
+
},
|
|
3480
|
+
},
|
|
3319
3481
|
{
|
|
3320
3482
|
name: 'entitlement_seed_plans',
|
|
3321
3483
|
description: 'Seed PlanEntitlement rows from live PackageVersions in the messaging DB. Maps each plan×feature to its current value. Idempotent — inserts new rows, versions changed rows. Run after entitlement_seed and after any PackageVersion change. Superadmin only.',
|
|
@@ -4318,6 +4480,8 @@ async function handleTool(name, args) {
|
|
|
4318
4480
|
articleId: args.articleId,
|
|
4319
4481
|
heading: args.heading,
|
|
4320
4482
|
annotations: args.annotations ?? [],
|
|
4483
|
+
click: args.click,
|
|
4484
|
+
popup: args.popup ?? false,
|
|
4321
4485
|
auth: { token, apiBase: API_BASE },
|
|
4322
4486
|
});
|
|
4323
4487
|
return result.insertedAfterHeading
|
|
@@ -4899,6 +5063,26 @@ async function handleTool(name, args) {
|
|
|
4899
5063
|
return `Sent to Teams webhook "${webhook.name}".`;
|
|
4900
5064
|
}
|
|
4901
5065
|
|
|
5066
|
+
case 'teams_push_settings_get':
|
|
5067
|
+
return await api('GET', '/TeamsBot/PushSettings');
|
|
5068
|
+
|
|
5069
|
+
case 'teams_push_settings_update': {
|
|
5070
|
+
const body = {};
|
|
5071
|
+
for (const k of ['pushNewChat','pushMissed','pushTicketCreated','pushTicketAssigned','pushDailyDigest','dailyPushCap','resetDailyPushCap'])
|
|
5072
|
+
if (args[k] !== undefined) body[k] = args[k];
|
|
5073
|
+
return await api('PUT', '/TeamsBot/PushSettings', body);
|
|
5074
|
+
}
|
|
5075
|
+
|
|
5076
|
+
case 'teams_channel_config_get':
|
|
5077
|
+
return await api('GET', '/TeamsBot/ChannelConfig');
|
|
5078
|
+
|
|
5079
|
+
case 'teams_channel_config_update': {
|
|
5080
|
+
const body = {};
|
|
5081
|
+
for (const k of ['enabled','welcomeMessage','teamId','clearTeamId'])
|
|
5082
|
+
if (args[k] !== undefined) body[k] = args[k];
|
|
5083
|
+
return await api('PUT', '/TeamsBot/ChannelConfig', body);
|
|
5084
|
+
}
|
|
5085
|
+
|
|
4902
5086
|
case 'teams_notify_card': {
|
|
4903
5087
|
const webhooks = await api('GET', '/TeamsNotification');
|
|
4904
5088
|
if (!Array.isArray(webhooks) || !webhooks.length)
|
|
@@ -5301,7 +5485,8 @@ async function handleTool(name, args) {
|
|
|
5301
5485
|
const velaroLoggerP = safeJson(async () => {
|
|
5302
5486
|
if (!MESSAGING_API) return { _error: 'VELARO_MESSAGING_API not set — add it to MCP env config' };
|
|
5303
5487
|
const q = encodeURIComponent('ERROR|WF-AI-ROUTE|PERF-SLOW');
|
|
5304
|
-
|
|
5488
|
+
const res = await messagingApi(`/superadmin/logs/search?q=${q}&from=${encodeURIComponent(from1h)}&take=30®ex=true`);
|
|
5489
|
+
return extractLogEntries(res);
|
|
5305
5490
|
});
|
|
5306
5491
|
|
|
5307
5492
|
// ── Layer 4b: Loggly ERROR + SLOW + AI-routing logs (last 1h) — SECONDARY (migrating away)
|
|
@@ -6004,6 +6189,57 @@ async function handleTool(name, args) {
|
|
|
6004
6189
|
return lines.join('\n');
|
|
6005
6190
|
}
|
|
6006
6191
|
|
|
6192
|
+
// ── Unified Outbound Activity ──────────────────────────────────────────
|
|
6193
|
+
case 'outbound_activity_list':
|
|
6194
|
+
case 'outbound_activity_for_contact': {
|
|
6195
|
+
const forContact = name === 'outbound_activity_for_contact';
|
|
6196
|
+
const email = String(args.email ?? '').trim();
|
|
6197
|
+
// Required scoping identifier — never fall through to a site-wide listing.
|
|
6198
|
+
if (forContact && !email) return 'Error: email is required for outbound_activity_for_contact';
|
|
6199
|
+
|
|
6200
|
+
const params = new URLSearchParams();
|
|
6201
|
+
params.set('take', String(args.take ?? 25));
|
|
6202
|
+
params.set('skip', String(args.skip ?? 0));
|
|
6203
|
+
if (args.source) params.set('source', args.source);
|
|
6204
|
+
if (args.status) params.set('status', args.status);
|
|
6205
|
+
if (!forContact && args.search) params.set('search', args.search);
|
|
6206
|
+
|
|
6207
|
+
const path = forContact
|
|
6208
|
+
? `/Marketing/Contacts/${encodeURIComponent(email)}/OutboundActivity?${params}`
|
|
6209
|
+
: `/Marketing/OutboundActivity?${params}`;
|
|
6210
|
+
|
|
6211
|
+
const r = await api('GET', path);
|
|
6212
|
+
const items = r?.items ?? [];
|
|
6213
|
+
const srcLabel = { Transactional: 'Transactional', Campaign: 'Sequence', Broadcast: 'Broadcast' };
|
|
6214
|
+
|
|
6215
|
+
const lines = [forContact ? `=== Outbound Activity: ${email} ===` : '=== Outbound Activity ==='];
|
|
6216
|
+
|
|
6217
|
+
// A partial feed is never reported as if it were complete.
|
|
6218
|
+
if (r?.degradedSources?.length) {
|
|
6219
|
+
lines.push(` ! INCOMPLETE - could not load: ${r.degradedSources.map(s => srcLabel[s] ?? s).join(', ')}. Rows from those systems are MISSING below.`);
|
|
6220
|
+
}
|
|
6221
|
+
if (!items.length) {
|
|
6222
|
+
lines.push(' (no outbound activity matched)');
|
|
6223
|
+
return lines.join('\n');
|
|
6224
|
+
}
|
|
6225
|
+
|
|
6226
|
+
for (const i of items) {
|
|
6227
|
+
const when = i.sentAt ? new Date(i.sentAt).toISOString() : 'not sent';
|
|
6228
|
+
lines.push(` [${i.status}] ${srcLabel[i.source] ?? i.source} ${when} -> ${i.toEmail ?? '(none)'}`);
|
|
6229
|
+
lines.push(` ${i.subject ?? ''}`);
|
|
6230
|
+
const meta = [];
|
|
6231
|
+
if (i.campaignOrBroadcastName) meta.push(`campaign: ${i.campaignOrBroadcastName}`);
|
|
6232
|
+
if (i.tag) meta.push(`tag: ${i.tag}`);
|
|
6233
|
+
if (i.channel && i.channel !== 'email') meta.push(`channel: ${i.channel}`);
|
|
6234
|
+
// Transactional sends have no open/click tracking at all — report nothing rather than a
|
|
6235
|
+
// zero that would read as a measured zero.
|
|
6236
|
+
if (i.source !== 'Transactional') meta.push(`opens ${i.openCount}, clicks ${i.clickCount}`);
|
|
6237
|
+
if (meta.length) lines.push(` ${meta.join(' | ')}`);
|
|
6238
|
+
}
|
|
6239
|
+
lines.push(` ${items.length} shown of ${r?.totalCountIsApproximate ? 'about ' : ''}${r?.totalCount ?? items.length}.`);
|
|
6240
|
+
return lines.join('\n');
|
|
6241
|
+
}
|
|
6242
|
+
|
|
6007
6243
|
// ── Audit Trail ────────────────────────────────────────────────────────
|
|
6008
6244
|
case 'audit_trail_list': {
|
|
6009
6245
|
const params = new URLSearchParams();
|
|
@@ -6102,13 +6338,18 @@ async function handleTool(name, args) {
|
|
|
6102
6338
|
case 'velaro_logs_search': {
|
|
6103
6339
|
const from = new Date(Date.now() - parseDuration(args.last));
|
|
6104
6340
|
const params = new URLSearchParams({ from: from.toISOString(), take: String(args.take ?? 50) });
|
|
6105
|
-
if (args.q)
|
|
6106
|
-
if (args.level)
|
|
6107
|
-
if (args.
|
|
6108
|
-
if (args.
|
|
6109
|
-
if (args.
|
|
6110
|
-
|
|
6111
|
-
if (
|
|
6341
|
+
if (args.q) params.set('q', args.q);
|
|
6342
|
+
if (args.level) params.set('level', args.level.toUpperCase());
|
|
6343
|
+
if (args.logger) params.set('logger', args.logger);
|
|
6344
|
+
if (args.integration) params.set('integration', args.integration);
|
|
6345
|
+
if (args.siteId) params.set('siteId', String(args.siteId));
|
|
6346
|
+
if (args.source) params.set('source', args.source);
|
|
6347
|
+
if (args.platform) params.set('platform', args.platform);
|
|
6348
|
+
if (args.environment) params.set('environment', args.environment);
|
|
6349
|
+
if (args.correlationId) params.set('correlationId', args.correlationId);
|
|
6350
|
+
if (args.regex) params.set('regex', 'true');
|
|
6351
|
+
const entries = extractLogEntries(await messagingApi(`/superadmin/logs/search?${params}`));
|
|
6352
|
+
if (!entries.length) return 'No results.';
|
|
6112
6353
|
const lines = [`${entries.length} result(s) from ${from.toISOString().slice(0, 16)} UTC\n`];
|
|
6113
6354
|
for (const e of entries) {
|
|
6114
6355
|
const ts = new Date(e.timestamp ?? e.LogTimestamp).toISOString().replace('T', ' ').slice(0, 19);
|
|
@@ -6376,6 +6617,14 @@ async function handleTool(name, args) {
|
|
|
6376
6617
|
].join('\n');
|
|
6377
6618
|
}
|
|
6378
6619
|
|
|
6620
|
+
case 'support_site_deployments': {
|
|
6621
|
+
const data = await api('GET', `/SupportTools/sites/${args.siteId}/deployments`);
|
|
6622
|
+
if (!data.deployments?.length) return `No active widget deployments for site ${args.siteId}.`;
|
|
6623
|
+
return data.deployments.map(d =>
|
|
6624
|
+
`[id=${d.id}] ${d.displayName || '(unnamed)'} | embed key: ${d.deploymentId} | team: ${d.teamId} | surveys: pre-chat ${d.preChatSurveyId ?? '—'}, post-chat ${d.postChatSurveyId ?? '—'}, unavailable ${d.unavailableSurveyId ?? '—'}`
|
|
6625
|
+
).join('\n');
|
|
6626
|
+
}
|
|
6627
|
+
|
|
6379
6628
|
case 'support_site_agents': {
|
|
6380
6629
|
const agents = await api('GET', `/SupportTools/sites/${args.siteId}/agents`);
|
|
6381
6630
|
if (!Array.isArray(agents) || !agents.length) return `No agents found for site ${args.siteId}.`;
|
|
@@ -6468,10 +6717,26 @@ async function handleTool(name, args) {
|
|
|
6468
6717
|
}
|
|
6469
6718
|
|
|
6470
6719
|
case 'calendly_get_status': {
|
|
6471
|
-
|
|
6472
|
-
|
|
6720
|
+
// `env`, when given, targets one specific admin API environment instead of whatever
|
|
6721
|
+
// VELARO_ADMIN_API this server process happens to be configured with — see ADMIN_API_HOSTS
|
|
6722
|
+
// and resolveAdminBase() above. No default override here: an unspecified `env` means "use
|
|
6723
|
+
// this server's configured environment," same as every other tool in this file.
|
|
6724
|
+
const base = resolveAdminBase(args.env);
|
|
6725
|
+
const data = await api('GET', `/Calendly/superadmin/status?siteId=${args.siteId}`, undefined, base);
|
|
6726
|
+
const envSuffix = ` (${args.env ?? 'this server\'s configured env'}: ${base})`;
|
|
6727
|
+
if (!data.configured) {
|
|
6728
|
+
// exists === true means a row IS present but Enabled is false — a real, different state
|
|
6729
|
+
// from "never configured," which used to render identically (see CalendlyController.cs
|
|
6730
|
+
// SuperAdminStatus) and is one plausible (unconfirmed) explanation for the 2026-09-08
|
|
6731
|
+
// Donaldson bot Calendly status contradiction. exists === undefined means this admin API
|
|
6732
|
+
// build predates the field (staging/production deploy independently) — don't assert a
|
|
6733
|
+
// specific DB state the response didn't actually report.
|
|
6734
|
+
if (data.exists === true) return `Site ${args.siteId}${envSuffix}: Calendly configured but DISABLED (a config row exists with Enabled=false).`;
|
|
6735
|
+
if (data.exists === undefined) return `Site ${args.siteId}${envSuffix}: Calendly not enabled. (This API build does not report "exists" yet, so a disabled row can't be distinguished from no row at all — re-check once this endpoint is deployed there.)`;
|
|
6736
|
+
return `Site ${args.siteId}${envSuffix}: Calendly NOT configured (no config row found in this environment).`;
|
|
6737
|
+
}
|
|
6473
6738
|
return [
|
|
6474
|
-
`Site ${args.siteId}: Calendly configured ✓`,
|
|
6739
|
+
`Site ${args.siteId}${envSuffix}: Calendly configured ✓`,
|
|
6475
6740
|
` Account: ${data.userName}`,
|
|
6476
6741
|
` Default type: ${data.defaultEventTypeName || '(none)'}`,
|
|
6477
6742
|
` Single-use: ${data.useSingleUseLinks ? 'yes' : 'no'}`,
|
|
@@ -6480,20 +6745,22 @@ async function handleTool(name, args) {
|
|
|
6480
6745
|
}
|
|
6481
6746
|
|
|
6482
6747
|
case 'calendly_configure': {
|
|
6748
|
+
const base = resolveAdminBase(args.env);
|
|
6483
6749
|
const res = await api('POST', `/Calendly/superadmin/config?siteId=${args.siteId}`, {
|
|
6484
6750
|
accessToken: args.accessToken,
|
|
6485
6751
|
displayName: args.displayName ?? '',
|
|
6486
6752
|
defaultEventTypeUri: args.defaultEventTypeUri ?? '',
|
|
6487
6753
|
defaultEventTypeName: args.defaultEventTypeName ?? '',
|
|
6488
6754
|
useSingleUseLinks: args.useSingleUseLinks ?? true,
|
|
6489
|
-
});
|
|
6755
|
+
}, base);
|
|
6490
6756
|
if (!res?.success) throw new Error(res?.error || 'Configure failed');
|
|
6491
|
-
return `✓ Calendly configured for site ${args.siteId}. Account: ${res.userName}`;
|
|
6757
|
+
return `✓ Calendly configured for site ${args.siteId} (${args.env ?? 'this server\'s configured env'}: ${base}). Account: ${res.userName}`;
|
|
6492
6758
|
}
|
|
6493
6759
|
|
|
6494
6760
|
case 'calendly_disconnect': {
|
|
6495
|
-
|
|
6496
|
-
|
|
6761
|
+
const base = resolveAdminBase(args.env);
|
|
6762
|
+
await api('DELETE', `/Calendly/superadmin/config?siteId=${args.siteId}`, undefined, base);
|
|
6763
|
+
return `✓ Calendly disconnected from site ${args.siteId} (${args.env ?? 'this server\'s configured env'}: ${base}).`;
|
|
6497
6764
|
}
|
|
6498
6765
|
|
|
6499
6766
|
// ── Acuity Scheduling ─────────────────────────────────────────────────────
|
|
@@ -7641,6 +7908,15 @@ async function handleTool(name, args) {
|
|
|
7641
7908
|
return `✅ Site ${args.siteId} comped on plan ${args.planKey}${expiry}. Use entitlement_set_override to customize features.`;
|
|
7642
7909
|
}
|
|
7643
7910
|
|
|
7911
|
+
case 'entitlement_set_lock': {
|
|
7912
|
+
if (!args.siteId) throw new Error('siteId is required');
|
|
7913
|
+
const lockedAt = args.lockedAt ?? null;
|
|
7914
|
+
const result = await api('PUT', `/Entitlements/admin/lock/${args.siteId}`, { lockedAt });
|
|
7915
|
+
return lockedAt
|
|
7916
|
+
? `✅ Site ${args.siteId} entitlements locked to ${result.entitlementLockedAt} (plan ${result.planKey}).`
|
|
7917
|
+
: `✅ Site ${args.siteId} entitlement lock cleared. Resolves against the current plan version again.`;
|
|
7918
|
+
}
|
|
7919
|
+
|
|
7644
7920
|
case 'entitlement_seed_plans': {
|
|
7645
7921
|
const result = await api('POST', '/Entitlements/admin/seed-plans');
|
|
7646
7922
|
if (result.error) return `❌ ${result.error}`;
|