@sendinel/mcp-server 1.0.184 → 1.0.185
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.
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Stored campaign rates are measurements only when an importer supplied their source. */
|
|
2
|
+
export declare function campaignMetricsView<T extends Record<string, unknown>>(campaign: T): T & {
|
|
3
|
+
open_rate: unknown;
|
|
4
|
+
click_rate: unknown;
|
|
5
|
+
unsubscribe_rate: unknown;
|
|
6
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Stored campaign rates are measurements only when an importer supplied their source. */
|
|
2
|
+
export function campaignMetricsView(campaign) {
|
|
3
|
+
return {
|
|
4
|
+
...campaign,
|
|
5
|
+
open_rate: campaign.metrics_source == null ? null : campaign.open_rate,
|
|
6
|
+
click_rate: campaign.metrics_source == null ? null : campaign.click_rate,
|
|
7
|
+
unsubscribe_rate: campaign.metrics_source == null ? null : campaign.unsubscribe_rate,
|
|
8
|
+
};
|
|
9
|
+
}
|
package/build/tools/campaigns.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { campaignMetricsView } from '../lib/campaign-metrics.js';
|
|
1
2
|
import { withCreatedItems } from '../lib/mcp-created-items.js';
|
|
2
3
|
import { validateStepMultiEventRules, stepMultiEventRulesView, validateStepAbsenceRule, stepAbsenceRuleView } from "@sendinel/domain";
|
|
3
4
|
import { readCampaignMetricGroups, SENT_STATUSES } from "@sendinel/domain";
|
|
@@ -799,7 +800,7 @@ export function registerCampaignTools(server) {
|
|
|
799
800
|
}, async ({ site_id, status, limit, period }) => {
|
|
800
801
|
let q = db()
|
|
801
802
|
.from('campaigns')
|
|
802
|
-
.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, send_at')
|
|
803
|
+
.select('id, site_id, name, type, status, sends_per_hour, total_enrolled, total_sent, total_opened, total_clicked, open_rate, click_rate, unsubscribe_rate, metrics_source, 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, send_at')
|
|
803
804
|
.eq('project_id', getProjectId())
|
|
804
805
|
.order('created_at', { ascending: false })
|
|
805
806
|
.limit(limit ?? 25);
|
|
@@ -818,7 +819,7 @@ export function registerCampaignTools(server) {
|
|
|
818
819
|
const withPeople = await Promise.all(rows.map(async (row) => {
|
|
819
820
|
const periods = await readCampaignPeriods(db(), getProjectId(), row);
|
|
820
821
|
return {
|
|
821
|
-
...row,
|
|
822
|
+
...campaignMetricsView(row),
|
|
822
823
|
reentry_mode: row.period_key ? "per_event_period" : "once_ever",
|
|
823
824
|
requires_approval_description: ACTIVATION_APPROVAL_DESCRIPTION,
|
|
824
825
|
...buildCampaignTriggerOutput(row, seenEvents),
|
|
@@ -938,7 +939,7 @@ export function registerCampaignTools(server) {
|
|
|
938
939
|
if (error)
|
|
939
940
|
return { content: [{ type: 'text', text: `Error: ${dbToolError(error).error}` }] };
|
|
940
941
|
const warnings = await campaignTimezoneWarnings(data);
|
|
941
|
-
return { content: [{ type: 'text', text: JSON.stringify({ ...data, requires_approval_description: ACTIVATION_APPROVAL_DESCRIPTION, fires_on: buildCampaignFiresOn(data), trigger_diagnostics: buildCampaignTriggerDiagnostics(data), ...(warnings.length ? { warnings } : {}) }, null, 2) }] };
|
|
942
|
+
return { content: [{ type: 'text', text: JSON.stringify({ ...campaignMetricsView(data), requires_approval_description: ACTIVATION_APPROVAL_DESCRIPTION, fires_on: buildCampaignFiresOn(data), trigger_diagnostics: buildCampaignTriggerDiagnostics(data), ...(warnings.length ? { warnings } : {}) }, null, 2) }] };
|
|
942
943
|
});
|
|
943
944
|
const propertyConditions = z.array(z.union([
|
|
944
945
|
z.object({ key: z.string(), op: z.enum(['eq', 'neq']), value: z.string() }).strict(),
|
|
@@ -1142,7 +1143,7 @@ export function registerCampaignTools(server) {
|
|
|
1142
1143
|
status,
|
|
1143
1144
|
}).catch(() => { });
|
|
1144
1145
|
}
|
|
1145
|
-
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
1146
|
+
return { content: [{ type: 'text', text: JSON.stringify(campaignMetricsView(data), null, 2) }] };
|
|
1146
1147
|
});
|
|
1147
1148
|
server.registerTool('enroll_contact', {
|
|
1148
1149
|
title: 'Enroll Contact',
|
|
@@ -1930,7 +1931,7 @@ export function registerCampaignTools(server) {
|
|
|
1930
1931
|
details: { template_id, template_slug: template.slug, source: 'mcp' },
|
|
1931
1932
|
}).catch(() => { });
|
|
1932
1933
|
return j({
|
|
1933
|
-
...campaign,
|
|
1934
|
+
...campaignMetricsView(campaign),
|
|
1934
1935
|
steps_copied: steps.length,
|
|
1935
1936
|
cloned_from_template_id: template_id,
|
|
1936
1937
|
message: `Created draft campaign "${cloneName}" from template "${template.name}" (${steps.length} step(s) copied). It is a DRAFT — not active, and no contacts are enrolled.`,
|
|
@@ -3438,8 +3439,10 @@ Return ONLY valid JSON with this exact shape:
|
|
|
3438
3439
|
}).catch(() => { }); // fire-and-forget
|
|
3439
3440
|
}
|
|
3440
3441
|
await logAudit({ action: 'campaign.created', resourceType: 'campaign', resourceId: campaign.id, details: { type: campaignType, ...targeting, activated: willActivate } });
|
|
3442
|
+
const { open_rate, click_rate, unsubscribe_rate } = campaignMetricsView(campaign);
|
|
3441
3443
|
return j({
|
|
3442
3444
|
campaign_id: campaign.id,
|
|
3445
|
+
open_rate, click_rate, unsubscribe_rate,
|
|
3443
3446
|
name,
|
|
3444
3447
|
type: campaignType,
|
|
3445
3448
|
send_class: campaign.send_class,
|
|
@@ -3668,7 +3671,7 @@ Return ONLY valid JSON with this exact shape:
|
|
|
3668
3671
|
}, async ({ approval_id, ...args }) => {
|
|
3669
3672
|
if (approval_id) {
|
|
3670
3673
|
const result = await resumeCampaignEdit('set_campaign_trigger', approval_id, args);
|
|
3671
|
-
return j(result.error ? result : { ...result, ...await campaignTriggerOutput(result) });
|
|
3674
|
+
return j(result.error ? result : { ...campaignMetricsView(result), ...await campaignTriggerOutput(result) });
|
|
3672
3675
|
}
|
|
3673
3676
|
if (!args.campaign_id)
|
|
3674
3677
|
return j({ error: 'campaign_id is required', code: 'validation_failed' });
|
|
@@ -3708,7 +3711,7 @@ Return ONLY valid JSON with this exact shape:
|
|
|
3708
3711
|
if (reentry.period_key !== undefined)
|
|
3709
3712
|
updates.period_key = reentry.period_key;
|
|
3710
3713
|
const result = await finishCampaignEdit('set_campaign_trigger', args, existing, updates, params => requestApproval('set_campaign_trigger', params));
|
|
3711
|
-
return j(result.error || result.requires_approval ? result : { ...result, ...await campaignTriggerOutput(result) });
|
|
3714
|
+
return j(result.error || result.requires_approval ? result : { ...campaignMetricsView(result), ...await campaignTriggerOutput(result) });
|
|
3712
3715
|
});
|
|
3713
3716
|
server.registerTool('update_campaign', {
|
|
3714
3717
|
title: 'Update Campaign',
|
|
@@ -3744,7 +3747,7 @@ Return ONLY valid JSON with this exact shape:
|
|
|
3744
3747
|
if (result.error)
|
|
3745
3748
|
return j(result);
|
|
3746
3749
|
const warnings = await campaignTimezoneWarnings(result);
|
|
3747
|
-
return j({ ...result, ...await campaignTriggerOutput(result), ...(warnings.length ? { warnings } : {}) });
|
|
3750
|
+
return j({ ...campaignMetricsView(result), ...await campaignTriggerOutput(result), ...(warnings.length ? { warnings } : {}) });
|
|
3748
3751
|
}
|
|
3749
3752
|
const { campaign_id, name, site_id, segment_id, send_at, topic, trigger_event_name, trigger_tag, trigger_type, trigger_config, type, sends_per_hour, send_in_contact_timezone, send_class, unsub_footer_template, reentry_mode, period_key } = input;
|
|
3750
3753
|
if (!campaign_id)
|
|
@@ -3860,7 +3863,7 @@ Return ONLY valid JSON with this exact shape:
|
|
|
3860
3863
|
const diagnostics = buildCampaignTriggerDiagnostics(data);
|
|
3861
3864
|
const warning = diagnostics.find((d) => d.includes('never fire')) || (diagnostics.length ? diagnostics[0] : null);
|
|
3862
3865
|
return j({
|
|
3863
|
-
...data,
|
|
3866
|
+
...campaignMetricsView(data),
|
|
3864
3867
|
fires_on: buildCampaignFiresOn(data),
|
|
3865
3868
|
trigger_diagnostics: diagnostics,
|
|
3866
3869
|
warnings: [...diagnostics, ...await campaignTimezoneWarnings(data)],
|
|
@@ -3948,7 +3951,7 @@ Return ONLY valid JSON with this exact shape:
|
|
|
3948
3951
|
const failedCount = failedSendCount ?? 0;
|
|
3949
3952
|
const unresolvedCount = dlqUnresolvedCount ?? 0;
|
|
3950
3953
|
return j({
|
|
3951
|
-
...campaign,
|
|
3954
|
+
...campaignMetricsView(campaign),
|
|
3952
3955
|
reentry_mode: campaign.period_key ? "per_event_period" : "once_ever",
|
|
3953
3956
|
requires_approval_description: ACTIVATION_APPROVAL_DESCRIPTION,
|
|
3954
3957
|
...await campaignTriggerOutput(campaign),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sendinel/mcp-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.185",
|
|
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",
|