@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.
- package/README.md +35 -22
- package/build/anthropic-model.d.ts +30 -0
- package/build/anthropic-model.js +26 -1
- package/build/db.d.ts +17 -0
- package/build/db.js +15 -1
- package/build/index.js +6 -0
- package/build/lib/action-approvals.js +28 -0
- package/build/lib/brand-asset-persist.d.ts +1 -0
- package/build/lib/brand-asset-persist.js +20 -1
- package/build/lib/composer-blocks.d.ts +25 -25
- package/build/lib/composer-blocks.js +25 -5
- package/build/lib/connector-table-discovery.d.ts +12 -0
- package/build/lib/connector-table-discovery.js +117 -0
- package/build/lib/custom-properties-diff.d.ts +16 -0
- package/build/lib/custom-properties-diff.js +22 -0
- package/build/lib/schedule.d.ts +7 -2
- package/build/lib/schedule.js +8 -0
- package/build/lib/v1-client.d.ts +3 -0
- package/build/lib/v1-client.js +45 -0
- package/build/lib/voice.d.ts +7 -30
- package/build/lib/voice.js +6 -138
- package/build/prompts.d.ts +6 -0
- package/build/prompts.js +18 -0
- package/build/scoped-token.d.ts +1 -1
- package/build/server.d.ts +1 -0
- package/build/server.js +65 -6
- package/build/tools/analytics.js +97 -0
- package/build/tools/brand-kit.js +29 -30
- package/build/tools/campaigns.js +326 -52
- package/build/tools/cloud-import.d.ts +2 -0
- package/build/tools/cloud-import.js +76 -0
- package/build/tools/compound.d.ts +11 -0
- package/build/tools/compound.js +37 -7
- package/build/tools/connector.d.ts +2 -0
- package/build/tools/connector.js +280 -0
- package/build/tools/contacts.js +744 -16
- package/build/tools/content.js +228 -0
- package/build/tools/data.js +441 -0
- package/build/tools/delivery-ops.js +508 -1
- package/build/tools/forms.js +7 -6
- package/build/tools/gateway.d.ts +6 -0
- package/build/tools/gateway.js +92 -0
- package/build/tools/gdpr.js +185 -0
- package/build/tools/landing-pages.d.ts +2 -0
- package/build/tools/landing-pages.js +202 -0
- package/build/tools/lists.d.ts +2 -0
- package/build/tools/lists.js +428 -0
- package/build/tools/newsletters.js +1 -8
- package/build/tools/org.js +549 -33
- package/build/tools/segments.js +35 -184
- package/build/tools/signatures.d.ts +2 -0
- package/build/tools/signatures.js +168 -0
- package/build/tools/sites.js +3 -1
- package/build/tools/skills.d.ts +2 -0
- package/build/tools/skills.js +29 -0
- package/build/tools/templates.js +133 -7
- package/package.json +14 -13
package/build/tools/templates.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { snapshotTemplateVersion, versionHasBodySnapshot } from '@sendinel/domain';
|
|
1
2
|
import { z } from 'zod';
|
|
2
|
-
import {
|
|
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.
|
|
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.
|
|
26
|
+
"@anthropic-ai/sdk": "^0.117.1",
|
|
27
27
|
"@desler/models": "file:../models",
|
|
28
|
-
"@
|
|
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.
|
|
33
|
-
"file-type": "^
|
|
34
|
-
"jose": "^6.2.
|
|
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.
|
|
37
|
-
"undici": "^
|
|
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
|
|
43
|
+
"@types/node": "^26.2.0",
|
|
43
44
|
"@types/qrcode": "^1.5.6",
|
|
44
|
-
"tsx": "^4.
|
|
45
|
-
"typescript": "^
|
|
46
|
-
"vitest": "^4.
|
|
45
|
+
"tsx": "^4.23.11",
|
|
46
|
+
"typescript": "^7.0.2",
|
|
47
|
+
"vitest": "^4.1.10"
|
|
47
48
|
},
|
|
48
49
|
"publishConfig": {
|
|
49
50
|
"access": "public"
|