@velaro/mcp-server 0.6.94 → 0.6.96

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 +284 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velaro/mcp-server",
3
- "version": "0.6.94",
3
+ "version": "0.6.96",
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
@@ -1699,6 +1699,79 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
1699
1699
  },
1700
1700
 
1701
1701
  // ── Livefluence Migration ─────────────────────────────────────────────────
1702
+ {
1703
+ name: 'livefluence_site_workflows_list',
1704
+ description: 'Velaro staff only (superadmin). List ANOTHER v20 site\'s workflows: id, name, enabled, priority, action, team/AI target. Use after livefluence_migrate to see what exists on the destination site (e.g. 100140).',
1705
+ inputSchema: { type: 'object', properties: { site_id: { type: 'number', description: 'V20 site ID to inspect' } }, required: ['site_id'] },
1706
+ },
1707
+ {
1708
+ name: 'livefluence_site_workflows_set_enabled',
1709
+ description: 'Velaro staff only (superadmin). Enable or disable workflows on a v20 site by ids or names. Idempotent, audit-logged. Enabling REFUSES a workflow whose trigger is not fully converted from v10 unless force_partial=true; pass v10_site_id to check each against its same-named v10 rule. Use dry_run=true first.',
1710
+ inputSchema: {
1711
+ type: 'object',
1712
+ properties: {
1713
+ site_id: { type: 'number', description: 'V20 site ID' },
1714
+ workflow_ids: { type: 'array', items: { type: 'number' } },
1715
+ names: { type: 'array', items: { type: 'string' } },
1716
+ enabled: { type: 'boolean', description: 'true = enable, false = disable' },
1717
+ v10_site_id: { type: 'number', description: 'Verify conversion against this v10 site rules' },
1718
+ force_partial: { type: 'boolean', description: 'Enable even if v10 expression terms were not converted' },
1719
+ dry_run: { type: 'boolean' },
1720
+ },
1721
+ required: ['site_id', 'enabled'],
1722
+ },
1723
+ },
1724
+ {
1725
+ name: 'livefluence_site_workflow_create',
1726
+ description: 'Velaro staff only (superadmin). Create a workflow on a v20 site from a nodes-blob JSON (object of nodes with a trigger node). Disabled unless enabled=true. Idempotent by name; validates that referenced AI configs/teams exist on the site. Use dry_run=true first.',
1727
+ inputSchema: {
1728
+ type: 'object',
1729
+ properties: {
1730
+ site_id: { type: 'number' },
1731
+ name: { type: 'string' },
1732
+ nodes_blob: { type: 'string', description: 'JSON string of the nodes object' },
1733
+ trigger_type: { type: 'string', description: 'Default conversation_opened' },
1734
+ enabled: { type: 'boolean' },
1735
+ dry_run: { type: 'boolean' },
1736
+ },
1737
+ required: ['site_id', 'name', 'nodes_blob'],
1738
+ },
1739
+ },
1740
+ {
1741
+ name: 'livefluence_site_workflow_set_action',
1742
+ description: 'Velaro staff only (superadmin). Set a workflow action node on a v20 site to transferToAI {aiConfigId} or routeToTeam {teamId}. Validates the target exists on that site. Idempotent, audit-logged.',
1743
+ inputSchema: {
1744
+ type: 'object',
1745
+ properties: {
1746
+ site_id: { type: 'number' },
1747
+ workflow_id: { type: 'number' },
1748
+ action_type: { type: 'string', enum: ['transferToAI', 'routeToTeam'] },
1749
+ target_id: { type: 'number', description: 'aiConfigId for transferToAI, teamId for routeToTeam' },
1750
+ dry_run: { type: 'boolean' },
1751
+ },
1752
+ required: ['site_id', 'workflow_id', 'action_type', 'target_id'],
1753
+ },
1754
+ },
1755
+ {
1756
+ name: 'livefluence_site_rules_list',
1757
+ description: 'Velaro staff only (superadmin). List a v20 site Rules (e.g. the use-ai rules seeded by livefluence_seed_url_rules) with enabled state.',
1758
+ inputSchema: { type: 'object', properties: { site_id: { type: 'number' } }, required: ['site_id'] },
1759
+ },
1760
+ {
1761
+ name: 'livefluence_site_rules_set_enabled',
1762
+ description: 'Velaro staff only (superadmin). Switch v20 Rules off/on without deleting them (reversible), by rule ids or by trigger (e.g. use-ai). Use to turn off the url-rules seeded earlier so they do not double-route with migrated workflows. Idempotent, audit-logged.',
1763
+ inputSchema: {
1764
+ type: 'object',
1765
+ properties: {
1766
+ site_id: { type: 'number' },
1767
+ rule_ids: { type: 'array', items: { type: 'number' } },
1768
+ trigger: { type: 'string', description: 'Every rule with this trigger, e.g. use-ai' },
1769
+ enabled: { type: 'boolean' },
1770
+ dry_run: { type: 'boolean' },
1771
+ },
1772
+ required: ['site_id', 'enabled'],
1773
+ },
1774
+ },
1702
1775
  {
1703
1776
  name: 'livefluence_seed_url_rules',
1704
1777
  description: 'Velaro staff only. Seed v20 use-ai routing rules on a target site from v10 URL rules. Each rule matches "url contains <path>" with scheme/host stripped (path-only), so a demo page path triggers the same rule as the customer real URL. Idempotent. Domain-only URLs are skipped. Use dry_run=true first.',
@@ -2160,6 +2233,20 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
2160
2233
  required: ['jobId'],
2161
2234
  },
2162
2235
  },
2236
+ {
2237
+ name: 'index_create',
2238
+ description: 'Velaro staff only. Create a named virtual knowledge index on ANY site (e.g. "100140-troubleshooting", "100140-manual-catalog") so uploads/publishes with that indexName work. Idempotent: returns the existing index if the name already exists. Respects the site index quota. Wraps POST KnowledgeSetup/Staff/{siteId}/create-index.',
2239
+ inputSchema: {
2240
+ type: 'object',
2241
+ properties: {
2242
+ siteId: { type: 'number', description: 'Target site ID' },
2243
+ indexName: { type: 'string', description: 'Index name (lowercased, a-z 0-9 and dashes)' },
2244
+ purpose: { type: 'string', enum: ['general', 'troubleshooting', 'catalog'], description: 'Index purpose (default general)' },
2245
+ excludeFromAllSources: { type: 'boolean', description: 'Keep out of the ["*"] all-sources search' },
2246
+ },
2247
+ required: ['siteId', 'indexName'],
2248
+ },
2249
+ },
2163
2250
  {
2164
2251
  name: 'uploaded_file_reindex',
2165
2252
  description: 'Re-index an already-uploaded AI training file from blob storage — use when a file shows 0 chunks (e.g. PDF uploaded before PDF support was added). Reads from blob and re-runs full ingestion. Use index_status after ~30s to verify chunks appear.',
@@ -5281,16 +5368,97 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
5281
5368
  },
5282
5369
  {
5283
5370
  name: 'jobboard_move_application_stage',
5284
- description: 'Move a candidate application to a new HR/agency-side pipeline stage on the applicant Kanban board (Applied, Screened, Interview, Offer, Hired, Rejected).',
5371
+ description: 'Move a candidate application to a new HR/agency-side pipeline stage on the applicant Kanban board. Default stages: Applied, Screened, Interview, Offer, Hired, Rejected; a site with the Job board pipeline package uses its own configured stage keys (see jobboard_pipeline_get). Optional note is recorded in the stage history.',
5285
5372
  inputSchema: {
5286
5373
  type: 'object',
5287
5374
  properties: {
5288
5375
  applicationId: { type: 'number' },
5289
- stage: { type: 'string', description: 'Applied | Screened | Interview | Offer | Hired | Rejected' },
5376
+ stage: { type: 'string', description: 'A stage key from jobboard_pipeline_get (default list: Applied | Screened | Interview | Offer | Hired | Rejected)' },
5377
+ note: { type: 'string', description: 'Optional note recorded in the stage history (2000 characters max)' },
5290
5378
  },
5291
5379
  required: ['applicationId', 'stage'],
5292
5380
  },
5293
5381
  },
5382
+ // -- Job Board pipeline (package "job-board-pipeline", default OFF: 403 job_board_pipeline_not_entitled without it, except jobboard_pipeline_get) --
5383
+ {
5384
+ name: 'jobboard_pipeline_get',
5385
+ description: 'The recruiter pipeline stages for this site. Returns { entitled, source: "default"|"caseType", caseTypeId, stages:[{key,label,color,applicantVisible,applicantLabel}] }. Without the Job board pipeline package it returns the default list with entitled:false (stage moves still work on the default stages).',
5386
+ inputSchema: { type: 'object', properties: {}, required: [] },
5387
+ },
5388
+ {
5389
+ name: 'jobboard_pipeline_set',
5390
+ description: 'Replace the pipeline stages (1 to 12, unique keys up to 64 characters). Removing a stage that still holds applications is refused with a 400 naming the count; move them first. Requires the Job board pipeline package (403 otherwise). Applicants see only stages with applicantVisible=true (using applicantLabel or label); every other stage shows as In review.',
5391
+ inputSchema: {
5392
+ type: 'object',
5393
+ properties: {
5394
+ stages: {
5395
+ type: 'array',
5396
+ description: 'The full ordered stage list',
5397
+ items: {
5398
+ type: 'object',
5399
+ properties: {
5400
+ key: { type: 'string' },
5401
+ label: { type: 'string' },
5402
+ color: { type: 'string', description: '#rrggbb (optional)' },
5403
+ applicantVisible: { type: 'boolean' },
5404
+ applicantLabel: { type: 'string', description: 'Name applicants see (optional)' },
5405
+ },
5406
+ required: ['key', 'label'],
5407
+ },
5408
+ },
5409
+ },
5410
+ required: ['stages'],
5411
+ },
5412
+ },
5413
+ {
5414
+ name: 'jobboard_application_history',
5415
+ description: 'Stage, note and assignment history of one application, newest first: { events:[{id,kind:"Stage"|"Note"|"Assign",fromStage,toStage,actorType:"User"|"ApiKey"|"CaseSync"|"System",actorId,at,note}] }. For an Assign event the new recruiter is in note (empty = unassigned) and toStage is only the stage at that time. Requires the Job board pipeline package (403 otherwise); 404 for an id not on this site.',
5416
+ inputSchema: { type: 'object', properties: { applicationId: { type: 'number' } }, required: ['applicationId'] },
5417
+ },
5418
+ {
5419
+ name: 'jobboard_application_note',
5420
+ description: 'Add an internal note to an application (1 to 2000 characters). Never shown to the applicant. Requires the Job board pipeline package (403 otherwise).',
5421
+ inputSchema: {
5422
+ type: 'object',
5423
+ properties: { applicationId: { type: 'number' }, note: { type: 'string' } },
5424
+ required: ['applicationId', 'note'],
5425
+ },
5426
+ },
5427
+ {
5428
+ name: 'jobboard_application_assign',
5429
+ description: 'Assign a recruiter to an application (free text up to 200 characters, for example an email). Pass assignedTo as null or an empty string to unassign. Requires the Job board pipeline package (403 otherwise).',
5430
+ inputSchema: {
5431
+ type: 'object',
5432
+ properties: { applicationId: { type: 'number' }, assignedTo: { type: ['string', 'null'] } },
5433
+ required: ['applicationId'],
5434
+ },
5435
+ },
5436
+ {
5437
+ name: 'jobboard_retention_get',
5438
+ description: 'Applicant record retention settings: { enabled, dryRun, retentionDays, isFederalContractor, legalHold, floorDays, effectiveDays, lastRunAt, lastRunDeleted, lastRunWouldDelete }. Records are never deleted before 1 year (2 years for federal contractors; 29 CFR 1602.14), so effectiveDays is never below floorDays. Requires the Job board pipeline package (403 otherwise).',
5439
+ inputSchema: { type: 'object', properties: {}, required: [] },
5440
+ },
5441
+ {
5442
+ name: 'jobboard_retention_set',
5443
+ description: 'Change retention settings (apply-if-present). retentionDays below the legal floor is accepted and the floor applies. legalHold=true stops all deletion. Administrators only; requires the Job board pipeline package (403 otherwise).',
5444
+ inputSchema: {
5445
+ type: 'object',
5446
+ properties: {
5447
+ enabled: { type: 'boolean' },
5448
+ dryRun: { type: 'boolean', description: 'Count only, never delete' },
5449
+ retentionDays: { type: 'number' },
5450
+ isFederalContractor: { type: 'boolean' },
5451
+ legalHold: { type: 'boolean' },
5452
+ confirm: { type: 'boolean', description: 'Required true when dryRun=false or legalHold=false (this can start real deletion or lift a hold)' },
5453
+ },
5454
+ required: [],
5455
+ },
5456
+ },
5457
+ {
5458
+ name: 'jobboard_retention_run',
5459
+ description: 'Run retention now. dryRun defaults to true: counts what is due and deletes nothing. dryRun=false really deletes records past retention (never before the legal floor, never under a legal hold). Returns counts only: { dryRun, due, deleted, resumesDeleted, skippedLegalHold, floorDays }. Administrators only; requires the Job board pipeline package (403 otherwise).',
5460
+ inputSchema: { type: 'object', properties: { dryRun: { type: 'boolean', description: 'Default true' }, confirm: { type: 'boolean', description: 'Required true when dryRun=false (permanently deletes records)' } }, required: [] },
5461
+ },
5294
5462
 
5295
5463
  // ── Velaro Appointments ──────────────────────────────────────────────────────
5296
5464
  // Backend: server/Velaro.Messaging/Controllers/AppointmentsController.cs.
@@ -7064,6 +7232,47 @@ async function handleTool(name, args) {
7064
7232
  return JSON.stringify(result, null, 2);
7065
7233
  }
7066
7234
 
7235
+ case 'livefluence_site_workflows_list': {
7236
+ const result = await api('GET', `/DatabaseTool/SiteWorkflows/${args.site_id}`);
7237
+ return JSON.stringify(result, null, 2);
7238
+ }
7239
+
7240
+ case 'livefluence_site_workflows_set_enabled': {
7241
+ const result = await api('POST', '/DatabaseTool/SiteWorkflows/SetEnabled', {
7242
+ siteId: args.site_id, workflowIds: args.workflow_ids ?? [], names: args.names ?? [],
7243
+ enabled: !!args.enabled, dryRun: !!args.dry_run, forcePartial: !!args.force_partial, v10SiteId: args.v10_site_id ?? null,
7244
+ });
7245
+ return JSON.stringify(result, null, 2);
7246
+ }
7247
+
7248
+ case 'livefluence_site_workflow_create': {
7249
+ const result = await api('POST', '/DatabaseTool/SiteWorkflows/Create', {
7250
+ siteId: args.site_id, name: args.name, triggerType: args.trigger_type ?? 'conversation_opened',
7251
+ nodesBlob: typeof args.nodes_blob === 'string' ? args.nodes_blob : JSON.stringify(args.nodes_blob),
7252
+ enabled: !!args.enabled, dryRun: !!args.dry_run,
7253
+ });
7254
+ return JSON.stringify(result, null, 2);
7255
+ }
7256
+
7257
+ case 'livefluence_site_workflow_set_action': {
7258
+ const result = await api('POST', '/DatabaseTool/SiteWorkflows/SetAction', {
7259
+ siteId: args.site_id, workflowId: args.workflow_id, actionType: args.action_type, targetId: args.target_id, dryRun: !!args.dry_run,
7260
+ });
7261
+ return JSON.stringify(result, null, 2);
7262
+ }
7263
+
7264
+ case 'livefluence_site_rules_list': {
7265
+ const result = await api('GET', `/DatabaseTool/SiteRules/${args.site_id}`);
7266
+ return JSON.stringify(result, null, 2);
7267
+ }
7268
+
7269
+ case 'livefluence_site_rules_set_enabled': {
7270
+ const result = await api('POST', '/DatabaseTool/SiteRules/SetEnabled', {
7271
+ siteId: args.site_id, ruleIds: args.rule_ids ?? [], trigger: args.trigger ?? null, enabled: !!args.enabled, dryRun: !!args.dry_run,
7272
+ });
7273
+ return JSON.stringify(result, null, 2);
7274
+ }
7275
+
7067
7276
  case 'livefluence_seed_url_rules': {
7068
7277
  const result = await api('POST', '/DatabaseTool/V10SeedUrlRules', {
7069
7278
  siteId: args.site_id, aiConfigId: args.ai_config_id, dryRun: !!args.dry_run, rules: args.rules,
@@ -7545,6 +7754,15 @@ async function handleTool(name, args) {
7545
7754
  return lines.join('\n');
7546
7755
  }
7547
7756
 
7757
+ case 'index_create': {
7758
+ if (!args.siteId || !args.indexName) return 'siteId and indexName are required.';
7759
+ const body = { indexName: args.indexName, purpose: args.purpose ?? 'general' };
7760
+ if (args.excludeFromAllSources !== undefined) body.excludeFromAllSources = args.excludeFromAllSources;
7761
+ const idx = await messagingRequest('POST', `/KnowledgeSetup/Staff/${encodeURIComponent(args.siteId)}/create-index`, body);
7762
+ if (idx?.error) return `Create index error: ${idx.error}`;
7763
+ return `Index ready on site ${args.siteId}: ${idx?.name} (id=${idx?.id}, purpose=${idx?.purpose}, excludeFromAllSources=${!!idx?.excludeFromAllSources}).`;
7764
+ }
7765
+
7548
7766
  case 'uploaded_files_list': {
7549
7767
  const files = await messagingRequest('GET', '/uploadedFiles/list/AI');
7550
7768
  if (!Array.isArray(files) || !files.length) return 'No AI training files uploaded for this site.';
@@ -10678,9 +10896,72 @@ async function handleTool(name, args) {
10678
10896
  }
10679
10897
 
10680
10898
  case 'jobboard_move_application_stage': {
10681
- const app = await messagingPut(`/Recruiting/Applications/${args.applicationId}/Stage`, { stage: args.stage });
10899
+ const id = Number(args.applicationId);
10900
+ if (!Number.isInteger(id) || id <= 0) throw new Error('applicationId must be a positive integer.');
10901
+ const stage = String(args.stage ?? '').trim();
10902
+ if (!stage) throw new Error('stage is required.');
10903
+ const body = { stage };
10904
+ if (args.note !== undefined && String(args.note).trim()) {
10905
+ if (String(args.note).length > 2000) throw new Error('note must be 2000 characters or fewer.');
10906
+ body.note = String(args.note).trim();
10907
+ }
10908
+ const app = await messagingPut(`/Recruiting/Applications/${id}/Stage`, body);
10682
10909
  return JSON.stringify(app, null, 2);
10683
10910
  }
10911
+ case 'jobboard_pipeline_get': {
10912
+ return JSON.stringify(await messagingGet('/Recruiting/Pipeline'), null, 2);
10913
+ }
10914
+ case 'jobboard_pipeline_set': {
10915
+ const stages = args.stages;
10916
+ if (!Array.isArray(stages) || stages.length < 1 || stages.length > 12) throw new Error('stages must be an array of 1 to 12 stages.');
10917
+ const keys = new Set();
10918
+ for (const s of stages) {
10919
+ if (!s || typeof s.key !== 'string' || !s.key.trim() || s.key.length > 64) throw new Error('every stage needs a key (up to 64 characters).');
10920
+ if (typeof s.label !== 'string' || !s.label.trim()) throw new Error(`stage ${s.key} needs a label.`);
10921
+ if (keys.has(s.key)) throw new Error(`duplicate stage key: ${s.key}.`);
10922
+ keys.add(s.key);
10923
+ }
10924
+ return JSON.stringify(await messagingPut('/Recruiting/Pipeline', { stages }), null, 2);
10925
+ }
10926
+ case 'jobboard_application_history': {
10927
+ const id = Number(args.applicationId);
10928
+ if (!Number.isInteger(id) || id <= 0) throw new Error('applicationId must be a positive integer.');
10929
+ return JSON.stringify(await messagingGet(`/Recruiting/Applications/${id}/History`), null, 2);
10930
+ }
10931
+ case 'jobboard_application_note': {
10932
+ const id = Number(args.applicationId);
10933
+ if (!Number.isInteger(id) || id <= 0) throw new Error('applicationId must be a positive integer.');
10934
+ const note = String(args.note ?? '').trim();
10935
+ if (!note || note.length > 2000) throw new Error('note must be 1 to 2000 characters.');
10936
+ return JSON.stringify(await messagingPost(`/Recruiting/Applications/${id}/Notes`, { note }), null, 2);
10937
+ }
10938
+ case 'jobboard_application_assign': {
10939
+ const id = Number(args.applicationId);
10940
+ if (!Number.isInteger(id) || id <= 0) throw new Error('applicationId must be a positive integer.');
10941
+ const who = String(args.assignedTo ?? '').trim();
10942
+ if (who.length > 200) throw new Error('assignedTo must be 200 characters or fewer.');
10943
+ return JSON.stringify(await messagingPut(`/Recruiting/Applications/${id}/Assign`, { assignedTo: who || null }), null, 2);
10944
+ }
10945
+ case 'jobboard_retention_get': {
10946
+ return JSON.stringify(await messagingGet('/Recruiting/Retention'), null, 2);
10947
+ }
10948
+ case 'jobboard_retention_set': {
10949
+ const body = {};
10950
+ for (const k of ['enabled', 'dryRun', 'isFederalContractor', 'legalHold']) if (args[k] !== undefined) body[k] = Boolean(args[k]);
10951
+ if (args.retentionDays !== undefined) {
10952
+ const n = Number(args.retentionDays);
10953
+ if (!Number.isInteger(n) || n < 1 || n > 36500) throw new Error('retentionDays must be a whole number from 1 to 36500.');
10954
+ body.retentionDays = n;
10955
+ }
10956
+ if (!Object.keys(body).length) throw new Error('Specify at least one retention setting (use jobboard_retention_get to read).');
10957
+ if ((body.dryRun === false || body.legalHold === false) && args.confirm !== true) throw new Error('dryRun=false or legalHold=false can start real deletion of applicant records or lift a legal hold. Preview with jobboard_retention_run first, then repeat with confirm=true.');
10958
+ return JSON.stringify(await messagingPut('/Recruiting/Retention', body), null, 2);
10959
+ }
10960
+ case 'jobboard_retention_run': {
10961
+ const dry = args.dryRun === undefined ? true : Boolean(args.dryRun);
10962
+ if (!dry && args.confirm !== true) throw new Error('dryRun=false permanently deletes applicant records past retention. Preview first (dryRun=true), then repeat with confirm=true.');
10963
+ return JSON.stringify(await messagingPost(`/Recruiting/Retention/Run?dryRun=${dry ? 'true' : 'false'}`, {}), null, 2);
10964
+ }
10684
10965
 
10685
10966
  // -- Velaro Appointments --
10686
10967
  case 'appointments_manual_book': {