@sendinel/mcp-server 1.0.46 → 1.0.47

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.
@@ -625,15 +625,33 @@ export function registerContactTools(server) {
625
625
  }, async () => {
626
626
  const projectId = getProjectId();
627
627
  const cutoff = daysAgo(90);
628
- // Deliberately NOT paged in #3755: the update below REPLACES each contact's tags with ['cold']
629
- // instead of adding it. Paging would widen that data loss to every inactive contact. Fix the
630
- // overwrite first (#3834), then page this read.
631
- const { data: contacts } = await db().from('subscribers').select('id, email, tags, last_engaged_at').eq('project_id', projectId).lt('last_engaged_at', cutoff).limit(1000);
632
- const ids = (contacts ?? []).map((contact) => contact.id);
633
- if (ids.length) {
634
- await db().from('subscribers').update({ tags: ['cold'] }).eq('project_id', projectId).in('id', ids);
635
- }
636
- return j({ tagged: ids.length, suppressed: 0, skipped: 0 });
628
+ // Overwrite of existing tags is fixed (#3834). Writes are per-row, so paging this
629
+ // read past 1000 is now safe as a follow-up.
630
+ const { data: contacts, error: readError } = await db().from('subscribers').select('id, email, first_name, last_name, custom_properties, tags, last_engaged_at').eq('project_id', projectId).lt('last_engaged_at', cutoff).limit(1000);
631
+ if (readError)
632
+ return j({ error: readError.message });
633
+ let tagged = 0;
634
+ let skipped = 0;
635
+ let failed = 0;
636
+ let firstError;
637
+ for (const contact of contacts ?? []) {
638
+ const current = contact.tags ?? [];
639
+ if (current.includes('cold')) {
640
+ skipped++;
641
+ continue;
642
+ }
643
+ const merged = [...current, 'cold'];
644
+ const { error } = await db().from('subscribers').update({ tags: merged }).eq('id', contact.id).eq('project_id', projectId);
645
+ if (error) {
646
+ failed++;
647
+ if (!firstError)
648
+ firstError = error.message;
649
+ continue;
650
+ }
651
+ tagged++;
652
+ await writeBackMcpContact(projectId, contact, { tags: merged });
653
+ }
654
+ return j({ tagged, suppressed: 0, skipped, ...(failed ? { failed, error: firstError } : {}) });
637
655
  });
638
656
  server.registerTool('list_hygiene_audit', {
639
657
  title: 'List Hygiene Audit',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sendinel/mcp-server",
3
- "version": "1.0.46",
3
+ "version": "1.0.47",
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",