@sendinel/mcp-server 1.0.151 → 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.
|
@@ -13,6 +13,7 @@ type CampaignSetRow = {
|
|
|
13
13
|
recipient_count?: number | null;
|
|
14
14
|
audience?: Record<string, unknown> | null;
|
|
15
15
|
audience_error?: string;
|
|
16
|
+
code?: string;
|
|
16
17
|
};
|
|
17
18
|
type StepRow = {
|
|
18
19
|
campaign_id: string;
|
|
@@ -41,6 +42,7 @@ export declare function previewOf(campaigns: CampaignSetRow[], steps: StepRow[])
|
|
|
41
42
|
recipient_count?: number | null | undefined;
|
|
42
43
|
audience?: Record<string, unknown> | null | undefined;
|
|
43
44
|
audience_error?: string | undefined;
|
|
45
|
+
code?: string | undefined;
|
|
44
46
|
activation?: string | undefined;
|
|
45
47
|
emails: {
|
|
46
48
|
step_order: number;
|
|
@@ -77,7 +77,7 @@ export function previewOf(campaigns, steps) {
|
|
|
77
77
|
...(c.type === 'broadcast' ? {
|
|
78
78
|
recipient_count: c.recipient_count ?? null,
|
|
79
79
|
audience: c.audience ?? null,
|
|
80
|
-
...(c.audience_error ? { audience_error: c.audience_error } : {}),
|
|
80
|
+
...(c.audience_error ? { audience_error: c.audience_error, code: c.code } : {}),
|
|
81
81
|
activation: BROADCAST_ACTIVATION,
|
|
82
82
|
} : {}),
|
|
83
83
|
emails: steps
|
|
@@ -114,8 +114,10 @@ export async function loadSet(projectId, campaignIds) {
|
|
|
114
114
|
Object.assign(campaign, { recipient_count, audience });
|
|
115
115
|
}
|
|
116
116
|
catch (error) {
|
|
117
|
+
console.error("[campaign-sets] Audience lookup failed:", error);
|
|
118
|
+
const safe = dbToolError(error, "Unable to load campaign audience");
|
|
117
119
|
Object.assign(campaign, { recipient_count: null, audience: null,
|
|
118
|
-
audience_error: error
|
|
120
|
+
audience_error: safe.error, code: safe.code });
|
|
119
121
|
}
|
|
120
122
|
}
|
|
121
123
|
return { campaigns: loaded, steps: (steps ?? []) };
|
package/build/tools/campaigns.js
CHANGED
|
@@ -2842,11 +2842,12 @@ Do not include body_html in your response; Sendinel renders body_html from block
|
|
|
2842
2842
|
}
|
|
2843
2843
|
catch (err) {
|
|
2844
2844
|
await db().from('email_log').update({ status: 'failed' }).eq('id', logRow.id);
|
|
2845
|
+
console.error("[campaigns] Reply action mint failed:", err);
|
|
2845
2846
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
2846
2847
|
if (errMsg.includes('23505') || errMsg.includes('uidx_reply_actions_pending_correlation') || errMsg.includes('already pending')) {
|
|
2847
2848
|
return txt('An awaiting-reply send for this correlation_id is already pending.');
|
|
2848
2849
|
}
|
|
2849
|
-
return
|
|
2850
|
+
return toolError(dbToolError(err, "Failed to mint reply action"));
|
|
2850
2851
|
}
|
|
2851
2852
|
const payload = {
|
|
2852
2853
|
from: `${site.from_name} <${site.from_email}>`,
|
package/build/tools/compound.js
CHANGED
|
@@ -6,7 +6,7 @@ import { getProjectId } from '../project.js';
|
|
|
6
6
|
import { buildVoicePrompt, deriveTemplateVariables } from '../lib/voice.js';
|
|
7
7
|
import { parseModelJson, getAnthropicFastModelId } from '../anthropic-model.js';
|
|
8
8
|
import { BLOCK_GENERATION_SCHEMA, blocksToHtml, blocksToText, normalizeComposerBlocks } from '../lib/composer-blocks.js';
|
|
9
|
-
import { dbToolError } from '../lib/db-error.js';
|
|
9
|
+
import { dbToolError, toolError } from '../lib/db-error.js';
|
|
10
10
|
// Haiku 5.5 (fast role) thinks before answering; thinking counts toward max_tokens (#4193).
|
|
11
11
|
const FAST_MIN_MAX_TOKENS = 8000;
|
|
12
12
|
const j = (data) => ({ content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] });
|
|
@@ -221,7 +221,8 @@ Respond with JSON: { "subject": "...", "blocks": [...], "body_text": "..." }`,
|
|
|
221
221
|
};
|
|
222
222
|
}
|
|
223
223
|
catch (error) {
|
|
224
|
-
|
|
224
|
+
console.error("[compound] Generation failed:", error);
|
|
225
|
+
return toolError(dbToolError(error, "Generation failed: unable to generate template"));
|
|
225
226
|
}
|
|
226
227
|
const { data: template, error } = await db().from('campaign_templates')
|
|
227
228
|
.insert({
|
package/build/tools/contacts.js
CHANGED
|
@@ -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}$/;
|
|
@@ -182,7 +166,8 @@ async function writeBackMcpContact(projectId, contact, patch) {
|
|
|
182
166
|
await customer.schema('email').from('subscribers').update(update).eq('id', target.rowId);
|
|
183
167
|
}
|
|
184
168
|
catch (error) {
|
|
185
|
-
|
|
169
|
+
console.error('[contacts] BYOD writeback failed:', error);
|
|
170
|
+
logAudit({ action: 'byod_writeback_failed', resourceType: 'contact', resourceId: contact?.id, details: { email: contact?.email, ...dbToolError(error, 'Contact writeback failed') } });
|
|
186
171
|
}
|
|
187
172
|
}
|
|
188
173
|
function daysAgo(days) {
|
|
@@ -1835,21 +1820,10 @@ Requirements: urgency without desperation, acknowledge the gap, one clear CTA, v
|
|
|
1835
1820
|
return j({ error: 'Custom field definitions unavailable; update not performed', code: 'INTERNAL_ERROR' });
|
|
1836
1821
|
}
|
|
1837
1822
|
fields = Object.fromEntries(definitions.map(field => [field.key, field]));
|
|
1838
|
-
const
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
const type = fields[key].type;
|
|
1843
|
-
if (type === 'multi_select')
|
|
1844
|
-
return typeof value !== 'string' && (!Array.isArray(value) || value.some(item => typeof item !== 'string'));
|
|
1845
|
-
return typeof value === 'object' && type !== 'contact_reference';
|
|
1846
|
-
});
|
|
1847
|
-
const { anomalies } = normalizeContactProperties(values, { fields });
|
|
1848
|
-
if (invalidShape || anomalies.length) {
|
|
1849
|
-
const key = invalidShape?.[0] ?? anomalies[0].key;
|
|
1850
|
-
return contactInputError('validation_failed', `custom_properties.${key} does not match its registered ${fields[key].type} definition or options`);
|
|
1851
|
-
}
|
|
1852
|
-
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;
|
|
1853
1827
|
}
|
|
1854
1828
|
const embedOk = await hasCompaniesEmbed(projectId);
|
|
1855
1829
|
const updates = {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sendinel/mcp-server",
|
|
3
|
-
"version": "1.0.
|
|
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",
|