@sendinel/mcp-server 1.0.63 → 1.0.64
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/lib/action-approvals.d.ts +22 -0
- package/build/lib/action-approvals.js +94 -1
- package/build/lib/approval-exempt.d.ts +13 -0
- package/build/lib/approval-exempt.js +134 -0
- package/build/tools/ab-testing.js +4 -2
- package/build/tools/analytics.js +4 -2
- package/build/tools/automations.js +6 -3
- package/build/tools/brand-kit.js +4 -2
- package/build/tools/campaigns.js +23 -18
- package/build/tools/contacts.js +17 -13
- package/build/tools/content.js +7 -5
- package/build/tools/data.js +13 -10
- package/build/tools/delivery-ops.js +4 -3
- package/build/tools/landing-pages.js +7 -5
- package/build/tools/lists.js +4 -4
- package/build/tools/newsletters.js +8 -5
- package/build/tools/org.js +13 -10
- package/build/tools/segments.js +4 -2
- package/build/tools/signatures.js +4 -3
- package/build/tools/sms.js +11 -7
- package/build/tools/social-posts.js +4 -3
- package/build/tools/social-replies.js +4 -3
- package/build/tools/templates.js +4 -3
- package/build/tools/webhooks.js +10 -6
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Anthropic from '@anthropic-ai/sdk';
|
|
2
|
+
import { withApproval } from '../lib/action-approvals.js';
|
|
2
3
|
import { z } from 'zod';
|
|
3
4
|
import { db } from '../db.js';
|
|
4
5
|
import { getProjectId } from '../project.js';
|
|
@@ -558,8 +559,8 @@ export function registerSocialPostTools(server) {
|
|
|
558
559
|
openWorldHint: true,
|
|
559
560
|
},
|
|
560
561
|
description: 'Archive a social post so it disappears from active planner and list views.',
|
|
561
|
-
inputSchema: { post_id: z.string().uuid() },
|
|
562
|
-
}, async ({ post_id }) => {
|
|
562
|
+
inputSchema: { approval_id: z.string().uuid().optional().describe('Get this by calling once without it, then have a human approve the returned approve_url. Re-call with the same arguments plus this approval_id to run the action.'), post_id: z.string().uuid() },
|
|
563
|
+
}, withApproval('delete_social_post', async ({ post_id }) => {
|
|
563
564
|
const { data, error } = await db()
|
|
564
565
|
.from('social_posts')
|
|
565
566
|
.update({ status: 'archived', updated_at: new Date().toISOString() })
|
|
@@ -571,5 +572,5 @@ export function registerSocialPostTools(server) {
|
|
|
571
572
|
return txt(`Error: ${error.message}`);
|
|
572
573
|
fireWebhookEvent('social.post.archived', { social_post_id: data.id, title: data.title }, getProjectId()).catch(() => { });
|
|
573
574
|
return j(data);
|
|
574
|
-
});
|
|
575
|
+
}));
|
|
575
576
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Anthropic from '@anthropic-ai/sdk';
|
|
2
|
+
import { withApproval } from '../lib/action-approvals.js';
|
|
2
3
|
import { z } from 'zod';
|
|
3
4
|
import { parseLinkedInCommentUrl, parseLinkedInPostUrl, parseLinkedInTargetUrl } from '@sendinel/domain';
|
|
4
5
|
import { db, requestContext } from '../db.js';
|
|
@@ -144,10 +145,10 @@ export function registerSocialReplyTools(server) {
|
|
|
144
145
|
inputSchema: { post_url_or_urn: z.string().min(1), text: replyText, target_member_id: z.string().uuid().optional() },
|
|
145
146
|
}, async ({ post_url_or_urn, text, target_member_id }) => proposePersonal('post_comment', post_url_or_urn, text, target_member_id));
|
|
146
147
|
server.registerTool('reply_to_comment', {
|
|
147
|
-
title: 'Propose LinkedIn Comment Reply', description: 'Create a review card for a personal LinkedIn reply. Never posts.',
|
|
148
|
+
title: 'Propose LinkedIn Comment Reply', description: 'Create a review card for a personal LinkedIn reply. Never posts. Requires human approval: the first call returns an approve_url and does nothing; call again with the same arguments plus approval_id once approved.',
|
|
148
149
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
149
|
-
inputSchema: { comment_url_or_urn: z.string().min(1), text: replyText, target_member_id: z.string().uuid().optional() },
|
|
150
|
-
}, async ({ comment_url_or_urn, text, target_member_id }) => proposePersonal('comment_reply', comment_url_or_urn, text, target_member_id));
|
|
150
|
+
inputSchema: { comment_url_or_urn: z.string().min(1), text: replyText, target_member_id: z.string().uuid().optional(), approval_id: z.string().uuid().optional().describe('Get this by calling once without it, then have a human approve the returned approve_url. Re-call with the same arguments plus this approval_id to run the action.') },
|
|
151
|
+
}, withApproval('reply_to_comment', async ({ comment_url_or_urn, text, target_member_id }) => proposePersonal('comment_reply', comment_url_or_urn, text, target_member_id)));
|
|
151
152
|
server.registerTool('request_social_reply', {
|
|
152
153
|
title: 'Request LinkedIn Reply Proposal',
|
|
153
154
|
description: 'Create a review card for a personal LinkedIn reply from an external trigger or email notification. Never posts; a human Accept posts it.',
|
package/build/tools/templates.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withApproval } from '../lib/action-approvals.js';
|
|
1
2
|
import { snapshotTemplateVersion, versionHasBodySnapshot } from '@sendinel/domain';
|
|
2
3
|
import { z } from 'zod';
|
|
3
4
|
import { logAudit } from '../audit.js';
|
|
@@ -377,8 +378,8 @@ export function registerTemplateTools(server) {
|
|
|
377
378
|
openWorldHint: false,
|
|
378
379
|
},
|
|
379
380
|
description: 'Archive an email template. System templates cannot be deleted.',
|
|
380
|
-
inputSchema: { id: z.string().uuid() },
|
|
381
|
-
}, async ({ id }) => {
|
|
381
|
+
inputSchema: { approval_id: z.string().uuid().optional().describe('Get this by calling once without it, then have a human approve the returned approve_url. Re-call with the same arguments plus this approval_id to run the action.'), id: z.string().uuid() },
|
|
382
|
+
}, withApproval('delete_template', async ({ id }) => {
|
|
382
383
|
const projectId = getProjectId();
|
|
383
384
|
const { data: existing, error: existingErr } = await db().from('campaign_templates')
|
|
384
385
|
.select('id, project_id')
|
|
@@ -395,7 +396,7 @@ export function registerTemplateTools(server) {
|
|
|
395
396
|
if (error)
|
|
396
397
|
return j({ error: error.message });
|
|
397
398
|
return j({ ok: true, deleted: id });
|
|
398
|
-
});
|
|
399
|
+
}));
|
|
399
400
|
server.registerTool('restore_template_version', {
|
|
400
401
|
title: 'Restore Template Version',
|
|
401
402
|
annotations: {
|
package/build/tools/webhooks.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withApproval } from '../lib/action-approvals.js';
|
|
1
2
|
import { z } from 'zod';
|
|
2
3
|
import { db } from '../db.js';
|
|
3
4
|
import { getProjectId } from '../project.js';
|
|
@@ -34,11 +35,12 @@ export function registerWebhookTools(server) {
|
|
|
34
35
|
},
|
|
35
36
|
description: `Register a webhook URL to receive push notifications when events occur. Sendinel signs each payload with HMAC-SHA256 so you can verify authenticity. Valid events: ${VALID_EVENTS.join(', ')}`,
|
|
36
37
|
inputSchema: {
|
|
38
|
+
approval_id: z.string().uuid().optional().describe('Get this by calling once without it, then have a human approve the returned approve_url. Re-call with the same arguments plus this approval_id to run the action.'),
|
|
37
39
|
url: z.string().url().describe('HTTPS endpoint that will receive POST requests'),
|
|
38
40
|
events: z.array(z.enum(VALID_EVENTS)).min(1).describe('Events to subscribe to'),
|
|
39
41
|
description: z.string().optional().describe('Human-readable label, e.g. "Production orchestrator"'),
|
|
40
42
|
},
|
|
41
|
-
}, async ({ url, events, description }) => {
|
|
43
|
+
}, withApproval('subscribe_webhook', async ({ url, events, description }) => {
|
|
42
44
|
// SSRF protection
|
|
43
45
|
const validation = validateWebhookUrl(url);
|
|
44
46
|
if (!validation.valid)
|
|
@@ -63,7 +65,7 @@ export function registerWebhookTools(server) {
|
|
|
63
65
|
message: `Webhook registered. Store the secret — it won't be shown again. Sendinel will POST to ${url} with X-Sendinel-Signature header for verification.`,
|
|
64
66
|
verification_example: `const expected = crypto.createHmac('sha256', secret).update(requestBody).digest('hex'); if (header === 'sha256=' + expected) { /* valid */ }`,
|
|
65
67
|
});
|
|
66
|
-
});
|
|
68
|
+
}));
|
|
67
69
|
server.registerTool('list_webhook_subscriptions', {
|
|
68
70
|
title: 'List Webhook Subscriptions',
|
|
69
71
|
annotations: {
|
|
@@ -99,13 +101,14 @@ export function registerWebhookTools(server) {
|
|
|
99
101
|
},
|
|
100
102
|
description: 'Update a webhook subscription — change URL, events, or active status.',
|
|
101
103
|
inputSchema: {
|
|
104
|
+
approval_id: z.string().uuid().optional().describe('Get this by calling once without it, then have a human approve the returned approve_url. Re-call with the same arguments plus this approval_id to run the action.'),
|
|
102
105
|
subscription_id: z.string().uuid(),
|
|
103
106
|
url: z.string().url().optional(),
|
|
104
107
|
events: z.array(z.enum(VALID_EVENTS)).min(1).optional(),
|
|
105
108
|
active: z.boolean().optional(),
|
|
106
109
|
description: z.string().optional(),
|
|
107
110
|
},
|
|
108
|
-
}, async ({ subscription_id, url, events, active, description }) => {
|
|
111
|
+
}, withApproval('update_webhook_subscription', async ({ subscription_id, url, events, active, description }) => {
|
|
109
112
|
if (url) {
|
|
110
113
|
const validation = validateWebhookUrl(url);
|
|
111
114
|
if (!validation.valid)
|
|
@@ -130,7 +133,7 @@ export function registerWebhookTools(server) {
|
|
|
130
133
|
if (error)
|
|
131
134
|
return txt(`Error: ${error.message}`);
|
|
132
135
|
return j(data);
|
|
133
|
-
});
|
|
136
|
+
}));
|
|
134
137
|
server.registerTool('delete_webhook_subscription', {
|
|
135
138
|
title: 'Delete Webhook Subscription',
|
|
136
139
|
annotations: {
|
|
@@ -141,9 +144,10 @@ export function registerWebhookTools(server) {
|
|
|
141
144
|
},
|
|
142
145
|
description: 'Permanently delete a webhook subscription. This also deletes all delivery history for this subscription.',
|
|
143
146
|
inputSchema: {
|
|
147
|
+
approval_id: z.string().uuid().optional().describe('Get this by calling once without it, then have a human approve the returned approve_url. Re-call with the same arguments plus this approval_id to run the action.'),
|
|
144
148
|
subscription_id: z.string().uuid(),
|
|
145
149
|
},
|
|
146
|
-
}, async ({ subscription_id }) => {
|
|
150
|
+
}, withApproval('delete_webhook_subscription', async ({ subscription_id }) => {
|
|
147
151
|
const { error } = await db()
|
|
148
152
|
.from('webhook_subscriptions')
|
|
149
153
|
.delete()
|
|
@@ -152,5 +156,5 @@ export function registerWebhookTools(server) {
|
|
|
152
156
|
if (error)
|
|
153
157
|
return txt(`Error: ${error.message}`);
|
|
154
158
|
return txt(`Subscription ${subscription_id} deleted.`);
|
|
155
|
-
});
|
|
159
|
+
}));
|
|
156
160
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sendinel/mcp-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.64",
|
|
4
4
|
"description": "AI-controlled email operations control plane for campaigns, contacts, segments, analytics, and multi-provider sending via MCP.",
|
|
5
5
|
"mcpName": "ai.sendinel/mcp-server",
|
|
6
6
|
"type": "module",
|