@velaro/mcp-server 0.6.58 → 0.6.60
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 +102 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velaro/mcp-server",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.60",
|
|
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
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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)}`);
|
|
@@ -581,6 +635,12 @@ const TOOLS = [
|
|
|
581
635
|
},
|
|
582
636
|
},
|
|
583
637
|
|
|
638
|
+
{
|
|
639
|
+
name: 'scraper_verify_token',
|
|
640
|
+
description: "Show the calling site's web-scraper verification header (name + value), the scraper IP, and ready-to-send instructions for allowlisting Velaro's crawler with the customer's web host/WAF. Wraps GET ScraperJobs/VerifyToken. Site comes from auth context, never a parameter.",
|
|
641
|
+
inputSchema: { type: 'object', properties: {} },
|
|
642
|
+
},
|
|
643
|
+
|
|
584
644
|
// ── Startup migration diagnostics (AdminStartupDiagnostics canary table) ──
|
|
585
645
|
{
|
|
586
646
|
name: 'admin_startup_diagnostics',
|
|
@@ -2962,6 +3022,21 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
|
|
|
2962
3022
|
required: [],
|
|
2963
3023
|
},
|
|
2964
3024
|
},
|
|
3025
|
+
{
|
|
3026
|
+
name: 'ai_spend_report',
|
|
3027
|
+
description: 'SuperAdmin AI spend report: logged AI cost for prod and/or staging over a window, by site, model, key source (platform/copilot/byok) and site x feature, with cache-read ratio, QA share, top movers vs the previous period and anomalies. Also records/lists provider reconciliation (console total vs logged). Breaker trips are not in AiCostLogs so are reported as not available.',
|
|
3028
|
+
inputSchema: {
|
|
3029
|
+
type: 'object',
|
|
3030
|
+
properties: {
|
|
3031
|
+
env: { type: 'string', enum: ['both', 'prod', 'staging'], description: 'Environment. Default both.' },
|
|
3032
|
+
days: { type: 'number', description: 'Trailing window in days. Default 7, max 92.' },
|
|
3033
|
+
reconcile_entered_usd: { type: 'number', description: 'If set, records a reconciliation for the window with this provider console total in USD instead of only reading the report.' },
|
|
3034
|
+
provider: { type: 'string', description: 'Provider for reconciliation. Default Anthropic.' },
|
|
3035
|
+
key_bucket: { type: 'string', description: 'Per-key bucket for reconciliation: platform, copilot or byok. Omit for provider total.' },
|
|
3036
|
+
},
|
|
3037
|
+
required: [],
|
|
3038
|
+
},
|
|
3039
|
+
},
|
|
2965
3040
|
{
|
|
2966
3041
|
name: 'signalr_diagnostics',
|
|
2967
3042
|
description: 'Check health, connection counts, hub stats, and recent errors for Velaro\'s Azure SignalR Service instances. Covers both the infra-prod instance (velaro-eastus-prd-signalr, Premium P1) and the legacy prod instance (velaro-prod-us). Use this to diagnose "hub down", connection storms, capacity issues, or capacity saturation alerts.',
|
|
@@ -4756,6 +4831,11 @@ async function handleTool(name, args) {
|
|
|
4756
4831
|
return JSON.stringify(result, null, 2);
|
|
4757
4832
|
}
|
|
4758
4833
|
|
|
4834
|
+
case 'scraper_verify_token': {
|
|
4835
|
+
const result = await api('GET', 'ScraperJobs/VerifyToken');
|
|
4836
|
+
return JSON.stringify(result, null, 2);
|
|
4837
|
+
}
|
|
4838
|
+
|
|
4759
4839
|
case 'site_scraper_usage': {
|
|
4760
4840
|
const result = await api('GET', `ScraperJobs/StaffUsage/${encodeURIComponent(args.siteId)}`);
|
|
4761
4841
|
return JSON.stringify(result, null, 2);
|
|
@@ -7352,6 +7432,22 @@ async function handleTool(name, args) {
|
|
|
7352
7432
|
case 'velaro_diagnostic_start':
|
|
7353
7433
|
return DIAGNOSTIC_RUNBOOK;
|
|
7354
7434
|
|
|
7435
|
+
case 'ai_spend_report': {
|
|
7436
|
+
const days = Math.min(Math.max(Number(args.days) || 7, 1), 92);
|
|
7437
|
+
const to = new Date();
|
|
7438
|
+
const from = new Date(to.getTime() - days * 86400000);
|
|
7439
|
+
const env = args.env || 'both';
|
|
7440
|
+
if (args.reconcile_entered_usd !== undefined && args.reconcile_entered_usd !== null) {
|
|
7441
|
+
const row = await api('POST', '/SuperAdmin/AiSpend/Reconciliations', {
|
|
7442
|
+
periodStartUtc: from.toISOString(), periodEndUtc: to.toISOString(), environment: env,
|
|
7443
|
+
provider: args.provider || 'Anthropic', keyLabel: args.key_bucket || null, enteredUsd: Number(args.reconcile_entered_usd),
|
|
7444
|
+
});
|
|
7445
|
+
return JSON.stringify(row, null, 2);
|
|
7446
|
+
}
|
|
7447
|
+
const data = await api('GET', `/SuperAdmin/AiSpend/Report?env=${env}&from=${encodeURIComponent(from.toISOString())}&to=${encodeURIComponent(to.toISOString())}`);
|
|
7448
|
+
return JSON.stringify(data, null, 2);
|
|
7449
|
+
}
|
|
7450
|
+
|
|
7355
7451
|
case 'signalr_diagnostics': {
|
|
7356
7452
|
// Parse a SignalR connection string into { endpoint, accessKey }
|
|
7357
7453
|
function parseSignalRConn(conn) {
|
|
@@ -8207,7 +8303,7 @@ async function handleTool(name, args) {
|
|
|
8207
8303
|
}
|
|
8208
8304
|
|
|
8209
8305
|
case 'billing_plan_status': {
|
|
8210
|
-
const s = await api('GET', '/BillingPayment/plan-status');
|
|
8306
|
+
const s = await api('GET', '/BillingPayment/plan-status', undefined, MESSAGING_API);
|
|
8211
8307
|
const lines = [`Site ${s.siteId} — Subscription Status`];
|
|
8212
8308
|
if (s.subscription) {
|
|
8213
8309
|
lines.push(`Seats: ${s.subscription.maxLicensedSeats} | Conv/mo: ${s.subscription.maxConversationsPerMonth || '∞'} | Self-serve: ${s.subscription.enableSelfServeSeats ? 'YES' : 'no'}`);
|