@sendinel/mcp-server 1.0.194 → 1.0.196
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.d.ts +1 -0
- package/build/tools/campaigns.js +11 -2
- package/build/tools/warmup.js +21 -0
- package/package.json +1 -1
|
@@ -9,6 +9,7 @@ export declare function buildCampaignFiresOn(campaign: Record<string, unknown>):
|
|
|
9
9
|
*/
|
|
10
10
|
export declare function buildCampaignTriggerDiagnostics(campaign: Record<string, unknown>, seenEvents?: ReadonlySet<string> | null): string[];
|
|
11
11
|
export declare function campaignTriggerKind(campaign: Record<string, unknown>): 'event' | 'tag' | 'date_field' | 'contact_event' | 'manual';
|
|
12
|
+
export declare function campaignTriggerTypeNote(campaign: Record<string, unknown>): string | undefined;
|
|
12
13
|
export declare function applyCampaignTargetingFields(updates: Record<string, unknown>, input: {
|
|
13
14
|
trigger_event_name?: string | null;
|
|
14
15
|
trigger_tag?: string | null;
|
package/build/tools/campaigns.js
CHANGED
|
@@ -207,9 +207,18 @@ async function readCampaignTriggerHistory(campaigns) {
|
|
|
207
207
|
return null;
|
|
208
208
|
}
|
|
209
209
|
}
|
|
210
|
+
export function campaignTriggerTypeNote(campaign) {
|
|
211
|
+
const kind = campaignTriggerKind(campaign);
|
|
212
|
+
if (campaign.trigger_type === 'manual' && (kind === 'event' || kind === 'tag')) {
|
|
213
|
+
return `trigger_type "manual" only means no scheduled date_field/contact_event trigger; this campaign fires on ${kind} triggers (trigger_kind "${kind}").`;
|
|
214
|
+
}
|
|
215
|
+
return undefined;
|
|
216
|
+
}
|
|
210
217
|
function buildCampaignTriggerOutput(campaign, seenEvents) {
|
|
218
|
+
const note = campaignTriggerTypeNote(campaign);
|
|
211
219
|
return {
|
|
212
220
|
trigger_kind: campaignTriggerKind(campaign),
|
|
221
|
+
...(note ? { trigger_type_note: note } : {}),
|
|
213
222
|
fires_on: buildCampaignFiresOn(campaign),
|
|
214
223
|
trigger_diagnostics: buildCampaignTriggerDiagnostics(campaign, seenEvents),
|
|
215
224
|
};
|
|
@@ -3631,7 +3640,7 @@ Return ONLY valid JSON with this exact shape:
|
|
|
3631
3640
|
server.registerTool('set_campaign_trigger', {
|
|
3632
3641
|
title: 'Set Campaign Trigger',
|
|
3633
3642
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
3634
|
-
description: 'Send timing: the first step of an event-triggered product journey sends right away when it has no delay; later steps and delayed first steps wait for the site send window (contact zone, then campaign zone, then site zone, then UTC). Set or clear what a campaign fires on: trigger event name, tag, date_field, contact_event, or trigger_config events/tags and where event-data filter. Bind billing, payment failed, dunning or trial ending mail to an event trigger such as invoice_payment_failed or trial_ending. Drafts save directly; active/paused campaigns require standard human approval. Archived/completed campaigns cannot change. Returns saved trigger fields, derived trigger_kind, fires_on and trigger_diagnostics (including unseen-event warnings).',
|
|
3643
|
+
description: 'Send timing: the first step of an event-triggered product journey sends right away when it has no delay; later steps and delayed first steps wait for the site send window (contact zone, then campaign zone, then site zone, then UTC). Set or clear what a campaign fires on: trigger event name, tag, date_field, contact_event, or trigger_config events/tags and where event-data filter. Bind billing, payment failed, dunning or trial ending mail to an event trigger such as invoice_payment_failed or trial_ending. Drafts save directly; active/paused campaigns require standard human approval. Archived/completed campaigns cannot change. Returns saved trigger fields, derived trigger_kind, fires_on and trigger_diagnostics (including unseen-event warnings). trigger_kind is the canonical answer to what fires the campaign; trigger_type is the stored schedule mode and stays "manual" for event and tag triggers.',
|
|
3635
3644
|
inputSchema: {
|
|
3636
3645
|
...reentryFields,
|
|
3637
3646
|
campaign_id: z.string().uuid().optional(),
|
|
@@ -3852,7 +3861,7 @@ Return ONLY valid JSON with this exact shape:
|
|
|
3852
3861
|
idempotentHint: true,
|
|
3853
3862
|
openWorldHint: false,
|
|
3854
3863
|
},
|
|
3855
|
-
description: 'Get full campaign details including all steps, enrollment counts and derived trigger_kind (event/tag/date_field/contact_event/manual), 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,
|
|
3864
|
+
description: 'Get full campaign details including all steps, enrollment counts and derived trigger_kind (event/tag/date_field/contact_event/manual), 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. trigger_kind is the canonical answer to what fires the campaign; trigger_type is the stored schedule mode and stays "manual" for event and tag triggers. ' + ACTIVATION_APPROVAL_DESCRIPTION,
|
|
3856
3865
|
inputSchema: { campaign_id: z.string().uuid(), period: z.string().min(1).optional().describe("Return results for one enrollment period; totals remain all-time.") },
|
|
3857
3866
|
}, async ({ campaign_id, period }) => {
|
|
3858
3867
|
const projectId = getProjectId();
|
package/build/tools/warmup.js
CHANGED
|
@@ -2,6 +2,7 @@ import { z } from 'zod';
|
|
|
2
2
|
import { db } from '../db.js';
|
|
3
3
|
import { getProjectId } from '../project.js';
|
|
4
4
|
import { dbToolError } from '../lib/db-error.js';
|
|
5
|
+
import { filterSentRows } from '@sendinel/domain';
|
|
5
6
|
const j = (data) => ({ content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] });
|
|
6
7
|
export function registerWarmupTools(server) {
|
|
7
8
|
server.registerTool('get_warmup_status', {
|
|
@@ -25,6 +26,23 @@ export function registerWarmupTools(server) {
|
|
|
25
26
|
if (error)
|
|
26
27
|
return j(dbToolError(error));
|
|
27
28
|
const day = data?.warmup_day ?? 0;
|
|
29
|
+
// Pacing is "advance only after sends" (#4437): an idle site holds its day, so report
|
|
30
|
+
// when it last sent. Absence is not a value: no health row means nothing to report.
|
|
31
|
+
let lastSendDate = null;
|
|
32
|
+
if (data) {
|
|
33
|
+
const sendsQuery = db().from('email_log')
|
|
34
|
+
.select('sent_at')
|
|
35
|
+
.eq('project_id', getProjectId())
|
|
36
|
+
.eq('site_id', site_id)
|
|
37
|
+
.order('sent_at', { ascending: false });
|
|
38
|
+
const { data: lastSend, error: sendError } = await filterSentRows(sendsQuery)
|
|
39
|
+
.limit(1)
|
|
40
|
+
.maybeSingle();
|
|
41
|
+
if (sendError)
|
|
42
|
+
return j(dbToolError(sendError));
|
|
43
|
+
lastSendDate = lastSend?.sent_at ? String(lastSend.sent_at).slice(0, 10) : null;
|
|
44
|
+
}
|
|
45
|
+
const yesterday = new Date(Date.now() - 86400000).toISOString().slice(0, 10);
|
|
28
46
|
return j({
|
|
29
47
|
phase: day >= 42 ? 'complete' : day > 0 ? 'warming' : 'not_started',
|
|
30
48
|
daily_limit: data?.warmup_daily_limit ?? null,
|
|
@@ -32,6 +50,9 @@ export function registerWarmupTools(server) {
|
|
|
32
50
|
started_at: data?.check_date ?? null,
|
|
33
51
|
progress_pct: Math.min(100, Math.round((day / 42) * 100)),
|
|
34
52
|
next_milestone: day >= 42 ? null : `Day ${Math.min(42, day + 1)}`,
|
|
53
|
+
// Additive. started_at is the latest health row date, not a start date; kept for compatibility.
|
|
54
|
+
last_send_date: lastSendDate,
|
|
55
|
+
idle: data ? lastSendDate === null || lastSendDate < yesterday : false,
|
|
35
56
|
});
|
|
36
57
|
});
|
|
37
58
|
server.registerTool('update_warmup_schedule', {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sendinel/mcp-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.196",
|
|
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",
|