@sendinel/mcp-server 1.0.0

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 (90) hide show
  1. package/README.md +113 -0
  2. package/build/anthropic-model.d.ts +5 -0
  3. package/build/anthropic-model.js +6 -0
  4. package/build/audit.d.ts +21 -0
  5. package/build/audit.js +38 -0
  6. package/build/auth.d.ts +10 -0
  7. package/build/auth.js +57 -0
  8. package/build/byod-client.d.ts +30 -0
  9. package/build/byod-client.js +95 -0
  10. package/build/crypto.d.ts +2 -0
  11. package/build/crypto.js +27 -0
  12. package/build/db.d.ts +38 -0
  13. package/build/db.js +70 -0
  14. package/build/index.d.ts +1 -0
  15. package/build/index.js +97 -0
  16. package/build/lib/action-approvals.d.ts +30 -0
  17. package/build/lib/action-approvals.js +154 -0
  18. package/build/lib/schedule.d.ts +36 -0
  19. package/build/lib/schedule.js +63 -0
  20. package/build/lib/social-engine/archetypes.d.ts +27 -0
  21. package/build/lib/social-engine/archetypes.js +160 -0
  22. package/build/lib/social-engine/index.d.ts +5 -0
  23. package/build/lib/social-engine/index.js +6 -0
  24. package/build/lib/social-engine/parse.d.ts +7 -0
  25. package/build/lib/social-engine/parse.js +83 -0
  26. package/build/lib/social-engine/platform-constraints.d.ts +27 -0
  27. package/build/lib/social-engine/platform-constraints.js +70 -0
  28. package/build/lib/social-engine/prompt-builder.d.ts +13 -0
  29. package/build/lib/social-engine/prompt-builder.js +80 -0
  30. package/build/lib/social-engine/types.d.ts +60 -0
  31. package/build/lib/social-engine/types.js +19 -0
  32. package/build/lib/url-validation.d.ts +3 -0
  33. package/build/lib/url-validation.js +51 -0
  34. package/build/lib/voice.d.ts +32 -0
  35. package/build/lib/voice.js +140 -0
  36. package/build/lib/webhook-events.d.ts +15 -0
  37. package/build/lib/webhook-events.js +120 -0
  38. package/build/plan-limits.d.ts +8 -0
  39. package/build/plan-limits.js +9 -0
  40. package/build/project.d.ts +1 -0
  41. package/build/project.js +9 -0
  42. package/build/server.d.ts +18 -0
  43. package/build/server.js +235 -0
  44. package/build/tools/ab-testing.d.ts +2 -0
  45. package/build/tools/ab-testing.js +204 -0
  46. package/build/tools/advisor.d.ts +23 -0
  47. package/build/tools/advisor.js +762 -0
  48. package/build/tools/analytics.d.ts +33 -0
  49. package/build/tools/analytics.js +1105 -0
  50. package/build/tools/approvals.d.ts +2 -0
  51. package/build/tools/approvals.js +32 -0
  52. package/build/tools/automations.d.ts +2 -0
  53. package/build/tools/automations.js +344 -0
  54. package/build/tools/campaigns.d.ts +2 -0
  55. package/build/tools/campaigns.js +1335 -0
  56. package/build/tools/compound.d.ts +2 -0
  57. package/build/tools/compound.js +312 -0
  58. package/build/tools/contacts.d.ts +2 -0
  59. package/build/tools/contacts.js +1483 -0
  60. package/build/tools/content.d.ts +2 -0
  61. package/build/tools/content.js +68 -0
  62. package/build/tools/data-proposals.d.ts +2 -0
  63. package/build/tools/data-proposals.js +155 -0
  64. package/build/tools/data.d.ts +2 -0
  65. package/build/tools/data.js +707 -0
  66. package/build/tools/delivery-ops.d.ts +2 -0
  67. package/build/tools/delivery-ops.js +387 -0
  68. package/build/tools/drafts.d.ts +2 -0
  69. package/build/tools/drafts.js +204 -0
  70. package/build/tools/forms.d.ts +2 -0
  71. package/build/tools/forms.js +46 -0
  72. package/build/tools/gdpr.d.ts +2 -0
  73. package/build/tools/gdpr.js +61 -0
  74. package/build/tools/org.d.ts +2 -0
  75. package/build/tools/org.js +71 -0
  76. package/build/tools/segments.d.ts +2 -0
  77. package/build/tools/segments.js +384 -0
  78. package/build/tools/sites.d.ts +2 -0
  79. package/build/tools/sites.js +182 -0
  80. package/build/tools/sms.d.ts +2 -0
  81. package/build/tools/sms.js +489 -0
  82. package/build/tools/social-posts.d.ts +2 -0
  83. package/build/tools/social-posts.js +380 -0
  84. package/build/tools/templates.d.ts +2 -0
  85. package/build/tools/templates.js +282 -0
  86. package/build/tools/warmup.d.ts +2 -0
  87. package/build/tools/warmup.js +57 -0
  88. package/build/tools/webhooks.d.ts +2 -0
  89. package/build/tools/webhooks.js +127 -0
  90. package/package.json +63 -0
@@ -0,0 +1,57 @@
1
+ import { z } from 'zod';
2
+ import { db } from '../db.js';
3
+ import { getProjectId } from '../project.js';
4
+ const j = (data) => ({ content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] });
5
+ export function registerWarmupTools(server) {
6
+ server.registerTool('get_warmup_status', {
7
+ description: 'Get warmup status for a sending site.',
8
+ inputSchema: { site_id: z.string().uuid() },
9
+ }, async ({ site_id }) => {
10
+ const { data, error } = await db().from('domain_health')
11
+ .select('warmup_day, warmup_daily_limit, total_sent, check_date')
12
+ .eq('project_id', getProjectId())
13
+ .eq('site_id', site_id)
14
+ .order('check_date', { ascending: false })
15
+ .limit(1)
16
+ .maybeSingle();
17
+ if (error)
18
+ return j({ error: error.message });
19
+ const day = data?.warmup_day ?? 0;
20
+ return j({
21
+ phase: day >= 42 ? 'complete' : day > 0 ? 'warming' : 'not_started',
22
+ daily_limit: data?.warmup_daily_limit ?? null,
23
+ current_volume: data?.total_sent ?? 0,
24
+ started_at: data?.check_date ?? null,
25
+ progress_pct: Math.min(100, Math.round((day / 42) * 100)),
26
+ next_milestone: day >= 42 ? null : `Day ${Math.min(42, day + 1)}`,
27
+ });
28
+ });
29
+ server.registerTool('update_warmup_schedule', {
30
+ description: 'Update warmup daily limit or phase metadata for a site.',
31
+ inputSchema: { site_id: z.string().uuid(), daily_limit: z.number().int().min(1).optional(), phase: z.string().optional() },
32
+ }, async ({ site_id, daily_limit, phase }) => {
33
+ const { error } = await db().from('warmup_schedules')
34
+ .upsert({ project_id: getProjectId(), site_id, target_volume: daily_limit ?? null, status: phase ?? 'active', updated_at: new Date().toISOString() }, { onConflict: 'site_id' });
35
+ if (error)
36
+ return j({ error: error.message });
37
+ return j({ updated: true });
38
+ });
39
+ server.registerTool('pause_warmup', {
40
+ description: 'Pause warmup for a sending site.',
41
+ inputSchema: { site_id: z.string().uuid(), reason: z.string().optional() },
42
+ }, async ({ site_id, reason }) => {
43
+ const { error } = await db().from('warmup_schedules').upsert({ project_id: getProjectId(), site_id, status: 'paused', pause_reason: reason ?? null, updated_at: new Date().toISOString() }, { onConflict: 'site_id' });
44
+ if (error)
45
+ return j({ error: error.message });
46
+ return j({ paused: true });
47
+ });
48
+ server.registerTool('resume_warmup', {
49
+ description: 'Resume warmup for a sending site.',
50
+ inputSchema: { site_id: z.string().uuid() },
51
+ }, async ({ site_id }) => {
52
+ const { error } = await db().from('warmup_schedules').upsert({ project_id: getProjectId(), site_id, status: 'active', updated_at: new Date().toISOString() }, { onConflict: 'site_id' });
53
+ if (error)
54
+ return j({ error: error.message });
55
+ return j({ resumed: true });
56
+ });
57
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerWebhookTools(server: McpServer): void;
@@ -0,0 +1,127 @@
1
+ import { z } from 'zod';
2
+ import { db } from '../db.js';
3
+ import { getProjectId } from '../project.js';
4
+ import { validateWebhookUrl, generateWebhookSecret } from '../lib/webhook-events.js';
5
+ const txt = (text) => ({ content: [{ type: 'text', text }] });
6
+ const j = (data) => txt(JSON.stringify(data, null, 2));
7
+ const VALID_EVENTS = [
8
+ 'campaign.activated',
9
+ 'campaign.completed',
10
+ 'campaign.paused',
11
+ 'email.delivered',
12
+ 'email.bounced',
13
+ 'email.complained',
14
+ 'bounce_rate.warning',
15
+ 'domain.dns_lost',
16
+ 'approval.requested',
17
+ 'approval.decided',
18
+ 'social.post.created',
19
+ 'social.post.updated',
20
+ 'social.post.scheduled',
21
+ 'social.post.published',
22
+ 'social.post.failed',
23
+ 'social.post.archived',
24
+ ];
25
+ export function registerWebhookTools(server) {
26
+ server.registerTool('subscribe_webhook', {
27
+ 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(', ')}`,
28
+ inputSchema: {
29
+ url: z.string().url().describe('HTTPS endpoint that will receive POST requests'),
30
+ events: z.array(z.enum(VALID_EVENTS)).min(1).describe('Events to subscribe to'),
31
+ description: z.string().optional().describe('Human-readable label, e.g. "Production orchestrator"'),
32
+ },
33
+ }, async ({ url, events, description }) => {
34
+ // SSRF protection
35
+ const validation = validateWebhookUrl(url);
36
+ if (!validation.valid)
37
+ return txt(`Invalid URL: ${validation.reason}`);
38
+ const secret = generateWebhookSecret();
39
+ const { data, error } = await db()
40
+ .from('webhook_subscriptions')
41
+ .insert({
42
+ project_id: getProjectId(),
43
+ url,
44
+ secret,
45
+ events,
46
+ description: description ?? null,
47
+ })
48
+ .select('id, url, events, active, created_at')
49
+ .single();
50
+ if (error)
51
+ return txt(`Error creating subscription: ${error.message}`);
52
+ return j({
53
+ ...data,
54
+ secret, // Only returned on creation — store it securely
55
+ message: `Webhook registered. Store the secret — it won't be shown again. Sendinel will POST to ${url} with X-Sendinel-Signature header for verification.`,
56
+ verification_example: `const expected = crypto.createHmac('sha256', secret).update(requestBody).digest('hex'); if (header === 'sha256=' + expected) { /* valid */ }`,
57
+ });
58
+ });
59
+ server.registerTool('list_webhook_subscriptions', {
60
+ description: 'List all webhook subscriptions for this project. Secrets are redacted — only shown on creation.',
61
+ inputSchema: {
62
+ include_inactive: z.boolean().optional().default(false),
63
+ },
64
+ }, async ({ include_inactive }) => {
65
+ let q = db()
66
+ .from('webhook_subscriptions')
67
+ .select('id, url, events, active, description, created_at, updated_at')
68
+ .eq('project_id', getProjectId())
69
+ .order('created_at', { ascending: false });
70
+ if (!include_inactive)
71
+ q = q.eq('active', true);
72
+ const { data, error } = await q;
73
+ if (error)
74
+ return txt(`Error: ${error.message}`);
75
+ return j({ subscriptions: data ?? [], count: data?.length ?? 0 });
76
+ });
77
+ server.registerTool('update_webhook_subscription', {
78
+ description: 'Update a webhook subscription — change URL, events, or active status.',
79
+ inputSchema: {
80
+ subscription_id: z.string().uuid(),
81
+ url: z.string().url().optional(),
82
+ events: z.array(z.enum(VALID_EVENTS)).min(1).optional(),
83
+ active: z.boolean().optional(),
84
+ description: z.string().optional(),
85
+ },
86
+ }, async ({ subscription_id, url, events, active, description }) => {
87
+ if (url) {
88
+ const validation = validateWebhookUrl(url);
89
+ if (!validation.valid)
90
+ return txt(`Invalid URL: ${validation.reason}`);
91
+ }
92
+ const updates = { updated_at: new Date().toISOString() };
93
+ if (url !== undefined)
94
+ updates.url = url;
95
+ if (events !== undefined)
96
+ updates.events = events;
97
+ if (active !== undefined)
98
+ updates.active = active;
99
+ if (description !== undefined)
100
+ updates.description = description;
101
+ const { data, error } = await db()
102
+ .from('webhook_subscriptions')
103
+ .update(updates)
104
+ .eq('id', subscription_id)
105
+ .eq('project_id', getProjectId())
106
+ .select('id, url, events, active, description, updated_at')
107
+ .single();
108
+ if (error)
109
+ return txt(`Error: ${error.message}`);
110
+ return j(data);
111
+ });
112
+ server.registerTool('delete_webhook_subscription', {
113
+ description: 'Permanently delete a webhook subscription. This also deletes all delivery history for this subscription.',
114
+ inputSchema: {
115
+ subscription_id: z.string().uuid(),
116
+ },
117
+ }, async ({ subscription_id }) => {
118
+ const { error } = await db()
119
+ .from('webhook_subscriptions')
120
+ .delete()
121
+ .eq('id', subscription_id)
122
+ .eq('project_id', getProjectId());
123
+ if (error)
124
+ return txt(`Error: ${error.message}`);
125
+ return txt(`Subscription ${subscription_id} deleted.`);
126
+ });
127
+ }
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@sendinel/mcp-server",
3
+ "version": "1.0.0",
4
+ "description": "AI-controlled email operations control plane for campaigns, contacts, segments, analytics, and multi-provider sending via MCP.",
5
+ "mcpName": "io.github.kmdesle/sendinel",
6
+ "type": "module",
7
+ "bin": {
8
+ "sendinel-mcp": "build/index.js"
9
+ },
10
+ "files": [
11
+ "build/",
12
+ "README.md"
13
+ ],
14
+ "scripts": {
15
+ "prebuild": "npm --prefix ../models run build",
16
+ "clean": "node -e \"require('node:fs').rmSync('build', { recursive: true, force: true })\"",
17
+ "dev": "tsx src/index.ts",
18
+ "dev:http": "MCP_TRANSPORT=http tsx src/index.ts",
19
+ "build": "npm run clean && tsc",
20
+ "start": "node build/index.js",
21
+ "start:http": "MCP_TRANSPORT=http node build/index.js",
22
+ "test": "vitest run",
23
+ "prepublishOnly": "npm run build"
24
+ },
25
+ "dependencies": {
26
+ "@anthropic-ai/sdk": "^0.93.0",
27
+ "@desler/models": "file:../models",
28
+ "@modelcontextprotocol/sdk": "^1.27.1",
29
+ "@supabase/supabase-js": "^2.98.0",
30
+ "dotenv": "^16.4.7",
31
+ "express": "^5.2.1",
32
+ "resend": "^6.9.3",
33
+ "zod": "^3.25.0"
34
+ },
35
+ "devDependencies": {
36
+ "@types/express": "^5.0.3",
37
+ "@types/node": "^26.0.1",
38
+ "tsx": "^4.19.4",
39
+ "typescript": "^5.9.3",
40
+ "vitest": "^4.0.18"
41
+ },
42
+ "publishConfig": {
43
+ "access": "public"
44
+ },
45
+ "keywords": [
46
+ "mcp",
47
+ "model-context-protocol",
48
+ "sendinel",
49
+ "email",
50
+ "claude",
51
+ "cursor",
52
+ "codex"
53
+ ],
54
+ "homepage": "https://sendinel.ai/developer",
55
+ "bugs": {
56
+ "url": "https://github.com/kmdesle/sendinel-ai/issues"
57
+ },
58
+ "repository": {
59
+ "type": "git",
60
+ "url": "git+https://github.com/kmdesle/sendinel-ai.git"
61
+ },
62
+ "license": "UNLICENSED"
63
+ }