@sendinel/mcp-server 1.0.3 → 1.0.5

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 (60) hide show
  1. package/build/audit.d.ts +1 -0
  2. package/build/audit.js +9 -3
  3. package/build/auth.d.ts +10 -0
  4. package/build/auth.js +82 -9
  5. package/build/db.d.ts +12 -0
  6. package/build/db.js +37 -2
  7. package/build/index.js +38 -12
  8. package/build/lib/action-approvals.d.ts +28 -1
  9. package/build/lib/action-approvals.js +89 -2
  10. package/build/lib/brand-asset-persist.d.ts +35 -0
  11. package/build/lib/brand-asset-persist.js +161 -0
  12. package/build/lib/brand-asset-usage.d.ts +13 -0
  13. package/build/lib/brand-asset-usage.js +46 -0
  14. package/build/lib/email-provider.d.ts +19 -0
  15. package/build/lib/email-provider.js +281 -0
  16. package/build/lib/ssrf-pinned-fetch.d.ts +16 -0
  17. package/build/lib/ssrf-pinned-fetch.js +101 -0
  18. package/build/lib/survey-link-resolver.d.ts +6 -0
  19. package/build/lib/survey-link-resolver.js +23 -0
  20. package/build/lib/svg-sanitize.d.ts +2 -0
  21. package/build/lib/svg-sanitize.js +110 -0
  22. package/build/lib/webhook-events.d.ts +1 -1
  23. package/build/scoped-token.d.ts +11 -0
  24. package/build/scoped-token.js +59 -0
  25. package/build/server.js +30 -9
  26. package/build/tools/ab-testing.js +28 -0
  27. package/build/tools/advisor.js +52 -2
  28. package/build/tools/ai-work.d.ts +2 -0
  29. package/build/tools/ai-work.js +460 -0
  30. package/build/tools/analytics.d.ts +1 -0
  31. package/build/tools/analytics.js +203 -0
  32. package/build/tools/approvals.js +15 -1
  33. package/build/tools/automations.js +288 -65
  34. package/build/tools/brand-kit.d.ts +2 -0
  35. package/build/tools/brand-kit.js +277 -0
  36. package/build/tools/campaigns.js +900 -69
  37. package/build/tools/compound.js +28 -0
  38. package/build/tools/contacts.js +338 -17
  39. package/build/tools/content.js +266 -1
  40. package/build/tools/data-proposals.js +21 -0
  41. package/build/tools/data.js +121 -5
  42. package/build/tools/delivery-ops.js +84 -0
  43. package/build/tools/drafts.js +64 -1
  44. package/build/tools/forms.js +42 -9
  45. package/build/tools/gdpr.js +22 -3
  46. package/build/tools/newsletters.d.ts +2 -0
  47. package/build/tools/newsletters.js +583 -0
  48. package/build/tools/org.js +297 -3
  49. package/build/tools/segments.js +45 -3
  50. package/build/tools/short-links.d.ts +2 -0
  51. package/build/tools/short-links.js +157 -0
  52. package/build/tools/sites.js +35 -0
  53. package/build/tools/sms.js +49 -0
  54. package/build/tools/social-posts.js +81 -5
  55. package/build/tools/surveys.d.ts +2 -0
  56. package/build/tools/surveys.js +222 -0
  57. package/build/tools/templates.js +78 -2
  58. package/build/tools/warmup.js +28 -0
  59. package/build/tools/webhooks.js +29 -0
  60. package/package.json +10 -4
@@ -7,9 +7,133 @@ import { buildVoicePrompt, buildEmailSystemPrompt } from '../lib/voice.js';
7
7
  import { getAnthropicModelId } from '../anthropic-model.js';
8
8
  const txt = (text) => ({ content: [{ type: 'text', text }] });
9
9
  const j = (data) => txt(JSON.stringify(data, null, 2));
10
+ function normalizeValue(value) {
11
+ if (typeof value !== 'string')
12
+ return null;
13
+ const normalized = value.trim().toLowerCase().replace(/[\s-]+/g, '_');
14
+ return normalized || null;
15
+ }
16
+ function normalizeList(value) {
17
+ const raw = Array.isArray(value) ? value : typeof value === 'string' ? [value] : [];
18
+ return Array.from(new Set(raw.map(normalizeValue).filter((item) => Boolean(item))));
19
+ }
20
+ function triggerConfig(value) {
21
+ if (!value || typeof value !== 'object' || Array.isArray(value))
22
+ return { events: [], tags: [] };
23
+ const config = value;
24
+ return {
25
+ events: normalizeList(config.events ?? config.event_names ?? config.trigger_event_names),
26
+ tags: normalizeList(config.tags ?? config.trigger_tags ?? config.entity_tags ?? config.project_tags),
27
+ };
28
+ }
29
+ function campaignMatchesTrigger(campaign, input) {
30
+ const config = triggerConfig(campaign.trigger_config);
31
+ const legacyEvent = normalizeValue(campaign.trigger_event_name);
32
+ const legacyTag = normalizeValue(campaign.trigger_tag);
33
+ const campaignEvents = Array.from(new Set([...(legacyEvent ? [legacyEvent] : []), ...config.events]));
34
+ const campaignTags = Array.from(new Set([...(legacyTag ? [legacyTag] : []), ...config.tags]));
35
+ const event = normalizeValue(input.event);
36
+ const tags = new Set((input.tags ?? []).map(normalizeValue).filter((tag) => Boolean(tag)));
37
+ if (campaignEvents.length > 0 && (!event || !campaignEvents.includes(event)))
38
+ return false;
39
+ if (campaignTags.length > 0 && !campaignTags.some((tag) => tags.has(tag)))
40
+ return false;
41
+ return campaignEvents.length > 0 || campaignTags.length > 0;
42
+ }
43
+ async function enrollSubscriberIntoTriggeredCampaigns(input) {
44
+ const { subscriber_id, campaign_id, site_id, trigger_event_name, trigger_event_names, trigger_tag, trigger_tags, next_send_at, context } = input;
45
+ const triggerEvents = Array.from(new Set([...normalizeList(trigger_event_names), ...(trigger_event_name ? [trigger_event_name] : [])]));
46
+ const triggerTags = Array.from(new Set([...normalizeList(trigger_tags), ...(trigger_tag ? [trigger_tag] : [])]));
47
+ const projectId = getProjectId();
48
+ const { data: subscriber, error: subscriberError } = await db()
49
+ .from('subscribers')
50
+ .select('id, email, global_unsubscribed, cooled_at')
51
+ .eq('id', subscriber_id)
52
+ .eq('project_id', projectId)
53
+ .maybeSingle();
54
+ if (subscriberError)
55
+ return txt(`Error: ${subscriberError.message}`);
56
+ if (!subscriber)
57
+ return j({ error: 'Subscriber not found', code: 'NOT_FOUND' });
58
+ if (subscriber.global_unsubscribed)
59
+ return j({ enrolled: 0, skipped: 'global_unsubscribed' });
60
+ if (subscriber.cooled_at)
61
+ return j({ enrolled: 0, skipped: 'cooled_contact' });
62
+ const { data: suppression } = await db()
63
+ .from('suppressions')
64
+ .select('id')
65
+ .eq('project_id', projectId)
66
+ .eq('email', subscriber.email)
67
+ .maybeSingle();
68
+ if (suppression)
69
+ return j({ enrolled: 0, skipped: 'suppressed' });
70
+ let query = db()
71
+ .from('campaigns')
72
+ .select('id, name, site_id, trigger_event_name, trigger_tag, trigger_config')
73
+ .eq('project_id', projectId)
74
+ .eq('type', 'triggered')
75
+ .eq('status', 'active');
76
+ if (campaign_id)
77
+ query = query.eq('id', campaign_id);
78
+ if (site_id)
79
+ query = query.eq('site_id', site_id);
80
+ if (!campaign_id && !triggerEvents.length && !triggerTags.length) {
81
+ return txt('Provide campaign_id, trigger_event_name(s), or trigger_tag(s).');
82
+ }
83
+ const { data: candidateCampaigns, error: campaignError } = await query;
84
+ if (campaignError)
85
+ return txt(`Error: ${campaignError.message}`);
86
+ const campaigns = campaign_id
87
+ ? (candidateCampaigns ?? [])
88
+ : (candidateCampaigns ?? []).filter((campaign) => triggerEvents.some(event => campaignMatchesTrigger(campaign, { event, tags: triggerTags }))
89
+ || (!triggerEvents.length && campaignMatchesTrigger(campaign, { tags: triggerTags })));
90
+ if (!campaigns?.length)
91
+ return j({ enrolled: 0, campaigns: [], skipped: 'no_matching_active_automation' });
92
+ const sendAt = next_send_at ?? new Date().toISOString();
93
+ const rows = campaigns.map((campaign) => ({
94
+ campaign_id: campaign.id,
95
+ contact_id: subscriber_id,
96
+ current_step: 0,
97
+ status: 'active',
98
+ enrolled_at: new Date().toISOString(),
99
+ next_send_at: sendAt,
100
+ enrollment_context: context ?? {},
101
+ }));
102
+ const { data: inserted, error: enrollError } = await db()
103
+ .from('campaign_enrollments')
104
+ .upsert(rows, { onConflict: 'campaign_id,contact_id', ignoreDuplicates: true })
105
+ .select('id, campaign_id');
106
+ if (enrollError)
107
+ return txt(`Error: ${enrollError.message}`);
108
+ if (inserted?.length) {
109
+ for (const campaign of campaigns) {
110
+ const count = inserted.filter((row) => row.campaign_id === campaign.id).length;
111
+ if (count > 0) {
112
+ const { data: current } = await db().from('campaigns').select('total_enrolled').eq('id', campaign.id).maybeSingle();
113
+ await db().from('campaigns').update({ total_enrolled: (current?.total_enrolled ?? 0) + count }).eq('id', campaign.id);
114
+ }
115
+ }
116
+ }
117
+ return j({
118
+ enrolled: inserted?.length ?? 0,
119
+ subscriber_id,
120
+ campaigns: campaigns.map((campaign) => ({
121
+ id: campaign.id,
122
+ name: campaign.name,
123
+ matched: Boolean(inserted?.some((row) => row.campaign_id === campaign.id)),
124
+ })),
125
+ });
126
+ }
10
127
  export function registerAutomationTools(server) {
11
128
  // ── list_automations ──────────────────────────────────────────────────────
12
129
  server.registerTool('list_automations', {
130
+ title: 'List Automations',
131
+ annotations: {
132
+ readOnlyHint: true,
133
+ destructiveHint: false,
134
+ idempotentHint: true,
135
+ openWorldHint: false,
136
+ },
13
137
  description: 'List content automation sources (RSS feeds, GitHub Issues, webhooks, schedules). Each source auto-generates email drafts when new content is found.',
14
138
  inputSchema: {
15
139
  site_id: z.string().uuid().optional().describe('Filter by site UUID'),
@@ -31,97 +155,189 @@ export function registerAutomationTools(server) {
31
155
  return j(data ?? []);
32
156
  });
33
157
  server.registerTool('enroll_automation', {
158
+ title: 'Enroll Automation',
159
+ annotations: {
160
+ readOnlyHint: false,
161
+ destructiveHint: false,
162
+ idempotentHint: false,
163
+ openWorldHint: false,
164
+ },
34
165
  description: 'Enroll a subscriber into active triggered automation campaigns. Match by campaign_id, trigger_event_name, or trigger_tag. Safe to call repeatedly.',
35
166
  inputSchema: {
36
167
  subscriber_id: z.string().uuid().describe('Subscriber/contact UUID to enroll'),
37
168
  campaign_id: z.string().uuid().optional().describe('Specific triggered campaign UUID'),
38
169
  site_id: z.string().uuid().optional().describe('Optional site scope when matching by trigger'),
39
170
  trigger_event_name: z.string().optional().describe('Event trigger name, e.g. signup'),
171
+ trigger_event_names: z.array(z.string()).optional().describe('OR-combined event trigger names, e.g. ["contact_created", "form_submitted"]'),
40
172
  trigger_tag: z.string().optional().describe('Tag trigger name'),
173
+ trigger_tags: z.array(z.string()).optional().describe('Tag/entity filters for reusable triggered campaigns'),
41
174
  next_send_at: z.string().datetime().optional().describe('First send time. Defaults to now.'),
42
175
  context: z.record(z.string(), z.unknown()).optional().describe('Optional enrollment context metadata'),
43
176
  },
44
- }, async ({ subscriber_id, campaign_id, site_id, trigger_event_name, trigger_tag, next_send_at, context }) => {
177
+ }, enrollSubscriberIntoTriggeredCampaigns);
178
+ server.registerTool('create_escalation_ladder', {
179
+ title: 'Create Escalation Ladder',
180
+ annotations: {
181
+ readOnlyHint: false,
182
+ destructiveHint: false,
183
+ idempotentHint: false,
184
+ openWorldHint: false,
185
+ },
186
+ description: 'Create or update response-gated escalation ladder steps for an active triggered campaign. This defines the ladder only; automatic advancement remains disabled until response detection is wired.',
187
+ inputSchema: {
188
+ campaign_id: z.string().uuid().describe('Active triggered campaign UUID'),
189
+ steps: z.array(z.object({
190
+ step_index: z.number().int().min(0).describe('Zero-based ladder step index'),
191
+ wait_hours: z.number().int().min(0).default(0).describe('Hours to wait before this step is eligible'),
192
+ channel: z.enum(['email', 'sms', 'voicemail', 'manual']).describe('Planned channel for the step'),
193
+ action_type: z.enum(['send_win_back_draft', 'send_template', 'notify_human', 'no_op']).describe('Action to take if the contact has not responded'),
194
+ content_ref: z.record(z.string(), z.unknown()).optional().describe('Reference to existing content/template/draft metadata'),
195
+ response_gate: z.record(z.string(), z.boolean()).optional().describe('Response signals that halt the ladder, e.g. { "reply": true, "click": true, "booking": true }'),
196
+ })).min(1).max(20),
197
+ },
198
+ }, async ({ campaign_id, steps }) => {
45
199
  const projectId = getProjectId();
46
- const { data: subscriber, error: subscriberError } = await db()
47
- .from('subscribers')
48
- .select('id, email, global_unsubscribed')
49
- .eq('id', subscriber_id)
200
+ const { data: campaign, error: campaignError } = await db()
201
+ .from('campaigns')
202
+ .select('id, name, type, status')
203
+ .eq('id', campaign_id)
50
204
  .eq('project_id', projectId)
51
205
  .maybeSingle();
52
- if (subscriberError)
53
- return txt(`Error: ${subscriberError.message}`);
54
- if (!subscriber)
55
- return j({ error: 'Subscriber not found', code: 'NOT_FOUND' });
56
- if (subscriber.global_unsubscribed)
57
- return j({ enrolled: 0, skipped: 'global_unsubscribed' });
58
- const { data: suppression } = await db()
59
- .from('suppressions')
206
+ if (campaignError)
207
+ return txt(`Error: ${campaignError.message}`);
208
+ if (!campaign)
209
+ return j({ error: 'Campaign not found', code: 'NOT_FOUND' });
210
+ if (campaign.type !== 'triggered')
211
+ return j({ error: 'Escalation ladders require a triggered campaign', code: 'INVALID_CAMPAIGN_TYPE' });
212
+ const rows = steps.map((step) => ({
213
+ campaign_id,
214
+ step_index: step.step_index,
215
+ wait_hours: step.wait_hours ?? 0,
216
+ channel: step.channel,
217
+ action_type: step.action_type,
218
+ content_ref: step.content_ref ?? {},
219
+ response_gate: step.response_gate ?? { reply: true, click: true, booking: true },
220
+ updated_at: new Date().toISOString(),
221
+ }));
222
+ const { data, error } = await db()
223
+ .from('campaign_step_gates')
224
+ .upsert(rows, { onConflict: 'campaign_id,step_index' })
225
+ .select('id, campaign_id, step_index, wait_hours, channel, action_type, content_ref, response_gate')
226
+ .order('step_index', { ascending: true });
227
+ if (error)
228
+ return txt(`Error: ${error.message}`);
229
+ return j({
230
+ campaign_id,
231
+ campaign_name: campaign.name,
232
+ status: campaign.status,
233
+ steps: data ?? [],
234
+ advancement_enabled: false,
235
+ note: 'Ladder definition saved. Automatic response-gated advancement is intentionally disabled until reply/booking signal schemas are confirmed.',
236
+ });
237
+ });
238
+ server.registerTool('enroll_in_escalation_ladder', {
239
+ title: 'Enroll In Escalation Ladder',
240
+ annotations: {
241
+ readOnlyHint: false,
242
+ destructiveHint: false,
243
+ idempotentHint: false,
244
+ openWorldHint: false,
245
+ },
246
+ description: 'Enroll a subscriber into an active triggered campaign that has escalation ladder steps. Reuses enroll_automation suppression and unsubscribe checks.',
247
+ inputSchema: {
248
+ subscriber_id: z.string().uuid().describe('Subscriber/contact UUID to enroll'),
249
+ campaign_id: z.string().uuid().describe('Triggered campaign UUID with ladder steps'),
250
+ next_send_at: z.string().datetime().optional().describe('First eligible send time. Defaults to now.'),
251
+ context: z.record(z.string(), z.unknown()).optional().describe('Optional enrollment context metadata'),
252
+ },
253
+ }, async ({ subscriber_id, campaign_id, next_send_at, context }) => {
254
+ const projectId = getProjectId();
255
+ const { data: gate, error: gateError } = await db()
256
+ .from('campaign_step_gates')
60
257
  .select('id')
61
- .eq('project_id', projectId)
62
- .eq('email', subscriber.email)
258
+ .eq('campaign_id', campaign_id)
259
+ .limit(1)
63
260
  .maybeSingle();
64
- if (suppression)
65
- return j({ enrolled: 0, skipped: 'suppressed' });
66
- let query = db()
261
+ if (gateError)
262
+ return txt(`Error: ${gateError.message}`);
263
+ if (!gate)
264
+ return j({ enrolled: 0, skipped: 'no_escalation_ladder_steps' });
265
+ return enrollSubscriberIntoTriggeredCampaigns({
266
+ subscriber_id,
267
+ campaign_id,
268
+ next_send_at,
269
+ context: { ...(context ?? {}), escalation_ladder: true, project_id: projectId },
270
+ });
271
+ });
272
+ server.registerTool('list_escalation_ladder_status', {
273
+ title: 'List Escalation Ladder Status',
274
+ annotations: {
275
+ readOnlyHint: true,
276
+ destructiveHint: false,
277
+ idempotentHint: true,
278
+ openWorldHint: false,
279
+ },
280
+ description: 'List enrollment status for a response-gated escalation ladder campaign, including current step and response-exit fields.',
281
+ inputSchema: {
282
+ campaign_id: z.string().uuid().describe('Escalation ladder campaign UUID'),
283
+ },
284
+ }, async ({ campaign_id }) => {
285
+ const projectId = getProjectId();
286
+ const { data: campaign, error: campaignError } = await db()
67
287
  .from('campaigns')
68
- .select('id, name, site_id, trigger_event_name, trigger_tag')
288
+ .select('id, name, status')
289
+ .eq('id', campaign_id)
69
290
  .eq('project_id', projectId)
70
- .eq('type', 'triggered')
71
- .eq('status', 'active');
72
- if (campaign_id)
73
- query = query.eq('id', campaign_id);
74
- if (site_id)
75
- query = query.eq('site_id', site_id);
76
- if (trigger_event_name)
77
- query = query.eq('trigger_event_name', trigger_event_name.trim());
78
- if (trigger_tag)
79
- query = query.eq('trigger_tag', trigger_tag.trim().toLowerCase());
80
- if (!campaign_id && !trigger_event_name && !trigger_tag) {
81
- return txt('Provide campaign_id, trigger_event_name, or trigger_tag.');
82
- }
83
- const { data: campaigns, error: campaignError } = await query;
291
+ .maybeSingle();
84
292
  if (campaignError)
85
293
  return txt(`Error: ${campaignError.message}`);
86
- if (!campaigns?.length)
87
- return j({ enrolled: 0, campaigns: [], skipped: 'no_matching_active_automation' });
88
- const sendAt = next_send_at ?? new Date().toISOString();
89
- const rows = campaigns.map((campaign) => ({
90
- campaign_id: campaign.id,
91
- contact_id: subscriber_id,
92
- current_step: 0,
93
- status: 'active',
94
- enrolled_at: new Date().toISOString(),
95
- next_send_at: sendAt,
96
- enrollment_context: context ?? {},
97
- }));
98
- const { data: inserted, error: enrollError } = await db()
99
- .from('campaign_enrollments')
100
- .upsert(rows, { onConflict: 'campaign_id,contact_id', ignoreDuplicates: true })
101
- .select('id, campaign_id');
102
- if (enrollError)
103
- return txt(`Error: ${enrollError.message}`);
104
- if (inserted?.length) {
105
- for (const campaign of campaigns) {
106
- const count = inserted.filter((row) => row.campaign_id === campaign.id).length;
107
- if (count > 0) {
108
- const { data: current } = await db().from('campaigns').select('total_enrolled').eq('id', campaign.id).maybeSingle();
109
- await db().from('campaigns').update({ total_enrolled: (current?.total_enrolled ?? 0) + count }).eq('id', campaign.id);
110
- }
111
- }
294
+ if (!campaign)
295
+ return j({ error: 'Campaign not found', code: 'NOT_FOUND' });
296
+ const [{ data: steps, error: stepsError }, { data: enrollments, error: enrollmentsError }] = await Promise.all([
297
+ db()
298
+ .from('campaign_step_gates')
299
+ .select('id, step_index, wait_hours, channel, action_type, response_gate')
300
+ .eq('campaign_id', campaign_id)
301
+ .order('step_index', { ascending: true }),
302
+ db()
303
+ .from('campaign_enrollments')
304
+ .select('id, contact_id, current_step, status, enrolled_at, next_send_at, completed_at, exited_reason, response_detected_at, response_type, enrollment_context')
305
+ .eq('campaign_id', campaign_id)
306
+ .order('enrolled_at', { ascending: false }),
307
+ ]);
308
+ if (stepsError)
309
+ return txt(`Error: ${stepsError.message}`);
310
+ if (enrollmentsError)
311
+ return txt(`Error: ${enrollmentsError.message}`);
312
+ const contactIds = [...new Set((enrollments ?? []).map((row) => row.contact_id))];
313
+ let contactsById = new Map();
314
+ if (contactIds.length) {
315
+ const { data: contacts } = await db()
316
+ .from('subscribers')
317
+ .select('id, email, first_name')
318
+ .eq('project_id', projectId)
319
+ .in('id', contactIds);
320
+ contactsById = new Map((contacts ?? []).map((contact) => [contact.id, contact]));
112
321
  }
113
322
  return j({
114
- enrolled: inserted?.length ?? 0,
115
- subscriber_id,
116
- campaigns: campaigns.map((campaign) => ({
117
- id: campaign.id,
118
- name: campaign.name,
119
- matched: Boolean(inserted?.some((row) => row.campaign_id === campaign.id)),
323
+ campaign,
324
+ steps: steps ?? [],
325
+ advancement_enabled: false,
326
+ enrollments: (enrollments ?? []).map((row) => ({
327
+ ...row,
328
+ subscriber: contactsById.get(row.contact_id) ?? null,
120
329
  })),
121
330
  });
122
331
  });
123
332
  // ── preview_automation ────────────────────────────────────────────────────
124
333
  server.registerTool('preview_automation', {
334
+ title: 'Preview Automation',
335
+ annotations: {
336
+ readOnlyHint: true,
337
+ destructiveHint: false,
338
+ idempotentHint: true,
339
+ openWorldHint: false,
340
+ },
125
341
  description: 'Preview what content would be fetched from a source right now, without generating a draft. Use this to inspect GitHub issues, RSS items, etc. before triggering a full run.',
126
342
  inputSchema: {
127
343
  source_id: z.string().uuid().describe('The content source UUID to preview'),
@@ -199,6 +415,13 @@ export function registerAutomationTools(server) {
199
415
  });
200
416
  // ── trigger_automation ────────────────────────────────────────────────────
201
417
  server.registerTool('trigger_automation', {
418
+ title: 'Trigger Automation',
419
+ annotations: {
420
+ readOnlyHint: false,
421
+ destructiveHint: false,
422
+ idempotentHint: false,
423
+ openWorldHint: true,
424
+ },
202
425
  description: 'Run a content source on demand right now — fetches new content, generates a certificate-prompted email draft, and adds it to the approval queue. Returns the draft ID. Use preview_automation first to confirm what content will be pulled.',
203
426
  inputSchema: {
204
427
  source_id: z.string().uuid().describe('The content source UUID to trigger'),
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerBrandKitTools(server: McpServer): void;