@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.
- package/build/audit.d.ts +1 -0
- package/build/audit.js +9 -3
- package/build/auth.d.ts +10 -0
- package/build/auth.js +82 -9
- package/build/db.d.ts +12 -0
- package/build/db.js +37 -2
- package/build/index.js +38 -12
- package/build/lib/action-approvals.d.ts +28 -1
- package/build/lib/action-approvals.js +89 -2
- package/build/lib/brand-asset-persist.d.ts +35 -0
- package/build/lib/brand-asset-persist.js +161 -0
- package/build/lib/brand-asset-usage.d.ts +13 -0
- package/build/lib/brand-asset-usage.js +46 -0
- package/build/lib/email-provider.d.ts +19 -0
- package/build/lib/email-provider.js +281 -0
- package/build/lib/ssrf-pinned-fetch.d.ts +16 -0
- package/build/lib/ssrf-pinned-fetch.js +101 -0
- package/build/lib/survey-link-resolver.d.ts +6 -0
- package/build/lib/survey-link-resolver.js +23 -0
- package/build/lib/svg-sanitize.d.ts +2 -0
- package/build/lib/svg-sanitize.js +110 -0
- package/build/lib/webhook-events.d.ts +1 -1
- package/build/scoped-token.d.ts +11 -0
- package/build/scoped-token.js +59 -0
- package/build/server.js +30 -9
- package/build/tools/ab-testing.js +28 -0
- package/build/tools/advisor.js +52 -2
- package/build/tools/ai-work.d.ts +2 -0
- package/build/tools/ai-work.js +460 -0
- package/build/tools/analytics.d.ts +1 -0
- package/build/tools/analytics.js +203 -0
- package/build/tools/approvals.js +15 -1
- package/build/tools/automations.js +288 -65
- package/build/tools/brand-kit.d.ts +2 -0
- package/build/tools/brand-kit.js +277 -0
- package/build/tools/campaigns.js +900 -69
- package/build/tools/compound.js +28 -0
- package/build/tools/contacts.js +338 -17
- package/build/tools/content.js +266 -1
- package/build/tools/data-proposals.js +21 -0
- package/build/tools/data.js +121 -5
- package/build/tools/delivery-ops.js +84 -0
- package/build/tools/drafts.js +64 -1
- package/build/tools/forms.js +42 -9
- package/build/tools/gdpr.js +22 -3
- package/build/tools/newsletters.d.ts +2 -0
- package/build/tools/newsletters.js +583 -0
- package/build/tools/org.js +297 -3
- package/build/tools/segments.js +45 -3
- package/build/tools/short-links.d.ts +2 -0
- package/build/tools/short-links.js +157 -0
- package/build/tools/sites.js +35 -0
- package/build/tools/sms.js +49 -0
- package/build/tools/social-posts.js +81 -5
- package/build/tools/surveys.d.ts +2 -0
- package/build/tools/surveys.js +222 -0
- package/build/tools/templates.js +78 -2
- package/build/tools/warmup.js +28 -0
- package/build/tools/webhooks.js +29 -0
- package/package.json +10 -4
package/build/tools/analytics.js
CHANGED
|
@@ -147,8 +147,85 @@ function distinctEventCount(events, eventType, logIds) {
|
|
|
147
147
|
.filter(event => event.event_type === eventType && event.email_log_id && logIds.has(event.email_log_id))
|
|
148
148
|
.map(event => event.email_log_id)).size;
|
|
149
149
|
}
|
|
150
|
+
function isTrueConversionEvent(event) {
|
|
151
|
+
const normalized = (event ?? '').trim().toLowerCase().replace(/[\s-]+/g, '_');
|
|
152
|
+
if (!normalized)
|
|
153
|
+
return false;
|
|
154
|
+
return !['open', 'opened', 'email_open', 'click', 'clicked', 'email_click', 'page_view', 'view', 'impression'].includes(normalized);
|
|
155
|
+
}
|
|
156
|
+
function buildTrueConversionAnalytics(params) {
|
|
157
|
+
const sent = params.logs.length;
|
|
158
|
+
const delivered = params.logs.filter(row => ['delivered', 'opened', 'clicked'].includes(row.status)).length;
|
|
159
|
+
const trueRows = params.attributionRows.filter(row => isTrueConversionEvent(row.conversion_event));
|
|
160
|
+
const campaignDenominators = new Map();
|
|
161
|
+
for (const row of params.logs) {
|
|
162
|
+
if (typeof row.campaign_id !== 'string')
|
|
163
|
+
continue;
|
|
164
|
+
campaignDenominators.set(row.campaign_id, (campaignDenominators.get(row.campaign_id) ?? 0) + 1);
|
|
165
|
+
}
|
|
166
|
+
const byEvent = new Map();
|
|
167
|
+
const byVariant = new Map();
|
|
168
|
+
let totalRevenue = 0;
|
|
169
|
+
for (const row of trueRows) {
|
|
170
|
+
const revenue = Number(row.revenue_amount ?? 0) || 0;
|
|
171
|
+
totalRevenue += revenue;
|
|
172
|
+
const event = row.conversion_event?.trim() || 'conversion';
|
|
173
|
+
const eventStats = byEvent.get(event) ?? { conversion_event: event, count: 0, revenue: 0 };
|
|
174
|
+
eventStats.count += 1;
|
|
175
|
+
eventStats.revenue += revenue;
|
|
176
|
+
byEvent.set(event, eventStats);
|
|
177
|
+
const variant = row.utm_content?.trim() || '(none)';
|
|
178
|
+
const variantKey = `${row.campaign_id ?? '(none)'}:${variant}`;
|
|
179
|
+
const variantStats = byVariant.get(variantKey) ?? { variant, campaign_id: row.campaign_id ?? null, count: 0, revenue: 0 };
|
|
180
|
+
variantStats.count += 1;
|
|
181
|
+
variantStats.revenue += revenue;
|
|
182
|
+
byVariant.set(variantKey, variantStats);
|
|
183
|
+
}
|
|
184
|
+
const percentOf = (count, denominator) => denominator > 0 ? `${((count / denominator) * 100).toFixed(1)}%` : 'N/A';
|
|
185
|
+
const stage = (name, count, prior) => ({
|
|
186
|
+
stage: name,
|
|
187
|
+
count,
|
|
188
|
+
rate_from_sent: percentOf(count, sent),
|
|
189
|
+
rate_from_previous: prior == null ? '100.0%' : percentOf(count, prior),
|
|
190
|
+
drop_off_from_previous: prior == null ? 0 : Math.max(0, prior - count),
|
|
191
|
+
});
|
|
192
|
+
return {
|
|
193
|
+
definition: 'revenue_attribution rows excluding open/click/page-view vanity events',
|
|
194
|
+
true_conversions: trueRows.length,
|
|
195
|
+
true_conversion_rate: percentOf(trueRows.length, sent),
|
|
196
|
+
revenue: Number(totalRevenue.toFixed(2)),
|
|
197
|
+
by_event: Array.from(byEvent.values())
|
|
198
|
+
.map(row => ({ ...row, revenue: Number(row.revenue.toFixed(2)), true_conversion_rate: percentOf(row.count, sent) }))
|
|
199
|
+
.sort((a, b) => b.count - a.count),
|
|
200
|
+
funnel: [
|
|
201
|
+
stage('sent', sent, null),
|
|
202
|
+
stage('delivered', delivered, sent),
|
|
203
|
+
stage('opened', params.opened, delivered),
|
|
204
|
+
stage('clicked', params.clicked, params.opened),
|
|
205
|
+
stage('true_conversion', trueRows.length, params.clicked || params.opened || delivered),
|
|
206
|
+
],
|
|
207
|
+
by_variant: Array.from(byVariant.values())
|
|
208
|
+
.map(row => {
|
|
209
|
+
const denominator = row.campaign_id ? (campaignDenominators.get(row.campaign_id) ?? sent) : sent;
|
|
210
|
+
return {
|
|
211
|
+
...row,
|
|
212
|
+
revenue: Number(row.revenue.toFixed(2)),
|
|
213
|
+
denominator,
|
|
214
|
+
true_conversion_rate: percentOf(row.count, denominator),
|
|
215
|
+
};
|
|
216
|
+
})
|
|
217
|
+
.sort((a, b) => b.count - a.count || b.revenue - a.revenue),
|
|
218
|
+
};
|
|
219
|
+
}
|
|
150
220
|
export function registerAnalyticsTools(server) {
|
|
151
221
|
server.registerTool('list_event_log', {
|
|
222
|
+
title: 'List Event Log',
|
|
223
|
+
annotations: {
|
|
224
|
+
readOnlyHint: true,
|
|
225
|
+
destructiveHint: false,
|
|
226
|
+
idempotentHint: true,
|
|
227
|
+
openWorldHint: false,
|
|
228
|
+
},
|
|
152
229
|
description: 'List received API event tracking calls with optional filters.',
|
|
153
230
|
inputSchema: {
|
|
154
231
|
event_name: z.string().optional(),
|
|
@@ -177,6 +254,13 @@ export function registerAnalyticsTools(server) {
|
|
|
177
254
|
return j({ events: data ?? [] });
|
|
178
255
|
});
|
|
179
256
|
server.registerTool('get_portfolio_analytics', {
|
|
257
|
+
title: 'Get Portfolio Analytics',
|
|
258
|
+
annotations: {
|
|
259
|
+
readOnlyHint: true,
|
|
260
|
+
destructiveHint: false,
|
|
261
|
+
idempotentHint: true,
|
|
262
|
+
openWorldHint: false,
|
|
263
|
+
},
|
|
180
264
|
description: 'Get cross-site analytics totals and per-site breakdown.',
|
|
181
265
|
inputSchema: { period_days: z.number().int().min(1).max(365).optional().default(30) },
|
|
182
266
|
}, async ({ period_days = 30 }) => {
|
|
@@ -197,6 +281,13 @@ export function registerAnalyticsTools(server) {
|
|
|
197
281
|
});
|
|
198
282
|
});
|
|
199
283
|
server.registerTool('list_projects', {
|
|
284
|
+
title: 'List Projects',
|
|
285
|
+
annotations: {
|
|
286
|
+
readOnlyHint: true,
|
|
287
|
+
destructiveHint: false,
|
|
288
|
+
idempotentHint: true,
|
|
289
|
+
openWorldHint: false,
|
|
290
|
+
},
|
|
200
291
|
description: 'List all projects accessible to this API key. Returns project ID, name, plan, and associated organization. Useful for orchestrators managing multiple products through a single Sendinel account.',
|
|
201
292
|
inputSchema: {},
|
|
202
293
|
}, async () => {
|
|
@@ -238,6 +329,13 @@ export function registerAnalyticsTools(server) {
|
|
|
238
329
|
});
|
|
239
330
|
});
|
|
240
331
|
server.registerTool('get_sites', {
|
|
332
|
+
title: 'Get Sites',
|
|
333
|
+
annotations: {
|
|
334
|
+
readOnlyHint: true,
|
|
335
|
+
destructiveHint: false,
|
|
336
|
+
idempotentHint: true,
|
|
337
|
+
openWorldHint: false,
|
|
338
|
+
},
|
|
241
339
|
description: 'List all active Sendinel sites',
|
|
242
340
|
inputSchema: {},
|
|
243
341
|
}, async () => {
|
|
@@ -252,6 +350,13 @@ export function registerAnalyticsTools(server) {
|
|
|
252
350
|
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
253
351
|
});
|
|
254
352
|
server.registerTool('get_stats', {
|
|
353
|
+
title: 'Get Stats',
|
|
354
|
+
annotations: {
|
|
355
|
+
readOnlyHint: true,
|
|
356
|
+
destructiveHint: false,
|
|
357
|
+
idempotentHint: true,
|
|
358
|
+
openWorldHint: false,
|
|
359
|
+
},
|
|
255
360
|
description: 'Aggregate email metrics for a site over a date range. Returns sent, delivered, bounce/complaint rates, Sender Health Score (0–100), send volume by day, list growth rate, inactive subscriber percentage, and open/click rates when tracking is enabled.',
|
|
256
361
|
inputSchema: {
|
|
257
362
|
site_id: z.string().uuid().describe('Site UUID'),
|
|
@@ -338,6 +443,13 @@ export function registerAnalyticsTools(server) {
|
|
|
338
443
|
return { content: [{ type: 'text', text: JSON.stringify(response, null, 2) }] };
|
|
339
444
|
});
|
|
340
445
|
server.registerTool('get_domain_health', {
|
|
446
|
+
title: 'Get Domain Health',
|
|
447
|
+
annotations: {
|
|
448
|
+
readOnlyHint: true,
|
|
449
|
+
destructiveHint: false,
|
|
450
|
+
idempotentHint: true,
|
|
451
|
+
openWorldHint: true,
|
|
452
|
+
},
|
|
341
453
|
description: 'SPF/DKIM/DMARC status and warmup progress for a site',
|
|
342
454
|
inputSchema: {
|
|
343
455
|
site_id: z.string().uuid().describe('Site UUID'),
|
|
@@ -355,6 +467,13 @@ export function registerAnalyticsTools(server) {
|
|
|
355
467
|
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
356
468
|
});
|
|
357
469
|
server.registerTool('get_engagement_insights', {
|
|
470
|
+
title: 'Get Engagement Insights',
|
|
471
|
+
annotations: {
|
|
472
|
+
readOnlyHint: true,
|
|
473
|
+
destructiveHint: false,
|
|
474
|
+
idempotentHint: true,
|
|
475
|
+
openWorldHint: false,
|
|
476
|
+
},
|
|
358
477
|
description: 'Get engagement scoring summary: lifecycle distribution, score buckets, top engaged contacts, at-risk contacts, and never-engaged counts. Use this data to recommend re-engagement campaigns, identify high-value contacts, or suggest list hygiene actions.',
|
|
359
478
|
inputSchema: {
|
|
360
479
|
site_id: z.string().uuid().optional().describe('Optional site UUID for site-specific tracking settings and engagement anomaly analysis'),
|
|
@@ -447,6 +566,13 @@ export function registerAnalyticsTools(server) {
|
|
|
447
566
|
}, null, 2) }] };
|
|
448
567
|
});
|
|
449
568
|
server.registerTool('deliverability_check', {
|
|
569
|
+
title: 'Deliverability Check',
|
|
570
|
+
annotations: {
|
|
571
|
+
readOnlyHint: true,
|
|
572
|
+
destructiveHint: false,
|
|
573
|
+
idempotentHint: true,
|
|
574
|
+
openWorldHint: true,
|
|
575
|
+
},
|
|
450
576
|
description: 'Run a deliverability check for a site: DNS records (SPF/DKIM/DMARC), warmup status, bounce/complaint rates, and risk assessment',
|
|
451
577
|
inputSchema: {
|
|
452
578
|
site_id: z.string().uuid(),
|
|
@@ -601,6 +727,13 @@ export function registerAnalyticsTools(server) {
|
|
|
601
727
|
});
|
|
602
728
|
});
|
|
603
729
|
server.registerTool('performance_report', {
|
|
730
|
+
title: 'Performance Report',
|
|
731
|
+
annotations: {
|
|
732
|
+
readOnlyHint: true,
|
|
733
|
+
destructiveHint: false,
|
|
734
|
+
idempotentHint: true,
|
|
735
|
+
openWorldHint: false,
|
|
736
|
+
},
|
|
604
737
|
description: 'Generate a performance report: delivery/open/click rates, best send times, trends. Optionally group by campaign, site, company, day, or week.',
|
|
605
738
|
inputSchema: {
|
|
606
739
|
days: z.number().int().min(1).max(365).optional().describe('Lookback period in days, default 30'),
|
|
@@ -626,6 +759,20 @@ export function registerAnalyticsTools(server) {
|
|
|
626
759
|
const rows = logs ?? [];
|
|
627
760
|
if (!rows.length)
|
|
628
761
|
return j({ message: `No email activity in the last ${days} days.`, total: 0 });
|
|
762
|
+
let attributionQuery = db()
|
|
763
|
+
.from('revenue_attribution')
|
|
764
|
+
.select('campaign_id, conversion_event, utm_content, revenue_amount')
|
|
765
|
+
.eq('project_id', projectId)
|
|
766
|
+
.gte('converted_at', since);
|
|
767
|
+
if (campaign_id)
|
|
768
|
+
attributionQuery = attributionQuery.eq('campaign_id', campaign_id);
|
|
769
|
+
const { data: attributionRows, error: attributionError } = await attributionQuery;
|
|
770
|
+
if (attributionError)
|
|
771
|
+
return txt(`Error: ${attributionError.message}`);
|
|
772
|
+
const campaignIdsInScope = new Set(rows.map((row) => typeof row.campaign_id === 'string' ? row.campaign_id : null).filter(Boolean));
|
|
773
|
+
const scopedAttributionRows = site_id
|
|
774
|
+
? (attributionRows ?? []).filter((row) => row.campaign_id && campaignIdsInScope.has(row.campaign_id))
|
|
775
|
+
: (attributionRows ?? []);
|
|
629
776
|
const computeMetrics = (set) => {
|
|
630
777
|
const total = set.length;
|
|
631
778
|
const delivered = set.filter(r => ['delivered', 'opened', 'clicked'].includes(r.status)).length;
|
|
@@ -644,6 +791,12 @@ export function registerAnalyticsTools(server) {
|
|
|
644
791
|
};
|
|
645
792
|
};
|
|
646
793
|
const overall = computeMetrics(rows);
|
|
794
|
+
const trueConversionAnalytics = buildTrueConversionAnalytics({
|
|
795
|
+
logs: rows,
|
|
796
|
+
attributionRows: scopedAttributionRows,
|
|
797
|
+
opened: parseInt(String(overall.opened), 10) || 0,
|
|
798
|
+
clicked: parseInt(String(overall.clicked), 10) || 0,
|
|
799
|
+
});
|
|
647
800
|
const hourBuckets = {};
|
|
648
801
|
rows.forEach((r) => {
|
|
649
802
|
if (!r.sent_at)
|
|
@@ -744,6 +897,7 @@ export function registerAnalyticsTools(server) {
|
|
|
744
897
|
return j({
|
|
745
898
|
period: `Last ${days} days`,
|
|
746
899
|
overall,
|
|
900
|
+
true_conversion_analytics: trueConversionAnalytics,
|
|
747
901
|
best_send_times_utc: bestHours,
|
|
748
902
|
trend_vs_prior_period: trend,
|
|
749
903
|
...(grouped ? { [`by_${group_by}`]: grouped } : {}),
|
|
@@ -751,6 +905,13 @@ export function registerAnalyticsTools(server) {
|
|
|
751
905
|
});
|
|
752
906
|
// ── Cross-project analytics ─────────────────────────────────────────────
|
|
753
907
|
server.registerTool('get_portfolio_stats', {
|
|
908
|
+
title: 'Get Portfolio Stats',
|
|
909
|
+
annotations: {
|
|
910
|
+
readOnlyHint: true,
|
|
911
|
+
destructiveHint: false,
|
|
912
|
+
idempotentHint: true,
|
|
913
|
+
openWorldHint: false,
|
|
914
|
+
},
|
|
754
915
|
description: 'Aggregate email metrics across all projects in your organization. Returns per-project and combined totals for the specified period. Useful for orchestrators managing multiple products.',
|
|
755
916
|
inputSchema: {
|
|
756
917
|
days: z.number().int().min(1).max(90).optional().default(30).describe('Lookback period in days (default: 30)'),
|
|
@@ -843,6 +1004,13 @@ export function registerAnalyticsTools(server) {
|
|
|
843
1004
|
});
|
|
844
1005
|
});
|
|
845
1006
|
server.registerTool('abuse_monitor_status', {
|
|
1007
|
+
title: 'Abuse Monitor Status',
|
|
1008
|
+
annotations: {
|
|
1009
|
+
readOnlyHint: true,
|
|
1010
|
+
destructiveHint: false,
|
|
1011
|
+
idempotentHint: true,
|
|
1012
|
+
openWorldHint: false,
|
|
1013
|
+
},
|
|
846
1014
|
description: 'Return per-tenant abuse monitor metrics for a site, including complaint rate and pause level.',
|
|
847
1015
|
inputSchema: {
|
|
848
1016
|
siteId: z.string().uuid(),
|
|
@@ -880,6 +1048,13 @@ export function registerAnalyticsTools(server) {
|
|
|
880
1048
|
});
|
|
881
1049
|
});
|
|
882
1050
|
server.registerTool('abuse_monitor_override', {
|
|
1051
|
+
title: 'Abuse Monitor Override',
|
|
1052
|
+
annotations: {
|
|
1053
|
+
readOnlyHint: false,
|
|
1054
|
+
destructiveHint: false,
|
|
1055
|
+
idempotentHint: false,
|
|
1056
|
+
openWorldHint: false,
|
|
1057
|
+
},
|
|
883
1058
|
description: 'Create a temporary abuse monitor override for a paused site. Overrides expire automatically and are capped at 72 hours.',
|
|
884
1059
|
inputSchema: {
|
|
885
1060
|
siteId: z.string().uuid(),
|
|
@@ -927,6 +1102,13 @@ export function registerAnalyticsTools(server) {
|
|
|
927
1102
|
});
|
|
928
1103
|
});
|
|
929
1104
|
server.registerTool('get_site_insights', {
|
|
1105
|
+
title: 'Get Site Insights',
|
|
1106
|
+
annotations: {
|
|
1107
|
+
readOnlyHint: true,
|
|
1108
|
+
destructiveHint: false,
|
|
1109
|
+
idempotentHint: true,
|
|
1110
|
+
openWorldHint: false,
|
|
1111
|
+
},
|
|
930
1112
|
description: 'Get pre-computed AI insights for a site — anomalies, deliverability alerts, send-time recommendations, and attribution summaries. Call this before making campaign decisions to check for active warnings.',
|
|
931
1113
|
inputSchema: {
|
|
932
1114
|
site_id: z.string().uuid().describe('Site UUID'),
|
|
@@ -960,6 +1142,13 @@ export function registerAnalyticsTools(server) {
|
|
|
960
1142
|
return j((data ?? []).sort((a, b) => (rank[a.severity] ?? 9) - (rank[b.severity] ?? 9)));
|
|
961
1143
|
});
|
|
962
1144
|
server.registerTool('create_social_campaign', {
|
|
1145
|
+
title: 'Create Social Campaign',
|
|
1146
|
+
annotations: {
|
|
1147
|
+
readOnlyHint: false,
|
|
1148
|
+
destructiveHint: false,
|
|
1149
|
+
idempotentHint: false,
|
|
1150
|
+
openWorldHint: false,
|
|
1151
|
+
},
|
|
963
1152
|
description: 'Create a social media campaign with a UTM-tracked link. Returns the tracked URL to paste into the social post.',
|
|
964
1153
|
inputSchema: {
|
|
965
1154
|
name: z.string(),
|
|
@@ -993,6 +1182,13 @@ export function registerAnalyticsTools(server) {
|
|
|
993
1182
|
return j({ id: data.id, tracked_url: data.tracked_url, campaign: data });
|
|
994
1183
|
});
|
|
995
1184
|
server.registerTool('list_social_campaigns', {
|
|
1185
|
+
title: 'List Social Campaigns',
|
|
1186
|
+
annotations: {
|
|
1187
|
+
readOnlyHint: true,
|
|
1188
|
+
destructiveHint: false,
|
|
1189
|
+
idempotentHint: true,
|
|
1190
|
+
openWorldHint: false,
|
|
1191
|
+
},
|
|
996
1192
|
description: 'List social campaigns with their signup attribution and manual metrics. Use to compare channel performance.',
|
|
997
1193
|
inputSchema: {
|
|
998
1194
|
site_id: z.string().uuid().optional(),
|
|
@@ -1016,6 +1212,13 @@ export function registerAnalyticsTools(server) {
|
|
|
1016
1212
|
return j({ campaigns: data ?? [] });
|
|
1017
1213
|
});
|
|
1018
1214
|
server.registerTool('schedule_social_post', {
|
|
1215
|
+
title: 'Schedule Social Post',
|
|
1216
|
+
annotations: {
|
|
1217
|
+
readOnlyHint: false,
|
|
1218
|
+
destructiveHint: true,
|
|
1219
|
+
idempotentHint: false,
|
|
1220
|
+
openWorldHint: true,
|
|
1221
|
+
},
|
|
1019
1222
|
description: 'Schedule a social campaign post via a connected BYO publisher (UploadPost, Buffer). Returns publisherPostId on success.',
|
|
1020
1223
|
inputSchema: {
|
|
1021
1224
|
campaign_id: z.string().uuid().describe('UUID of the social campaign in Sendinel'),
|
package/build/tools/approvals.js
CHANGED
|
@@ -6,6 +6,13 @@ const txt = (text) => ({ content: [{ type: 'text', text }] });
|
|
|
6
6
|
const j = (data) => txt(JSON.stringify(data, null, 2));
|
|
7
7
|
export function registerApprovalTools(server) {
|
|
8
8
|
server.registerTool('check_approval', {
|
|
9
|
+
title: 'Check Approval',
|
|
10
|
+
annotations: {
|
|
11
|
+
readOnlyHint: true,
|
|
12
|
+
destructiveHint: false,
|
|
13
|
+
idempotentHint: true,
|
|
14
|
+
openWorldHint: false,
|
|
15
|
+
},
|
|
9
16
|
description: 'Check the status of a pending action approval. Use this to poll after requesting an action that requires human approval. Returns approved/pending/rejected/expired.',
|
|
10
17
|
inputSchema: {
|
|
11
18
|
approval_id: z.string().uuid().describe('The approval ID returned by the original tool call'),
|
|
@@ -15,12 +22,19 @@ export function registerApprovalTools(server) {
|
|
|
15
22
|
return j(result);
|
|
16
23
|
});
|
|
17
24
|
server.registerTool('list_pending_approvals', {
|
|
25
|
+
title: 'List Pending Approvals',
|
|
26
|
+
annotations: {
|
|
27
|
+
readOnlyHint: true,
|
|
28
|
+
destructiveHint: false,
|
|
29
|
+
idempotentHint: true,
|
|
30
|
+
openWorldHint: false,
|
|
31
|
+
},
|
|
18
32
|
description: 'List all pending approval requests for this project. Shows what actions are waiting for human approval.',
|
|
19
33
|
inputSchema: {},
|
|
20
34
|
}, async () => {
|
|
21
35
|
const { data, error } = await db()
|
|
22
36
|
.from('action_approvals')
|
|
23
|
-
.select('id, action, tier, summary, status, requested_at, expires_at')
|
|
37
|
+
.select('id, action, tier, channel, summary, status, requested_at, expires_at')
|
|
24
38
|
.eq('project_id', getProjectId())
|
|
25
39
|
.eq('status', 'pending')
|
|
26
40
|
.order('requested_at', { ascending: false })
|