@sendinel/mcp-server 1.0.92 → 1.0.93
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.
- package/build/tools/campaigns.js +23 -11
- package/package.json +1 -1
package/build/tools/campaigns.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import Anthropic from '@anthropic-ai/sdk';
|
|
3
|
-
import { assertSendAccepted, SendRefusedError, BLOCKING_CONTENT_ISSUE_KINDS, computeSendContentKey, findContentIssues, sendingHostOf, NAME_FALLBACKS, ONE_SEND_MATCH_STATUSES, ONE_SEND_SENT_STATUSES, ONE_SEND_WINDOW_DAYS, normalizeTriggerConfig, renderTemplateString as renderTemplateVars, resolveBrandTokens, readAllRows, } from '@sendinel/domain';
|
|
3
|
+
import { assertSendAccepted, SendRefusedError, BLOCKING_CONTENT_ISSUE_KINDS, computeSendContentKey, findContentIssues, sendingHostOf, NAME_FALLBACKS, ONE_SEND_MATCH_STATUSES, ONE_SEND_SENT_STATUSES, ONE_SEND_WINDOW_DAYS, normalizeTriggerConfig, renderTemplateString as renderTemplateVars, resolveBrandTokens, brandWarnings, readAllRows, } from '@sendinel/domain';
|
|
4
4
|
// Subpath import, not the barrel above -- reply-actions.ts uses node:crypto,
|
|
5
5
|
// and MCP-server-side that's fine (this never runs in an Edge Function), but
|
|
6
6
|
// keeping it off the shared barrel avoids re-creating the Edge-Function
|
|
@@ -2342,7 +2342,7 @@ Do not include body_html in your response; Sendinel renders body_html from block
|
|
|
2342
2342
|
// Site verification
|
|
2343
2343
|
const { data: site } = await db()
|
|
2344
2344
|
.from('sites')
|
|
2345
|
-
.select('from_email, from_name, name, brand_voice')
|
|
2345
|
+
.select('from_email, from_name, name, brand_voice, physical_address')
|
|
2346
2346
|
.eq('id', site_id)
|
|
2347
2347
|
.eq('project_id', projectId)
|
|
2348
2348
|
.maybeSingle();
|
|
@@ -2351,6 +2351,7 @@ Do not include body_html in your response; Sendinel renders body_html from block
|
|
|
2351
2351
|
let resolvedSubject = subject;
|
|
2352
2352
|
let resolvedHtml = html;
|
|
2353
2353
|
let resolvedText = text;
|
|
2354
|
+
let brandWarningCodes = [];
|
|
2354
2355
|
if (template_id) {
|
|
2355
2356
|
const { data: template, error: templateError } = await db()
|
|
2356
2357
|
.from('campaign_templates')
|
|
@@ -2366,7 +2367,10 @@ Do not include body_html in your response; Sendinel renders body_html from block
|
|
|
2366
2367
|
const projectBrand = (project?.brand_settings ?? null);
|
|
2367
2368
|
const siteBrand = (site.brand_voice ?? null);
|
|
2368
2369
|
const mergedBrand = projectBrand || siteBrand ? { ...(projectBrand ?? {}), ...(siteBrand ?? {}) } : null;
|
|
2369
|
-
const brandVars = resolveBrandTokens(mergedBrand, site.name || undefined);
|
|
2370
|
+
const brandVars = resolveBrandTokens(mergedBrand, site.name || undefined, { address: site.physical_address ?? undefined });
|
|
2371
|
+
brandWarningCodes = brandWarnings(mergedBrand);
|
|
2372
|
+
if (brandWarningCodes.length)
|
|
2373
|
+
console.warn('MCP send brand warnings', { projectId, site_id, warnings: brandWarningCodes });
|
|
2370
2374
|
const vars = template_vars ?? {};
|
|
2371
2375
|
const renderVars = { ...brandVars, ...vars };
|
|
2372
2376
|
const subjectFallingBack = !subject?.trim();
|
|
@@ -2378,6 +2382,7 @@ Do not include body_html in your response; Sendinel renders body_html from block
|
|
|
2378
2382
|
const satisfied = new Set([
|
|
2379
2383
|
...Object.keys(vars),
|
|
2380
2384
|
'brand_name', 'brand_logo_url', 'brand_bg_color', 'brand_text_color', 'brand_accent_color',
|
|
2385
|
+
'brand_logo_html', 'brand_font', 'brand_address',
|
|
2381
2386
|
...Object.keys(NAME_FALLBACKS),
|
|
2382
2387
|
]);
|
|
2383
2388
|
const missing = requiredVars.filter((name) => !satisfied.has(name));
|
|
@@ -2385,14 +2390,14 @@ Do not include body_html in your response; Sendinel renders body_html from block
|
|
|
2385
2390
|
return txt(`Error: Missing required template variable(s): ${missing.join(', ')}`);
|
|
2386
2391
|
}
|
|
2387
2392
|
if (template.format === 'text') {
|
|
2388
|
-
resolvedText = renderTemplateVars(template.body_text ?? '', renderVars);
|
|
2393
|
+
resolvedText = renderTemplateVars(template.body_text ?? '', renderVars, { brandTokens: brandVars });
|
|
2389
2394
|
resolvedHtml = undefined;
|
|
2390
2395
|
}
|
|
2391
2396
|
else {
|
|
2392
|
-
resolvedHtml = renderTemplateVars(template.body_html ?? '', renderVars);
|
|
2397
|
+
resolvedHtml = renderTemplateVars(template.body_html ?? '', renderVars, { brandTokens: brandVars });
|
|
2393
2398
|
resolvedText = text ?? stripHtml(resolvedHtml);
|
|
2394
2399
|
}
|
|
2395
|
-
resolvedSubject = subject?.trim() || renderTemplateVars(template.subject_hint ?? '', renderVars);
|
|
2400
|
+
resolvedSubject = subject?.trim() || renderTemplateVars(template.subject_hint ?? '', renderVars, { brandTokens: brandVars });
|
|
2396
2401
|
}
|
|
2397
2402
|
if (!resolvedSubject?.trim())
|
|
2398
2403
|
return txt('Error: subject is required');
|
|
@@ -2432,6 +2437,7 @@ Do not include body_html in your response; Sendinel renders body_html from block
|
|
|
2432
2437
|
}).select('id').single();
|
|
2433
2438
|
return j({
|
|
2434
2439
|
sent: true,
|
|
2440
|
+
...(brandWarningCodes.length ? { warnings: brandWarningCodes } : {}),
|
|
2435
2441
|
id: logRow?.id,
|
|
2436
2442
|
to,
|
|
2437
2443
|
provider: sendResult.provider,
|
|
@@ -2525,7 +2531,7 @@ Do not include body_html in your response; Sendinel renders body_html from block
|
|
|
2525
2531
|
// Site verification
|
|
2526
2532
|
const { data: site } = await db()
|
|
2527
2533
|
.from('sites')
|
|
2528
|
-
.select('from_email, from_name, name, brand_voice')
|
|
2534
|
+
.select('from_email, from_name, name, brand_voice, physical_address')
|
|
2529
2535
|
.eq('id', site_id)
|
|
2530
2536
|
.eq('project_id', projectId)
|
|
2531
2537
|
.maybeSingle();
|
|
@@ -2534,6 +2540,7 @@ Do not include body_html in your response; Sendinel renders body_html from block
|
|
|
2534
2540
|
let resolvedSubject = subject;
|
|
2535
2541
|
let resolvedHtml = html;
|
|
2536
2542
|
let resolvedText = text;
|
|
2543
|
+
let brandWarningCodes = [];
|
|
2537
2544
|
if (template_id) {
|
|
2538
2545
|
const { data: template, error: templateError } = await db()
|
|
2539
2546
|
.from('campaign_templates')
|
|
@@ -2549,7 +2556,10 @@ Do not include body_html in your response; Sendinel renders body_html from block
|
|
|
2549
2556
|
const projectBrand = (project?.brand_settings ?? null);
|
|
2550
2557
|
const siteBrand = (site.brand_voice ?? null);
|
|
2551
2558
|
const mergedBrand = projectBrand || siteBrand ? { ...(projectBrand ?? {}), ...(siteBrand ?? {}) } : null;
|
|
2552
|
-
const brandVars = resolveBrandTokens(mergedBrand, site.name || undefined);
|
|
2559
|
+
const brandVars = resolveBrandTokens(mergedBrand, site.name || undefined, { address: site.physical_address ?? undefined });
|
|
2560
|
+
brandWarningCodes = brandWarnings(mergedBrand);
|
|
2561
|
+
if (brandWarningCodes.length)
|
|
2562
|
+
console.warn('MCP send brand warnings', { projectId, site_id, warnings: brandWarningCodes });
|
|
2553
2563
|
const vars = template_vars ?? {};
|
|
2554
2564
|
const renderVars = { ...brandVars, ...vars };
|
|
2555
2565
|
const subjectFallingBack = !subject?.trim();
|
|
@@ -2561,6 +2571,7 @@ Do not include body_html in your response; Sendinel renders body_html from block
|
|
|
2561
2571
|
const satisfied = new Set([
|
|
2562
2572
|
...Object.keys(vars),
|
|
2563
2573
|
'brand_name', 'brand_logo_url', 'brand_bg_color', 'brand_text_color', 'brand_accent_color',
|
|
2574
|
+
'brand_logo_html', 'brand_font', 'brand_address',
|
|
2564
2575
|
...Object.keys(NAME_FALLBACKS),
|
|
2565
2576
|
]);
|
|
2566
2577
|
const missing = requiredVars.filter((name) => !satisfied.has(name));
|
|
@@ -2568,14 +2579,14 @@ Do not include body_html in your response; Sendinel renders body_html from block
|
|
|
2568
2579
|
return txt(`Error: Missing required template variable(s): ${missing.join(', ')}`);
|
|
2569
2580
|
}
|
|
2570
2581
|
if (template.format === 'text') {
|
|
2571
|
-
resolvedText = renderTemplateVars(template.body_text ?? '', renderVars);
|
|
2582
|
+
resolvedText = renderTemplateVars(template.body_text ?? '', renderVars, { brandTokens: brandVars });
|
|
2572
2583
|
resolvedHtml = undefined;
|
|
2573
2584
|
}
|
|
2574
2585
|
else {
|
|
2575
|
-
resolvedHtml = renderTemplateVars(template.body_html ?? '', renderVars);
|
|
2586
|
+
resolvedHtml = renderTemplateVars(template.body_html ?? '', renderVars, { brandTokens: brandVars });
|
|
2576
2587
|
resolvedText = text ?? stripHtml(resolvedHtml);
|
|
2577
2588
|
}
|
|
2578
|
-
resolvedSubject = subject?.trim() || renderTemplateVars(template.subject_hint ?? '', renderVars);
|
|
2589
|
+
resolvedSubject = subject?.trim() || renderTemplateVars(template.subject_hint ?? '', renderVars, { brandTokens: brandVars });
|
|
2579
2590
|
}
|
|
2580
2591
|
if (!resolvedSubject?.trim())
|
|
2581
2592
|
return txt('Error: subject is required');
|
|
@@ -2666,6 +2677,7 @@ Do not include body_html in your response; Sendinel renders body_html from block
|
|
|
2666
2677
|
}).eq('id', logRow.id);
|
|
2667
2678
|
return j({
|
|
2668
2679
|
sent: true,
|
|
2680
|
+
...(brandWarningCodes.length ? { warnings: brandWarningCodes } : {}),
|
|
2669
2681
|
id: logRow.id,
|
|
2670
2682
|
reply_action_id: minted.id,
|
|
2671
2683
|
correlation_id,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sendinel/mcp-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.93",
|
|
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",
|