@velaro/mcp-server 0.6.96 → 0.6.98

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 +131 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velaro/mcp-server",
3
- "version": "0.6.96",
3
+ "version": "0.6.98",
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
@@ -160,9 +160,17 @@ function avatarOverrideBody(args) {
160
160
  for (const k of ['providerSlug', 'avatarId', 'personalityPresetSlug', 'voiceId', 'displayName', 'backgroundMode', 'backgroundValue']) {
161
161
  if (args[k] !== undefined) body[k] = args[k];
162
162
  }
163
+ if (args.motionLevel !== undefined) body.motionLevel = parseAvatarMotionLevel(args.motionLevel);
163
164
  return body;
164
165
  }
165
166
 
167
+ /** Closed vocabulary for avatar body motion; "" resets/inherits. Fails readably instead of a bare 400. */
168
+ function parseAvatarMotionLevel(raw) {
169
+ const level = String(raw).trim().toLowerCase();
170
+ if (!['', 'off', 'subtle', 'natural', 'expressive'].includes(level)) throw new Error('motionLevel must be off, subtle, natural or expressive (empty string resets/inherits).');
171
+ return level;
172
+ }
173
+
166
174
  async function messagingRequest(method, path, body) {
167
175
  if (!MESSAGING_API) throw new Error(
168
176
  'VELARO_MESSAGING_API is not configured. Add "VELARO_MESSAGING_API": "https://velaro-messaging-api-staging.azurewebsites.net" to your MCP env config.'
@@ -3386,7 +3394,7 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
3386
3394
  // ── AI Video Avatar management (superadmin) ────────────────────────────────
3387
3395
  {
3388
3396
  name: 'avatar_get_config',
3389
- description: 'Get the AI Video Avatar (Simli/D-ID/HeyGen) configuration for any customer site — provider, avatar/face ID, voice ID, enabled state, "call me" option, callback confirm window. Velaro admin only. Never returns provider API keys (Velaro uses its own master keys, never per-site).',
3397
+ description: 'Get the AI Video Avatar (Simli/D-ID/HeyGen) configuration for any customer site — provider, avatar/face ID, voice ID, enabled state, "call me" option, callback confirm window, body motion level and strength. Velaro admin only. Never returns provider API keys (Velaro uses its own master keys, never per-site).',
3390
3398
  inputSchema: {
3391
3399
  type: 'object',
3392
3400
  properties: {
@@ -3412,6 +3420,8 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
3412
3420
  showBadge: { type: 'boolean', description: 'Show the name badge over the avatar video. Default true.' },
3413
3421
  sizePx: { type: 'number', description: 'Max avatar panel width in px (160-640, square). Default 360.' },
3414
3422
  allowVisitorCollapse: { type: 'boolean', description: 'Let visitors hide or collapse the avatar panel (up/down arrow and X). Default true.' },
3423
+ motionLevel: { type: 'string', enum: ['', 'off', 'subtle', 'natural', 'expressive'], description: 'Body motion preset layered over the lip-sync (shoulders, breathing, head, brows). Empty string resets to the platform default (natural). Visitors with reduced-motion enabled always get a still avatar.' },
3424
+ motionIntensityPercent: { type: 'integer', minimum: 25, maximum: 150, description: 'Body motion strength, whole percent 25-150 (default 100); multiplies every motion amplitude.' },
3415
3425
  maxAvatarMinutesPerMonth: { type: 'number', description: 'Monthly avatar video minutes cap for the site. Whole number 1-100000; never unlimited (0/negative rejected).' },
3416
3426
  maxAvatarTtsChars: { type: 'number', description: 'Monthly avatar TTS character cap (avatar_tts_chars) for the site. Whole number 1-100000000; never unlimited (0/negative rejected). Overage pricing is a platform-wide ladder, not per site.' },
3417
3427
  },
@@ -3461,6 +3471,7 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
3461
3471
  displayName: { type: 'string', description: 'Visitor-facing avatar name.' },
3462
3472
  backgroundMode: { type: 'string', description: 'none | color | gradient | image. "none" = explicitly no backdrop.' },
3463
3473
  backgroundValue: { type: 'string', description: 'Hex color, "#a,#b" gradient, or https image URL.' },
3474
+ motionLevel: { type: 'string', enum: ['', 'off', 'subtle', 'natural', 'expressive'], description: 'Body motion preset for this group (perWorkflow ?? perGroup ?? perSite ?? natural). Empty string inherits.' },
3464
3475
  replace: { type: 'boolean', description: 'true = replace the whole override (omitted fields inherit) instead of merging.' },
3465
3476
  },
3466
3477
  required: ['groupId'],
@@ -3490,6 +3501,7 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
3490
3501
  displayName: { type: 'string' },
3491
3502
  backgroundMode: { type: 'string', description: 'none | color | gradient | image' },
3492
3503
  backgroundValue: { type: 'string' },
3504
+ motionLevel: { type: 'string', enum: ['', 'off', 'subtle', 'natural', 'expressive'], description: 'Body motion preset for this workflow. Empty string inherits.' },
3493
3505
  replace: { type: 'boolean' },
3494
3506
  },
3495
3507
  required: ['workflowId'],
@@ -5333,6 +5345,11 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
5333
5345
  description: 'Hosted public job pages for this site: is the package included, are the pages published, and the opaque board link, sitemap, Indeed feed and Google feed URLs (the feed URLs contain a secret key). Returns { entitled, enabled, boardUrl, sitemapUrl, indeedFeedUrl, googleFeedUrl, publicApplyEnabled, eeocSelfIdAvailable, liveListings }; { entitled:false } without the package.',
5334
5346
  inputSchema: { type: 'object', properties: {}, required: [] },
5335
5347
  },
5348
+ {
5349
+ name: 'jobboard_applicant_accounts_status',
5350
+ description: 'Applicant accounts on the hosted job board (applicant sign-in with an emailed one-time code, my applications, saved jobs, resume library). Returns { entitled, hostedEnabled, signInUrl, accounts, signedInLast30Days, deletionPending, savedJobs, linkedApplications } (counts only, never emails or resumes); { entitled:false } without the Job Board applicant accounts package.',
5351
+ inputSchema: { type: 'object', properties: {}, required: [] },
5352
+ },
5336
5353
  {
5337
5354
  name: 'jobboard_hosted_set',
5338
5355
  description: 'Publish or unpublish the hosted job pages (apply-if-present). enabled=true issues the opaque board link and feed key the first time. regenerateLink=true issues a NEW board link and every old hosted URL answers 404 (administrators only). Requires the Job Board hosted pages package (403 otherwise).',
@@ -5379,6 +5396,60 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
5379
5396
  required: ['applicationId', 'stage'],
5380
5397
  },
5381
5398
  },
5399
+ // -- Job Board employers + moderation (package "job-board-employers", default OFF: 403 job_board_employers_not_entitled without it) --
5400
+ {
5401
+ name: 'jobboard_employers_list',
5402
+ description: 'List the employers on this site\'s job board with status, trusted flag (first posting approved), member count, live/total listings and how many listings wait for moderation. Requires the Job board employers package (403 otherwise).',
5403
+ inputSchema: { type: 'object', properties: {}, required: [] },
5404
+ },
5405
+ {
5406
+ name: 'jobboard_employer_create',
5407
+ description: 'Create an employer on the job board (name 2-200 characters, unique per site; optional verified email domain). Administrator only. Invite its first member with jobboard_employer_invite.',
5408
+ inputSchema: {
5409
+ type: 'object',
5410
+ properties: { name: { type: 'string' }, verifiedDomain: { type: 'string', description: 'Optional, e.g. example.com' } },
5411
+ required: ['name'],
5412
+ },
5413
+ },
5414
+ {
5415
+ name: 'jobboard_employer_invite',
5416
+ description: 'Email a single-use invitation (valid 7 days) to join an employer as Owner, Recruiter (default) or Viewer. Employers manage only their own listings and applicants and never see EEO data. Administrator only; 409 if the email already belongs to another employer or the hosted board is off.',
5417
+ inputSchema: {
5418
+ type: 'object',
5419
+ properties: {
5420
+ employerId: { type: 'number' },
5421
+ email: { type: 'string' },
5422
+ displayName: { type: 'string' },
5423
+ role: { type: 'string', description: 'Owner | Recruiter | Viewer (default Recruiter)' },
5424
+ },
5425
+ required: ['employerId', 'email'],
5426
+ },
5427
+ },
5428
+ {
5429
+ name: 'jobboard_employer_suspend',
5430
+ description: 'Suspend an employer: its members are signed out at once and every published listing is paused (off the public board). A reason of 3-500 characters is required. Administrator only.',
5431
+ inputSchema: { type: 'object', properties: { employerId: { type: 'number' }, reason: { type: 'string' } }, required: ['employerId', 'reason'] },
5432
+ },
5433
+ {
5434
+ name: 'jobboard_employer_reactivate',
5435
+ description: 'Reactivate a suspended employer. Its paused listings stay paused until the employer resumes them. Administrator only.',
5436
+ inputSchema: { type: 'object', properties: { employerId: { type: 'number' } }, required: ['employerId'] },
5437
+ },
5438
+ {
5439
+ name: 'jobboard_moderation_list',
5440
+ description: 'The moderation queue: employer listings waiting for a decision (an employer\'s first posting, or any posting that matched a scam signal such as fee requests, SSN/bank requests, gift cards, wire transfers, WhatsApp-only contact). Each item carries the matched flags. Pending listings are invisible on the public board.',
5441
+ inputSchema: { type: 'object', properties: {}, required: [] },
5442
+ },
5443
+ {
5444
+ name: 'jobboard_moderation_approve',
5445
+ description: 'Approve a pending employer listing: it goes live, the employer becomes trusted (later clean postings publish at once), and the decision is audited. Administrator only; 409 unless the listing is waiting for moderation.',
5446
+ inputSchema: { type: 'object', properties: { listingId: { type: 'number' } }, required: ['listingId'] },
5447
+ },
5448
+ {
5449
+ name: 'jobboard_moderation_reject',
5450
+ description: 'Reject a pending employer listing with a reason (3-500 characters) the employer will see; the listing stays hidden and the decision is audited. Administrator only.',
5451
+ inputSchema: { type: 'object', properties: { listingId: { type: 'number' }, reason: { type: 'string' } }, required: ['listingId', 'reason'] },
5452
+ },
5382
5453
  // -- Job Board pipeline (package "job-board-pipeline", default OFF: 403 job_board_pipeline_not_entitled without it, except jobboard_pipeline_get) --
5383
5454
  {
5384
5455
  name: 'jobboard_pipeline_get',
@@ -9074,6 +9145,7 @@ async function handleTool(name, args) {
9074
9145
  ` Display name: ${data.displayName || '(AI)'}`,
9075
9146
  ` Badge shown: ${data.showBadge}`,
9076
9147
  ` Size: ${data.sizePx}px`,
9148
+ ` Body motion: ${data.motionLevel || '(default: natural)'} @ ${data.motionIntensityPercent ?? 100}%`,
9077
9149
  ].join('\n');
9078
9150
  }
9079
9151
 
@@ -9089,6 +9161,11 @@ async function handleTool(name, args) {
9089
9161
  if (args.showBadge !== undefined) body.showBadge = args.showBadge;
9090
9162
  if (args.sizePx !== undefined) body.sizePx = args.sizePx;
9091
9163
  if (args.allowVisitorCollapse !== undefined) body.allowVisitorCollapse = args.allowVisitorCollapse;
9164
+ if (args.motionLevel !== undefined) body.motionLevel = parseAvatarMotionLevel(args.motionLevel);
9165
+ if (args.motionIntensityPercent !== undefined) {
9166
+ if (!Number.isInteger(args.motionIntensityPercent) || args.motionIntensityPercent < 25 || args.motionIntensityPercent > 150) throw new Error('motionIntensityPercent must be a whole number between 25 and 150.');
9167
+ body.motionIntensityPercent = args.motionIntensityPercent;
9168
+ }
9092
9169
  for (const [arg, max] of [['maxAvatarMinutesPerMonth', 100000], ['maxAvatarTtsChars', 100000000]]) {
9093
9170
  if (args[arg] === undefined) continue;
9094
9171
  if (!Number.isInteger(args[arg]) || args[arg] < 1 || args[arg] > max) throw new Error(`${arg} must be a whole number between 1 and ${max} (never unlimited).`);
@@ -10857,6 +10934,10 @@ async function handleTool(name, args) {
10857
10934
  const r = await messagingGet('/Recruiting/Hosted');
10858
10935
  return JSON.stringify(r, null, 2);
10859
10936
  }
10937
+ case 'jobboard_applicant_accounts_status': {
10938
+ const r = await messagingGet('/Recruiting/Applicants');
10939
+ return JSON.stringify(r, null, 2);
10940
+ }
10860
10941
  case 'jobboard_hosted_set': {
10861
10942
  const body = {};
10862
10943
  if (args.enabled !== undefined) body.enabled = Boolean(args.enabled);
@@ -10895,6 +10976,55 @@ async function handleTool(name, args) {
10895
10976
  return JSON.stringify(r, null, 2);
10896
10977
  }
10897
10978
 
10979
+ case 'jobboard_employers_list': {
10980
+ return JSON.stringify(await messagingGet('/Recruiting/Employers'), null, 2);
10981
+ }
10982
+ case 'jobboard_employer_create': {
10983
+ const name = String(args.name ?? '').trim();
10984
+ if (name.length < 2 || name.length > 200) throw new Error('name must be 2 to 200 characters.');
10985
+ const body = { name };
10986
+ if (args.verifiedDomain !== undefined && String(args.verifiedDomain).trim()) body.verifiedDomain = String(args.verifiedDomain).trim();
10987
+ return JSON.stringify(await messagingPost('/Recruiting/Employers', body), null, 2);
10988
+ }
10989
+ case 'jobboard_employer_invite': {
10990
+ const id = Number(args.employerId);
10991
+ if (!Number.isInteger(id) || id <= 0) throw new Error('employerId must be a positive integer.');
10992
+ const email = String(args.email ?? '').trim();
10993
+ if (!/^[^@\s]+@[^@\s]+\.[^@\s]{2,}$/.test(email)) throw new Error('email must be a valid email address.');
10994
+ const role = ['Owner', 'Recruiter', 'Viewer'].find((r) => r.toLowerCase() === String(args.role ?? 'Recruiter').trim().toLowerCase());
10995
+ if (!role) throw new Error('role must be Owner, Recruiter or Viewer.');
10996
+ const body = { email, role };
10997
+ if (args.displayName !== undefined && String(args.displayName).trim()) body.displayName = String(args.displayName).trim();
10998
+ return JSON.stringify(await messagingPost(`/Recruiting/Employers/${id}/Invite`, body), null, 2);
10999
+ }
11000
+ case 'jobboard_employer_suspend': {
11001
+ const id = Number(args.employerId);
11002
+ if (!Number.isInteger(id) || id <= 0) throw new Error('employerId must be a positive integer.');
11003
+ const reason = String(args.reason ?? '').trim();
11004
+ if (reason.length < 3 || reason.length > 500) throw new Error('reason must be 3 to 500 characters.');
11005
+ return JSON.stringify(await messagingPost(`/Recruiting/Employers/${id}/Suspend`, { reason }), null, 2);
11006
+ }
11007
+ case 'jobboard_employer_reactivate': {
11008
+ const id = Number(args.employerId);
11009
+ if (!Number.isInteger(id) || id <= 0) throw new Error('employerId must be a positive integer.');
11010
+ return JSON.stringify(await messagingPost(`/Recruiting/Employers/${id}/Reactivate`, {}), null, 2);
11011
+ }
11012
+ case 'jobboard_moderation_list': {
11013
+ return JSON.stringify(await messagingGet('/Recruiting/Moderation'), null, 2);
11014
+ }
11015
+ case 'jobboard_moderation_approve': {
11016
+ const id = Number(args.listingId);
11017
+ if (!Number.isInteger(id) || id <= 0) throw new Error('listingId must be a positive integer.');
11018
+ return JSON.stringify(await messagingPost(`/Recruiting/Moderation/${id}/Approve`, {}), null, 2);
11019
+ }
11020
+ case 'jobboard_moderation_reject': {
11021
+ const id = Number(args.listingId);
11022
+ if (!Number.isInteger(id) || id <= 0) throw new Error('listingId must be a positive integer.');
11023
+ const reason = String(args.reason ?? '').trim();
11024
+ if (reason.length < 3 || reason.length > 500) throw new Error('reason must be 3 to 500 characters.');
11025
+ return JSON.stringify(await messagingPost(`/Recruiting/Moderation/${id}/Reject`, { reason }), null, 2);
11026
+ }
11027
+
10898
11028
  case 'jobboard_move_application_stage': {
10899
11029
  const id = Number(args.applicationId);
10900
11030
  if (!Number.isInteger(id) || id <= 0) throw new Error('applicationId must be a positive integer.');