@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
@@ -7,6 +7,7 @@ import { validateTemplate } from '../lib/voice.js';
7
7
  import { logAudit } from '../audit.js';
8
8
  import { eventKeysFromHistory, contactPropertyKeys } from '../lib/content-sources.js';
9
9
  import { loadSet, previewOf, writeCampaignDrafts } from './campaign-sets.js';
10
+ import { dbToolError } from '../lib/db-error.js';
10
11
  /**
11
12
  * convert_imported_campaigns (#3689): turns campaigns imported from another platform (Customer.io
12
13
  * first) into new Sendinel drafts that can send. Call it without create to preview: converted
@@ -44,7 +45,7 @@ function reviewView(plan) {
44
45
  original_subject: email.original_subject,
45
46
  subject: email.subject,
46
47
  text: email.preview_text,
47
- check_these: email.flags.map((flag) => ({ kind: flag.kind, message: flag.message, ...(flag.excerpt ? { where: flag.excerpt } : {}) })),
48
+ check_these: email.flags.map((flag) => ({ kind: flag.kind, message: dbToolError(flag).error, ...(flag.excerpt ? { where: flag.excerpt } : {}) })),
48
49
  })),
49
50
  ...(plan.blocking.length ? { must_fix: plan.blocking } : {}),
50
51
  ...(plan.notes.length ? { notes: plan.notes } : {}),
@@ -70,7 +71,7 @@ export function registerConvertImportedTools(server) {
70
71
  .eq('project_id', projectId)
71
72
  .in('id', ids);
72
73
  if (sourceErr)
73
- return j({ error: sourceErr.message, code: 'CAMPAIGN_LOOKUP_FAILED' });
74
+ return j({ ...dbToolError(sourceErr), code: 'CAMPAIGN_LOOKUP_FAILED' });
74
75
  const byId = new Map((sources ?? []).map((c) => [c.id, c]));
75
76
  const missing = ids.filter((id) => !byId.has(id));
76
77
  if (missing.length)
@@ -83,7 +84,7 @@ export function registerConvertImportedTools(server) {
83
84
  .select('campaign_id, step_order, subject, body_html, body_text, delay_days, delay_hours')
84
85
  .in('campaign_id', ids);
85
86
  if (stepErr)
86
- return j({ error: stepErr.message, code: 'STEP_LOOKUP_FAILED' });
87
+ return j({ ...dbToolError(stepErr), code: 'STEP_LOOKUP_FAILED' });
87
88
  const steps = (stepRows ?? []);
88
89
  // Event data seen in history, per trigger. A failed lookup is reported, never treated as "none".
89
90
  const historyNotes = [];
@@ -110,7 +111,7 @@ export function registerConvertImportedTools(server) {
110
111
  .eq('project_id', projectId)
111
112
  .in('metadata->>converted_from', ids);
112
113
  if (existingErr)
113
- return j({ error: existingErr.message, code: 'CAMPAIGN_LOOKUP_FAILED' });
114
+ return j({ ...dbToolError(existingErr), code: 'CAMPAIGN_LOOKUP_FAILED' });
114
115
  const earlier = (existing ?? []);
115
116
  plans.forEach((plan) => {
116
117
  const twin = earlier.find((e) => e.metadata?.converted_from === plan.source_campaign_id && e.name === plan.name);
@@ -138,7 +139,7 @@ export function registerConvertImportedTools(server) {
138
139
  // Sender: the given one, the imports' own, or the only one. Never guessed between several.
139
140
  const { data: sites, error: sitesErr } = await db().from('sites').select('id, name, from_email, unsub_link_enabled').eq('project_id', projectId);
140
141
  if (sitesErr)
141
- return j({ error: sitesErr.message, code: 'SITE_LOOKUP_FAILED' });
142
+ return j({ ...dbToolError(sitesErr), code: 'SITE_LOOKUP_FAILED' });
142
143
  const importSites = [...new Set(ids.map((id) => byId.get(id).site_id).filter(Boolean))];
143
144
  const pick = site_id ?? (importSites.length === 1 ? importSites[0] : sites?.length === 1 ? sites[0].id : null);
144
145
  const site = (sites ?? []).find((s) => s.id === pick);
@@ -170,7 +171,7 @@ export function registerConvertImportedTools(server) {
170
171
  metadata: {
171
172
  converted_from: plan.source_campaign_id,
172
173
  converted_from_name: plan.source_name,
173
- review_flags: plan.emails.flatMap((email) => email.flags.map((flag) => ({ email: email.email, kind: flag.kind, message: flag.message, ...(flag.excerpt ? { excerpt: flag.excerpt } : {}) }))),
174
+ review_flags: plan.emails.flatMap((email) => email.flags.map((flag) => ({ email: email.email, kind: flag.kind, message: dbToolError(flag).error, ...(flag.excerpt ? { excerpt: flag.excerpt } : {}) }))),
174
175
  },
175
176
  emails: plan.emails.map((email) => ({
176
177
  subject: email.subject,
@@ -200,7 +201,7 @@ export function registerConvertImportedTools(server) {
200
201
  preview = previewOf(loaded.campaigns, loaded.steps);
201
202
  }
202
203
  catch (error) {
203
- return j({ campaign_set_id: setId, campaign_ids: createdIds, status: 'draft', warning: `Created, but the preview could not be read back: ${error instanceof Error ? error.message : String(error)}` });
204
+ return j({ campaign_set_id: setId, campaign_ids: createdIds, status: 'draft', warning: `Created, but the preview could not be read back: ${error instanceof Error ? dbToolError(error).error : String(error)}` });
204
205
  }
205
206
  return j({
206
207
  campaign_set_id: setId,
@@ -3,6 +3,7 @@ import { z } from 'zod';
3
3
  import { db } from '../db.js';
4
4
  import { getProjectId } from '../project.js';
5
5
  import { getAnthropicModelId } from '../anthropic-model.js';
6
+ import { dbToolError } from '../lib/db-error.js';
6
7
  const txt = (text) => ({ content: [{ type: 'text', text }] });
7
8
  const j = (data) => txt(JSON.stringify(data, null, 2));
8
9
  async function engagementSnapshot(projectId) {
@@ -47,7 +48,7 @@ export function registerDataProposalTools(server) {
47
48
  .eq('id', projectId)
48
49
  .maybeSingle();
49
50
  if (projectError)
50
- return txt(`Error: ${projectError.message}`);
51
+ return txt(`Error: ${dbToolError(projectError).error}`);
51
52
  if (!project)
52
53
  return txt('Project not found.');
53
54
  const snapshot = await engagementSnapshot(projectId);
@@ -76,7 +77,7 @@ export function registerDataProposalTools(server) {
76
77
  .select('*')
77
78
  .single();
78
79
  if (error)
79
- return txt(`Error: ${error.message}`);
80
+ return txt(`Error: ${dbToolError(error).error}`);
80
81
  return j({ proposal: data });
81
82
  });
82
83
  server.registerTool('list_data_proposals', {
@@ -103,7 +104,7 @@ export function registerDataProposalTools(server) {
103
104
  query = query.eq('status', status);
104
105
  const { data, error } = await query;
105
106
  if (error)
106
- return txt(`Error: ${error.message}`);
107
+ return txt(`Error: ${dbToolError(error).error}`);
107
108
  return j({ proposals: data ?? [] });
108
109
  });
109
110
  server.registerTool('approve_data_proposal', {
@@ -127,7 +128,7 @@ export function registerDataProposalTools(server) {
127
128
  .eq('project_id', projectId)
128
129
  .maybeSingle();
129
130
  if (proposalError)
130
- return txt(`Error: ${proposalError.message}`);
131
+ return txt(`Error: ${dbToolError(proposalError).error}`);
131
132
  if (!proposal)
132
133
  return txt('Proposal not found.');
133
134
  if (!['pending', 'approved'].includes(proposal.status))
@@ -141,7 +142,7 @@ export function registerDataProposalTools(server) {
141
142
  .limit(1)
142
143
  .maybeSingle();
143
144
  if (siteError)
144
- return txt(`Error: ${siteError.message}`);
145
+ return txt(`Error: ${dbToolError(siteError).error}`);
145
146
  if (!site)
146
147
  return txt('No active site available for draft conversion.');
147
148
  const { data: draft, error: draftError } = await db()
@@ -158,7 +159,7 @@ export function registerDataProposalTools(server) {
158
159
  .select('id')
159
160
  .single();
160
161
  if (draftError)
161
- return txt(`Error: ${draftError.message}`);
162
+ return txt(`Error: ${dbToolError(draftError).error}`);
162
163
  const { data: updated, error: updateError } = await db()
163
164
  .from('data_proposals')
164
165
  .update({
@@ -171,7 +172,7 @@ export function registerDataProposalTools(server) {
171
172
  .select('*')
172
173
  .single();
173
174
  if (updateError)
174
- return txt(`Error: ${updateError.message}`);
175
+ return txt(`Error: ${dbToolError(updateError).error}`);
175
176
  return j({ proposal: updated, draft_id: draft.id });
176
177
  });
177
178
  }
@@ -7,6 +7,7 @@ import { normalizeActiveCampaignAccountUrl } from '../lib/url-validation.js';
7
7
  import { assertChannelCanExecute, checkApproval, currentChannelContext, requestApproval, withApproval } from '../lib/action-approvals.js';
8
8
  import { getProjectId } from '../project.js';
9
9
  import { logAudit } from '../audit.js';
10
+ import { dbToolError } from '../lib/db-error.js';
10
11
  const j = (data) => ({ content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] });
11
12
  function generateApiKey() {
12
13
  const secret = `snk_${randomBytes(32).toString('hex')}`;
@@ -263,14 +264,14 @@ export function registerDataTools(server) {
263
264
  query = query.eq('is_active', true);
264
265
  const { data, error } = await query;
265
266
  if (error)
266
- return j({ error: error.message });
267
+ return j(dbToolError(error));
267
268
  return j({
268
269
  platforms: CONNECTED_PLATFORMS.filter((platform) => platform !== 'kit'),
269
270
  connections: (data ?? []).map(sanitizeConnection),
270
271
  });
271
272
  }
272
273
  catch (error) {
273
- return j({ error: error instanceof Error ? error.message : 'Failed to list connected platforms' });
274
+ return j({ error: error instanceof Error ? dbToolError(error).error : 'Failed to list connected platforms' });
274
275
  }
275
276
  });
276
277
  server.registerTool('list_provider_credentials', {
@@ -309,9 +310,9 @@ export function registerDataTools(server) {
309
310
  .limit(50);
310
311
  const [platformResult, importResult] = await Promise.all([platformQuery, importQuery]);
311
312
  if (platformResult.error)
312
- return j({ error: platformResult.error.message });
313
+ return j(dbToolError(platformResult.error));
313
314
  if (importResult.error)
314
- return j({ error: importResult.error.message });
315
+ return j(dbToolError(importResult.error));
315
316
  const mapRow = (row, source) => {
316
317
  let credential;
317
318
  try {
@@ -351,7 +352,7 @@ export function registerDataTools(server) {
351
352
  return j({ credentials: [...platformCredentials, ...importCredentials] });
352
353
  }
353
354
  catch (error) {
354
- return j({ error: error instanceof Error ? error.message : 'Failed to list provider credentials' });
355
+ return j({ error: error instanceof Error ? dbToolError(error).error : 'Failed to list provider credentials' });
355
356
  }
356
357
  });
357
358
  server.registerTool('delete_provider_credential', {
@@ -389,7 +390,7 @@ export function registerDataTools(server) {
389
390
  .eq('project_id', context.projectId)
390
391
  .maybeSingle();
391
392
  if (credentialErr)
392
- return j({ error: credentialErr.message });
393
+ return j(dbToolError(credentialErr));
393
394
  if (!credential)
394
395
  return j({ error: 'Credential not found' });
395
396
  const provider = parsed.source === 'platform' ? credential.platform : credential.provider;
@@ -449,7 +450,7 @@ export function registerDataTools(server) {
449
450
  .select('id, platform')
450
451
  .maybeSingle();
451
452
  if (error)
452
- return j({ error: error.message });
453
+ return j(dbToolError(error));
453
454
  if (!data)
454
455
  return j({ error: 'Credential not found' });
455
456
  await logAudit({
@@ -471,7 +472,7 @@ export function registerDataTools(server) {
471
472
  .select('id, provider')
472
473
  .maybeSingle();
473
474
  if (error)
474
- return j({ error: error.message });
475
+ return j(dbToolError(error));
475
476
  if (!data)
476
477
  return j({ error: 'Credential not found' });
477
478
  await logAudit({
@@ -483,7 +484,7 @@ export function registerDataTools(server) {
483
484
  return j({ deleted: true, id: credential_id, source: 'import', provider: data.provider });
484
485
  }
485
486
  catch (error) {
486
- return j({ error: error instanceof Error ? error.message : 'Failed to delete provider credential' });
487
+ return j({ error: error instanceof Error ? dbToolError(error).error : 'Failed to delete provider credential' });
487
488
  }
488
489
  });
489
490
  server.registerTool('connect_platform', {
@@ -535,7 +536,7 @@ export function registerDataTools(server) {
535
536
  .select('id, org_id, project_id, platform, display_name, config, status, is_active, last_synced_at, last_error, created_at, updated_at')
536
537
  .single();
537
538
  if (error)
538
- return j({ error: error.message });
539
+ return j(dbToolError(error));
539
540
  await logAudit({
540
541
  action: 'platform_connection.connected',
541
542
  resourceType: 'platform_connection',
@@ -549,7 +550,7 @@ export function registerDataTools(server) {
549
550
  });
550
551
  }
551
552
  catch (error) {
552
- return j({ error: error instanceof Error ? error.message : 'Failed to connect platform' });
553
+ return j({ error: error instanceof Error ? dbToolError(error).error : 'Failed to connect platform' });
553
554
  }
554
555
  });
555
556
  server.registerTool('disconnect_platform', {
@@ -585,7 +586,7 @@ export function registerDataTools(server) {
585
586
  query = query.eq('platform', normalizePlatform(platform));
586
587
  const { data, error } = await query.select('id, platform');
587
588
  if (error)
588
- return j({ error: error.message });
589
+ return j(dbToolError(error));
589
590
  await logAudit({
590
591
  action: 'platform_connection.disconnected',
591
592
  resourceType: 'platform_connection',
@@ -595,7 +596,7 @@ export function registerDataTools(server) {
595
596
  return j({ disconnected: data?.length ?? 0, connections: data ?? [] });
596
597
  }
597
598
  catch (error) {
598
- return j({ error: error instanceof Error ? error.message : 'Failed to disconnect platform' });
599
+ return j({ error: error instanceof Error ? dbToolError(error).error : 'Failed to disconnect platform' });
599
600
  }
600
601
  }));
601
602
  server.registerTool('list_platform_events', {
@@ -628,11 +629,11 @@ export function registerDataTools(server) {
628
629
  query = query.eq('platform', normalizePlatform(platform));
629
630
  const { data, error } = await query;
630
631
  if (error)
631
- return j({ error: error.message });
632
+ return j(dbToolError(error));
632
633
  return j({ events: data ?? [] });
633
634
  }
634
635
  catch (error) {
635
- return j({ error: error instanceof Error ? error.message : 'Failed to list platform events' });
636
+ return j({ error: error instanceof Error ? dbToolError(error).error : 'Failed to list platform events' });
636
637
  }
637
638
  });
638
639
  server.registerTool('configure_platform_trigger', {
@@ -673,7 +674,7 @@ export function registerDataTools(server) {
673
674
  .eq('is_active', true)
674
675
  .maybeSingle();
675
676
  if (connectionError)
676
- return j({ error: connectionError.message });
677
+ return j(dbToolError(connectionError));
677
678
  if (!connection)
678
679
  return j({ error: 'active platform connection not found' });
679
680
  const currentConfig = connection.config ?? connection.meta ?? {};
@@ -692,7 +693,7 @@ export function registerDataTools(server) {
692
693
  .select('id, platform, display_name, config, status, is_active, last_synced_at, updated_at')
693
694
  .single();
694
695
  if (error)
695
- return j({ error: error.message });
696
+ return j(dbToolError(error));
696
697
  await logAudit({
697
698
  action: 'platform_trigger.configured',
698
699
  resourceType: 'platform_connection',
@@ -702,7 +703,7 @@ export function registerDataTools(server) {
702
703
  return j({ connection: sanitizeConnection(data), trigger });
703
704
  }
704
705
  catch (error) {
705
- return j({ error: error instanceof Error ? error.message : 'Failed to configure platform trigger' });
706
+ return j({ error: error instanceof Error ? dbToolError(error).error : 'Failed to configure platform trigger' });
706
707
  }
707
708
  });
708
709
  server.registerTool('list_custom_webhooks', {
@@ -734,7 +735,7 @@ export function registerDataTools(server) {
734
735
  query = query.eq('is_active', true);
735
736
  const { data, error } = await query;
736
737
  if (error)
737
- return j({ error: error.message });
738
+ return j(dbToolError(error));
738
739
  return j({
739
740
  connections: (data ?? []).map((connection) => ({
740
741
  id: connection.id,
@@ -749,7 +750,7 @@ export function registerDataTools(server) {
749
750
  });
750
751
  }
751
752
  catch (error) {
752
- return j({ error: error instanceof Error ? error.message : 'Failed to list custom webhooks' });
753
+ return j({ error: error instanceof Error ? dbToolError(error).error : 'Failed to list custom webhooks' });
753
754
  }
754
755
  });
755
756
  server.registerTool('create_custom_webhook', {
@@ -798,7 +799,7 @@ export function registerDataTools(server) {
798
799
  .select('id')
799
800
  .single();
800
801
  if (error || !data)
801
- return j({ error: error?.message ?? 'Failed to create custom webhook' });
802
+ return j({ error: (error ? dbToolError(error).error : undefined) ?? 'Failed to create custom webhook' });
802
803
  const endpointUrl = customWebhookEndpointUrl(data.id);
803
804
  await client
804
805
  .schema('email')
@@ -818,7 +819,7 @@ export function registerDataTools(server) {
818
819
  });
819
820
  }
820
821
  catch (error) {
821
- return j({ error: error instanceof Error ? error.message : 'Failed to create custom webhook' });
822
+ return j({ error: error instanceof Error ? dbToolError(error).error : 'Failed to create custom webhook' });
822
823
  }
823
824
  });
824
825
  server.registerTool('rotate_custom_webhook_secret', {
@@ -849,7 +850,7 @@ export function registerDataTools(server) {
849
850
  .eq('is_active', true)
850
851
  .maybeSingle();
851
852
  if (lookupError)
852
- return j({ error: lookupError.message });
853
+ return j(dbToolError(lookupError));
853
854
  if (!connection)
854
855
  return j({ error: 'Custom webhook not found' });
855
856
  if (!confirmed) {
@@ -903,7 +904,7 @@ export function registerDataTools(server) {
903
904
  .select('id')
904
905
  .maybeSingle();
905
906
  if (error)
906
- return j({ error: error.message });
907
+ return j(dbToolError(error));
907
908
  if (!updated)
908
909
  return j({ error: 'Custom webhook is no longer active' });
909
910
  await logAudit({
@@ -919,7 +920,7 @@ export function registerDataTools(server) {
919
920
  });
920
921
  }
921
922
  catch (error) {
922
- return j({ error: error instanceof Error ? error.message : 'Failed to rotate custom webhook secret' });
923
+ return j({ error: error instanceof Error ? dbToolError(error).error : 'Failed to rotate custom webhook secret' });
923
924
  }
924
925
  });
925
926
  server.registerTool('list_import_history', {
@@ -939,7 +940,7 @@ export function registerDataTools(server) {
939
940
  .order('created_at', { ascending: false })
940
941
  .limit(limit);
941
942
  if (error)
942
- return j({ error: error.message });
943
+ return j(dbToolError(error));
943
944
  return j({
944
945
  imports: (data ?? []).map((row) => ({
945
946
  ...row,
@@ -966,7 +967,7 @@ export function registerDataTools(server) {
966
967
  .is('revoked_at', null)
967
968
  .order('created_at', { ascending: false });
968
969
  if (error)
969
- return j({ error: error.message });
970
+ return j(dbToolError(error));
970
971
  return j({ keys: data ?? [] });
971
972
  });
972
973
  server.registerTool('create_api_key', {
@@ -997,7 +998,7 @@ export function registerDataTools(server) {
997
998
  .select('id, name, slug')
998
999
  .single();
999
1000
  if (error)
1000
- return j({ error: error.message });
1001
+ return j(dbToolError(error));
1001
1002
  return j({ key_id: data.id, name: data.name, slug: data.slug, secret });
1002
1003
  }));
1003
1004
  server.registerTool('revoke_api_key', {
@@ -1013,7 +1014,7 @@ export function registerDataTools(server) {
1013
1014
  }, withApproval('revoke_api_key', async ({ key_id }) => {
1014
1015
  const { error } = await db().from('api_keys').update({ revoked_at: new Date().toISOString() }).eq('project_id', getProjectId()).eq('id', key_id);
1015
1016
  if (error)
1016
- return j({ error: error.message });
1017
+ return j(dbToolError(error));
1017
1018
  return j({ revoked: true });
1018
1019
  }));
1019
1020
  server.registerTool('migrate_campaigns_from', {
@@ -1042,7 +1043,7 @@ export function registerDataTools(server) {
1042
1043
  .eq('id', projectId)
1043
1044
  .maybeSingle();
1044
1045
  if (projectError)
1045
- return j({ error: projectError.message });
1046
+ return j(dbToolError(projectError));
1046
1047
  if (!project?.org_id)
1047
1048
  return j({ error: 'project not found' });
1048
1049
  let normalizedAccountUrl = account_url;
@@ -1051,7 +1052,7 @@ export function registerDataTools(server) {
1051
1052
  normalizedAccountUrl = normalizeActiveCampaignAccountUrl(account_url ?? '');
1052
1053
  }
1053
1054
  catch (error) {
1054
- return j({ error: error instanceof Error ? error.message : 'Invalid ActiveCampaign account URL' });
1055
+ return j({ error: error instanceof Error ? dbToolError(error).error : 'Invalid ActiveCampaign account URL' });
1055
1056
  }
1056
1057
  }
1057
1058
  const credentials = { api_key: oauth_token, account_url: normalizedAccountUrl };
@@ -1073,7 +1074,7 @@ export function registerDataTools(server) {
1073
1074
  .select('id')
1074
1075
  .single();
1075
1076
  if (error)
1076
- return j({ error: error.message });
1077
+ return j(dbToolError(error));
1077
1078
  return j({
1078
1079
  job_id: data.id,
1079
1080
  status: 'pending',
@@ -1116,7 +1117,7 @@ export function registerDataTools(server) {
1116
1117
  .eq('is_active', true)
1117
1118
  .maybeSingle();
1118
1119
  if (connectionError)
1119
- return j({ error: connectionError.message });
1120
+ return j(dbToolError(connectionError));
1120
1121
  if (!connection)
1121
1122
  return j({ error: 'platform connection not found' });
1122
1123
  if (!MIGRATION_PROVIDERS.includes(connection.platform))
@@ -1142,7 +1143,7 @@ export function registerDataTools(server) {
1142
1143
  .select('id')
1143
1144
  .single();
1144
1145
  if (error)
1145
- return j({ error: error.message });
1146
+ return j(dbToolError(error));
1146
1147
  await client.schema('email').from('platform_connections').update({ last_synced_at: new Date().toISOString() }).eq('id', connection_id);
1147
1148
  return j({ job_id: data.id, status: 'pending', connection_id, provider: connection.platform });
1148
1149
  });
@@ -1165,7 +1166,7 @@ export function registerDataTools(server) {
1165
1166
  query = query.eq('read', false);
1166
1167
  const { data, error } = await query;
1167
1168
  if (error)
1168
- return j({ error: error.message });
1169
+ return j(dbToolError(error));
1169
1170
  return j({ notifications: (data ?? []).map((row) => ({ ...row, read: row.read === true, read_at: null })) });
1170
1171
  });
1171
1172
  server.registerTool('mark_notifications_read', {
@@ -1184,7 +1185,7 @@ export function registerDataTools(server) {
1184
1185
  query = query.in('id', ids);
1185
1186
  const { data, error } = await query.select('id');
1186
1187
  if (error)
1187
- return j({ error: error.message });
1188
+ return j(dbToolError(error));
1188
1189
  return j({ updated: data?.length ?? 0 });
1189
1190
  });
1190
1191
  server.registerTool('tenant_export_full', {
@@ -1278,10 +1279,10 @@ export function registerDataTools(server) {
1278
1279
  upload = await client.storage.from(bucket).upload(path, createZip(files), { contentType: 'application/zip', upsert: true });
1279
1280
  }
1280
1281
  if (upload.error)
1281
- return j({ error: upload.error.message });
1282
+ return j(dbToolError(upload.error));
1282
1283
  const { data, error } = await client.storage.from(bucket).createSignedUrl(path, 24 * 3600);
1283
1284
  if (error)
1284
- return j({ error: error.message });
1285
+ return j(dbToolError(error));
1285
1286
  return j({
1286
1287
  download_url: data.signedUrl,
1287
1288
  expires_at: new Date(Date.now() + 24 * 3600 * 1000).toISOString(),