@velaro/mcp-server 0.6.92 → 0.6.93

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 +2 -2
  2. package/server.js +148 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velaro/mcp-server",
3
- "version": "0.6.92",
3
+ "version": "0.6.93",
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": {
@@ -10,7 +10,7 @@
10
10
  "node": ">=18.0.0"
11
11
  },
12
12
  "dependencies": {
13
- "@modelcontextprotocol/sdk": "^1.30.0",
13
+ "@modelcontextprotocol/sdk": "^1.32.1",
14
14
  "qs": "^6.16.0"
15
15
  },
16
16
  "overrides": {
package/server.js CHANGED
@@ -1048,6 +1048,7 @@ Key node shapes:
1048
1048
  propName:"myAnswerVar" } // variable that stores the result as JSON array string
1049
1049
  • condition: { type:"action", actionType:"condition", conditionOptions:[...] }
1050
1050
  • transferToAI: { type:"action", actionType:"transferToAI", aiConfigId:N }
1051
+ 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.
1051
1052
 
1052
1053
  For multiSelect dynamic options: the variable named in dynamicOptionsVariable must contain a JSON array like [{"label":"Red","value":"red"},{"label":"Blue","value":"blue"}]. Populate it via an HTTP Request node before the ask node.`,
1053
1054
  inputSchema: {
@@ -1061,6 +1062,20 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
1061
1062
  },
1062
1063
  },
1063
1064
 
1065
+ {
1066
+ name: 'ai_followups_site_get',
1067
+ description: "Read this account's default for AI follow-up suggestions (the AI Chatbot node's \"AI mode\": after each web-chat bot reply the bot offers 2-4 short tappable follow-up replies). A node's own suggestFollowUps setting wins over this default; platform default is off.",
1068
+ inputSchema: { type: 'object', properties: {}, required: [] },
1069
+ },
1070
+ {
1071
+ name: 'ai_followups_site_set',
1072
+ description: "Set this account's default for AI follow-up suggestions. enabled=true/false, or omit/null to clear (platform default: off). Per-node settings (workflow_update_node: suggestFollowUps) override this. Returns the value read back from the server.",
1073
+ inputSchema: {
1074
+ type: 'object',
1075
+ properties: { enabled: { type: ['boolean', 'null'], description: 'true = on, false = off, null/omitted = clear' } },
1076
+ required: [],
1077
+ },
1078
+ },
1064
1079
  // ── Surveys ──────────────────────────────────────────────────────────────
1065
1080
  {
1066
1081
  name: 'survey_list',
@@ -1594,10 +1609,28 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
1594
1609
  },
1595
1610
  buttonLabel: { type: 'string', description: 'Optional action button label' },
1596
1611
  buttonUrl: { type: 'string', description: 'Optional action button URL (required if buttonLabel is set)' },
1612
+ mentions: { type: 'array', description: 'People to @mention (real Teams notification). Velaro staff only. Uses the ops Teams sender instead of a webhook.', items: { type: 'object', properties: { user: { type: 'string', description: 'Entra object id or UPN, e.g. andrea@velaro.com' }, name: { type: 'string', description: 'Display name shown in the @mention' } }, required: ['user'] } },
1613
+ destination: { type: 'string', description: 'Named channel in the DevOps team instead of General: prs, workflows, deploys, alerts, qa, general. Velaro staff only; uses the ops Teams sender instead of a webhook.' },
1597
1614
  },
1598
1615
  required: ['title'],
1599
1616
  },
1600
1617
  },
1618
+ {
1619
+ name: 'teams_send_direct',
1620
+ description: 'Send a 1:1 Teams message (Adaptive Card) directly to any user in the Velaro tenant by UPN/email or Entra object id. Velaro staff only. Graph chat first, Velaro Bot proactive fallback.',
1621
+ inputSchema: {
1622
+ type: 'object',
1623
+ properties: {
1624
+ user: { type: 'string', description: 'UPN/email (alex@velaro.com) or Entra object id' },
1625
+ title: { type: 'string', description: 'Card title' },
1626
+ message: { type: 'string', description: 'Message text' },
1627
+ fields: { type: 'array', description: 'Optional fact rows', items: { type: 'object', properties: { label: { type: 'string' }, value: { type: 'string' } }, required: ['label', 'value'] } },
1628
+ buttonLabel: { type: 'string', description: 'Optional button label' },
1629
+ buttonUrl: { type: 'string', description: 'Optional button URL' },
1630
+ },
1631
+ required: ['user', 'title'],
1632
+ },
1633
+ },
1601
1634
 
1602
1635
  // ── Slack ─────────────────────────────────────────────────────────────────
1603
1636
  {
@@ -3057,7 +3090,7 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
3057
3090
  inputSchema: {
3058
3091
  type: 'object',
3059
3092
  properties: {
3060
- report: { type: 'string', enum: ['program_breakdown', 'agent_scorecard', 'qa_coverage', 'coaching_queue', 'sentiment_breakdown', 'topic_radar', 'weekly_brief', 'weekly_brief_send_test'] },
3093
+ report: { type: 'string', enum: ['program_breakdown', 'agent_scorecard', 'qa_coverage', 'coaching_queue', 'sentiment_breakdown', 'topic_radar', 'weekly_brief', 'weekly_brief_send_test', 'repeat_contact', 'demand_coverage', 'bot_containment', 'effort_index', 'promise_ledger', 'promise_dismiss'] },
3061
3094
  from: { type: 'string', description: 'yyyy-MM-dd start (default: 30 days ago).' },
3062
3095
  to: { type: 'string', description: 'yyyy-MM-dd end, inclusive (default: today).' },
3063
3096
  groupBy: { type: 'string', description: 'program_breakdown: website|team|platform|device|entrypage. sentiment_breakdown: website|team|agent|theme.' },
@@ -3067,6 +3100,14 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
3067
3100
  perAgent: { type: 'number', description: 'coaching_queue: chats listed per agent (default 5).' },
3068
3101
  limit: { type: 'number', description: 'topic_radar: max topics (default 25).' },
3069
3102
  days: { type: 'number', description: 'weekly_brief: days to cover (default 7).' },
3103
+ windowDays: { type: 'number', description: 'repeat_contact: days a contact has to come back to count as a repeat (1-30, default 7).' },
3104
+ weeks: { type: 'number', description: 'demand_coverage: whole local weeks of history (1-12, default 6). from/to are IGNORED by demand_coverage.' },
3105
+ handleMinutes: { type: 'number', description: 'demand_coverage: assumed minutes per chat (1-120, default 10).' },
3106
+ maxConcurrent: { type: 'number', description: 'demand_coverage: assumed chats an agent handles at once (1-10, default 3).' },
3107
+ timeZone: { type: 'string', description: 'demand_coverage: IANA time zone (default America/Los_Angeles).' },
3108
+ lowScore: { type: 'number', description: 'bot_containment: QA score below which a handed-off chat counts as regret (1-100, default 60).' },
3109
+ includeDemo: { type: 'boolean', description: 'true = show only the seeded sample (demo) data. Default false.' },
3110
+ promiseId: { type: 'string', description: 'promise_dismiss: the promise id to dismiss (managers only).' },
3070
3111
  },
3071
3112
  required: ['report'],
3072
3113
  },
@@ -3750,6 +3791,15 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
3750
3791
  required: [],
3751
3792
  },
3752
3793
  },
3794
+ {
3795
+ name: 'ai_federation',
3796
+ description: 'SuperAdmin: Anthropic Workload Identity Federation for the AI gateway. action=status shows enabled, circuit breaker state, last success/failure reason, federated vs API-key fallback counts and token expiry. action=test does a real token exchange plus a 1-token Haiku message and reports which path (federated, apikey, apikey-fallback) served it. action=reset_breaker closes the breaker so the next call retries federation.',
3797
+ inputSchema: {
3798
+ type: 'object',
3799
+ properties: { action: { type: 'string', enum: ['status', 'test', 'reset_breaker'], description: 'Default status.' } },
3800
+ required: [],
3801
+ },
3802
+ },
3753
3803
  {
3754
3804
  name: 'status_incident_list',
3755
3805
  description: 'Velaro staff only (SuperAdmin). Lists public status page incidents, including component overrides (IncidentType "Override").',
@@ -4998,6 +5048,7 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
4998
5048
  properties: {
4999
5049
  publicApplyEnabled: { type: 'boolean' },
5000
5050
  autoCreateCaseTypeId: { type: 'number', description: 'Case type id; 0 clears the auto-create link' },
5051
+ defaultListingDays: { type: 'number', description: '1-365: how many days a new listing stays live when its feed gives no expiry. Requires the Job Board lifecycle package.' },
5001
5052
  },
5002
5053
  required: [],
5003
5054
  },
@@ -5103,6 +5154,29 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
5103
5154
  required: ['sourceId', 'title'],
5104
5155
  },
5105
5156
  },
5157
+ // -- Job Board listing lifecycle (package "job-board-lifecycle", default OFF: 403 for a site without it) --
5158
+ {
5159
+ name: 'jobboard_lifecycle_status',
5160
+ description: 'Does this site have the Job Board listing lifecycle package (close/renew, expiry, JSON-LD listing pages), and what is its default listing duration? Returns { entitled, defaultListingDays }.',
5161
+ inputSchema: { type: 'object', properties: {}, required: [] },
5162
+ },
5163
+ {
5164
+ name: 'jobboard_close_listing',
5165
+ description: 'Close a job listing: it stops appearing in search and the public board, the public listing page answers 410, and applying to it answers 410. Idempotent. Requires the Job Board lifecycle package (403 otherwise); 404 for an id that is not on this site.',
5166
+ inputSchema: { type: 'object', properties: { listingId: { type: 'number' } }, required: ['listingId'] },
5167
+ },
5168
+ {
5169
+ name: 'jobboard_renew_listing',
5170
+ description: 'Renew a job listing: extends its expiry by `days` (default: the site\'s default listing days) from the later of its current expiry and now, increments its renewal count and republishes it if it was closed or expired. Requires the Job Board lifecycle package (403 otherwise).',
5171
+ inputSchema: {
5172
+ type: 'object',
5173
+ properties: {
5174
+ listingId: { type: 'number' },
5175
+ days: { type: 'number', description: '1-365; omit for the site default' },
5176
+ },
5177
+ required: ['listingId'],
5178
+ },
5179
+ },
5106
5180
  {
5107
5181
  name: 'jobboard_move_application_stage',
5108
5182
  description: 'Move a candidate application to a new HR/agency-side pipeline stage on the applicant Kanban board (Applied, Screened, Interview, Offer, Hired, Rejected).',
@@ -5994,6 +6068,17 @@ async function handleTool(name, args) {
5994
6068
  // [Authorize] does not accept vel_live_* service keys (only a handful of controllers opt
5995
6069
  // into that manually) — these tools need VELARO_JWT, same requirement as the existing
5996
6070
  // notifications_get/ai_config_staff_get tools they're modeled on.
6071
+ case 'ai_followups_site_get': {
6072
+ const r = await messagingGet('/SiteSettings/AiFollowUps');
6073
+ return JSON.stringify({ enabled: r?.enabled ?? null }, null, 2);
6074
+ }
6075
+
6076
+ case 'ai_followups_site_set': {
6077
+ await messagingPut('/SiteSettings/AiFollowUps', { enabled: typeof args.enabled === 'boolean' ? args.enabled : null });
6078
+ const r = await messagingGet('/SiteSettings/AiFollowUps');
6079
+ return JSON.stringify({ enabled: r?.enabled ?? null }, null, 2);
6080
+ }
6081
+
5997
6082
  case 'survey_list': {
5998
6083
  const surveys = (await messagingGet('/Surveys/list')) ?? [];
5999
6084
  if (!surveys.length) return 'No surveys found.';
@@ -6492,7 +6577,26 @@ async function handleTool(name, args) {
6492
6577
  return await api('PUT', '/TeamsBot/ChannelConfig', body);
6493
6578
  }
6494
6579
 
6580
+ case 'teams_send_direct': {
6581
+ const r = await api('POST', '/TeamsMessaging/Direct', {
6582
+ user: args.user, title: args.title, message: args.message || '',
6583
+ facts: (args.fields || []).map(f => ({ label: f.label, value: f.value })),
6584
+ actionUrl: args.buttonUrl, actionLabel: args.buttonLabel,
6585
+ });
6586
+ return r?.sent ? `Direct message sent to ${args.user} via ${r.path}.` : `Send failed: ${r?.error || 'unknown error'} ${(r?.warnings || []).join('; ')}`;
6587
+ }
6588
+
6495
6589
  case 'teams_notify_card': {
6590
+ if ((args.mentions && args.mentions.length) || args.destination) {
6591
+ const r = await api('POST', '/TeamsMessaging/Card', {
6592
+ title: args.title,
6593
+ facts: (args.fields || []).map(f => ({ label: f.label, value: f.value })),
6594
+ actionUrl: args.buttonUrl, actionLabel: args.buttonLabel,
6595
+ destination: args.destination,
6596
+ mentions: (args.mentions || []).map(m => ({ user: m.user, name: m.name })),
6597
+ });
6598
+ return r?.sent ? `Card sent via ${r.path}${r.warnings?.length ? ' (' + r.warnings.join('; ') + ')' : ''}.` : `Send failed: ${r?.error || 'unknown error'}`;
6599
+ }
6496
6600
  const webhooks = await api('GET', '/TeamsNotification');
6497
6601
  if (!Array.isArray(webhooks) || !webhooks.length)
6498
6602
  return 'No Teams webhooks configured. Go to Integrations → Microsoft Teams to add one, or call velaro teams create <name> <webhookUrl> via CLI.';
@@ -8485,12 +8589,18 @@ async function handleTool(name, args) {
8485
8589
  program_breakdown: 'program-breakdown', agent_scorecard: 'agent-scorecard', qa_coverage: 'qa-coverage',
8486
8590
  coaching_queue: 'coaching-queue', sentiment_breakdown: 'sentiment-breakdown', topic_radar: 'topic-radar',
8487
8591
  weekly_brief: 'weekly-brief', weekly_brief_send_test: 'weekly-brief/send-test',
8592
+ repeat_contact: 'repeat-contact', demand_coverage: 'demand-coverage', bot_containment: 'bot-containment',
8593
+ effort_index: 'effort-index', promise_ledger: 'promise-ledger',
8488
8594
  };
8595
+ if (args.report === 'promise_dismiss') {
8596
+ if (!args.promiseId) throw new Error('promiseId is required for promise_dismiss');
8597
+ return JSON.stringify(await messagingPost('/api/enterprise-reports/promise-ledger/' + encodeURIComponent(String(args.promiseId)) + '/dismiss', {}), null, 2);
8598
+ }
8489
8599
  const seg = ER_PATHS[args.report];
8490
8600
  if (!seg) throw new Error('report must be one of: ' + Object.keys(ER_PATHS).join(', '));
8491
8601
  if (args.report === 'weekly_brief_send_test') return JSON.stringify(await messagingPost('/api/enterprise-reports/' + seg, {}), null, 2);
8492
8602
  const qs = new URLSearchParams();
8493
- for (const k of ['from', 'to', 'groupBy', 'deploymentId', 'teamId', 'threshold', 'perAgent', 'limit', 'days']) {
8603
+ for (const k of ['from', 'to', 'groupBy', 'deploymentId', 'teamId', 'threshold', 'perAgent', 'limit', 'days', 'windowDays', 'weeks', 'handleMinutes', 'maxConcurrent', 'timeZone', 'lowScore', 'includeDemo']) {
8494
8604
  if (args[k] != null && args[k] !== '') qs.set(k, String(args[k]));
8495
8605
  }
8496
8606
  const q = qs.toString();
@@ -8908,6 +9018,13 @@ async function handleTool(name, args) {
8908
9018
  return JSON.stringify(data, null, 2);
8909
9019
  }
8910
9020
 
9021
+ case 'ai_federation': {
9022
+ const action = args.action || 'status';
9023
+ if (action === 'test') return JSON.stringify(await api('POST', '/AiFederation/test', {}), null, 2);
9024
+ if (action === 'reset_breaker') return JSON.stringify(await api('POST', '/AiFederation/reset-breaker', {}), null, 2);
9025
+ return JSON.stringify(await api('GET', '/AiFederation/status'), null, 2);
9026
+ }
9027
+
8911
9028
  case 'status_incident_list': {
8912
9029
  const rows = await messagingRequest('GET', `/superadmin/status-incidents?filter=${encodeURIComponent(args.filter || 'active')}`);
8913
9030
  return JSON.stringify(rows, null, 2);
@@ -10330,7 +10447,12 @@ async function handleTool(name, args) {
10330
10447
  if (!Number.isInteger(n) || n < 0) throw new Error('autoCreateCaseTypeId must be an integer >= 0 (0 clears).');
10331
10448
  body.autoCreateCaseTypeId = n;
10332
10449
  }
10333
- if (!Object.keys(body).length) throw new Error('Specify publicApplyEnabled and/or autoCreateCaseTypeId.');
10450
+ if (args.defaultListingDays !== undefined) {
10451
+ const d = Number(args.defaultListingDays);
10452
+ if (!Number.isInteger(d) || d < 1 || d > 365) throw new Error('defaultListingDays must be an integer from 1 to 365.');
10453
+ body.defaultListingDays = d;
10454
+ }
10455
+ if (!Object.keys(body).length) throw new Error('Specify publicApplyEnabled, autoCreateCaseTypeId and/or defaultListingDays.');
10334
10456
  const r = await messagingPut('/Recruiting/ApplySettings', body);
10335
10457
  return JSON.stringify(r, null, 2);
10336
10458
  }
@@ -10367,6 +10489,29 @@ async function handleTool(name, args) {
10367
10489
  }, null, 2);
10368
10490
  }
10369
10491
 
10492
+ case 'jobboard_lifecycle_status': {
10493
+ const r = await messagingGet('/Recruiting/Lifecycle');
10494
+ return JSON.stringify(r, null, 2);
10495
+ }
10496
+ case 'jobboard_close_listing': {
10497
+ const id = Number(args.listingId);
10498
+ if (!Number.isInteger(id) || id <= 0) throw new Error('listingId must be a positive integer.');
10499
+ const r = await messagingPost(`/Recruiting/Jobs/${id}/close`, {});
10500
+ return JSON.stringify(r, null, 2);
10501
+ }
10502
+ case 'jobboard_renew_listing': {
10503
+ const id = Number(args.listingId);
10504
+ if (!Number.isInteger(id) || id <= 0) throw new Error('listingId must be a positive integer.');
10505
+ const body = {};
10506
+ if (args.days !== undefined) {
10507
+ const d = Number(args.days);
10508
+ if (!Number.isInteger(d) || d < 1 || d > 365) throw new Error('days must be an integer from 1 to 365.');
10509
+ body.days = d;
10510
+ }
10511
+ const r = await messagingPost(`/Recruiting/Jobs/${id}/renew`, body);
10512
+ return JSON.stringify(r, null, 2);
10513
+ }
10514
+
10370
10515
  case 'jobboard_move_application_stage': {
10371
10516
  const app = await messagingPut(`/Recruiting/Applications/${args.applicationId}/Stage`, { stage: args.stage });
10372
10517
  return JSON.stringify(app, null, 2);