@velaro/mcp-server 2.0.3 → 2.0.4
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/parity/agent-crm-identity.customer.js +87 -0
- package/server.js +34 -0
- package/tool-annotations.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velaro/mcp-server",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.4",
|
|
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": {
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// MCP parity for `velaro agent crm-identity` (cli/lib/commands/agent.js). Backend: velaro-messaging
|
|
2
|
+
// api/v1/AgentCrmIdentities (administrator key). Site from the credential, never an argument.
|
|
3
|
+
// A CRM login (NetSuite / Dynamics 365 / HubSpot user) linked to a Velaro agent makes the CRM call pop show that
|
|
4
|
+
// agent's calls and makes calls logged to the CRM owned by that CRM user.
|
|
5
|
+
|
|
6
|
+
const PROVIDERS = ['netsuite', 'dynamics', 'hubspot', 'salesforce', 'zoho', 'other'];
|
|
7
|
+
|
|
8
|
+
const agentProps = {
|
|
9
|
+
workspaceUserId: { type: 'number', description: 'Velaro agent id (WorkspaceUser id). Use this or agentEmail.' },
|
|
10
|
+
agentEmail: { type: 'string', description: 'Velaro agent email. Use this or workspaceUserId.' },
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const tools = [
|
|
14
|
+
{
|
|
15
|
+
name: 'agent_crm_identity_list',
|
|
16
|
+
description: 'List the CRM logins linked to your agents (all agents, or one agent). Each row: CRM, login email, CRM user id, agent, and whether it was linked manually or automatically.',
|
|
17
|
+
inputSchema: { type: 'object', properties: { workspaceUserId: agentProps.workspaceUserId } },
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: 'agent_crm_identity_add',
|
|
21
|
+
description: 'Link a CRM login to a Velaro agent so the CRM call pop shows that agent\'s calls and calls logged to the CRM are owned by that CRM user. Give the login email, the CRM user id (NetSuite employee id, Dynamics systemuserid, HubSpot owner id), or both.',
|
|
22
|
+
inputSchema: {
|
|
23
|
+
type: 'object',
|
|
24
|
+
properties: {
|
|
25
|
+
...agentProps,
|
|
26
|
+
provider: { type: 'string', enum: PROVIDERS, description: 'Which CRM the login belongs to.' },
|
|
27
|
+
loginEmail: { type: 'string', description: 'The CRM login email.' },
|
|
28
|
+
crmUserId: { type: 'string', description: 'The CRM user id.' },
|
|
29
|
+
},
|
|
30
|
+
required: ['provider'],
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: 'agent_crm_identity_remove',
|
|
35
|
+
description: 'Unlink a CRM login from its agent (id from agent_crm_identity_list).',
|
|
36
|
+
inputSchema: { type: 'object', properties: { id: { type: 'number', description: 'CRM login link id.' } }, required: ['id'] },
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'agent_crm_identity_list_unlinked',
|
|
40
|
+
description: 'List CRM logins that opened the Velaro call pop inside your CRM but are linked to no agent (they never see a call until linked).',
|
|
41
|
+
inputSchema: { type: 'object', properties: {} },
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: 'agent_crm_identity_link',
|
|
45
|
+
description: 'Link one unlinked CRM login (id from agent_crm_identity_list_unlinked) to a Velaro agent.',
|
|
46
|
+
inputSchema: {
|
|
47
|
+
type: 'object',
|
|
48
|
+
properties: { id: { type: 'number', description: 'Unlinked CRM login id.' }, ...agentProps },
|
|
49
|
+
required: ['id'],
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
function agentBody(a) {
|
|
55
|
+
if (!a.workspaceUserId && !a.agentEmail) throw new Error('Pass workspaceUserId or agentEmail.');
|
|
56
|
+
return { workspaceUserId: a.workspaceUserId ?? null, agentEmail: a.agentEmail ?? null };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const handlers = {
|
|
60
|
+
agent_crm_identity_list: async (a, ctx) => {
|
|
61
|
+
const q = a.workspaceUserId ? `?workspaceUserId=${encodeURIComponent(a.workspaceUserId)}` : '';
|
|
62
|
+
const r = await ctx.messagingGet(`/api/v1/AgentCrmIdentities${q}`);
|
|
63
|
+
return { identities: r?.data?.identities ?? [] };
|
|
64
|
+
},
|
|
65
|
+
agent_crm_identity_add: async (a, ctx) => {
|
|
66
|
+
if (!a.loginEmail && !a.crmUserId) throw new Error('Pass loginEmail, crmUserId, or both.');
|
|
67
|
+
const r = await ctx.messagingPost('/api/v1/AgentCrmIdentities', {
|
|
68
|
+
...agentBody(a),
|
|
69
|
+
provider: a.provider,
|
|
70
|
+
loginEmail: a.loginEmail ?? null,
|
|
71
|
+
crmUserId: a.crmUserId ?? null,
|
|
72
|
+
});
|
|
73
|
+
return r?.data ?? r;
|
|
74
|
+
},
|
|
75
|
+
agent_crm_identity_remove: async (a, ctx) => {
|
|
76
|
+
await ctx.messagingDel(`/api/v1/AgentCrmIdentities/${encodeURIComponent(a.id)}`);
|
|
77
|
+
return { removed: true, id: a.id };
|
|
78
|
+
},
|
|
79
|
+
agent_crm_identity_list_unlinked: async (_a, ctx) => {
|
|
80
|
+
const r = await ctx.messagingGet('/api/v1/AgentCrmIdentities/unlinked-logins');
|
|
81
|
+
return { logins: r?.data?.logins ?? [] };
|
|
82
|
+
},
|
|
83
|
+
agent_crm_identity_link: async (a, ctx) => {
|
|
84
|
+
const r = await ctx.messagingPost(`/api/v1/AgentCrmIdentities/unlinked-logins/${encodeURIComponent(a.id)}/link`, agentBody(a));
|
|
85
|
+
return r?.data ?? r;
|
|
86
|
+
},
|
|
87
|
+
};
|
package/server.js
CHANGED
|
@@ -967,6 +967,7 @@ Key node shapes:
|
|
|
967
967
|
• transferToAI: { type:"action", actionType:"transferToAI", aiConfigId:N }
|
|
968
968
|
• selectPreChat: { type:"action", actionType:"selectPreChat", surveyId:N, surveyCompact:true|false|null, child:N }
|
|
969
969
|
surveyCompact (optional tri-state): null/omitted = follow the survey's own compactMode, true = compact, false = regular.
|
|
970
|
+
• redirectToUrl: { type:"action", actionType:"redirectToUrl", redirectUrl:"https://host/path" | "//host/path", child:N } (chat_button_clicked only: browser goes to the page instead of opening chat; http/https only; use on an Unavailable branch)
|
|
970
971
|
• sendSurvey: { type:"action", actionType:"sendSurvey", sendSurveyId:N, surveyDeliveryMode:"conversational"|"form", closeAfterSurvey:true|false, child:N }
|
|
971
972
|
closeAfterSurvey (optional, default false): when true the chat closes once the visitor completes the survey (use for Unavailable/after-hours surveys that replace a live chat). Omit to keep the old behaviour.
|
|
972
973
|
AI follow-up suggestions ("AI mode"): add suggestFollowUps:true|false (omit = use the account default, set via ai_followups_site_set), suggestFollowUpCount:2-4, suggestFollowUpInstruction:"<=300 chars". After each web-chat bot reply the bot offers short tappable follow-up replies; typing stays available.
|
|
@@ -2407,6 +2408,17 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
2407
2408
|
required: [],
|
|
2408
2409
|
},
|
|
2409
2410
|
},
|
|
2411
|
+
{
|
|
2412
|
+
name: 'outbound_activity_volume',
|
|
2413
|
+
description: 'Daily outbound email volume for this account over the last N days (1-90, default 30), split sent vs failed, for campaign (sequence) and broadcast sends. Transactional mail is reported as NOT COUNTED until messaging exposes a daily aggregate. Site scoping is enforced server-side.',
|
|
2414
|
+
inputSchema: {
|
|
2415
|
+
type: 'object',
|
|
2416
|
+
properties: {
|
|
2417
|
+
days: { type: 'number', description: 'Window in days, 1-90 (default 30)' },
|
|
2418
|
+
},
|
|
2419
|
+
required: [],
|
|
2420
|
+
},
|
|
2421
|
+
},
|
|
2410
2422
|
{
|
|
2411
2423
|
name: 'outbound_activity_for_contact',
|
|
2412
2424
|
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.',
|
|
@@ -7070,6 +7082,28 @@ reply: ${r?.reply}`;
|
|
|
7070
7082
|
return JSON.stringify(r, null, 2);
|
|
7071
7083
|
}
|
|
7072
7084
|
|
|
7085
|
+
// ── Unified Outbound Activity: daily volume ──────────────────────────
|
|
7086
|
+
case 'outbound_activity_volume': {
|
|
7087
|
+
const days = Math.min(90, Math.max(1, Number(args.days ?? 30)));
|
|
7088
|
+
const v = await api('GET', `/Marketing/OutboundActivity/Volume?days=${days}`);
|
|
7089
|
+
const byDay = new Map();
|
|
7090
|
+
for (const b of v?.buckets ?? []) {
|
|
7091
|
+
const d = byDay.get(b.date) ?? { sent: 0, failed: 0 };
|
|
7092
|
+
if (b.status === 'Failed') d.failed += b.count;
|
|
7093
|
+
else if (b.status === 'Sent') d.sent += b.count;
|
|
7094
|
+
byDay.set(b.date, d);
|
|
7095
|
+
}
|
|
7096
|
+
const lines = [`=== Outbound volume, last ${days} days (campaign + broadcast) ===`];
|
|
7097
|
+
if (v?.degradedSources?.length) {
|
|
7098
|
+
lines.push(` ! NOT COUNTED: ${v.degradedSources.join(', ')}`);
|
|
7099
|
+
}
|
|
7100
|
+
for (const [date, d] of [...byDay.entries()].sort(([a], [b]) => a.localeCompare(b))) {
|
|
7101
|
+
lines.push(` ${date} sent ${d.sent} failed ${d.failed}`);
|
|
7102
|
+
}
|
|
7103
|
+
if (!byDay.size) lines.push(' No campaign or broadcast sends in this window.');
|
|
7104
|
+
return lines.join('\n');
|
|
7105
|
+
}
|
|
7106
|
+
|
|
7073
7107
|
// ── Unified Outbound Activity ──────────────────────────────────────────
|
|
7074
7108
|
case 'outbound_activity_list':
|
|
7075
7109
|
case 'outbound_activity_for_contact': {
|
package/tool-annotations.js
CHANGED
|
@@ -57,7 +57,7 @@ const READ_ONLY_EXPLICIT = new Set([
|
|
|
57
57
|
// Write tools that are additive even though their name carries a destructive-looking token.
|
|
58
58
|
const NON_DESTRUCTIVE_EXPLICIT = new Set([
|
|
59
59
|
'safety_settings_test', 'safety_need_help', 'canned_learning_record_edit', 'netsuite_suiteapp_download',
|
|
60
|
-
'chat_topics_reclassify',
|
|
60
|
+
'chat_topics_reclassify', 'agent_crm_identity_link',
|
|
61
61
|
]);
|
|
62
62
|
|
|
63
63
|
const DESTRUCTIVE_TOKENS = new Set([
|