@sendinel/mcp-server 1.0.192 → 1.0.194
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.
|
@@ -15,4 +15,17 @@ export declare function storedDomainDns(health?: StoredHealth | null): {
|
|
|
15
15
|
};
|
|
16
16
|
export declare function readLatestStoredDomainHealth(domainId: string): any;
|
|
17
17
|
export declare function storedDnsReasons(dns: ReturnType<typeof storedDomainDns>): string[];
|
|
18
|
+
export type DnsCheckState = 'not_checked' | 'pass' | 'fail';
|
|
19
|
+
/**
|
|
20
|
+
* Explicit, additive rendering of a stored DNS observation. null never means pass or fail:
|
|
21
|
+
* it is 'not_checked'. dns_note says why this can differ from the domain's `verified` status.
|
|
22
|
+
*/
|
|
23
|
+
export declare function dnsCheckSummary(dns: ReturnType<typeof storedDomainDns>): {
|
|
24
|
+
spf: DnsCheckState;
|
|
25
|
+
dkim: DnsCheckState;
|
|
26
|
+
dmarc: DnsCheckState;
|
|
27
|
+
dns_check_status: string;
|
|
28
|
+
checked_at: string;
|
|
29
|
+
dns_note: string | null;
|
|
30
|
+
};
|
|
18
31
|
export {};
|
|
@@ -32,3 +32,20 @@ export function storedDnsReasons(dns) {
|
|
|
32
32
|
}
|
|
33
33
|
return reasons;
|
|
34
34
|
}
|
|
35
|
+
const dnsState = (value) => value === null ? 'not_checked' : value ? 'pass' : 'fail';
|
|
36
|
+
/**
|
|
37
|
+
* Explicit, additive rendering of a stored DNS observation. null never means pass or fail:
|
|
38
|
+
* it is 'not_checked'. dns_note says why this can differ from the domain's `verified` status.
|
|
39
|
+
*/
|
|
40
|
+
export function dnsCheckSummary(dns) {
|
|
41
|
+
const states = { spf: dnsState(dns.spf), dkim: dnsState(dns.dkim), dmarc: dnsState(dns.dmarc) };
|
|
42
|
+
const values = Object.values(states);
|
|
43
|
+
const dns_check_status = values.every(s => s === 'not_checked') ? 'not_checked'
|
|
44
|
+
: values.includes('not_checked') ? 'partially_checked' : 'checked';
|
|
45
|
+
const when = dns.checked_at ? `last DNS check ${dns.checked_at}` : 'DNS never checked (checked_at: never)';
|
|
46
|
+
const basis = 'Domain "verified" (list_domains status) is the provider registration status; SPF/DKIM/DMARC come only from the last stored DNS check';
|
|
47
|
+
const dns_note = dns_check_status === 'checked' && !Object.values(states).includes('fail') ? null
|
|
48
|
+
: dns_check_status === 'not_checked' ? `${basis}. ${when}: not_checked means unknown, not failing. Run refresh_domain_dns.`
|
|
49
|
+
: `${basis}. ${when}; not_checked means unknown, not failing${values.includes('fail') ? '; fail means the stored DNS check found a problem' : ''}.`;
|
|
50
|
+
return { ...states, dns_check_status, checked_at: dns.checked_at ?? 'never', dns_note };
|
|
51
|
+
}
|
package/build/tools/advisor.js
CHANGED
|
@@ -7,6 +7,7 @@ import { getProjectId } from '../project.js';
|
|
|
7
7
|
import { toolError, dbToolError } from '../lib/db-error.js';
|
|
8
8
|
import { buildVoicePrompt } from '../lib/voice.js';
|
|
9
9
|
import { getAnthropicModelId } from '../anthropic-model.js';
|
|
10
|
+
import { storedDomainDns, dnsCheckSummary } from '../lib/domain-health.js';
|
|
10
11
|
import { dkimRemediationText } from '../lib/email-provider.js';
|
|
11
12
|
const txt = (text) => ({ content: [{ type: 'text', text }] });
|
|
12
13
|
const j = (data) => txt(JSON.stringify(data, null, 2));
|
|
@@ -641,7 +642,7 @@ Return ONLY valid JSON (no markdown fences): { "topics": [{ "title": "...", "rat
|
|
|
641
642
|
// 2. Fetch latest domain_health
|
|
642
643
|
const { data: healthRows, error: healthError } = await db()
|
|
643
644
|
.from('domain_health')
|
|
644
|
-
.select('spf_valid, dkim_valid, dmarc_valid, bounce_rate, complaint_rate, warmup_day, warmup_daily_limit, delivery_rate, total_sent')
|
|
645
|
+
.select('check_date, spf_valid, dkim_valid, dmarc_valid, bounce_rate, complaint_rate, warmup_day, warmup_daily_limit, delivery_rate, total_sent')
|
|
645
646
|
.eq('site_id', site_id)
|
|
646
647
|
.order('check_date', { ascending: false })
|
|
647
648
|
.limit(1);
|
|
@@ -693,6 +694,10 @@ Return ONLY valid JSON (no markdown fences): { "topics": [{ "title": "...", "rat
|
|
|
693
694
|
issues.push({ type: 'elevated_complaint', severity: 'high', detail: `Complaint rate ${complaintRate}% approaching danger zone` });
|
|
694
695
|
if (spfFailed || dkimFailed)
|
|
695
696
|
issues.push({ type: 'dns_invalid', severity: 'high', detail: `Missing DNS: ${spfFailed ? 'SPF' : ''}${spfFailed && dkimFailed ? ' + ' : ''}${dkimFailed ? 'DKIM' : ''}` });
|
|
697
|
+
const storedDns = storedDomainDns(health);
|
|
698
|
+
const dnsUnchecked = [spfValid, dkimValid, dmarcValid].some(v => v === null);
|
|
699
|
+
if (dnsUnchecked)
|
|
700
|
+
issues.push({ type: 'dns_unchecked', severity: 'low', detail: 'SPF/DKIM/DMARC have not been checked in stored domain health (unknown, not failing). Run refresh_domain_dns.' });
|
|
696
701
|
if (dmarcFailed)
|
|
697
702
|
issues.push({ type: 'no_dmarc', severity: 'medium', detail: 'DMARC record missing or invalid' });
|
|
698
703
|
if (unchecked.length)
|
|
@@ -806,7 +811,7 @@ Return ONLY valid JSON (no markdown fences): { "topics": [{ "title": "...", "rat
|
|
|
806
811
|
complaint_rate: `${complaintRate}%`,
|
|
807
812
|
open_rate: `${openRate}%`,
|
|
808
813
|
},
|
|
809
|
-
dns: { spf_valid: spfValid, dkim_valid: dkimValid, dmarc_valid: dmarcValid },
|
|
814
|
+
dns: { spf_valid: spfValid, dkim_valid: dkimValid, dmarc_valid: dmarcValid, ...dnsCheckSummary(storedDns), verification_source: storedDns.verification_source },
|
|
810
815
|
warmup: warmupDay != null ? { day: warmupDay, daily_limit: warmupDailyLimit } : null,
|
|
811
816
|
new_suppressions_7d: newSuppressions ?? 0,
|
|
812
817
|
issues,
|
package/build/tools/analytics.js
CHANGED
|
@@ -9,7 +9,7 @@ import { getProjectId } from '../project.js';
|
|
|
9
9
|
import { resolveProjectClient } from '../byod-client.js';
|
|
10
10
|
import { logAudit } from '../audit.js';
|
|
11
11
|
import { dbToolError, toolError } from '../lib/db-error.js';
|
|
12
|
-
import { storedDomainDns, storedDnsReasons } from '../lib/domain-health.js';
|
|
12
|
+
import { storedDomainDns, storedDnsReasons, dnsCheckSummary } from '../lib/domain-health.js';
|
|
13
13
|
const txt = (text) => ({ content: [{ type: 'text', text }] });
|
|
14
14
|
const j = (data) => txt(JSON.stringify(data, null, 2));
|
|
15
15
|
const percent = (rate) => `${(rate * 100).toFixed(1)}%`;
|
|
@@ -597,7 +597,7 @@ export function registerAnalyticsTools(server) {
|
|
|
597
597
|
.limit(days);
|
|
598
598
|
if (error)
|
|
599
599
|
return toolError(dbToolError(error, 'Unable to load domain health'));
|
|
600
|
-
return j((data ?? []).map(({ sites: _sites, ...row }) => ({ ...row, dns: storedDomainDns(row) })));
|
|
600
|
+
return j((data ?? []).map(({ sites: _sites, ...row }) => ({ ...row, dns: storedDomainDns(row), dns_check: dnsCheckSummary(storedDomainDns(row)) })));
|
|
601
601
|
});
|
|
602
602
|
server.registerTool('get_engagement_insights', {
|
|
603
603
|
title: 'Get Engagement Insights',
|
|
@@ -772,6 +772,7 @@ export function registerAnalyticsTools(server) {
|
|
|
772
772
|
resend_domain_status: null, // Retained for compatibility; no live Resend query is made.
|
|
773
773
|
checked_at: storedDns.checked_at,
|
|
774
774
|
dns: dnsResults,
|
|
775
|
+
dns_check: dnsCheckSummary(storedDns),
|
|
775
776
|
warmup: { day: warmupDay, daily_limit: warmupLimit, completed: warmupDay ? warmupDay >= 43 : null },
|
|
776
777
|
recent_7d: { total_sent: totalSent, bounced, complained, bounce_rate: `${bounceRate.toFixed(2)}%`, complaint_rate: `${complaintRate.toFixed(3)}%` },
|
|
777
778
|
health_trend: health.slice(0, 7).map((h) => ({ date: h.check_date, sent: h.total_sent, delivery_rate: h.delivery_rate, bounce_rate: h.bounce_rate })),
|
|
@@ -7,7 +7,7 @@ import { assertChannelCanExecute, checkApproval, currentChannelContext, requestA
|
|
|
7
7
|
import { callSendinelV1 } from '../lib/v1-client.js';
|
|
8
8
|
import { getProjectId } from '../project.js';
|
|
9
9
|
import { dbToolError, toolError } from '../lib/db-error.js';
|
|
10
|
-
import { readLatestStoredDomainHealth, storedDomainDns, storedDnsReasons } from '../lib/domain-health.js';
|
|
10
|
+
import { readLatestStoredDomainHealth, storedDomainDns, storedDnsReasons, dnsCheckSummary } from '../lib/domain-health.js';
|
|
11
11
|
const j = (data) => ({ content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] });
|
|
12
12
|
function asRecord(value) {
|
|
13
13
|
return value && typeof value === 'object' ? value : {};
|
|
@@ -829,7 +829,7 @@ export function registerDeliveryOpsTools(server) {
|
|
|
829
829
|
readiness.ready = readiness.ready === false || auth.includes(false) ? false
|
|
830
830
|
: readiness.ready === null || auth.includes(null) ? null : true;
|
|
831
831
|
readiness.reasons.push(...storedDnsReasons(dns));
|
|
832
|
-
domains.push({ ...domain, dns, readiness });
|
|
832
|
+
domains.push({ ...domain, dns, readiness, registration_status: domain.status, dns_check: dnsCheckSummary(dns) });
|
|
833
833
|
}
|
|
834
834
|
return j({ domains, count: domains.length });
|
|
835
835
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sendinel/mcp-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.194",
|
|
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",
|