@velaro/mcp-server 2.0.12 → 2.0.14

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 (3) hide show
  1. package/README.md +3 -1
  2. package/package.json +1 -1
  3. package/server.js +64 -2
package/README.md CHANGED
@@ -158,7 +158,9 @@ This works with any MCP client that supports HTTP/SSE transport — including Cl
158
158
  | Tool | What It Does |
159
159
  |------|-------------|
160
160
  | `signature_policy_get` | Read the company email signature policy: mode, company signature, legal disclaimer, per-team overrides (admin only) |
161
- | `signature_policy_set` | Change the signature policy (locked, company-default or personal-only), the company signature, the optional legal disclaimer, company-wide or for one team (admin only) |
161
+ | `signature_policy_set` | Change the signature policy (locked, company-default or personal-only), the company signature, the optional legal disclaimer, the name line (real or display name, agent and/or team name), the team and agent override switches, company-wide or for one team (admin only) |
162
+ | `signature_policy_history` | List saved versions of the signature policy, newest first (admin only) |
163
+ | `signature_policy_rollback` | Restore a saved version of the signature policy as a new version (site administrators only) |
162
164
 
163
165
  ### Live Operations
164
166
  | Tool | What It Does |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velaro/mcp-server",
3
- "version": "2.0.12",
3
+ "version": "2.0.14",
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
@@ -439,9 +439,25 @@ const TOOLS = [
439
439
  corporateSignature: { type: ['string', 'null'], description: 'Company signature HTML, max 4000 characters. Empty or null clears it.' },
440
440
  disclaimerEnabled: { type: ['boolean', 'null'], description: 'Turn the legal disclaimer on or off. null follows the company-wide choice. Off by default.' },
441
441
  disclaimerText: { type: ['string', 'null'], description: 'Legal disclaimer text, max 2000 characters. Empty or null clears it.' },
442
+ nameFormat: { type: ['string', 'null'], enum: ['none', 'name', 'team', 'name_and_team', 'default', null], description: 'Name line added above the signature: none | name | team | name_and_team. default/null inherits. Platform default is none.' },
443
+ nameSource: { type: ['string', 'null'], enum: ['real_name', 'display_name', 'default', null], description: 'Which name the name line uses. The display name falls back to the real name when the agent has none. Platform default is real_name.' },
444
+ teamLabel: { type: ['string', 'null'], description: 'Team name shown by the team formats, max 100 characters (for example Customer Support). Empty or null uses the routing group name.' },
445
+ allowTeamOverrides: { type: ['boolean', 'null'], description: 'Company-wide row only: may team admins change group signature settings. null = allowed.' },
446
+ allowAgentOverrides:{ type: ['boolean', 'null'], description: 'Company-wide row only: may agents choose their own name line (never while locked). null = allowed.' },
447
+ applyTo: { type: 'string', enum: ['inheritingGroups', 'allGroups', 'newGroupsOnly'], description: 'Company-wide row only: how a name line change reaches existing groups.' },
442
448
  },
443
449
  },
444
450
  },
451
+ {
452
+ name: 'signature_policy_history',
453
+ description: 'List saved versions of the signature policy for the company-wide row (teamId 0) or one team, newest first, with who changed it and when. Admin only.',
454
+ inputSchema: { type: 'object', properties: { teamId: { type: 'integer', description: '0 = company-wide (default)' } } },
455
+ },
456
+ {
457
+ name: 'signature_policy_rollback',
458
+ description: 'Restore a saved version of the signature policy (site administrators only). The restored settings are saved as a NEW version. Use signature_policy_history to find the versionId.',
459
+ inputSchema: { type: 'object', properties: { versionId: { type: 'integer' } }, required: ['versionId'] },
460
+ },
445
461
  {
446
462
  name: 'kb_list_topics',
447
463
  description: 'List all KB topics. Call this first to find the topicId needed for creating articles.',
@@ -3450,6 +3466,7 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
3450
3466
  applicationInstanceObjectId: { type: 'string', description: 'Teams resource account (application instance) object ID. Pass "" to clear.' },
3451
3467
  assignedPhoneNumber: { type: 'string', description: 'Phone number assigned to the resource account. Pass "" to clear.' },
3452
3468
  webhookSecret: { type: 'string', description: 'Webhook secret. Omit to auto-generate on create or leave unchanged on update.' },
3469
+ queueMaxWaitSeconds: { type: 'number', description: 'Site default for how long a caller waits for a free agent, 30 to 600 seconds. Omit to leave unchanged; pass 0 to reset to the platform default (120). Valid values are 0 or 30 to 600.' },
3453
3470
  },
3454
3471
  required: [],
3455
3472
  },
@@ -3461,7 +3478,7 @@ For multiSelect dynamic options: the variable named in dynamicOptionsVariable mu
3461
3478
  },
3462
3479
  {
3463
3480
  name: 'teams_phone_test_config',
3464
- description: 'Run the Teams Phone readiness checks and return each check (name, ok, message) plus callingReady. Surfaces what is missing (admin consent, resource account, Graph reachability) rather than only being discovered when a call is missed. Lives in velaro-messaging — requires VELARO_MESSAGING_API.',
3481
+ description: 'Run the Teams Phone calling readiness checks on the ACS path and return each check (name, ok, message) plus callingReady: tenant ID saved, resource account saved, admin consent and inbound call routing. A response with no checks is never a pass. Surfaces what is missing rather than only being discovered when a call is missed. Lives in velaro-messaging — requires VELARO_MESSAGING_API.',
3465
3482
  inputSchema: { type: 'object', properties: {}, required: [] },
3466
3483
  },
3467
3484
 
@@ -5009,7 +5026,35 @@ async function handleTool(name, args) {
5009
5026
  corporateSignature: cur.corporateSignature || null,
5010
5027
  disclaimerEnabled: cur.disclaimerEnabled ?? null,
5011
5028
  disclaimerText: cur.disclaimerText || null,
5029
+ // PUT replaces the row: carry the flow-down fields or a mode change would wipe them.
5030
+ nameSource: cur.nameSource ?? null,
5031
+ nameFormat: cur.nameFormat ?? null,
5032
+ teamLabel: cur.teamLabel || null,
5033
+ allowTeamOverrides: cur.allowTeamOverrides ?? null,
5034
+ allowAgentOverrides: cur.allowAgentOverrides ?? null,
5012
5035
  };
5036
+ const nameEnum = (v, allowed, label) => {
5037
+ if (v === undefined) return undefined;
5038
+ if (v === null || v === 'default') return null;
5039
+ if (!allowed.includes(v)) throw new Error(`${label} must be one of ${allowed.join(', ')} or default`);
5040
+ return v;
5041
+ };
5042
+ const nf = nameEnum(args.nameFormat, ['none', 'name', 'team', 'name_and_team'], 'nameFormat');
5043
+ if (nf !== undefined) body.nameFormat = nf;
5044
+ const ns = nameEnum(args.nameSource, ['real_name', 'display_name'], 'nameSource');
5045
+ if (ns !== undefined) body.nameSource = ns;
5046
+ if (args.teamLabel !== undefined) body.teamLabel = args.teamLabel || null;
5047
+ if (body.teamLabel && body.teamLabel.length > 100) throw new Error('teamLabel is limited to 100 characters');
5048
+ for (const k of ['allowTeamOverrides', 'allowAgentOverrides']) {
5049
+ if (args[k] !== undefined) {
5050
+ if (args[k] !== null && typeof args[k] !== 'boolean') throw new Error(`${k} must be true, false or null`);
5051
+ body[k] = args[k];
5052
+ }
5053
+ }
5054
+ if (args.applyTo !== undefined) {
5055
+ if (!['inheritingGroups', 'allGroups', 'newGroupsOnly'].includes(args.applyTo)) throw new Error('applyTo must be inheritingGroups, allGroups or newGroupsOnly');
5056
+ body.applyTo = args.applyTo;
5057
+ }
5013
5058
  if (args.mode !== undefined) {
5014
5059
  if (args.mode === null || args.mode === 'default') body.mode = null;
5015
5060
  else if (modes[args.mode]) body.mode = modes[args.mode];
@@ -5027,6 +5072,17 @@ async function handleTool(name, args) {
5027
5072
  return JSON.stringify(await messagingGet('/Signatures/policy'), null, 2);
5028
5073
  }
5029
5074
 
5075
+ case 'signature_policy_history': {
5076
+ const tid = args.teamId === undefined || args.teamId === null ? 0 : args.teamId;
5077
+ if (!Number.isInteger(tid) || tid < 0) throw new Error('teamId must be 0 (company-wide) or a team id');
5078
+ return JSON.stringify(await messagingGet(`/Signatures/policy/history?teamId=${tid}`), null, 2);
5079
+ }
5080
+
5081
+ case 'signature_policy_rollback': {
5082
+ if (!Number.isInteger(args.versionId) || args.versionId <= 0) throw new Error('versionId must be a positive integer');
5083
+ return JSON.stringify(await messagingPost(`/Signatures/policy/rollback/${args.versionId}`, {}), null, 2);
5084
+ }
5085
+
5030
5086
  case 'kb_list_topics': {
5031
5087
  const data = await api('GET', '/api/kb/topics');
5032
5088
  const topics = data?.topics ?? [];
@@ -7777,7 +7833,11 @@ reply: ${r?.reply}`;
7777
7833
  applicationInstanceObjectId: args.applicationInstanceObjectId,
7778
7834
  assignedPhoneNumber: args.assignedPhoneNumber,
7779
7835
  webhookSecret: args.webhookSecret,
7836
+ queueMaxWaitSeconds: args.queueMaxWaitSeconds,
7780
7837
  };
7838
+ if (args.queueMaxWaitSeconds != null && (!Number.isInteger(args.queueMaxWaitSeconds) || (args.queueMaxWaitSeconds !== 0 && (args.queueMaxWaitSeconds < 30 || args.queueMaxWaitSeconds > 600)))) {
7839
+ throw new Error('queueMaxWaitSeconds must be 0 (reset to default) or a whole number from 30 to 600.');
7840
+ }
7781
7841
  const result = existing?.configured
7782
7842
  ? await messagingPut('/TeamsPhone/Config', body)
7783
7843
  : await messagingPost('/TeamsPhone/Config', body);
@@ -7789,7 +7849,9 @@ reply: ${r?.reply}`;
7789
7849
  }
7790
7850
  case 'teams_phone_test_config': {
7791
7851
  const result = await messagingPost('/TeamsPhone/Config/Test', {});
7792
- const lines = [`${result.success ? 'Success' : 'Failed'}: ${result.message ?? '(no message)'}`];
7852
+ const noChecks = !Array.isArray(result.checks) || result.checks.length === 0;
7853
+ const passed = result.success === true && !noChecks;
7854
+ const lines = [`${passed ? 'Success' : 'Failed'}: ${result.message ?? (noChecks ? 'The test returned no checks, so nothing was verified.' : '(no message)')}`];
7793
7855
  if (typeof result.callingReady === 'boolean') lines.push(`Inbound calling ready: ${result.callingReady}`);
7794
7856
  if (Array.isArray(result.checks)) {
7795
7857
  for (const ck of result.checks) lines.push(`${ck?.ok ? 'PASS' : 'FAIL'} ${ck?.name ?? ''}${ck?.message ? ': ' + ck.message : ''}`);