@velaro/mcp-server 0.6.59 → 0.6.61

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/server.js +144 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velaro/mcp-server",
3
- "version": "0.6.59",
3
+ "version": "0.6.61",
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
@@ -245,19 +245,73 @@ function mcpFindUnresolved(value) {
245
245
  .map(([k]) => k);
246
246
  }
247
247
 
248
+ // The hosted gateway requires an initialized session (Mcp-Session-Id + MCP-Protocol-Version) on
249
+ // every call after "initialize". Cache one per process and re-initialize when it expires (404).
250
+ let gatewaySession = null;
251
+ async function gatewayInit() {
252
+ const res = await fetch(`${API_BASE}/mcp`, {
253
+ method: 'POST',
254
+ headers: { Authorization: authHeader(), 'Content-Type': 'application/json' },
255
+ body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'initialize', params: { protocolVersion: '2025-11-25', capabilities: {}, clientInfo: { name: 'velaro-mcp-server', version: '1' } } }),
256
+ });
257
+ if (!res.ok) {
258
+ const text = await res.text().catch(() => '');
259
+ throw new Error(`Velaro MCP gateway initialize -> ${res.status}: ${text.slice(0, 300)}`);
260
+ }
261
+ const sid = res.headers.get('mcp-session-id');
262
+ if (!sid) throw new Error('Velaro MCP gateway initialize returned no Mcp-Session-Id');
263
+ gatewaySession = { sid, version: res.headers.get('mcp-protocol-version') || '2025-11-25' };
264
+ return gatewaySession;
265
+ }
266
+
267
+ // Tools served by velaro-messaging's admin-tools gateway (McpAdminGatewayController, POST /mcp/v1/admin-tools/call,
268
+ // no session). The admin /mcp gateway only serves kb_* tools, so these used to fail there.
269
+ const MESSAGING_ADMIN_TOOLS = new Set([
270
+ 'workflow_create', 'workflow_list', 'workflow_get', 'workflow_toggle',
271
+ 'bot_create', 'bot_list', 'bot_get', 'bot_update_prompt', 'bot_set_knowledge',
272
+ 'ai_config_attach_index', 'call_pop_settings_get', 'call_pop_settings_set',
273
+ 'agent_list', 'team_list', 'conversation_list', 'conversation_get', 'conversation_search',
274
+ 'contact_search', 'contact_get', 'routing_list', 'routing_get', 'site_info',
275
+ 'ms_bookings_cancel_appointment', 'ms_bookings_check_availability_range', 'ms_bookings_reschedule_appointment',
276
+ 'outlook_calendar_cancel', 'outlook_calendar_get_available_range',
277
+ ]);
278
+
279
+ async function messagingAdminRpc(toolName, args) {
280
+ if (!MESSAGING_API) throw new Error('VELARO_MESSAGING_API is not configured.');
281
+ const res = await fetch(`${MESSAGING_API}/mcp/v1/admin-tools/call`, {
282
+ method: 'POST',
283
+ headers: { Authorization: authHeader(), 'Content-Type': 'application/json' },
284
+ body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/call', params: { name: toolName, arguments: args ?? {} } }),
285
+ signal: AbortSignal.timeout(30000),
286
+ });
287
+ const data = await res.json().catch(() => ({}));
288
+ if (!res.ok || data.error) {
289
+ throw new Error(`Velaro admin-tools ${toolName} -> ${res.status}: ${data.error?.message || JSON.stringify(data).slice(0, 300)}`);
290
+ }
291
+ const content = data.result?.content?.[0]?.text;
292
+ if (content === undefined) throw new Error('Empty response from admin-tools gateway');
293
+ return content;
294
+ }
295
+
248
296
  // Call the hosted MCP gateway (JSON-RPC) for tools that are dispatched server-side.
249
297
  async function rpc(toolName, args) {
298
+ if (MESSAGING_ADMIN_TOOLS.has(toolName)) return messagingAdminRpc(toolName, args);
250
299
  const body = {
251
300
  jsonrpc: '2.0',
252
301
  id: 1,
253
302
  method: 'tools/call',
254
303
  params: { name: toolName, arguments: args ?? {} },
255
304
  };
256
- const res = await fetch(`${API_BASE}/mcp`, {
257
- method: 'POST',
258
- headers: { Authorization: authHeader(), 'Content-Type': 'application/json' },
259
- body: JSON.stringify(body),
260
- });
305
+ const send = async () => {
306
+ const s = gatewaySession || await gatewayInit();
307
+ return fetch(`${API_BASE}/mcp`, {
308
+ method: 'POST',
309
+ headers: { Authorization: authHeader(), 'Content-Type': 'application/json', 'Mcp-Session-Id': s.sid, 'MCP-Protocol-Version': s.version },
310
+ body: JSON.stringify(body),
311
+ });
312
+ };
313
+ let res = await send();
314
+ if (res.status === 404) { gatewaySession = null; res = await send(); }
261
315
  if (!res.ok) {
262
316
  const text = await res.text().catch(() => '');
263
317
  throw new Error(`Velaro MCP gateway ${toolName} -> ${res.status}: ${text.slice(0, 300)}`);
@@ -2017,6 +2071,39 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
2017
2071
  },
2018
2072
  },
2019
2073
 
2074
+ // -- Email delivery visibility (Velaro staff only; velaro-messaging SuperAdmin) --
2075
+ {
2076
+ name: 'email_queue_status',
2077
+ description: 'Velaro staff only. Live ACS email send-queue health: queued/parked counts, oldest open item age, cooldown state, retries, 429s, sends in the last hour and a drain-time estimate. Aggregates only, no message bodies. Wraps GET SuperAdmin/email-delivery/status on velaro-messaging.',
2078
+ inputSchema: {
2079
+ type: 'object',
2080
+ properties: { siteId: { type: 'number', description: 'Optional: limit to one site ID' } },
2081
+ required: [],
2082
+ },
2083
+ },
2084
+ {
2085
+ name: 'email_delivery_stats',
2086
+ description: 'Velaro staff only. Email throughput per day and per hour, retry counts, priority/tag breakdown, attempts histogram, and ACS-side delivery outcomes (succeeded/failed/pending). Wraps GET SuperAdmin/email-delivery/stats on velaro-messaging.',
2087
+ inputSchema: {
2088
+ type: 'object',
2089
+ properties: {
2090
+ siteId: { type: 'number', description: 'Optional: limit to one site ID' },
2091
+ days: { type: 'number', description: 'Daily window, 1-30 (default 30; ACS-side capped at 14)' },
2092
+ hours: { type: 'number', description: 'Hourly window, 1-168 (default 48)' },
2093
+ },
2094
+ required: [],
2095
+ },
2096
+ },
2097
+ {
2098
+ name: 'email_queue_replay',
2099
+ description: 'Velaro staff ADMIN ONLY. Replays the queued ACS email backlog now. dryRun defaults to TRUE and only reports what a replay would send; pass dryRun=false to clear the cooldown and drain the queue immediately. Wraps POST SuperAdmin/email-delivery/replay on velaro-messaging.',
2100
+ inputSchema: {
2101
+ type: 'object',
2102
+ properties: { dryRun: { type: 'boolean', description: 'Default true. Set false to actually replay.' } },
2103
+ required: [],
2104
+ },
2105
+ },
2106
+
2020
2107
  // ── Unified Outbound Activity (Klaviyo-parity Gap 6) ──────────────────────
2021
2108
  {
2022
2109
  name: 'outbound_activity_list',
@@ -2448,6 +2535,26 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
2448
2535
  required: ['siteId'],
2449
2536
  },
2450
2537
  },
2538
+ // -- Staff: schedule adherence by site + role (SupportTools-style: explicit siteId, never auth-derived) --
2539
+ // Backend: velaro-messaging ReportsController.ScheduleAdherenceDiagnosticsAsync, gated by
2540
+ // IsSuperAdminAsync(allowSupport: true) -> 403 for non-staff. role=agent REQUIRES agentId (fails closed).
2541
+ {
2542
+ name: 'support_schedule_adherence',
2543
+ description: 'Velaro staff only. Schedule-adherence overview (totals + per-agent metrics), per-day breakdown (incl. nonAvailableMinutes), current status (intraday) and range-end-day timeline for a site, scoped as a manager (all agents) or as one agent (own row only) -- the exact scope the customer UI applies. Use for schedule-adherence QA instead of a browser pass.',
2544
+ inputSchema: {
2545
+ type: 'object',
2546
+ properties: {
2547
+ siteId: { type: 'number', description: 'Target customer site ID' },
2548
+ role: { type: 'string', enum: ['manager', 'agent'], description: 'Scope to apply. Default manager.' },
2549
+ agentId: { type: 'number', description: 'WorkspaceUser id; required when role=agent.' },
2550
+ start: { type: 'string', description: 'yyyy-MM-dd (default: 6 days ago, site tz).' },
2551
+ end: { type: 'string', description: 'yyyy-MM-dd (default: today, site tz).' },
2552
+ tz: { type: 'string', description: 'IANA time zone override.' },
2553
+ },
2554
+ required: ['siteId'],
2555
+ },
2556
+ },
2557
+
2451
2558
  {
2452
2559
  name: 'support_site_agents',
2453
2560
  description: 'List all agents/admins on a customer site with their roles, active status, and last login date. Velaro staff only.',
@@ -6638,6 +6745,30 @@ async function handleTool(name, args) {
6638
6745
  return lines.join('\n');
6639
6746
  }
6640
6747
 
6748
+ // -- Email delivery visibility (Velaro staff only) --
6749
+ case 'email_queue_status': {
6750
+ const q = args.siteId != null ? `?siteId=${encodeURIComponent(args.siteId)}` : '';
6751
+ const r = await messagingGet(`/SuperAdmin/email-delivery/status${q}`);
6752
+ return JSON.stringify(r, null, 2);
6753
+ }
6754
+
6755
+ case 'email_delivery_stats': {
6756
+ const params = new URLSearchParams();
6757
+ if (args.siteId != null) params.set('siteId', String(args.siteId));
6758
+ if (args.days != null) params.set('days', String(args.days));
6759
+ if (args.hours != null) params.set('hours', String(args.hours));
6760
+ const qs = params.toString();
6761
+ const r = await messagingGet(`/SuperAdmin/email-delivery/stats${qs ? '?' + qs : ''}`);
6762
+ return JSON.stringify(r, null, 2);
6763
+ }
6764
+
6765
+ case 'email_queue_replay': {
6766
+ // Safe by default: anything other than an explicit boolean false stays a dry run.
6767
+ const dryRun = args.dryRun !== false;
6768
+ const r = await messagingPost(`/SuperAdmin/email-delivery/replay?dryRun=${dryRun}`);
6769
+ return JSON.stringify(r, null, 2);
6770
+ }
6771
+
6641
6772
  // ── Unified Outbound Activity ──────────────────────────────────────────
6642
6773
  case 'outbound_activity_list':
6643
6774
  case 'outbound_activity_for_contact': {
@@ -7074,6 +7205,13 @@ async function handleTool(name, args) {
7074
7205
  ).join('\n');
7075
7206
  }
7076
7207
 
7208
+ case 'support_schedule_adherence': {
7209
+ if (args.role === 'agent' && !args.agentId) throw new Error('agentId is required when role=agent');
7210
+ const qs = new URLSearchParams({ detail: 'true' });
7211
+ for (const k of ['role', 'agentId', 'start', 'end', 'tz']) if (args[k] != null && args[k] !== '') qs.set(k, String(args[k]));
7212
+ return JSON.stringify(await messagingGet(`/Reports/sites/${encodeURIComponent(args.siteId)}/schedule-adherence-diagnostics?${qs}`), null, 2);
7213
+ }
7214
+
7077
7215
  case 'support_site_agents': {
7078
7216
  const agents = await api('GET', `/SupportTools/sites/${args.siteId}/agents`);
7079
7217
  if (!Array.isArray(agents) || !agents.length) return `No agents found for site ${args.siteId}.`;
@@ -8249,7 +8387,7 @@ async function handleTool(name, args) {
8249
8387
  }
8250
8388
 
8251
8389
  case 'billing_plan_status': {
8252
- const s = await api('GET', '/BillingPayment/plan-status');
8390
+ const s = await api('GET', '/BillingPayment/plan-status', undefined, MESSAGING_API);
8253
8391
  const lines = [`Site ${s.siteId} — Subscription Status`];
8254
8392
  if (s.subscription) {
8255
8393
  lines.push(`Seats: ${s.subscription.maxLicensedSeats} | Conv/mo: ${s.subscription.maxConversationsPerMonth || '∞'} | Self-serve: ${s.subscription.enableSelfServeSeats ? 'YES' : 'no'}`);