@sendinel/mcp-server 1.0.157 → 1.0.159

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/README.md CHANGED
@@ -18,7 +18,7 @@ preserves REST outcomes, and redacts failures. See the
18
18
  [event-triggered campaign guide](../../docs/event-triggered-campaigns.md) for
19
19
  inputs, idempotency, subscription requirements, and conditional step examples.
20
20
 
21
- Sendinel currently exposes 35 tool groups and 346 tools (gateway mode exposes 4 meta-tools, sharing how_to with full mode). These counts are checked against the tool registry in CI, so this table matches what ships.
21
+ Sendinel currently exposes 35 tool groups and 348 tools (gateway mode exposes 4 meta-tools, sharing how_to with full mode). These counts are checked against the tool registry in CI, so this table matches what ships.
22
22
 
23
23
  | Group | Count | Purpose |
24
24
  |-------|-------|---------|
@@ -49,7 +49,7 @@ Sendinel currently exposes 35 tool groups and 346 tools (gateway mode exposes 4
49
49
  | `segments` | 6 | Audience builder: create, edit, natural-language generation, preview |
50
50
  | `short-links` | 3 | Short links, click stats, QR codes |
51
51
  | `signatures` | 4 | Sending signature blocks |
52
- | `sites` | 5 | Site, sender, and brand voice management |
52
+ | `sites` | 7 | Site, sender, and brand voice management |
53
53
  | `skills` | 3 | Operator skills catalog |
54
54
  | `sms` | 7 | SMS send, log, consent, inbound |
55
55
  | `social-posts` | 17 | Social campaign creation, publisher scheduling, social posts, engagement reads, and LinkedIn reply proposals for human review |
@@ -175,6 +175,7 @@ export function buildApprovalSummary(action, params, display = {}) {
175
175
  install_connector_view: (p) => `Reinstall the customer-side contact view for project ${p.project_id ?? 'current'}`,
176
176
  set_connector_mapping: () => `Rewrite the BYOD connector field mapping`,
177
177
  resync_connector: () => `Force a full BYOD connector resync`,
178
+ set_link_domain: (p) => `Set email link domain ${p.link_domain} for site ${p.site_id}${p.publish_dns ? ' and publish all DNS records through Cloudflare' : ''}`,
178
179
  publish_dmarc_record: (p) => `Publish DMARC record for ${p.domain} - writes DNS in a customer-owned Cloudflare zone`,
179
180
  broadcast_to_segment: (p) => broadcastSummary(display) ?? `Broadcast "${p.name || p.subject || 'untitled'}" to segment ${p.segment_id} — will send emails to real contacts`,
180
181
  send_broadcast: (p) => {
@@ -1,4 +1,5 @@
1
1
  export const GATED_ACTIONS = new Set([
2
+ 'set_link_domain', // domain routing and optional customer DNS writes, one approval
2
3
  'recheck_domain_dns', // live provider verification may request an EE certificate
3
4
  'draft_agent_reply',
4
5
  'create_agent_mailbox',
@@ -14,6 +14,7 @@ export const APPROVAL_EXEMPT = {
14
14
  mark_agent_message: "changes only the bound mailbox message status; reversible and sends nothing",
15
15
  abuse_monitor_override: "admin-only override of Sendinel abuse monitoring on the caller own project; no outward send",
16
16
  add_blackout_date: "adds a no-send date; reduces sending",
17
+ check_link_domain: "records the link-domain status after checking DNS, ownership and the certificate; links switch on only after Vercel and a TLS handshake both prove it, and sends nothing itself",
17
18
  add_campaign_branch: "adds branching config to a campaign; recreatable and sends nothing itself",
18
19
  add_campaign_step: "adds an email to a campaign; sends only when the campaign is activated (gated)",
19
20
  add_subscriber: "adds one contact; contact count limits apply and nothing is sent",
package/build/server.js CHANGED
@@ -105,7 +105,7 @@ const WRITE_TOOLS = new Set([
105
105
  'create_social_post', 'update_social_post', 'delete_social_post',
106
106
  'draft_social_reply', 'propose_social_reply', 'comment_on_post', 'reply_to_comment', 'request_social_reply',
107
107
  'create_draft', 'approve_draft', 'reject_draft',
108
- 'create_site', 'create_sender', 'update_site',
108
+ 'create_site', 'create_sender', 'update_site', 'set_link_domain', 'check_link_domain',
109
109
  'create_template', 'update_template', 'delete_template', 'restore_template_version', 'delete_provider_credential', 'translate_template',
110
110
  'delete_subscriber_data', 'identify_inactive', 'set_subscriber_tags',
111
111
  'update_hygiene_config',
@@ -3553,7 +3553,7 @@ Return ONLY valid JSON with this exact shape:
3553
3553
  server.registerTool('set_campaign_trigger', {
3554
3554
  title: 'Set Campaign Trigger',
3555
3555
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
3556
- description: '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).',
3556
+ description: 'Send timing: the first step of an event-triggered product journey sends right away; later steps wait for the site send window (contact zone, then campaign 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).',
3557
3557
  inputSchema: {
3558
3558
  ...reentryFields,
3559
3559
  campaign_id: z.string().uuid().optional(),
@@ -4,6 +4,8 @@ import { logAudit } from '../audit.js';
4
4
  import { db } from '../db.js';
5
5
  import { getProjectId } from '../project.js';
6
6
  import { checkPlanLimit } from '../plan-limits.js';
7
+ import { withApproval } from '../lib/action-approvals.js';
8
+ import { callSendinelV1 } from '../lib/v1-client.js';
7
9
  import { dbToolError } from '../lib/db-error.js';
8
10
  const txt = (text) => ({ content: [{ type: 'text', text }] });
9
11
  const j = (data) => txt(JSON.stringify(data, null, 2));
@@ -107,6 +109,29 @@ async function createSender({ name, slug, domain_id, local_part, sending_domain,
107
109
  return j(data);
108
110
  }
109
111
  export function registerSiteTools(server) {
112
+ server.registerTool('set_link_domain', {
113
+ title: 'Set Link Domain',
114
+ description: 'Set a branded address for this site’s email links, such as links.example.com. Returns every DNS record, your DNS provider, setup steps and a plain status. Optionally publish all records through your connected Cloudflare account. One approval covers setup and optional DNS publishing.',
115
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
116
+ inputSchema: {
117
+ site_id: z.string().uuid(),
118
+ link_domain: z.string().min(1).describe('A subdomain for email links, such as links.example.com.'),
119
+ publish_dns: z.boolean().optional().describe('Publish the full record set using your connected Cloudflare account.'),
120
+ approval_id: z.string().uuid().optional().describe('Approval for this exact setup, including publish_dns.'),
121
+ },
122
+ }, withApproval('set_link_domain', async (args) => {
123
+ const result = await callSendinelV1('/api/v1/link-domain', { method: 'POST', body: args });
124
+ return result.ok ? j(result.data) : { ...j(result.error), isError: true };
125
+ }));
126
+ server.registerTool('check_link_domain', {
127
+ title: 'Check Link Domain',
128
+ description: 'Check whether this site’s branded email links are live. Returns every DNS record, your DNS provider, setup steps and a plain status. Checks DNS, ownership and the secure connection before enabling links.',
129
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
130
+ inputSchema: { site_id: z.string().uuid() },
131
+ }, async ({ site_id }) => {
132
+ const result = await callSendinelV1('/api/v1/link-domain', { method: 'GET', query: { site_id } });
133
+ return result.ok ? j(result.data) : { ...j(result.error), isError: true };
134
+ });
110
135
  server.registerTool('get_brand_voice', {
111
136
  title: 'Get Brand Voice',
112
137
  annotations: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sendinel/mcp-server",
3
- "version": "1.0.157",
3
+ "version": "1.0.159",
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",