@sendinel/mcp-server 1.0.152 → 1.0.153

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.
@@ -2,7 +2,7 @@ import { normalizeTagInput, tagInputWarnings } from '../lib/tags.js';
2
2
  import { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
3
3
  import { CONTACT_ENRICHMENT_PROVIDERS } from '@desler/models';
4
4
  import { createContactEnrichmentAdapters, enrichContactFromConnections, } from '@desler/models/contact-enrichment';
5
- import { buildPreviewAttentionEnvelope, applySegmentRules, isSegmentRulesError, ContactPropertyAnomalyLogger, normalizeContactProperties, getAttentionDecision, getContactNeighborhood, getScheduledTouches, findContactDuplicateGroups, readAllPages, readAllRows, chunkIds, } from '@sendinel/domain';
5
+ import { buildPreviewAttentionEnvelope, applySegmentRules, isSegmentRulesError, ContactPropertyAnomalyLogger, normalizeContactProperties, validateContactPropertyPatch, getAttentionDecision, getContactNeighborhood, getScheduledTouches, findContactDuplicateGroups, readAllPages, readAllRows, chunkIds, } from '@sendinel/domain';
6
6
  import { z } from 'zod';
7
7
  import { callSendinelV1 } from '../lib/v1-client.js';
8
8
  import { db, projectClientForWriteback } from '../db.js';
@@ -45,22 +45,6 @@ const CUSTOM_FIELD_COLUMNS = 'id, key, label, type, required, options, display_o
45
45
  async function registeredCustomFields(projectId) {
46
46
  return readAllRows(db().from('custom_field_definitions').select(CUSTOM_FIELD_COLUMNS).eq('project_id', projectId));
47
47
  }
48
- function keyDistance(left, right) {
49
- let row = Array.from({ length: right.length + 1 }, (_, index) => index);
50
- for (let i = 0; i < left.length; i++) {
51
- const next = [i + 1];
52
- for (let j = 0; j < right.length; j++)
53
- next.push(Math.min(next[j] + 1, row[j + 1] + 1, row[j] + (left[i] === right[j] ? 0 : 1)));
54
- row = next;
55
- }
56
- return row[right.length];
57
- }
58
- function unknownCustomPropertyWarnings(patch, fields) {
59
- return Object.keys(patch).filter(key => !Object.hasOwn(fields, key)).map(key => {
60
- const near = Object.keys(fields).filter(known => keyDistance(key, known) <= 2);
61
- return `Unknown custom property "${key}"${near.length ? `; did you mean ${near.map(value => `"${value}"`).join(' or ')}?` : '; no registered definition'}`;
62
- });
63
- }
64
48
  const SUBSCRIBER_VIEW_MAX_NAME_LENGTH = 100;
65
49
  const SUBSCRIBER_VIEW_MAX_COLUMNS = 150;
66
50
  const SUBSCRIBER_VIEW_KEY_RE = /^[a-zA-Z0-9_.:-]{1,160}$/;
@@ -1836,21 +1820,10 @@ Requirements: urgency without desperation, acknowledge the gap, one clear CTA, v
1836
1820
  return j({ error: 'Custom field definitions unavailable; update not performed', code: 'INTERNAL_ERROR' });
1837
1821
  }
1838
1822
  fields = Object.fromEntries(definitions.map(field => [field.key, field]));
1839
- const values = Object.fromEntries(Object.entries(custom_properties).filter(([, value]) => value !== null));
1840
- const invalidShape = Object.entries(values).find(([key, value]) => {
1841
- if (!Object.hasOwn(fields, key))
1842
- return false;
1843
- const type = fields[key].type;
1844
- if (type === 'multi_select')
1845
- return typeof value !== 'string' && (!Array.isArray(value) || value.some(item => typeof item !== 'string'));
1846
- return typeof value === 'object' && type !== 'contact_reference';
1847
- });
1848
- const { anomalies } = normalizeContactProperties(values, { fields });
1849
- if (invalidShape || anomalies.length) {
1850
- const key = invalidShape?.[0] ?? anomalies[0].key;
1851
- return contactInputError('validation_failed', `custom_properties.${key} does not match its registered ${fields[key].type} definition or options`);
1852
- }
1853
- propertyWarnings = unknownCustomPropertyWarnings(custom_properties, fields);
1823
+ const validation = validateContactPropertyPatch(custom_properties, fields);
1824
+ if (validation.error)
1825
+ return contactInputError('validation_failed', validation.error);
1826
+ propertyWarnings = validation.warnings;
1854
1827
  }
1855
1828
  const embedOk = await hasCompaniesEmbed(projectId);
1856
1829
  const updates = {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sendinel/mcp-server",
3
- "version": "1.0.152",
3
+ "version": "1.0.153",
4
4
  "description": "AI-controlled email operations control plane for campaigns, contacts, segments, analytics, and multi-provider sending via MCP.",
5
5
  "mcpName": "ai.sendinel/mcp-server",
6
6
  "type": "module",