@sendinel/mcp-server 1.0.202 → 1.0.204

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.
@@ -39,6 +39,8 @@ import { callSendinelV1 } from '../lib/v1-client.js';
39
39
  import { countUniqueEngagementByCampaign } from '../lib/campaign-engagement.js';
40
40
  const txt = (text) => ({ content: [{ type: 'text', text }] });
41
41
  const j = (data) => txt(JSON.stringify(data, null, 2));
42
+ // Saved campaigns also have requires_approval; only approval responses carry approval_id.
43
+ const isApprovalRequest = (result) => result?.requires_approval === true && typeof result?.approval_id === 'string';
42
44
  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.';
43
45
  const REENTRY_DESCRIPTION = 'Re-entry defaults to once ever. per_event_period uses a flat event-data period_key on event-triggered campaigns only. Changing a live campaign returns an approval request. Existing enrollments without a period cannot re-enter; sends wait for campaign-cycle approval.';
44
46
  const reentryFields = {
@@ -3694,7 +3696,7 @@ Return ONLY valid JSON with this exact shape:
3694
3696
  if (reentry.period_key !== undefined)
3695
3697
  updates.period_key = reentry.period_key;
3696
3698
  const result = await finishCampaignEdit('set_campaign_trigger', args, existing, updates, params => requestApproval('set_campaign_trigger', params));
3697
- return j(result.error || result.requires_approval ? result : { ...campaignMetricsView(result), ...await campaignTriggerOutput(result) });
3699
+ return j(result.error || isApprovalRequest(result) ? result : { ...campaignMetricsView(result), ...await campaignTriggerOutput(result) });
3698
3700
  });
3699
3701
  server.registerTool('update_campaign', {
3700
3702
  title: 'Update Campaign',
@@ -3832,7 +3834,7 @@ Return ONLY valid JSON with this exact shape:
3832
3834
  let data;
3833
3835
  if (existingCampaignForTargeting) {
3834
3836
  data = await finishCampaignEdit('update_campaign', input, existingCampaignForTargeting, updates, params => requestApproval('update_campaign', params));
3835
- if (data.error || data.requires_approval)
3837
+ if (data.error || isApprovalRequest(data))
3836
3838
  return j(data);
3837
3839
  }
3838
3840
  else {
@@ -3843,12 +3845,12 @@ Return ONLY valid JSON with this exact shape:
3843
3845
  data = result.data;
3844
3846
  await logAudit({ action: 'campaign.updated', resourceType: 'campaign', resourceId: campaign_id, details: { updates } });
3845
3847
  }
3846
- const diagnostics = buildCampaignTriggerDiagnostics(data);
3848
+ const triggerOutput = buildCampaignTriggerOutput(data);
3849
+ const diagnostics = triggerOutput.trigger_diagnostics;
3847
3850
  const warning = diagnostics.find((d) => d.includes('never fire')) || (diagnostics.length ? diagnostics[0] : null);
3848
3851
  return j({
3849
3852
  ...campaignMetricsView(data),
3850
- fires_on: buildCampaignFiresOn(data),
3851
- trigger_diagnostics: diagnostics,
3853
+ ...triggerOutput,
3852
3854
  warnings: [...diagnostics, ...await campaignTimezoneWarnings(data)],
3853
3855
  ...(warning ? { warning } : {}),
3854
3856
  });
@@ -1,4 +1,4 @@
1
- import { readPlanUsage, readAllRows } from "@sendinel/domain";
1
+ import { PLAN_LIMITS as DOMAIN_PLAN_LIMITS, readPlanUsage, readAllRows } from "@sendinel/domain";
2
2
  import { z } from 'zod';
3
3
  import { logAudit } from '../audit.js';
4
4
  import { adminDb, db } from '../db.js';
@@ -577,9 +577,21 @@ export function registerOrgTools(server) {
577
577
  idempotentHint: true,
578
578
  openWorldHint: false,
579
579
  },
580
- description: 'Return plan limits for all plan tiers.',
580
+ description: 'Return plan limits for all tiers, including campaign, A/B testing, landing page, member, and agent-mailbox limits.',
581
581
  inputSchema: {},
582
- }, async () => j(PLAN_LIMITS));
582
+ }, async () => j(Object.fromEntries(Object.entries(DOMAIN_PLAN_LIMITS).map(([plan, domainLimits]) => [
583
+ plan,
584
+ {
585
+ ...PLAN_LIMITS[plan],
586
+ campaign_limit: domainLimits.campaign_limit,
587
+ contact_limit: domainLimits.contact_limit,
588
+ monthly_send_limit: domainLimits.monthly_send_limit,
589
+ ab_testing_access: domainLimits.ab_testing_access,
590
+ landing_pages_access: domainLimits.landing_pages_access,
591
+ maxMembers: domainLimits.maxMembers,
592
+ maxAgentMailboxes: domainLimits.maxAgentMailboxes,
593
+ },
594
+ ]))));
583
595
  server.registerTool('list_audit_log', {
584
596
  title: 'List Audit Log',
585
597
  annotations: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sendinel/mcp-server",
3
- "version": "1.0.202",
3
+ "version": "1.0.204",
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",