@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.
@@ -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.92",
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",