@sendinel/mcp-server 1.0.4 → 1.0.5

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.
Files changed (60) hide show
  1. package/build/audit.d.ts +1 -0
  2. package/build/audit.js +9 -3
  3. package/build/auth.d.ts +10 -0
  4. package/build/auth.js +82 -9
  5. package/build/db.d.ts +12 -0
  6. package/build/db.js +37 -2
  7. package/build/index.js +38 -12
  8. package/build/lib/action-approvals.d.ts +28 -1
  9. package/build/lib/action-approvals.js +89 -2
  10. package/build/lib/brand-asset-persist.d.ts +35 -0
  11. package/build/lib/brand-asset-persist.js +161 -0
  12. package/build/lib/brand-asset-usage.d.ts +13 -0
  13. package/build/lib/brand-asset-usage.js +46 -0
  14. package/build/lib/email-provider.d.ts +19 -0
  15. package/build/lib/email-provider.js +281 -0
  16. package/build/lib/ssrf-pinned-fetch.d.ts +16 -0
  17. package/build/lib/ssrf-pinned-fetch.js +101 -0
  18. package/build/lib/survey-link-resolver.d.ts +6 -0
  19. package/build/lib/survey-link-resolver.js +23 -0
  20. package/build/lib/svg-sanitize.d.ts +2 -0
  21. package/build/lib/svg-sanitize.js +110 -0
  22. package/build/lib/webhook-events.d.ts +1 -1
  23. package/build/scoped-token.d.ts +11 -0
  24. package/build/scoped-token.js +59 -0
  25. package/build/server.js +30 -9
  26. package/build/tools/ab-testing.js +28 -0
  27. package/build/tools/advisor.js +52 -2
  28. package/build/tools/ai-work.d.ts +2 -0
  29. package/build/tools/ai-work.js +460 -0
  30. package/build/tools/analytics.d.ts +1 -0
  31. package/build/tools/analytics.js +203 -0
  32. package/build/tools/approvals.js +15 -1
  33. package/build/tools/automations.js +288 -65
  34. package/build/tools/brand-kit.d.ts +2 -0
  35. package/build/tools/brand-kit.js +277 -0
  36. package/build/tools/campaigns.js +900 -69
  37. package/build/tools/compound.js +28 -0
  38. package/build/tools/contacts.js +338 -17
  39. package/build/tools/content.js +266 -1
  40. package/build/tools/data-proposals.js +21 -0
  41. package/build/tools/data.js +121 -5
  42. package/build/tools/delivery-ops.js +84 -0
  43. package/build/tools/drafts.js +64 -1
  44. package/build/tools/forms.js +42 -9
  45. package/build/tools/gdpr.js +22 -3
  46. package/build/tools/newsletters.d.ts +2 -0
  47. package/build/tools/newsletters.js +583 -0
  48. package/build/tools/org.js +297 -3
  49. package/build/tools/segments.js +45 -3
  50. package/build/tools/short-links.d.ts +2 -0
  51. package/build/tools/short-links.js +157 -0
  52. package/build/tools/sites.js +35 -0
  53. package/build/tools/sms.js +49 -0
  54. package/build/tools/social-posts.js +81 -5
  55. package/build/tools/surveys.d.ts +2 -0
  56. package/build/tools/surveys.js +222 -0
  57. package/build/tools/templates.js +78 -2
  58. package/build/tools/warmup.js +28 -0
  59. package/build/tools/webhooks.js +29 -0
  60. package/package.json +9 -3
@@ -4,6 +4,13 @@ import { getProjectId } from '../project.js';
4
4
  const j = (data) => ({ content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] });
5
5
  export function registerDeliveryOpsTools(server) {
6
6
  server.registerTool('refresh_domain_dns', {
7
+ title: 'Refresh Domain DNS',
8
+ annotations: {
9
+ readOnlyHint: true,
10
+ destructiveHint: false,
11
+ idempotentHint: true,
12
+ openWorldHint: true,
13
+ },
7
14
  description: 'Refresh and return stored DNS verification status for a sending domain.',
8
15
  inputSchema: { domain_id: z.string().uuid() },
9
16
  }, async ({ domain_id }) => {
@@ -23,6 +30,13 @@ export function registerDeliveryOpsTools(server) {
23
30
  return j({ spf, dkim, dmarc, all_verified: Boolean(spf && dkim && dmarc) });
24
31
  });
25
32
  server.registerTool('dnsbl_check', {
33
+ title: 'DNSBL Check',
34
+ annotations: {
35
+ readOnlyHint: true,
36
+ destructiveHint: false,
37
+ idempotentHint: true,
38
+ openWorldHint: true,
39
+ },
26
40
  description: 'Return the latest DNSBL snapshot for all monitored zones for a site sending domain.',
27
41
  inputSchema: {
28
42
  siteId: z.string().uuid(),
@@ -58,6 +72,13 @@ export function registerDeliveryOpsTools(server) {
58
72
  });
59
73
  });
60
74
  server.registerTool('dnsbl_delisting_draft', {
75
+ title: 'DNSBL Delisting Draft',
76
+ annotations: {
77
+ readOnlyHint: true,
78
+ destructiveHint: false,
79
+ idempotentHint: true,
80
+ openWorldHint: true,
81
+ },
61
82
  description: 'Draft a DNSBL delisting ticket using tenant send metrics as evidence. Does not auto-submit.',
62
83
  inputSchema: {
63
84
  siteId: z.string().uuid(),
@@ -113,6 +134,13 @@ export function registerDeliveryOpsTools(server) {
113
134
  });
114
135
  });
115
136
  server.registerTool('get_cron_runs', {
137
+ title: 'Get Cron Runs',
138
+ annotations: {
139
+ readOnlyHint: true,
140
+ destructiveHint: false,
141
+ idempotentHint: true,
142
+ openWorldHint: false,
143
+ },
116
144
  description: 'List recent cron job executions to diagnose why scheduled sends are not firing.',
117
145
  inputSchema: {
118
146
  job_name: z.string().optional(),
@@ -136,6 +164,13 @@ export function registerDeliveryOpsTools(server) {
136
164
  return j({ runs: data ?? [], recent_failures: failed.length, total: data?.length ?? 0 });
137
165
  });
138
166
  server.registerTool('get_queue_status', {
167
+ title: 'Get Queue Status',
168
+ annotations: {
169
+ readOnlyHint: true,
170
+ destructiveHint: false,
171
+ idempotentHint: true,
172
+ openWorldHint: false,
173
+ },
139
174
  description: 'Get the current send queue status — pending, processing, retry, and failed counts.',
140
175
  inputSchema: {
141
176
  site_id: z.string().uuid().optional(),
@@ -167,6 +202,13 @@ export function registerDeliveryOpsTools(server) {
167
202
  });
168
203
  });
169
204
  server.registerTool('get_send_dlq', {
205
+ title: 'Get Send DLQ',
206
+ annotations: {
207
+ readOnlyHint: true,
208
+ destructiveHint: false,
209
+ idempotentHint: true,
210
+ openWorldHint: false,
211
+ },
170
212
  description: 'List messages in the send dead-letter queue — emails that failed all retries.',
171
213
  inputSchema: {
172
214
  limit: z.number().int().min(1).max(50).default(20).optional(),
@@ -187,6 +229,13 @@ export function registerDeliveryOpsTools(server) {
187
229
  return j({ dlq: data ?? [], count: data?.length ?? 0 });
188
230
  });
189
231
  server.registerTool('get_scoring_rules', {
232
+ title: 'Get Scoring Rules',
233
+ annotations: {
234
+ readOnlyHint: true,
235
+ destructiveHint: false,
236
+ idempotentHint: true,
237
+ openWorldHint: false,
238
+ },
190
239
  description: 'Get contact scoring rules for the current project.',
191
240
  inputSchema: {},
192
241
  }, async () => {
@@ -200,6 +249,13 @@ export function registerDeliveryOpsTools(server) {
200
249
  return j({ rules: data });
201
250
  });
202
251
  server.registerTool('update_scoring_rules', {
252
+ title: 'Update Scoring Rules',
253
+ annotations: {
254
+ readOnlyHint: false,
255
+ destructiveHint: false,
256
+ idempotentHint: true,
257
+ openWorldHint: false,
258
+ },
203
259
  description: 'Update contact scoring rules. Only the provided fields are changed.',
204
260
  inputSchema: {
205
261
  open_weight: z.number().min(0).max(100).optional(),
@@ -230,6 +286,13 @@ export function registerDeliveryOpsTools(server) {
230
286
  return j({ rules: data, ok: true });
231
287
  });
232
288
  server.registerTool('explain_contact_score', {
289
+ title: 'Explain Contact Score',
290
+ annotations: {
291
+ readOnlyHint: true,
292
+ destructiveHint: false,
293
+ idempotentHint: true,
294
+ openWorldHint: false,
295
+ },
233
296
  description: 'Explain why a subscriber has their current engagement score.',
234
297
  inputSchema: {
235
298
  contact_id: z.string().uuid().optional(),
@@ -299,6 +362,13 @@ export function registerDeliveryOpsTools(server) {
299
362
  });
300
363
  });
301
364
  server.registerTool('export_data', {
365
+ title: 'Export Data',
366
+ annotations: {
367
+ readOnlyHint: true,
368
+ destructiveHint: false,
369
+ idempotentHint: true,
370
+ openWorldHint: false,
371
+ },
302
372
  description: 'Export contacts, email log, campaign, or suppression data as JSON.',
303
373
  inputSchema: {
304
374
  resource: z.enum(['contacts', 'email_log', 'campaigns', 'suppressions']),
@@ -326,6 +396,13 @@ export function registerDeliveryOpsTools(server) {
326
396
  return j({ resource, count: data?.length ?? 0, data: data ?? [], truncated: (data?.length ?? 0) === limit });
327
397
  });
328
398
  server.registerTool('list_domains', {
399
+ title: 'List Domains',
400
+ annotations: {
401
+ readOnlyHint: true,
402
+ destructiveHint: false,
403
+ idempotentHint: true,
404
+ openWorldHint: false,
405
+ },
329
406
  description: 'List sending domains configured for the project and their current verification status.',
330
407
  inputSchema: { site_id: z.string().uuid().optional() },
331
408
  }, async ({ site_id }) => {
@@ -350,6 +427,13 @@ export function registerDeliveryOpsTools(server) {
350
427
  return j({ domains: data ?? [], count: data?.length ?? 0 });
351
428
  });
352
429
  server.registerTool('register_domain', {
430
+ title: 'Register Domain',
431
+ annotations: {
432
+ readOnlyHint: false,
433
+ destructiveHint: false,
434
+ idempotentHint: false,
435
+ openWorldHint: true,
436
+ },
353
437
  description: 'Register a sending domain for a site and link it to the site record.',
354
438
  inputSchema: {
355
439
  site_id: z.string().uuid(),
@@ -2,8 +2,16 @@ import { z } from 'zod';
2
2
  import { db } from '../db.js';
3
3
  import { getProjectId } from '../project.js';
4
4
  import { checkPlanLimit } from '../plan-limits.js';
5
+ import { recordBrandAssetUsage } from '../lib/brand-asset-usage.js';
5
6
  export function registerDraftTools(server) {
6
7
  server.registerTool('create_draft', {
8
+ title: 'Create Draft',
9
+ annotations: {
10
+ readOnlyHint: false,
11
+ destructiveHint: false,
12
+ idempotentHint: false,
13
+ openWorldHint: false,
14
+ },
7
15
  description: 'Create an AI-generated email draft pending human approval',
8
16
  inputSchema: {
9
17
  site_id: z.string().uuid(),
@@ -13,8 +21,9 @@ export function registerDraftTools(server) {
13
21
  campaign_id: z.string().uuid().optional(),
14
22
  prompt: z.string().optional().describe('The prompt or brief used to generate this draft'),
15
23
  model: z.string().optional().describe('Model that generated the draft, defaults to ANTHROPIC_MODEL_ID'),
24
+ brand_asset_ids: z.array(z.string().uuid()).optional().describe('Stable brand_media_assets ids explicitly referenced by this draft.'),
16
25
  },
17
- }, async ({ site_id, subject, body_html, body_text, campaign_id, prompt, model }) => {
26
+ }, async ({ site_id, subject, body_html, body_text, campaign_id, prompt, model, brand_asset_ids }) => {
18
27
  const limit = await checkPlanLimit('ai_drafts');
19
28
  if (!limit.allowed) {
20
29
  return { content: [{ type: 'text', text: `AI draft limit reached (${limit.current}/${limit.limit}). Upgrade to BYOD for unlimited drafts.` }] };
@@ -26,9 +35,26 @@ export function registerDraftTools(server) {
26
35
  .single();
27
36
  if (error)
28
37
  return { content: [{ type: 'text', text: `Error: ${error.message}` }] };
38
+ await recordBrandAssetUsage({
39
+ client: db(),
40
+ projectId: getProjectId(),
41
+ source: 'ai_draft',
42
+ resourceId: data.id,
43
+ html: body_html,
44
+ assetIds: brand_asset_ids,
45
+ }).catch((err) => {
46
+ console.warn('Failed to record brand asset usage for MCP draft:', err);
47
+ });
29
48
  return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
30
49
  });
31
50
  server.registerTool('list_drafts', {
51
+ title: 'List Drafts',
52
+ annotations: {
53
+ readOnlyHint: true,
54
+ destructiveHint: false,
55
+ idempotentHint: true,
56
+ openWorldHint: false,
57
+ },
32
58
  description: 'List AI email drafts by status',
33
59
  inputSchema: {
34
60
  site_id: z.string().uuid().optional(),
@@ -52,6 +78,13 @@ export function registerDraftTools(server) {
52
78
  return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
53
79
  });
54
80
  server.registerTool('get_draft', {
81
+ title: 'Get Draft',
82
+ annotations: {
83
+ readOnlyHint: true,
84
+ destructiveHint: false,
85
+ idempotentHint: true,
86
+ openWorldHint: false,
87
+ },
55
88
  description: 'Get one AI email draft by ID, including full body content.',
56
89
  inputSchema: {
57
90
  draft_id: z.string().uuid(),
@@ -70,6 +103,13 @@ export function registerDraftTools(server) {
70
103
  return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
71
104
  });
72
105
  server.registerTool('promote_draft_to_campaign', {
106
+ title: 'Promote Draft To Campaign',
107
+ annotations: {
108
+ readOnlyHint: false,
109
+ destructiveHint: false,
110
+ idempotentHint: false,
111
+ openWorldHint: false,
112
+ },
73
113
  description: 'Promote an AI draft into an email campaign in one call. Creates the campaign and first campaign step, then links the draft to the campaign. The campaign is created as draft for review/launch.',
74
114
  inputSchema: {
75
115
  draft_id: z.string().uuid(),
@@ -145,6 +185,15 @@ export function registerDraftTools(server) {
145
185
  .single();
146
186
  if (stepError)
147
187
  return { content: [{ type: 'text', text: `Campaign created (${campaign.id}) but failed to create step: ${stepError.message}` }] };
188
+ await recordBrandAssetUsage({
189
+ client: db(),
190
+ projectId,
191
+ source: 'campaign_step',
192
+ resourceId: step.id,
193
+ html: step.body_html,
194
+ }).catch((err) => {
195
+ console.warn('Failed to record brand asset usage for MCP promoted draft:', err);
196
+ });
148
197
  const { data: updatedDraft } = await db()
149
198
  .from('ai_drafts')
150
199
  .update({
@@ -160,6 +209,13 @@ export function registerDraftTools(server) {
160
209
  return { content: [{ type: 'text', text: JSON.stringify({ campaign, first_step: step, draft: updatedDraft }, null, 2) }] };
161
210
  });
162
211
  server.registerTool('approve_draft', {
212
+ title: 'Approve Draft',
213
+ annotations: {
214
+ readOnlyHint: false,
215
+ destructiveHint: false,
216
+ idempotentHint: true,
217
+ openWorldHint: false,
218
+ },
163
219
  description: 'Approve a pending AI draft, marking it ready to send',
164
220
  inputSchema: {
165
221
  draft_id: z.string().uuid(),
@@ -181,6 +237,13 @@ export function registerDraftTools(server) {
181
237
  return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
182
238
  });
183
239
  server.registerTool('reject_draft', {
240
+ title: 'Reject Draft',
241
+ annotations: {
242
+ readOnlyHint: false,
243
+ destructiveHint: false,
244
+ idempotentHint: true,
245
+ openWorldHint: false,
246
+ },
184
247
  description: 'Reject a pending AI draft with optional feedback',
185
248
  inputSchema: {
186
249
  draft_id: z.string().uuid(),
@@ -4,40 +4,73 @@ import { getProjectId } from '../project.js';
4
4
  const j = (data) => ({ content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] });
5
5
  export function registerFormTools(server) {
6
6
  server.registerTool('list_forms', {
7
+ title: 'List Forms',
8
+ annotations: {
9
+ readOnlyHint: true,
10
+ destructiveHint: false,
11
+ idempotentHint: true,
12
+ openWorldHint: false,
13
+ },
7
14
  description: 'List signup forms with field and submission counts.',
8
15
  inputSchema: { site_id: z.string().uuid().optional() },
9
16
  }, async ({ site_id }) => {
10
- let query = db().from('forms').select('id, name, slug, fields, active, site_id, created_at').eq('project_id', getProjectId()).order('created_at', { ascending: false });
17
+ let query = db().from('signup_forms').select('id, name, slug, fields, status, submissions, site_id, created_at').eq('project_id', getProjectId()).order('created_at', { ascending: false });
11
18
  if (site_id)
12
19
  query = query.eq('site_id', site_id);
13
20
  const { data, error } = await query;
14
21
  if (error)
15
22
  return j({ error: error.message });
16
- return j({ forms: (data ?? []).map((form) => ({ ...form, field_count: Array.isArray(form.fields) ? form.fields.length : 0, submission_count: 0 })) });
23
+ return j({ forms: (data ?? []).map((form) => ({ ...form, field_count: Array.isArray(form.fields) ? form.fields.length : 0, submission_count: form.submissions ?? 0 })) });
17
24
  });
18
25
  server.registerTool('get_form_stats', {
26
+ title: 'Get Form Stats',
27
+ annotations: {
28
+ readOnlyHint: true,
29
+ destructiveHint: false,
30
+ idempotentHint: true,
31
+ openWorldHint: false,
32
+ },
19
33
  description: 'Get submission stats for a signup form.',
20
34
  inputSchema: { form_id: z.string().uuid() },
21
35
  }, async ({ form_id }) => {
22
- const { count } = await db().from('form_submissions').select('id', { count: 'exact', head: true }).eq('form_id', form_id).eq('project_id', getProjectId());
36
+ const projectId = getProjectId();
37
+ const { data: form, error: formError } = await db().from('signup_forms').select('id, submissions').eq('id', form_id).eq('project_id', projectId).maybeSingle();
38
+ if (formError)
39
+ return j({ error: formError.message });
40
+ if (!form)
41
+ return j({ error: 'Form not found', code: 'NOT_FOUND' });
23
42
  const since = new Date(Date.now() - 7 * 86400000).toISOString();
24
- const { count: sevenDay } = await db().from('form_submissions').select('id', { count: 'exact', head: true }).eq('form_id', form_id).eq('project_id', getProjectId()).gte('created_at', since);
25
- return j({ submissions_total: count ?? 0, submissions_7d: sevenDay ?? 0, top_sources: [], conversion_rate: null });
43
+ const { count: sevenDay, error: eventsError } = await db().from('contact_events')
44
+ .select('id', { count: 'exact', head: true })
45
+ .eq('project_id', projectId)
46
+ .eq('event_type', 'form_submit')
47
+ .eq('metadata->>form_id', form_id)
48
+ .gte('created_at', since);
49
+ if (eventsError)
50
+ return j({ error: eventsError.message });
51
+ return j({ submissions_total: form.submissions ?? 0, submissions_7d: sevenDay ?? 0, top_sources: [], conversion_rate: null });
26
52
  });
27
53
  server.registerTool('create_form', {
54
+ title: 'Create Form',
55
+ annotations: {
56
+ readOnlyHint: false,
57
+ destructiveHint: false,
58
+ idempotentHint: false,
59
+ openWorldHint: false,
60
+ },
28
61
  description: 'Create a signup form.',
29
62
  inputSchema: {
30
63
  site_id: z.string().uuid(),
31
64
  name: z.string().min(1),
32
- fields: z.array(z.record(z.unknown())),
65
+ fields: z.array(z.record(z.string(), z.unknown())),
33
66
  redirect_url: z.string().url().optional(),
34
67
  double_opt_in: z.boolean().optional(),
35
68
  },
36
69
  }, async ({ site_id, name, fields, redirect_url, double_opt_in }) => {
37
70
  const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 80);
38
- const { data, error } = await db().from('forms')
39
- .insert({ project_id: getProjectId(), site_id, name, slug, fields, redirect_url: redirect_url ?? null, double_opt_in: double_opt_in ?? false, active: true })
40
- .select('id, name, slug, fields, active')
71
+ const { data, error } = await db().from('signup_forms')
72
+ .insert({ project_id: getProjectId(), site_id, name, slug, fields, settings: { redirect_url: redirect_url ?? null, double_opt_in: double_opt_in ?? false }, status: 'active' })
73
+ .select('id, name, slug, fields, status')
41
74
  .single();
42
75
  if (error)
43
76
  return j({ error: error.message });
@@ -2,11 +2,18 @@ import { z } from 'zod';
2
2
  import { db } from '../db.js';
3
3
  import { getProjectId } from '../project.js';
4
4
  import { logAudit } from '../audit.js';
5
- import { requestApproval, checkApproval } from '../lib/action-approvals.js';
5
+ import { assertChannelCanExecute, currentChannelContext, requestApproval, checkApproval } from '../lib/action-approvals.js';
6
6
  const txt = (text) => ({ content: [{ type: 'text', text }] });
7
7
  const j = (data) => txt(JSON.stringify(data, null, 2));
8
8
  export function registerGdprTools(server) {
9
9
  server.registerTool('delete_subscriber_data', {
10
+ title: 'Delete Subscriber Data',
11
+ annotations: {
12
+ readOnlyHint: false,
13
+ destructiveHint: true,
14
+ idempotentHint: false,
15
+ openWorldHint: false,
16
+ },
10
17
  description: 'GDPR Article 17: Permanently delete a subscriber and all their data. Anonymizes email_log, hashes suppressions, cascades to subscriptions/enrollments/tokens. This action is IRREVERSIBLE. Requires human approval via dashboard.',
11
18
  inputSchema: {
12
19
  contact_id: z.string().uuid().describe('UUID of the subscriber to delete'),
@@ -23,12 +30,17 @@ export function registerGdprTools(server) {
23
30
  .maybeSingle();
24
31
  if (!contact)
25
32
  return txt('Contact not found or does not belong to this project.');
33
+ const approvalParams = { contact_id, email: contact.email, reason };
26
34
  // Human approval required (always, even in autonomous mode — protected tier)
27
35
  if (!approval_id) {
28
- const approval = await requestApproval('delete_subscriber_data', { contact_id, email: contact.email, reason });
36
+ const channelContext = currentChannelContext();
37
+ const decision = assertChannelCanExecute('delete_subscriber_data', approvalParams, channelContext);
38
+ if (!decision.allowed)
39
+ return txt(decision.reason ?? 'This channel cannot request this action.');
40
+ const approval = await requestApproval('delete_subscriber_data', approvalParams, undefined, channelContext);
29
41
  return j(approval);
30
42
  }
31
- const check = await checkApproval(approval_id, { consume: true });
43
+ const check = await checkApproval(approval_id, { consume: true, action: 'delete_subscriber_data', params: approvalParams });
32
44
  if (!check.approved) {
33
45
  return txt(`Approval ${approval_id} is ${check.status}. ${check.status === 'pending' ? 'Waiting for human approval via dashboard.' : 'Request a new approval.'}`);
34
46
  }
@@ -44,6 +56,13 @@ export function registerGdprTools(server) {
44
56
  return j(data);
45
57
  });
46
58
  server.registerTool('list_deletion_log', {
59
+ title: 'List Deletion Log',
60
+ annotations: {
61
+ readOnlyHint: true,
62
+ destructiveHint: false,
63
+ idempotentHint: true,
64
+ openWorldHint: false,
65
+ },
47
66
  description: 'List GDPR deletion audit log entries',
48
67
  inputSchema: {
49
68
  limit: z.number().int().min(1).max(200).optional().default(50),
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerNewsletterTools(server: McpServer): void;