@sendinel/mcp-server 1.0.146 → 1.0.148

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 (42) hide show
  1. package/build/lib/action-approvals.js +3 -1
  2. package/build/lib/db-error.d.ts +39 -6
  3. package/build/lib/db-error.js +221 -11
  4. package/build/server.js +5 -3
  5. package/build/tools/ab-testing.js +4 -3
  6. package/build/tools/advisor.js +2 -2
  7. package/build/tools/ai-work.js +14 -13
  8. package/build/tools/analytics.js +33 -32
  9. package/build/tools/approvals.js +3 -1
  10. package/build/tools/automations.js +12 -11
  11. package/build/tools/brand-kit.js +4 -3
  12. package/build/tools/campaign-sets.js +11 -10
  13. package/build/tools/campaigns.js +81 -79
  14. package/build/tools/compound.js +7 -6
  15. package/build/tools/connector.js +4 -3
  16. package/build/tools/contacts.js +44 -45
  17. package/build/tools/content.js +16 -16
  18. package/build/tools/convert-imported.js +8 -7
  19. package/build/tools/data-proposals.js +8 -7
  20. package/build/tools/data.js +39 -38
  21. package/build/tools/delivery-ops.js +32 -32
  22. package/build/tools/drafts.js +10 -9
  23. package/build/tools/forms.js +5 -4
  24. package/build/tools/gdpr.js +10 -9
  25. package/build/tools/landing-pages.js +3 -3
  26. package/build/tools/lists.js +21 -20
  27. package/build/tools/newsletters.js +26 -25
  28. package/build/tools/org.js +26 -23
  29. package/build/tools/segments.js +4 -5
  30. package/build/tools/short-links.js +7 -6
  31. package/build/tools/signatures.js +10 -9
  32. package/build/tools/sites.js +9 -8
  33. package/build/tools/sms.js +14 -13
  34. package/build/tools/social-posts.js +20 -19
  35. package/build/tools/social-replies.js +7 -6
  36. package/build/tools/surveys.js +10 -9
  37. package/build/tools/templates.js +14 -13
  38. package/build/tools/warmup.js +5 -4
  39. package/build/tools/webhooks.js +5 -4
  40. package/package.json +1 -1
  41. package/build/lib/db-errors.d.ts +0 -12
  42. package/build/lib/db-errors.js +0 -20
@@ -6,6 +6,7 @@ import { db, requestContext } from '../db.js';
6
6
  import { getProjectId } from '../project.js';
7
7
  import { recordBrandAssetUsage } from '../lib/brand-asset-usage.js';
8
8
  import { deriveTemplateVariables } from '../lib/voice.js';
9
+ import { dbToolError } from '../lib/db-error.js';
9
10
  const txt = (text) => ({ content: [{ type: 'text', text }] });
10
11
  const j = (data) => txt(JSON.stringify(data, null, 2));
11
12
  function stripHtml(value) {
@@ -91,7 +92,7 @@ export function registerTemplateTools(server) {
91
92
  query = query.ilike('name', `%${search}%`);
92
93
  const { data, error } = await query;
93
94
  if (error)
94
- return j({ error: error.message });
95
+ return j(dbToolError(error));
95
96
  return j({ templates: data ?? [], count: data?.length ?? 0 });
96
97
  });
97
98
  server.registerTool('translate_template', {
@@ -117,7 +118,7 @@ export function registerTemplateTools(server) {
117
118
  const projectId = getProjectId();
118
119
  const { data: project, error: projectError } = await db().from('projects').select('org_id').eq('id', projectId).single();
119
120
  if (projectError || !project?.org_id)
120
- return j({ error: projectError?.message ?? 'Project not found' });
121
+ return j({ error: (projectError ? dbToolError(projectError).error : undefined) ?? 'Project not found' });
121
122
  const source = String(html ?? text ?? '').trim();
122
123
  if (!source)
123
124
  return j({ error: 'html or text is required' });
@@ -159,7 +160,7 @@ export function registerTemplateTools(server) {
159
160
  extracted_json: translated,
160
161
  }).select('id').single();
161
162
  if (error)
162
- return j({ error: error.message });
163
+ return j(dbToolError(error));
163
164
  let savedTemplateId = null;
164
165
  if (save) {
165
166
  const { data: saved, error: saveError } = await db().from('campaign_templates').insert({
@@ -179,7 +180,7 @@ export function registerTemplateTools(server) {
179
180
  external_id: source_template_id ?? null,
180
181
  }).select('id').single();
181
182
  if (saveError)
182
- return j({ error: saveError.message });
183
+ return j(dbToolError(saveError));
183
184
  savedTemplateId = saved?.id ?? null;
184
185
  if (savedTemplateId) {
185
186
  await recordBrandAssetUsage({
@@ -215,7 +216,7 @@ export function registerTemplateTools(server) {
215
216
  .or(`project_id.is.null,project_id.eq.${projectId}`)
216
217
  .maybeSingle();
217
218
  if (error)
218
- return j({ error: error.message });
219
+ return j(dbToolError(error));
219
220
  return j({ template: data });
220
221
  });
221
222
  server.registerTool('create_template', {
@@ -256,7 +257,7 @@ export function registerTemplateTools(server) {
256
257
  .select('id, name, category, description, subject_hint, brief_template, body_html, body_source, body_text, format, variables, tags, created_at')
257
258
  .single();
258
259
  if (error)
259
- return j({ error: error.message });
260
+ return j(dbToolError(error));
260
261
  await recordBrandAssetUsage({
261
262
  client: db(),
262
263
  projectId,
@@ -311,7 +312,7 @@ export function registerTemplateTools(server) {
311
312
  .eq('id', id)
312
313
  .maybeSingle();
313
314
  if (existingErr || !existing)
314
- return j({ error: existingErr?.message ?? 'Template not found', code: 'NOT_FOUND' });
315
+ return j({ error: (existingErr ? dbToolError(existingErr).error : undefined) ?? 'Template not found', code: 'NOT_FOUND' });
315
316
  if (!existing.project_id || existing.project_id !== projectId)
316
317
  return j({ error: 'Cannot edit system templates', code: 'FORBIDDEN' });
317
318
  const row = { updated_at: new Date().toISOString() };
@@ -342,7 +343,7 @@ export function registerTemplateTools(server) {
342
343
  .select('id, name, category, description, subject_hint, brief_template, body_html, body_source, body_text, format, variables, tags, updated_at')
343
344
  .single();
344
345
  if (error)
345
- return j({ error: error.message });
346
+ return j(dbToolError(error));
346
347
  await recordBrandAssetUsage({
347
348
  client: db(),
348
349
  projectId,
@@ -386,7 +387,7 @@ export function registerTemplateTools(server) {
386
387
  .eq('id', id)
387
388
  .maybeSingle();
388
389
  if (existingErr || !existing)
389
- return j({ error: existingErr?.message ?? 'Template not found', code: 'NOT_FOUND' });
390
+ return j({ error: (existingErr ? dbToolError(existingErr).error : undefined) ?? 'Template not found', code: 'NOT_FOUND' });
390
391
  if (!existing.project_id || existing.project_id !== projectId)
391
392
  return j({ error: 'Cannot delete system templates', code: 'FORBIDDEN' });
392
393
  const { error } = await db().from('campaign_templates')
@@ -394,7 +395,7 @@ export function registerTemplateTools(server) {
394
395
  .eq('id', id)
395
396
  .eq('project_id', projectId);
396
397
  if (error)
397
- return j({ error: error.message });
398
+ return j(dbToolError(error));
398
399
  return j({ ok: true, deleted: id });
399
400
  }));
400
401
  server.registerTool('restore_template_version', {
@@ -417,7 +418,7 @@ export function registerTemplateTools(server) {
417
418
  .eq('id', id)
418
419
  .maybeSingle();
419
420
  if (existingErr || !existing)
420
- return j({ error: existingErr?.message ?? 'Template not found', code: 'NOT_FOUND' });
421
+ return j({ error: (existingErr ? dbToolError(existingErr).error : undefined) ?? 'Template not found', code: 'NOT_FOUND' });
421
422
  if (!existing.project_id || existing.project_id !== projectId)
422
423
  return j({ error: 'Cannot restore a system template', code: 'FORBIDDEN' });
423
424
  const { data: version, error: versionErr } = await db().from('template_versions')
@@ -426,7 +427,7 @@ export function registerTemplateTools(server) {
426
427
  .eq('template_id', id)
427
428
  .maybeSingle();
428
429
  if (versionErr || !version)
429
- return j({ error: versionErr?.message ?? 'Version not found', code: 'NOT_FOUND' });
430
+ return j({ error: (versionErr ? dbToolError(versionErr).error : undefined) ?? 'Version not found', code: 'NOT_FOUND' });
430
431
  const bodyRestored = versionHasBodySnapshot(version);
431
432
  const row = {
432
433
  updated_at: new Date().toISOString(),
@@ -455,7 +456,7 @@ export function registerTemplateTools(server) {
455
456
  .select('id, name, category, description, subject_hint, brief_template, body_html, body_source, body_text, format, variables, tags, updated_at')
456
457
  .single();
457
458
  if (error)
458
- return j({ error: error.message });
459
+ return j(dbToolError(error));
459
460
  const nextVersion = await snapshotTemplateVersion(db(), {
460
461
  templateId: data.id,
461
462
  name: data.name,
@@ -1,6 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { db } from '../db.js';
3
3
  import { getProjectId } from '../project.js';
4
+ import { dbToolError } from '../lib/db-error.js';
4
5
  const j = (data) => ({ content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] });
5
6
  export function registerWarmupTools(server) {
6
7
  server.registerTool('get_warmup_status', {
@@ -22,7 +23,7 @@ export function registerWarmupTools(server) {
22
23
  .limit(1)
23
24
  .maybeSingle();
24
25
  if (error)
25
- return j({ error: error.message });
26
+ return j(dbToolError(error));
26
27
  const day = data?.warmup_day ?? 0;
27
28
  return j({
28
29
  phase: day >= 42 ? 'complete' : day > 0 ? 'warming' : 'not_started',
@@ -47,7 +48,7 @@ export function registerWarmupTools(server) {
47
48
  const { error } = await db().from('warmup_schedules')
48
49
  .upsert({ project_id: getProjectId(), site_id, target_volume: daily_limit ?? null, status: phase ?? 'active', updated_at: new Date().toISOString() }, { onConflict: 'site_id' });
49
50
  if (error)
50
- return j({ error: error.message });
51
+ return j(dbToolError(error));
51
52
  return j({ updated: true });
52
53
  });
53
54
  server.registerTool('pause_warmup', {
@@ -63,7 +64,7 @@ export function registerWarmupTools(server) {
63
64
  }, async ({ site_id, reason }) => {
64
65
  const { error } = await db().from('warmup_schedules').upsert({ project_id: getProjectId(), site_id, status: 'paused', pause_reason: reason ?? null, updated_at: new Date().toISOString() }, { onConflict: 'site_id' });
65
66
  if (error)
66
- return j({ error: error.message });
67
+ return j(dbToolError(error));
67
68
  return j({ paused: true });
68
69
  });
69
70
  server.registerTool('resume_warmup', {
@@ -79,7 +80,7 @@ export function registerWarmupTools(server) {
79
80
  }, async ({ site_id }) => {
80
81
  const { error } = await db().from('warmup_schedules').upsert({ project_id: getProjectId(), site_id, status: 'active', updated_at: new Date().toISOString() }, { onConflict: 'site_id' });
81
82
  if (error)
82
- return j({ error: error.message });
83
+ return j(dbToolError(error));
83
84
  return j({ resumed: true });
84
85
  });
85
86
  }
@@ -4,6 +4,7 @@ import { listSandboxWebhookEvents } from '@sendinel/domain';
4
4
  import { db, adminDb } from '../db.js';
5
5
  import { getProjectId } from '../project.js';
6
6
  import { validateWebhookUrl, generateWebhookSecret } from '../lib/webhook-events.js';
7
+ import { dbToolError } from '../lib/db-error.js';
7
8
  const txt = (text) => ({ content: [{ type: 'text', text }] });
8
9
  const j = (data) => txt(JSON.stringify(data, null, 2));
9
10
  const VALID_EVENTS = [
@@ -72,7 +73,7 @@ export function registerWebhookTools(server) {
72
73
  .select('id, url, events, active, created_at')
73
74
  .single();
74
75
  if (error)
75
- return txt(`Error creating subscription: ${error.message}`);
76
+ return txt(`Error creating subscription: ${dbToolError(error).error}`);
76
77
  return j({
77
78
  ...data,
78
79
  secret, // Only returned on creation — store it securely
@@ -102,7 +103,7 @@ export function registerWebhookTools(server) {
102
103
  q = q.eq('active', true);
103
104
  const { data, error } = await q;
104
105
  if (error)
105
- return txt(`Error: ${error.message}`);
106
+ return txt(`Error: ${dbToolError(error).error}`);
106
107
  return j({ subscriptions: data ?? [], count: data?.length ?? 0 });
107
108
  });
108
109
  server.registerTool('update_webhook_subscription', {
@@ -145,7 +146,7 @@ export function registerWebhookTools(server) {
145
146
  .select('id, url, events, active, description, updated_at')
146
147
  .single();
147
148
  if (error)
148
- return txt(`Error: ${error.message}`);
149
+ return txt(`Error: ${dbToolError(error).error}`);
149
150
  return j(data);
150
151
  }));
151
152
  server.registerTool('delete_webhook_subscription', {
@@ -168,7 +169,7 @@ export function registerWebhookTools(server) {
168
169
  .eq('id', subscription_id)
169
170
  .eq('project_id', getProjectId());
170
171
  if (error)
171
- return txt(`Error: ${error.message}`);
172
+ return txt(`Error: ${dbToolError(error).error}`);
172
173
  return txt(`Subscription ${subscription_id} deleted.`);
173
174
  }));
174
175
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sendinel/mcp-server",
3
- "version": "1.0.146",
3
+ "version": "1.0.148",
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",
@@ -1,12 +0,0 @@
1
- /** Caller-safe database errors. Never serialize database messages, details or hints. */
2
- export declare function mapDbError(error: unknown): {
3
- error: string;
4
- code: string;
5
- };
6
- export declare function dbErrorResult(error: unknown): {
7
- isError: boolean;
8
- content: {
9
- type: 'text';
10
- text: string;
11
- }[];
12
- };
@@ -1,20 +0,0 @@
1
- /** Caller-safe database errors. Never serialize database messages, details or hints. */
2
- export function mapDbError(error) {
3
- const code = error && typeof error === 'object' && 'code' in error ? String(error.code) : '';
4
- if (code === '23505')
5
- return { error: 'A record with these values already exists.', code: 'conflict' };
6
- if (code === '23503' || code === 'PGRST116') {
7
- return { error: 'The requested or referenced record was not found.', code: 'not_found' };
8
- }
9
- if (code.startsWith('23') || code.startsWith('22')) {
10
- return { error: 'The supplied values are not valid. Check the input and try again.', code: 'validation_failed' };
11
- }
12
- return { error: 'The operation could not be completed. Please try again later.', code: 'internal_error' };
13
- }
14
- export function dbErrorResult(error) {
15
- console.warn('[mcp] Database operation failed:', error);
16
- return {
17
- isError: true,
18
- content: [{ type: 'text', text: JSON.stringify(mapDbError(error)) }],
19
- };
20
- }