@sendinel/mcp-server 1.0.95 → 1.0.96

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,3 +1,4 @@
1
+ import { CAMPAIGN_BRANCH_CONDITIONS, campaignBranchConditionValue } from "@sendinel/domain";
1
2
  import { z } from 'zod';
2
3
  import Anthropic from '@anthropic-ai/sdk';
3
4
  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';
@@ -296,7 +297,6 @@ function normalizeConsistencyResult(value) {
296
297
  // app/api/dashboard/campaigns/goals/route.ts
297
298
  // If those lists change, change these. A value MCP accepts that REST rejects (or the
298
299
  // reverse) is a divergence between two front doors onto the same engine.
299
- const CAMPAIGN_BRANCH_CONDITIONS = ['tag_added', 'lifecycle_reached', 'conversion', 'link_clicked', 'custom_event', 'reply', 'click', 'booking'];
300
300
  const CAMPAIGN_BRANCH_ACTIONS = ['goto_step', 'goto_campaign', 'exit', 'complete'];
301
301
  const CAMPAIGN_GOAL_TYPES = ['conversion', 'tag_added', 'lifecycle_reached', 'link_clicked', 'custom_event'];
302
302
  const CAMPAIGN_GOAL_ACTIONS = ['exit', 'complete', 'move_to_campaign'];
@@ -3537,18 +3537,21 @@ Return ONLY valid JSON with this exact shape:
3537
3537
  server.registerTool('add_campaign_branch', {
3538
3538
  title: 'Add Campaign Branch',
3539
3539
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
3540
- description: 'Add an if/then rule to a campaign step so the sequence branches on what the contact does. Use this for conditional logic like: if they clicked the link send a different follow-up, if they replied skip ahead, if a tag was added move them to another campaign, otherwise continue. When the condition matches, the branch action runs instead of continuing to the next step. Lower priority is evaluated first.',
3540
+ description: 'Add an if/then rule to a campaign step so the sequence branches on what the contact does or their current custom properties. Use this for conditional logic like: if they clicked the link send a different follow-up, if they replied skip ahead, if a tag was added move them to another campaign, otherwise continue. When the condition matches, the branch action runs instead of continuing to the next step. Lower priority is evaluated first.',
3541
3541
  inputSchema: {
3542
3542
  campaign_id: z.string().uuid(),
3543
3543
  from_step_id: z.string().uuid().describe('The step this branch leaves from'),
3544
3544
  condition_type: z.enum(CAMPAIGN_BRANCH_CONDITIONS).describe('What triggers the branch'),
3545
- condition_value: z.string().optional().describe('Condition argument, e.g. the tag name for tag_added or the URL for link_clicked'),
3545
+ condition_value: z.union([z.string(), z.record(z.string(), z.unknown())]).optional().describe('Tag, URL or event name for existing conditions. For contact_property: {key, op: eq|neq, value: string} or {key, op: exists}; key matches [A-Za-z0-9_.-]{1,64}. Missing/null properties never match.'),
3546
3546
  action: z.enum(CAMPAIGN_BRANCH_ACTIONS).describe('goto_step and goto_campaign require their matching target'),
3547
3547
  target_step_id: z.string().uuid().optional().describe('Required when action is goto_step'),
3548
3548
  target_campaign_id: z.string().uuid().optional().describe('Required when action is goto_campaign'),
3549
3549
  priority: z.number().int().min(0).optional().default(0).describe('Lower runs first (default 0)'),
3550
3550
  },
3551
3551
  }, async ({ campaign_id, from_step_id, condition_type, condition_value, action, target_step_id, target_campaign_id, priority }) => {
3552
+ const conditionValue = campaignBranchConditionValue(condition_type, condition_value);
3553
+ if (!conditionValue.ok)
3554
+ return { ...j({ error: conditionValue.error, code: 'INVALID_CONDITION_VALUE' }), isError: true };
3552
3555
  if (!(await campaignInProject(campaign_id)))
3553
3556
  return j({ error: 'Campaign not found in this project', code: 'CAMPAIGN_NOT_FOUND' });
3554
3557
  // Same requirement the REST route enforces: an action naming a target must have one,
@@ -3562,7 +3565,7 @@ Return ONLY valid JSON with this exact shape:
3562
3565
  campaign_id,
3563
3566
  from_step_id,
3564
3567
  condition_type,
3565
- condition_value: condition_value?.trim() || null,
3568
+ condition_value: conditionValue.value,
3566
3569
  action,
3567
3570
  target_step_id: action === 'goto_step' ? target_step_id : null,
3568
3571
  target_campaign_id: action === 'goto_campaign' ? target_campaign_id : null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sendinel/mcp-server",
3
- "version": "1.0.95",
3
+ "version": "1.0.96",
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",