@sendinel/mcp-server 1.0.146 → 1.0.147

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 +26 -26
  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 +73 -74
  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
@@ -4,6 +4,7 @@ import { db } from '../db.js';
4
4
  import { getProjectId } from '../project.js';
5
5
  import { logAudit } from '../audit.js';
6
6
  import { assertChannelCanExecute, checkApproval, currentChannelContext, requestApproval, withApproval, } from '../lib/action-approvals.js';
7
+ import { dbToolError } from '../lib/db-error.js';
7
8
  const LIST_MEMBER_BATCH_SIZE = 500;
8
9
  const txt = (text) => ({ content: [{ type: 'text', text }] });
9
10
  const j = (data) => txt(JSON.stringify(data, null, 2));
@@ -97,14 +98,14 @@ export function registerListTools(server) {
97
98
  .order('created_at', { ascending: false })
98
99
  .limit(limit);
99
100
  if (error)
100
- return txt(`Error: ${error.message}`);
101
+ return txt(`Error: ${dbToolError(error).error}`);
101
102
  const lists = data ?? [];
102
103
  const listIds = lists.map((list) => list.id);
103
104
  const countMap = new Map();
104
105
  if (listIds.length > 0) {
105
106
  const { data: counts, error: countError } = await db().rpc('get_list_member_counts', { p_list_ids: listIds });
106
107
  if (countError)
107
- return txt(`Error: ${countError.message}`);
108
+ return txt(`Error: ${dbToolError(countError).error}`);
108
109
  for (const row of counts ?? []) {
109
110
  countMap.set(row.list_id, Number(row.member_count));
110
111
  }
@@ -135,7 +136,7 @@ export function registerListTools(server) {
135
136
  .select('id, name, description, created_at')
136
137
  .single();
137
138
  if (error)
138
- return txt(`Error: ${error.message}`);
139
+ return txt(`Error: ${dbToolError(error).error}`);
139
140
  await logAudit({
140
141
  action: 'list.created',
141
142
  resourceType: 'list',
@@ -170,7 +171,7 @@ export function registerListTools(server) {
170
171
  .select('id, name, description, updated_at')
171
172
  .maybeSingle();
172
173
  if (error)
173
- return txt(`Error: ${error.message}`);
174
+ return txt(`Error: ${dbToolError(error).error}`);
174
175
  if (!data)
175
176
  return txt('List not found');
176
177
  await logAudit({
@@ -194,7 +195,7 @@ export function registerListTools(server) {
194
195
  .eq('id', list_id)
195
196
  .eq('project_id', projectId);
196
197
  if (error)
197
- return txt(`Error: ${error.message}`);
198
+ return txt(`Error: ${dbToolError(error).error}`);
198
199
  await logAudit({ action: 'list.deleted', resourceType: 'list', resourceId: list_id });
199
200
  return j({ deleted: true, list_id });
200
201
  }));
@@ -218,15 +219,15 @@ export function registerListTools(server) {
218
219
  .eq('project_id', projectId)
219
220
  .maybeSingle();
220
221
  if (listError)
221
- return j({ error: listError.message });
222
+ return j(dbToolError(listError));
222
223
  if (!list)
223
224
  return j({ error: 'List not found', code: 'LIST_NOT_FOUND' });
224
225
  const validResult = await readValidContactIds(projectId, contact_ids);
225
226
  if (validResult.error)
226
- return j({ error: validResult.error.message });
227
+ return j(dbToolError(validResult.error));
227
228
  const memberResult = await readMemberIds(list_id, validResult.ids);
228
229
  if (memberResult.error)
229
- return j({ error: memberResult.error.message });
230
+ return j(dbToolError(memberResult.error));
230
231
  const wouldAdd = difference(validResult.ids, memberResult.ids);
231
232
  if (!confirmed) {
232
233
  return j({
@@ -254,10 +255,10 @@ export function registerListTools(server) {
254
255
  }
255
256
  const freshValidResult = await readValidContactIds(projectId, contact_ids);
256
257
  if (freshValidResult.error)
257
- return j({ error: freshValidResult.error.message });
258
+ return j(dbToolError(freshValidResult.error));
258
259
  const freshMemberResult = await readMemberIds(list_id, freshValidResult.ids);
259
260
  if (freshMemberResult.error)
260
- return j({ error: freshMemberResult.error.message });
261
+ return j(dbToolError(freshMemberResult.error));
261
262
  const freshWouldAdd = difference(freshValidResult.ids, freshMemberResult.ids);
262
263
  let added = 0;
263
264
  if (freshWouldAdd.length > 0) {
@@ -269,7 +270,7 @@ export function registerListTools(server) {
269
270
  })
270
271
  .select('id');
271
272
  if (error)
272
- return j({ error: error.message });
273
+ return j(dbToolError(error));
273
274
  added = inserted?.length ?? 0;
274
275
  }
275
276
  await logAudit({
@@ -300,12 +301,12 @@ export function registerListTools(server) {
300
301
  .eq('project_id', projectId)
301
302
  .maybeSingle();
302
303
  if (listError)
303
- return j({ error: listError.message });
304
+ return j(dbToolError(listError));
304
305
  if (!list)
305
306
  return j({ error: 'List not found', code: 'LIST_NOT_FOUND' });
306
307
  const memberResult = await readMemberIds(list_id, contact_ids);
307
308
  if (memberResult.error)
308
- return j({ error: memberResult.error.message });
309
+ return j(dbToolError(memberResult.error));
309
310
  if (!confirmed) {
310
311
  return j({
311
312
  confirmation_required: true,
@@ -330,7 +331,7 @@ export function registerListTools(server) {
330
331
  }
331
332
  const freshMemberResult = await readMemberIds(list_id, contact_ids);
332
333
  if (freshMemberResult.error)
333
- return j({ error: freshMemberResult.error.message });
334
+ return j(dbToolError(freshMemberResult.error));
334
335
  const removed = freshMemberResult.ids.length;
335
336
  if (removed > 0) {
336
337
  const { error } = await db()
@@ -339,7 +340,7 @@ export function registerListTools(server) {
339
340
  .eq('list_id', list_id)
340
341
  .in('contact_id', freshMemberResult.ids);
341
342
  if (error)
342
- return j({ error: error.message });
343
+ return j(dbToolError(error));
343
344
  }
344
345
  await logAudit({
345
346
  action: 'list.member_removed',
@@ -368,7 +369,7 @@ export function registerListTools(server) {
368
369
  catch (rulesError) {
369
370
  // #4270: invalid (or empty) filter rules add nobody and say why. Everyone is { match_all: true }.
370
371
  if (isSegmentRulesError(rulesError))
371
- return j({ error: rulesError.message, code: 'INVALID_SEGMENT_RULES', errors: rulesError.errors });
372
+ return j({ ...dbToolError(rulesError), code: 'INVALID_SEGMENT_RULES', errors: rulesError.errors });
372
373
  throw rulesError;
373
374
  }
374
375
  const approvalParams = { list_id, rules: normalizedRules };
@@ -379,13 +380,13 @@ export function registerListTools(server) {
379
380
  .eq('project_id', projectId)
380
381
  .maybeSingle();
381
382
  if (listError)
382
- return j({ error: listError.message });
383
+ return j(dbToolError(listError));
383
384
  if (!list)
384
385
  return j({ error: 'List not found', code: 'LIST_NOT_FOUND' });
385
386
  const matchedIds = await getSegmentContactIds(normalizedRules);
386
387
  const memberResult = await readMemberIds(list_id, matchedIds);
387
388
  if (memberResult.error)
388
- return j({ error: memberResult.error.message });
389
+ return j(dbToolError(memberResult.error));
389
390
  const wouldAdd = difference(matchedIds, memberResult.ids);
390
391
  if (!confirmed) {
391
392
  return j({
@@ -413,7 +414,7 @@ export function registerListTools(server) {
413
414
  const freshMatchedIds = await getSegmentContactIds(normalizedRules);
414
415
  const freshMemberResult = await readMemberIds(list_id, freshMatchedIds);
415
416
  if (freshMemberResult.error)
416
- return j({ error: freshMemberResult.error.message });
417
+ return j(dbToolError(freshMemberResult.error));
417
418
  const freshWouldAdd = difference(freshMatchedIds, freshMemberResult.ids);
418
419
  let added = 0;
419
420
  for (let index = 0; index < freshWouldAdd.length; index += LIST_MEMBER_BATCH_SIZE) {
@@ -423,7 +424,7 @@ export function registerListTools(server) {
423
424
  .upsert(rows, { onConflict: 'list_id,contact_id', ignoreDuplicates: true })
424
425
  .select('id');
425
426
  if (error)
426
- return j({ error: error.message });
427
+ return j(dbToolError(error));
427
428
  added += inserted?.length ?? 0;
428
429
  }
429
430
  await logAudit({
@@ -4,6 +4,7 @@ import { stripActiveEmailContent } from '@sendinel/domain';
4
4
  import { db } from '../db.js';
5
5
  import { getProjectId } from '../project.js';
6
6
  import { parseFutureDate } from '../lib/schedule.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 NEWSLETTER_COLUMNS = 'id, org_id, project_id, site_id, name, slug, description, sender_name, sender_email, reply_to, cadence, branding, public_signup_enabled, double_opt_in, status, subscriber_count, source, source_payload, created_at, updated_at';
@@ -53,9 +54,9 @@ async function loadSendableIssue(newsletterId, issueId, projectId) {
53
54
  .maybeSingle(),
54
55
  ]);
55
56
  if (newsletterError)
56
- return { error: newsletterError.message };
57
+ return { ...dbToolError(newsletterError) };
57
58
  if (issueError)
58
- return { error: issueError.message };
59
+ return { ...dbToolError(issueError) };
59
60
  if (!newsletter)
60
61
  return { error: 'Newsletter not found', code: 'NOT_FOUND' };
61
62
  if (!issue)
@@ -82,7 +83,7 @@ async function ensureNewsletterCampaign(newsletter, issue, projectId, scheduledA
82
83
  .select('id')
83
84
  .maybeSingle();
84
85
  if (error)
85
- return { error: error.message };
86
+ return { ...dbToolError(error) };
86
87
  if (!campaign)
87
88
  campaignId = null;
88
89
  }
@@ -98,7 +99,7 @@ async function ensureNewsletterCampaign(newsletter, issue, projectId, scheduledA
98
99
  metadata,
99
100
  }).select('id').single();
100
101
  if (error || !campaign)
101
- return { error: error?.message ?? 'Campaign create failed' };
102
+ return { error: (error ? dbToolError(error).error : undefined) ?? 'Campaign create failed' };
102
103
  campaignId = campaign.id;
103
104
  }
104
105
  const { data: firstStep, error: stepFetchError } = await db().from('campaign_steps')
@@ -107,13 +108,13 @@ async function ensureNewsletterCampaign(newsletter, issue, projectId, scheduledA
107
108
  .eq('step_order', 0)
108
109
  .maybeSingle();
109
110
  if (stepFetchError)
110
- return { error: stepFetchError.message };
111
+ return { ...dbToolError(stepFetchError) };
111
112
  if (firstStep?.id) {
112
113
  const { error } = await db().from('campaign_steps')
113
114
  .update({ subject: issue.subject, body_html: issue.content, body_text: issue.preheader })
114
115
  .eq('id', firstStep.id);
115
116
  if (error)
116
- return { error: error.message };
117
+ return { ...dbToolError(error) };
117
118
  }
118
119
  else {
119
120
  const { error } = await db().from('campaign_steps').insert({
@@ -126,7 +127,7 @@ async function ensureNewsletterCampaign(newsletter, issue, projectId, scheduledA
126
127
  delay_hours: 0,
127
128
  });
128
129
  if (error)
129
- return { error: error.message };
130
+ return { ...dbToolError(error) };
130
131
  }
131
132
  const { data: subscriptions, error: subscriptionError } = await db().from('newsletter_subscriptions')
132
133
  .select('subscriber_id')
@@ -135,7 +136,7 @@ async function ensureNewsletterCampaign(newsletter, issue, projectId, scheduledA
135
136
  .eq('status', 'active')
136
137
  .not('subscriber_id', 'is', null);
137
138
  if (subscriptionError)
138
- return { error: subscriptionError.message };
139
+ return { ...dbToolError(subscriptionError) };
139
140
  const now = new Date().toISOString();
140
141
  const nextSendAt = scheduledAt ?? now;
141
142
  const rows = (subscriptions ?? []).map((subscription) => ({
@@ -151,14 +152,14 @@ async function ensureNewsletterCampaign(newsletter, issue, projectId, scheduledA
151
152
  const { error } = await db().from('campaign_enrollments')
152
153
  .upsert(rows, { onConflict: 'campaign_id,contact_id', ignoreDuplicates: true });
153
154
  if (error)
154
- return { error: error.message };
155
+ return { ...dbToolError(error) };
155
156
  }
156
157
  const { error: statusError } = await db().from('campaigns')
157
158
  .update({ status: 'active', updated_at: now })
158
159
  .eq('id', campaignId)
159
160
  .eq('project_id', projectId);
160
161
  if (statusError)
161
- return { error: statusError.message };
162
+ return { ...dbToolError(statusError) };
162
163
  return { campaignId, enrolled: rows.length };
163
164
  }
164
165
  export function registerNewsletterTools(server) {
@@ -181,7 +182,7 @@ export function registerNewsletterTools(server) {
181
182
  query = status ? query.eq('status', status) : query.neq('status', 'archived');
182
183
  const { data, error } = await query;
183
184
  if (error)
184
- return j({ error: error.message });
185
+ return j(dbToolError(error));
185
186
  return j({ newsletters: data ?? [], count: data?.length ?? 0 });
186
187
  });
187
188
  server.registerTool('get_newsletter', {
@@ -197,7 +198,7 @@ export function registerNewsletterTools(server) {
197
198
  }, async ({ newsletter_id }) => {
198
199
  const { data, error } = await db().from('newsletters').select(NEWSLETTER_COLUMNS).eq('id', newsletter_id).eq('project_id', getProjectId()).maybeSingle();
199
200
  if (error)
200
- return j({ error: error.message });
201
+ return j(dbToolError(error));
201
202
  if (!data)
202
203
  return j({ error: 'Newsletter not found', code: 'NOT_FOUND' });
203
204
  return j(data);
@@ -256,7 +257,7 @@ export function registerNewsletterTools(server) {
256
257
  if (error?.code === '23505')
257
258
  return j({ error: 'slug already exists', code: 'conflict' });
258
259
  if (error)
259
- return j({ error: error.message, code: 'INTERNAL_ERROR' });
260
+ return j({ ...dbToolError(error), code: 'INTERNAL_ERROR' });
260
261
  return j(data);
261
262
  });
262
263
  server.registerTool('update_newsletter', {
@@ -303,7 +304,7 @@ export function registerNewsletterTools(server) {
303
304
  if (error?.code === '23505')
304
305
  return j({ error: 'slug already exists', code: 'conflict' });
305
306
  if (error)
306
- return j({ error: error.message, code: 'INTERNAL_ERROR' });
307
+ return j({ ...dbToolError(error), code: 'INTERNAL_ERROR' });
307
308
  if (!data)
308
309
  return j({ error: 'Newsletter not found', code: 'NOT_FOUND' });
309
310
  return j(data);
@@ -330,7 +331,7 @@ export function registerNewsletterTools(server) {
330
331
  const projectId = getProjectId();
331
332
  const { data: newsletter, error: newsletterError } = await db().from('newsletters').select('id, org_id').eq('id', newsletter_id).eq('project_id', projectId).maybeSingle();
332
333
  if (newsletterError)
333
- return j({ error: newsletterError.message });
334
+ return j(dbToolError(newsletterError));
334
335
  if (!newsletter)
335
336
  return j({ error: 'Newsletter not found', code: 'NOT_FOUND' });
336
337
  const { data: latest } = await db().from('newsletter_issues').select('issue_number').eq('newsletter_id', newsletter_id).order('issue_number', { ascending: false }).limit(1).maybeSingle();
@@ -346,7 +347,7 @@ export function registerNewsletterTools(server) {
346
347
  source_payload: { newsletter_id, subject, content, preheader },
347
348
  }).select(ISSUE_COLUMNS).single();
348
349
  if (error)
349
- return j({ error: error.message });
350
+ return j(dbToolError(error));
350
351
  return j(data);
351
352
  });
352
353
  server.registerTool('list_newsletter_issues', {
@@ -374,7 +375,7 @@ export function registerNewsletterTools(server) {
374
375
  query = query.eq('status', status);
375
376
  const { data, error } = await query;
376
377
  if (error)
377
- return j({ error: error.message });
378
+ return j(dbToolError(error));
378
379
  return j({ issues: data ?? [], count: data?.length ?? 0 });
379
380
  });
380
381
  server.registerTool('schedule_newsletter_issue', {
@@ -413,7 +414,7 @@ export function registerNewsletterTools(server) {
413
414
  .select(ISSUE_COLUMNS)
414
415
  .maybeSingle();
415
416
  if (error)
416
- return j({ error: error.message });
417
+ return j(dbToolError(error));
417
418
  return j({ issue: data, campaign_id: campaign.campaignId, enrolled: campaign.enrolled });
418
419
  }));
419
420
  server.registerTool('send_newsletter_issue', {
@@ -469,7 +470,7 @@ export function registerNewsletterTools(server) {
469
470
  .select(ISSUE_COLUMNS)
470
471
  .maybeSingle();
471
472
  if (error)
472
- return j({ error: error.message });
473
+ return j(dbToolError(error));
473
474
  return j({ issue: data, campaign_id: campaign.campaignId, enrolled: campaign.enrolled, blog_post_id: post?.id ?? null, archive_url: post?.external_url ?? null });
474
475
  }));
475
476
  server.registerTool('subscribe_to_newsletter', {
@@ -500,7 +501,7 @@ export function registerNewsletterTools(server) {
500
501
  .eq('status', 'active')
501
502
  .maybeSingle();
502
503
  if (newsletterError)
503
- return j({ error: newsletterError.message });
504
+ return j(dbToolError(newsletterError));
504
505
  if (!newsletter)
505
506
  return j({ error: 'Newsletter not found', code: 'NOT_FOUND' });
506
507
  const { data: subscriber, error: subscriberError } = await db().from('subscribers').upsert({
@@ -511,7 +512,7 @@ export function registerNewsletterTools(server) {
511
512
  source,
512
513
  }, { onConflict: 'project_id,email' }).select('id').single();
513
514
  if (subscriberError || !subscriber)
514
- return j({ error: subscriberError?.message ?? 'Subscriber upsert failed' });
515
+ return j({ error: (subscriberError ? dbToolError(subscriberError).error : undefined) ?? 'Subscriber upsert failed' });
515
516
  const activeImmediately = !newsletter.double_opt_in;
516
517
  const { data, error } = await db().from('newsletter_subscriptions').upsert({
517
518
  newsletter_id,
@@ -527,7 +528,7 @@ export function registerNewsletterTools(server) {
527
528
  unsubscribe_reason: null,
528
529
  }, { onConflict: 'newsletter_id,email' }).select(SUBSCRIPTION_COLUMNS).single();
529
530
  if (error)
530
- return j({ error: error.message });
531
+ return j(dbToolError(error));
531
532
  return j(data);
532
533
  });
533
534
  server.registerTool('unsubscribe_from_newsletter', {
@@ -560,7 +561,7 @@ export function registerNewsletterTools(server) {
560
561
  p_reason: reason,
561
562
  });
562
563
  if (error)
563
- return j({ error: error.message });
564
+ return j(dbToolError(error));
564
565
  if (!data?.success)
565
566
  return j({ error: data?.reason ?? 'Unsubscribe failed', code: 'NOT_FOUND' });
566
567
  return j(data);
@@ -585,7 +586,7 @@ export function registerNewsletterTools(server) {
585
586
  query = query.eq('status', status);
586
587
  const { data, error } = await query;
587
588
  if (error)
588
- return j({ error: error.message });
589
+ return j(dbToolError(error));
589
590
  return j({ subscribers: data ?? [], count: data?.length ?? 0 });
590
591
  });
591
592
  server.registerTool('get_newsletter_stats', {
@@ -605,7 +606,7 @@ export function registerNewsletterTools(server) {
605
606
  .eq('project_id', getProjectId())
606
607
  .eq('status', 'active');
607
608
  if (error)
608
- return j({ error: error.message });
609
+ return j(dbToolError(error));
609
610
  return j({ newsletter_id, active_subscribers: count ?? 0, issue_analytics_status: 'pending_phase_4' });
610
611
  });
611
612
  }
@@ -5,6 +5,7 @@ import { adminDb, db } from '../db.js';
5
5
  import { getProjectId } from '../project.js';
6
6
  import { assertChannelCanExecute, checkApproval, currentChannelContext, requestApproval, withApproval } from '../lib/action-approvals.js';
7
7
  import { callSendinelV1 } from '../lib/v1-client.js';
8
+ import { dbToolError } from '../lib/db-error.js';
8
9
  const j = (data) => ({ content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] });
9
10
  const PROVIDER_SWITCH_V1_PROVIDERS = ['resend', 'ses', 'elastic-email'];
10
11
  async function callProviderSwitchApi(path, init) {
@@ -60,7 +61,7 @@ async function resolveCurrentOrgId(requestedOrgId) {
60
61
  .eq('id', getProjectId())
61
62
  .maybeSingle();
62
63
  if (error)
63
- return { ok: false, error: error.message };
64
+ return { ok: false, ...dbToolError(error) };
64
65
  if (!project?.org_id)
65
66
  return { ok: false, error: 'Project has no organization', code: 'NO_ORG' };
66
67
  if (requestedOrgId && requestedOrgId !== project.org_id) {
@@ -89,7 +90,7 @@ async function getCurrentProjectOrg() {
89
90
  .eq('id', getProjectId())
90
91
  .maybeSingle();
91
92
  if (error)
92
- return { ok: false, error: error.message };
93
+ return { ok: false, ...dbToolError(error) };
93
94
  if (!data?.org_id)
94
95
  return { ok: false, error: 'Project organization not found', code: 'NOT_FOUND' };
95
96
  return { ok: true, project: data };
@@ -123,7 +124,7 @@ async function upsertWhiteLabelSettings(orgId, payload) {
123
124
  if (error) {
124
125
  if (error.code === '23505')
125
126
  return { ok: false, error: 'Custom domain is already claimed', code: 'CONFLICT' };
126
- return { ok: false, error: error.message };
127
+ return { ok: false, ...dbToolError(error) };
127
128
  }
128
129
  return { ok: true, settings: formatWhiteLabelSettings(data) };
129
130
  }
@@ -141,21 +142,23 @@ export function registerOrgTools(server) {
141
142
  }, async ({ org_id }) => {
142
143
  let orgId = org_id;
143
144
  if (!orgId) {
144
- const { data: project } = await db().from('projects').select('org_id').eq('id', getProjectId()).maybeSingle();
145
+ const { data: project, error } = await db().from('projects').select('org_id').eq('id', getProjectId()).maybeSingle();
146
+ if (error)
147
+ return j(dbToolError(error));
145
148
  orgId = project?.org_id;
146
149
  }
147
150
  if (!orgId)
148
151
  return j({ members: [] });
149
152
  const { data, error } = await db().from('org_members').select('user_id, role, created_at').eq('org_id', orgId);
150
153
  if (error)
151
- return j({ error: error.message });
154
+ return j(dbToolError(error));
152
155
  const membersWithUsers = await Promise.all((data ?? []).map(async (member) => {
153
156
  const { data: userData, error: userError } = await adminDb().auth.admin.getUserById(member.user_id);
154
157
  return { member, user: userData?.user ?? null, error: userError };
155
158
  }));
156
159
  const userError = membersWithUsers.find((result) => result.error)?.error;
157
160
  if (userError)
158
- return j({ error: userError.message });
161
+ return j(dbToolError(userError));
159
162
  return j({
160
163
  members: membersWithUsers.map(({ member, user }) => ({
161
164
  user_id: member.user_id,
@@ -214,7 +217,7 @@ export function registerOrgTools(server) {
214
217
  .eq('user_id', user_id)
215
218
  .maybeSingle();
216
219
  if (memberErr)
217
- return j({ error: memberErr.message });
220
+ return j(dbToolError(memberErr));
218
221
  if (!member)
219
222
  return j({ error: 'Member not found', code: 'NOT_FOUND' });
220
223
  const approvalParams = { user_id, org_id: context.orgId };
@@ -256,7 +259,7 @@ export function registerOrgTools(server) {
256
259
  .eq('org_id', context.orgId)
257
260
  .eq('user_id', user_id);
258
261
  if (error)
259
- return j({ error: error.message });
262
+ return j(dbToolError(error));
260
263
  void logAudit({
261
264
  action: 'remove_member',
262
265
  resourceType: 'org_member',
@@ -292,7 +295,7 @@ export function registerOrgTools(server) {
292
295
  .eq('user_id', user_id)
293
296
  .maybeSingle();
294
297
  if (memberErr)
295
- return j({ error: memberErr.message });
298
+ return j(dbToolError(memberErr));
296
299
  if (!member)
297
300
  return j({ error: 'Member not found', code: 'NOT_FOUND' });
298
301
  const approvalParams = { user_id, role, org_id: context.orgId };
@@ -327,7 +330,7 @@ export function registerOrgTools(server) {
327
330
  .select('user_id, role')
328
331
  .maybeSingle();
329
332
  if (error)
330
- return j({ error: error.message });
333
+ return j(dbToolError(error));
331
334
  void logAudit({
332
335
  action: 'update_member_role',
333
336
  resourceType: 'org_member',
@@ -362,7 +365,7 @@ export function registerOrgTools(server) {
362
365
  .eq('user_id', user_id)
363
366
  .maybeSingle();
364
367
  if (membershipErr)
365
- return j({ error: membershipErr.message });
368
+ return j(dbToolError(membershipErr));
366
369
  if (!membership)
367
370
  return j({ error: 'Member not found', code: 'NOT_FOUND' });
368
371
  if (membership.role === 'owner') {
@@ -373,7 +376,7 @@ export function registerOrgTools(server) {
373
376
  .select('id')
374
377
  .eq('org_id', context.orgId);
375
378
  if (orgProjectsErr)
376
- return j({ error: orgProjectsErr.message });
379
+ return j(dbToolError(orgProjectsErr));
377
380
  const validIds = new Set((orgProjects ?? []).map((project) => project.id));
378
381
  const assignmentMap = new Map();
379
382
  for (const project of projects)
@@ -391,7 +394,7 @@ export function registerOrgTools(server) {
391
394
  .eq('user_id', user_id)
392
395
  .in('project_id', idsToRemove);
393
396
  if (deleteErr)
394
- return j({ error: deleteErr.message });
397
+ return j(dbToolError(deleteErr));
395
398
  }
396
399
  if (requestedProjects.length > 0) {
397
400
  const rows = requestedProjects.map((project) => ({ user_id, project_id: project.project_id, role: project.role }));
@@ -399,7 +402,7 @@ export function registerOrgTools(server) {
399
402
  .from('user_project_access')
400
403
  .upsert(rows, { onConflict: 'user_id,project_id' });
401
404
  if (upsertErr)
402
- return j({ error: upsertErr.message });
405
+ return j(dbToolError(upsertErr));
403
406
  }
404
407
  void logAudit({
405
408
  action: 'set_member_project_access',
@@ -430,7 +433,7 @@ export function registerOrgTools(server) {
430
433
  .is('accepted_at', null)
431
434
  .gt('expires_at', new Date().toISOString());
432
435
  if (error)
433
- return j({ error: error.message });
436
+ return j(dbToolError(error));
434
437
  return j({ invitations: data ?? [] });
435
438
  });
436
439
  server.registerTool('resend_invitation', {
@@ -459,7 +462,7 @@ export function registerOrgTools(server) {
459
462
  .gt('expires_at', new Date().toISOString())
460
463
  .maybeSingle();
461
464
  if (invitationErr)
462
- return j({ error: invitationErr.message });
465
+ return j(dbToolError(invitationErr));
463
466
  if (!invitation)
464
467
  return j({ error: 'Invitation not found', code: 'NOT_FOUND' });
465
468
  const expiresAt = new Date(Date.now() + 7 * 86400000).toISOString();
@@ -472,7 +475,7 @@ export function registerOrgTools(server) {
472
475
  .select('id')
473
476
  .maybeSingle();
474
477
  if (updateErr)
475
- return j({ error: updateErr.message });
478
+ return j(dbToolError(updateErr));
476
479
  if (!updated)
477
480
  return j({ error: 'Invitation not found', code: 'NOT_FOUND' });
478
481
  const appUrl = (process.env.NEXT_PUBLIC_APP_URL ?? 'https://sendinel.ai').replace(/\/$/, '');
@@ -509,7 +512,7 @@ export function registerOrgTools(server) {
509
512
  .eq('org_id', context.orgId)
510
513
  .maybeSingle();
511
514
  if (invitationErr)
512
- return j({ error: invitationErr.message });
515
+ return j(dbToolError(invitationErr));
513
516
  if (!invitation || invitation.accepted_at)
514
517
  return j({ error: 'Invitation not found', code: 'NOT_FOUND' });
515
518
  const { error } = await db()
@@ -518,7 +521,7 @@ export function registerOrgTools(server) {
518
521
  .eq('id', invitation_id)
519
522
  .eq('org_id', context.orgId);
520
523
  if (error)
521
- return j({ error: error.message });
524
+ return j(dbToolError(error));
522
525
  void logAudit({
523
526
  action: 'invitation.revoked',
524
527
  resourceType: 'org_invitation',
@@ -613,7 +616,7 @@ export function registerOrgTools(server) {
613
616
  query = query.lte('created_at', `${date_to}T23:59:59.999Z`);
614
617
  const { data, count, error } = await query;
615
618
  if (error)
616
- return j({ error: error.message });
619
+ return j(dbToolError(error));
617
620
  return j({ entries: data ?? [], total: count ?? 0, limit: effectiveLimit, offset: effectiveOffset });
618
621
  });
619
622
  server.registerTool('get_audit_stats', {
@@ -720,10 +723,10 @@ export function registerOrgTools(server) {
720
723
  ? await adminDb().from('model_registry').select(columns).eq('org_id', org_id)
721
724
  : { data: [], error: null };
722
725
  if (scopedRows.error)
723
- return j({ error: scopedRows.error.message });
726
+ return j(dbToolError(scopedRows.error));
724
727
  const globalRows = await adminDb().from('model_registry').select(columns).is('org_id', null);
725
728
  if (globalRows.error)
726
- return j({ error: globalRows.error.message });
729
+ return j(dbToolError(globalRows.error));
727
730
  return j({
728
731
  overrides: [
729
732
  ...(scopedRows.data ?? []),
@@ -751,7 +754,7 @@ export function registerOrgTools(server) {
751
754
  .eq('org_id', context.project.org_id)
752
755
  .maybeSingle();
753
756
  if (error)
754
- return j({ error: error.message });
757
+ return j(dbToolError(error));
755
758
  return j({
756
759
  org_id: context.project.org_id,
757
760
  plan: context.project.plan ?? 'free',
@@ -1,10 +1,9 @@
1
- import { dbErrorResult } from '../lib/db-errors.js';
2
1
  import { withApproval } from '../lib/action-approvals.js';
3
2
  import { z } from 'zod';
4
3
  import Anthropic from '@anthropic-ai/sdk';
5
4
  import { db } from '../db.js';
6
5
  import { getProjectId } from '../project.js';
7
- import { dbToolError, toolError } from '../lib/db-error.js';
6
+ import { dbErrorResult, dbToolError, toolError } from '../lib/db-error.js';
8
7
  import { getAnthropicModelId } from '../anthropic-model.js';
9
8
  import { AI_MAX_TOKENS, outputConfig, anthropicCheckedText } from '@sendinel/domain';
10
9
  import { applySegmentRules, buildSegmentBaseQuery, evaluateSegmentRules, hydrateEmailSendsContacts, isSegmentRulesError, normalizeSegmentRulesToTree, prepareSegmentRulesForStorage, readAllRows, } from '@sendinel/domain';
@@ -132,7 +131,7 @@ export function registerSegmentTools(server) {
132
131
  .eq('project_id', getProjectId())
133
132
  .order('created_at', { ascending: false });
134
133
  if (error)
135
- return txt(`Error: ${error.message}`);
134
+ return txt(`Error: ${dbToolError(error).error}`);
136
135
  const segments = [];
137
136
  for (const seg of data ?? []) {
138
137
  let ids;
@@ -218,7 +217,7 @@ export function registerSegmentTools(server) {
218
217
  .eq('id', segment_id)
219
218
  .eq('project_id', getProjectId());
220
219
  if (error)
221
- return txt(`Error: ${error.message}`);
220
+ return txt(`Error: ${dbToolError(error).error}`);
222
221
  return j({ deleted: true, segment_id });
223
222
  }));
224
223
  server.registerTool('preview_segment', {
@@ -336,7 +335,7 @@ export function registerSegmentTools(server) {
336
335
  .select('id, name, description, rules, created_at')
337
336
  .single();
338
337
  if (error)
339
- return txt(`Error: ${error.message}`);
338
+ return txt(`Error: ${dbToolError(error).error}`);
340
339
  return j({ ...data, estimated_contacts: ids.length });
341
340
  });
342
341
  }