@sendinel/mcp-server 1.0.114 → 1.0.115

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.
@@ -31,6 +31,7 @@ import { callSendinelV1 } from '../lib/v1-client.js';
31
31
  import { countUniqueEngagementByCampaign } from '../lib/campaign-engagement.js';
32
32
  const txt = (text) => ({ content: [{ type: 'text', text }] });
33
33
  const j = (data) => txt(JSON.stringify(data, null, 2));
34
+ const ACTIVATION_APPROVAL_DESCRIPTION = 'Activation-only: requires_approval gates campaign activation. It does not stop sends of an already-active campaign or sends triggered by other paths.';
34
35
  const SENT_EMAIL_STATUSES = ['sent', 'delivered', 'opened', 'clicked', 'bounced', 'complained', 'unsubscribed'];
35
36
  function stripHtml(value) {
36
37
  return value
@@ -642,7 +643,7 @@ export function registerCampaignTools(server) {
642
643
  idempotentHint: true,
643
644
  openWorldHint: false,
644
645
  },
645
- description: 'List campaigns with saved trigger fields, fires_on summaries and trigger_diagnostics; optionally filter by site or status.',
646
+ description: 'List campaigns with saved trigger fields, fires_on summaries and trigger_diagnostics; optionally filter by site or status. ' + ACTIVATION_APPROVAL_DESCRIPTION,
646
647
  inputSchema: {
647
648
  site_id: z.string().uuid().optional(),
648
649
  period: z.string().min(1).optional().describe("Return one enrollment period in by_period; totals remain all-time."),
@@ -652,7 +653,7 @@ export function registerCampaignTools(server) {
652
653
  }, async ({ site_id, status, limit, period }) => {
653
654
  let q = db()
654
655
  .from('campaigns')
655
- .select('id, site_id, name, type, status, sends_per_hour, total_enrolled, total_sent, total_opened, total_clicked, created_at, period_key, segment_id, trigger_type, trigger_event_name, trigger_tag, trigger_config, send_class, unsub_footer_template')
656
+ .select('id, site_id, name, type, status, sends_per_hour, total_enrolled, total_sent, total_opened, total_clicked, created_at, period_key, segment_id, trigger_type, trigger_event_name, trigger_tag, trigger_config, send_class, unsub_footer_template, requires_approval, approved_by, approved_at')
656
657
  .eq('project_id', getProjectId())
657
658
  .order('created_at', { ascending: false })
658
659
  .limit(limit ?? 25);
@@ -672,6 +673,7 @@ export function registerCampaignTools(server) {
672
673
  const { period_key: _periodKey, ...legacy } = row;
673
674
  return {
674
675
  ...legacy,
676
+ requires_approval_description: ACTIVATION_APPROVAL_DESCRIPTION,
675
677
  fires_on: buildCampaignFiresOn(row),
676
678
  trigger_diagnostics: buildCampaignTriggerDiagnostics(row),
677
679
  ...campaignPeriodFields(periods, period),
@@ -735,7 +737,7 @@ export function registerCampaignTools(server) {
735
737
  type: z.enum(['sequence', 'broadcast', 'triggered']),
736
738
  description: z.string().optional(),
737
739
  sends_per_hour: z.number().int().min(1).nullable().optional().describe('Optional hourly send cap for wave sending. Null or omitted sends without a campaign-specific cap.'),
738
- requires_approval: z.boolean().optional().default(true),
740
+ requires_approval: z.boolean().optional().default(true).describe(ACTIVATION_APPROVAL_DESCRIPTION),
739
741
  trigger_event_name: z.string().optional().describe('For type=triggered: event name that fires this campaign'),
740
742
  trigger_tag: z.string().optional().describe('For type=triggered: contact tag that fires this campaign when applied'),
741
743
  trigger_type: z.enum(['date_field']).optional().describe('Set for elapsed-time triggers keyed on a contact custom_properties date field'),
@@ -773,7 +775,7 @@ export function registerCampaignTools(server) {
773
775
  .single();
774
776
  if (error)
775
777
  return { content: [{ type: 'text', text: `Error: ${error.message}` }] };
776
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
778
+ return { content: [{ type: 'text', text: JSON.stringify({ ...data, requires_approval_description: ACTIVATION_APPROVAL_DESCRIPTION }, null, 2) }] };
777
779
  });
778
780
  const propertyConditions = z.array(z.union([
779
781
  z.object({ key: z.string(), op: z.enum(['eq', 'neq']), value: z.string() }).strict(),
@@ -849,7 +851,7 @@ export function registerCampaignTools(server) {
849
851
  idempotentHint: true,
850
852
  openWorldHint: true,
851
853
  },
852
- description: 'Activate, pause, or archive a campaign. Activating requires human approval via the dashboard — call without approval_id to request one, then call again with the approved token.',
854
+ description: 'Activate, pause, or archive a campaign. Activating requires human approval via the dashboard — call without approval_id to request one, then call again with the approved token. ' + ACTIVATION_APPROVAL_DESCRIPTION,
853
855
  inputSchema: {
854
856
  campaign_id: z.string().uuid(),
855
857
  status: z.enum(['draft', 'active', 'paused', 'completed', 'archived']),
@@ -3558,7 +3560,7 @@ Return ONLY valid JSON with this exact shape:
3558
3560
  idempotentHint: true,
3559
3561
  openWorldHint: false,
3560
3562
  },
3561
- description: 'Get full campaign details including all steps, enrollment counts and saved trigger_type, trigger_event_name, trigger_tag, trigger_config (including where filter), fires_on and trigger_diagnostics. Read what event a dunning campaign fires on; use set_campaign_trigger to change it.',
3563
+ description: 'Get full campaign details including all steps, enrollment counts and saved trigger_type, trigger_event_name, trigger_tag, trigger_config (including where filter), fires_on and trigger_diagnostics. Read what event a dunning campaign fires on; use set_campaign_trigger to change it. ' + ACTIVATION_APPROVAL_DESCRIPTION,
3562
3564
  inputSchema: { campaign_id: z.string().uuid(), period: z.string().min(1).optional().describe("Return results for one enrollment period; totals remain all-time.") },
3563
3565
  }, async ({ campaign_id, period }) => {
3564
3566
  const projectId = getProjectId();
@@ -3629,6 +3631,7 @@ Return ONLY valid JSON with this exact shape:
3629
3631
  const unresolvedCount = dlqUnresolvedCount ?? 0;
3630
3632
  return j({
3631
3633
  ...campaign,
3634
+ requires_approval_description: ACTIVATION_APPROVAL_DESCRIPTION,
3632
3635
  fires_on: buildCampaignFiresOn(campaign),
3633
3636
  trigger_diagnostics: buildCampaignTriggerDiagnostics(campaign),
3634
3637
  total_sent: periods?.totals?.sent ?? sentCount ?? 0,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sendinel/mcp-server",
3
- "version": "1.0.114",
3
+ "version": "1.0.115",
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",