@sendinel/mcp-server 1.0.9 → 1.0.11

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 (57) hide show
  1. package/README.md +35 -22
  2. package/build/anthropic-model.d.ts +30 -0
  3. package/build/anthropic-model.js +26 -1
  4. package/build/db.d.ts +17 -0
  5. package/build/db.js +15 -1
  6. package/build/index.js +6 -0
  7. package/build/lib/action-approvals.js +28 -0
  8. package/build/lib/brand-asset-persist.d.ts +1 -0
  9. package/build/lib/brand-asset-persist.js +20 -1
  10. package/build/lib/composer-blocks.d.ts +25 -25
  11. package/build/lib/composer-blocks.js +25 -5
  12. package/build/lib/connector-table-discovery.d.ts +12 -0
  13. package/build/lib/connector-table-discovery.js +117 -0
  14. package/build/lib/custom-properties-diff.d.ts +16 -0
  15. package/build/lib/custom-properties-diff.js +22 -0
  16. package/build/lib/schedule.d.ts +7 -2
  17. package/build/lib/schedule.js +8 -0
  18. package/build/lib/v1-client.d.ts +3 -0
  19. package/build/lib/v1-client.js +45 -0
  20. package/build/lib/voice.d.ts +7 -30
  21. package/build/lib/voice.js +6 -138
  22. package/build/prompts.d.ts +6 -0
  23. package/build/prompts.js +18 -0
  24. package/build/scoped-token.d.ts +1 -1
  25. package/build/server.d.ts +1 -0
  26. package/build/server.js +65 -6
  27. package/build/tools/analytics.js +97 -0
  28. package/build/tools/brand-kit.js +29 -30
  29. package/build/tools/campaigns.js +326 -52
  30. package/build/tools/cloud-import.d.ts +2 -0
  31. package/build/tools/cloud-import.js +76 -0
  32. package/build/tools/compound.d.ts +11 -0
  33. package/build/tools/compound.js +37 -7
  34. package/build/tools/connector.d.ts +2 -0
  35. package/build/tools/connector.js +280 -0
  36. package/build/tools/contacts.js +744 -16
  37. package/build/tools/content.js +228 -0
  38. package/build/tools/data.js +441 -0
  39. package/build/tools/delivery-ops.js +508 -1
  40. package/build/tools/forms.js +7 -6
  41. package/build/tools/gateway.d.ts +6 -0
  42. package/build/tools/gateway.js +92 -0
  43. package/build/tools/gdpr.js +185 -0
  44. package/build/tools/landing-pages.d.ts +2 -0
  45. package/build/tools/landing-pages.js +202 -0
  46. package/build/tools/lists.d.ts +2 -0
  47. package/build/tools/lists.js +428 -0
  48. package/build/tools/newsletters.js +1 -8
  49. package/build/tools/org.js +549 -33
  50. package/build/tools/segments.js +35 -184
  51. package/build/tools/signatures.d.ts +2 -0
  52. package/build/tools/signatures.js +168 -0
  53. package/build/tools/sites.js +3 -1
  54. package/build/tools/skills.d.ts +2 -0
  55. package/build/tools/skills.js +29 -0
  56. package/build/tools/templates.js +133 -7
  57. package/package.json +14 -13
@@ -1,7 +1,10 @@
1
+ import { snapshotTemplateVersion, versionHasBodySnapshot } from '@sendinel/domain';
1
2
  import { z } from 'zod';
2
- import { db } from '../db.js';
3
+ import { logAudit } from '../audit.js';
4
+ import { db, requestContext } from '../db.js';
3
5
  import { getProjectId } from '../project.js';
4
6
  import { recordBrandAssetUsage } from '../lib/brand-asset-usage.js';
7
+ import { deriveTemplateVariables } from '../lib/voice.js';
5
8
  const txt = (text) => ({ content: [{ type: 'text', text }] });
6
9
  const j = (data) => txt(JSON.stringify(data, null, 2));
7
10
  function stripHtml(value) {
@@ -76,7 +79,7 @@ export function registerTemplateTools(server) {
76
79
  }, async ({ limit = 50, category, search }) => {
77
80
  const projectId = getProjectId();
78
81
  let query = db().from('campaign_templates')
79
- .select('id, name, category, description, subject_hint, brief_template, body_html, tags, project_id, created_at, updated_at')
82
+ .select('id, name, category, description, subject_hint, brief_template, body_html, variables, tags, project_id, created_at, updated_at, is_transactional')
80
83
  .eq('is_archived', false)
81
84
  .or(`project_id.is.null,project_id.eq.${projectId}`)
82
85
  .order('created_at', { ascending: false })
@@ -206,7 +209,7 @@ export function registerTemplateTools(server) {
206
209
  }, async ({ id }) => {
207
210
  const projectId = getProjectId();
208
211
  const { data, error } = await db().from('campaign_templates')
209
- .select('id, name, category, description, subject_hint, brief_template, body_html, tags, project_id, created_at, updated_at')
212
+ .select('id, name, category, description, subject_hint, brief_template, body_html, variables, tags, project_id, created_at, updated_at, is_transactional')
210
213
  .eq('id', id)
211
214
  .or(`project_id.is.null,project_id.eq.${projectId}`)
212
215
  .maybeSingle();
@@ -231,9 +234,10 @@ export function registerTemplateTools(server) {
231
234
  category: z.string().optional(),
232
235
  description: z.string().optional(),
233
236
  tags: z.array(z.string()).optional(),
237
+ is_transactional: z.boolean().optional(),
234
238
  brand_asset_ids: z.array(z.string().uuid()).optional().describe('Stable brand_media_assets ids explicitly referenced by this template.'),
235
239
  },
236
- }, async ({ name, brief, subject_hint, body_html, category, description, tags, brand_asset_ids }) => {
240
+ }, async ({ name, brief, subject_hint, body_html, category, description, tags, is_transactional, brand_asset_ids }) => {
237
241
  const projectId = getProjectId();
238
242
  const { data, error } = await db().from('campaign_templates')
239
243
  .insert({
@@ -242,11 +246,13 @@ export function registerTemplateTools(server) {
242
246
  brief_template: brief,
243
247
  subject_hint: subject_hint ?? '',
244
248
  body_html: body_html ?? null,
249
+ variables: deriveTemplateVariables({ body_html: body_html ?? null, subject_hint: subject_hint ?? '' }),
245
250
  category: category ?? 'general',
246
251
  description: description ?? '',
247
252
  tags: tags ?? [],
253
+ is_transactional: is_transactional ?? false,
248
254
  })
249
- .select('id, name, category, subject_hint, created_at')
255
+ .select('id, name, category, description, subject_hint, brief_template, body_html, body_source, body_text, format, variables, tags, created_at')
250
256
  .single();
251
257
  if (error)
252
258
  return j({ error: error.message });
@@ -260,6 +266,20 @@ export function registerTemplateTools(server) {
260
266
  }).catch((err) => {
261
267
  console.warn('Failed to record brand asset usage for MCP template:', err);
262
268
  });
269
+ await snapshotTemplateVersion(db(), {
270
+ templateId: data.id,
271
+ name: data.name,
272
+ category: data.category,
273
+ description: data.description,
274
+ subjectHint: data.subject_hint,
275
+ briefTemplate: data.brief_template,
276
+ bodyHtml: data.body_html,
277
+ bodySource: data.body_source,
278
+ bodyText: data.body_text,
279
+ format: data.format,
280
+ tags: data.tags,
281
+ createdBy: requestContext()?.actorId ?? null,
282
+ }).catch((err) => console.warn('Failed to snapshot template version for MCP create_template:', err));
263
283
  return j({ template: data, ok: true });
264
284
  });
265
285
  server.registerTool('update_template', {
@@ -280,12 +300,13 @@ export function registerTemplateTools(server) {
280
300
  category: z.string().optional(),
281
301
  description: z.string().optional(),
282
302
  tags: z.array(z.string()).optional(),
303
+ is_transactional: z.boolean().optional(),
283
304
  brand_asset_ids: z.array(z.string().uuid()).optional().describe('Stable brand_media_assets ids explicitly referenced by this template.'),
284
305
  },
285
306
  }, async ({ id, ...updates }) => {
286
307
  const projectId = getProjectId();
287
308
  const { data: existing, error: existingErr } = await db().from('campaign_templates')
288
- .select('id, project_id')
309
+ .select('id, project_id, body_html, subject_hint')
289
310
  .eq('id', id)
290
311
  .maybeSingle();
291
312
  if (existingErr || !existing)
@@ -307,11 +328,17 @@ export function registerTemplateTools(server) {
307
328
  row.description = updates.description;
308
329
  if (updates.tags !== undefined)
309
330
  row.tags = updates.tags;
331
+ if (updates.is_transactional !== undefined)
332
+ row.is_transactional = updates.is_transactional;
333
+ row.variables = deriveTemplateVariables({
334
+ body_html: updates.body_html !== undefined ? updates.body_html : existing.body_html,
335
+ subject_hint: updates.subject_hint !== undefined ? updates.subject_hint : existing.subject_hint,
336
+ });
310
337
  const { data, error } = await db().from('campaign_templates')
311
338
  .update(row)
312
339
  .eq('id', id)
313
340
  .eq('project_id', projectId)
314
- .select('id, name, category, subject_hint, updated_at')
341
+ .select('id, name, category, description, subject_hint, brief_template, body_html, body_source, body_text, format, variables, tags, updated_at')
315
342
  .single();
316
343
  if (error)
317
344
  return j({ error: error.message });
@@ -325,6 +352,20 @@ export function registerTemplateTools(server) {
325
352
  }).catch((err) => {
326
353
  console.warn('Failed to record brand asset usage for MCP template update:', err);
327
354
  });
355
+ await snapshotTemplateVersion(db(), {
356
+ templateId: data.id,
357
+ name: data.name,
358
+ category: data.category,
359
+ description: data.description,
360
+ subjectHint: data.subject_hint,
361
+ briefTemplate: data.brief_template,
362
+ bodyHtml: data.body_html,
363
+ bodySource: data.body_source,
364
+ bodyText: data.body_text,
365
+ format: data.format,
366
+ tags: data.tags,
367
+ createdBy: requestContext()?.actorId ?? null,
368
+ }).catch((err) => console.warn('Failed to snapshot template version for MCP update_template:', err));
328
369
  return j({ template: data, ok: true });
329
370
  });
330
371
  server.registerTool('delete_template', {
@@ -355,4 +396,89 @@ export function registerTemplateTools(server) {
355
396
  return j({ error: error.message });
356
397
  return j({ ok: true, deleted: id });
357
398
  });
399
+ server.registerTool('restore_template_version', {
400
+ title: 'Restore Template Version',
401
+ annotations: {
402
+ readOnlyHint: false,
403
+ destructiveHint: false,
404
+ idempotentHint: false,
405
+ openWorldHint: false,
406
+ },
407
+ description: 'Restore a template to a prior saved version. Versions saved before body-content capture existed are restored metadata-only.',
408
+ inputSchema: {
409
+ id: z.string().uuid(),
410
+ version_id: z.string().uuid(),
411
+ },
412
+ }, async ({ id, version_id }) => {
413
+ const projectId = getProjectId();
414
+ const { data: existing, error: existingErr } = await db().from('campaign_templates')
415
+ .select('id, project_id, body_html, body_text, subject_hint')
416
+ .eq('id', id)
417
+ .maybeSingle();
418
+ if (existingErr || !existing)
419
+ return j({ error: existingErr?.message ?? 'Template not found', code: 'NOT_FOUND' });
420
+ if (!existing.project_id || existing.project_id !== projectId)
421
+ return j({ error: 'Cannot restore a system template', code: 'FORBIDDEN' });
422
+ const { data: version, error: versionErr } = await db().from('template_versions')
423
+ .select('id, version, name, category, description, subject_hint, brief_template, tags, body_html, body_source, body_text, format')
424
+ .eq('id', version_id)
425
+ .eq('template_id', id)
426
+ .maybeSingle();
427
+ if (versionErr || !version)
428
+ return j({ error: versionErr?.message ?? 'Version not found', code: 'NOT_FOUND' });
429
+ const bodyRestored = versionHasBodySnapshot(version);
430
+ const row = {
431
+ updated_at: new Date().toISOString(),
432
+ name: version.name,
433
+ category: version.category,
434
+ description: version.description,
435
+ subject_hint: version.subject_hint,
436
+ brief_template: version.brief_template,
437
+ tags: version.tags,
438
+ };
439
+ if (bodyRestored) {
440
+ row.body_html = version.body_html;
441
+ row.body_source = version.body_source;
442
+ row.body_text = version.body_text;
443
+ row.format = version.format;
444
+ }
445
+ row.variables = deriveTemplateVariables({
446
+ body_html: bodyRestored ? version.body_html : existing.body_html,
447
+ body_text: bodyRestored ? version.body_text : existing.body_text,
448
+ subject_hint: version.subject_hint,
449
+ });
450
+ const { data, error } = await db().from('campaign_templates')
451
+ .update(row)
452
+ .eq('id', id)
453
+ .eq('project_id', projectId)
454
+ .select('id, name, category, description, subject_hint, brief_template, body_html, body_source, body_text, format, variables, tags, updated_at')
455
+ .single();
456
+ if (error)
457
+ return j({ error: error.message });
458
+ const nextVersion = await snapshotTemplateVersion(db(), {
459
+ templateId: data.id,
460
+ name: data.name,
461
+ category: data.category,
462
+ description: data.description,
463
+ subjectHint: data.subject_hint,
464
+ briefTemplate: data.brief_template,
465
+ bodyHtml: data.body_html,
466
+ bodySource: data.body_source,
467
+ bodyText: data.body_text,
468
+ format: data.format,
469
+ tags: data.tags,
470
+ createdBy: requestContext()?.actorId ?? null,
471
+ });
472
+ await logAudit({
473
+ action: 'template.restored',
474
+ resourceType: 'template',
475
+ resourceId: id,
476
+ details: {
477
+ restored_from_version: version.version,
478
+ new_version: nextVersion,
479
+ body_restored: bodyRestored,
480
+ },
481
+ });
482
+ return j({ template: data, ok: true, version: nextVersion, bodyRestored });
483
+ });
358
484
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sendinel/mcp-server",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
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",
@@ -12,7 +12,7 @@
12
12
  "README.md"
13
13
  ],
14
14
  "scripts": {
15
- "prebuild": "npm --prefix ../models run build",
15
+ "prebuild": "npm --prefix ../models run build && npm --prefix ../domain run build",
16
16
  "clean": "node -e \"require('node:fs').rmSync('build', { recursive: true, force: true })\"",
17
17
  "dev": "tsx src/index.ts",
18
18
  "dev:http": "MCP_TRANSPORT=http tsx src/index.ts",
@@ -23,27 +23,28 @@
23
23
  "prepublishOnly": "npm run build"
24
24
  },
25
25
  "dependencies": {
26
- "@anthropic-ai/sdk": "^0.93.0",
26
+ "@anthropic-ai/sdk": "^0.117.1",
27
27
  "@desler/models": "file:../models",
28
- "@modelcontextprotocol/sdk": "^1.27.1",
28
+ "@sendinel/domain": "file:../domain",
29
+ "@modelcontextprotocol/sdk": "^1.30.0",
29
30
  "@supabase/supabase-js": "^2.98.0",
30
31
  "dotenv": "^16.4.7",
31
32
  "express": "^5.2.1",
32
- "fast-xml-parser": "^5.7.3",
33
- "file-type": "^21.3.2",
34
- "jose": "^6.2.3",
33
+ "fast-xml-parser": "^5.10.1",
34
+ "file-type": "^22.0.1",
35
+ "jose": "^6.2.8",
35
36
  "qrcode": "^1.5.4",
36
- "resend": "^6.9.3",
37
- "undici": "^7.24.5",
37
+ "resend": "^6.20.0",
38
+ "undici": "^8.10.0",
38
39
  "zod": "^4.4.3"
39
40
  },
40
41
  "devDependencies": {
41
42
  "@types/express": "^5.0.3",
42
- "@types/node": "^26.0.1",
43
+ "@types/node": "^26.2.0",
43
44
  "@types/qrcode": "^1.5.6",
44
- "tsx": "^4.19.4",
45
- "typescript": "^6.0.3",
46
- "vitest": "^4.0.18"
45
+ "tsx": "^4.23.11",
46
+ "typescript": "^7.0.2",
47
+ "vitest": "^4.1.10"
47
48
  },
48
49
  "publishConfig": {
49
50
  "access": "public"